From e5af0f1d49547295be796a600c0841135f331618 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sat, 25 Feb 2023 06:27:13 +0300 Subject: [PATCH 01/81] build(nix): update flake to use flake-parts and nci flake-parts module --- flake.lock | 132 +++++++++++++++---------- flake.nix | 217 +++++++++++++++++++----------------------- helix-term/Cargo.toml | 4 - 3 files changed, 180 insertions(+), 173 deletions(-) diff --git a/flake.lock b/flake.lock index 4cf1018c4..de72c612a 100644 --- a/flake.lock +++ b/flake.lock @@ -16,22 +16,6 @@ "type": "github" } }, - "devshell": { - "flake": false, - "locked": { - "lastModified": 1667210711, - "narHash": "sha256-IoErjXZAkzYWHEpQqwu/DeRNJGFdR7X2OGbkhMqMrpw=", - "owner": "numtide", - "repo": "devshell", - "rev": "96a9dd12b8a447840cc246e17a47b81a4268bba7", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, "dream2nix": { "inputs": { "alejandra": [ @@ -42,10 +26,12 @@ ], "crane": "crane", "devshell": [ + "nci" + ], + "flake-parts": [ "nci", - "devshell" + "parts" ], - "flake-parts": "flake-parts", "flake-utils-pre-commit": [ "nci" ], @@ -70,14 +56,17 @@ ], "pre-commit-hooks": [ "nci" + ], + "pruned-racket-catalog": [ + "nci" ] }, "locked": { - "lastModified": 1671323629, - "narHash": "sha256-9KHTPjIDjfnzZ4NjpE3gGIVHVHopy6weRDYO/7Y3hF8=", + "lastModified": 1677289985, + "narHash": "sha256-lUp06cTTlWubeBGMZqPl9jODM99LpWMcwxRiscFAUJg=", "owner": "nix-community", "repo": "dream2nix", - "rev": "2d7d68505c8619410df2c6b6463985f97cbcba6e", + "rev": "28b973a8d4c30cc1cbb3377ea2023a76bc3fb889", "type": "github" }, "original": { @@ -86,24 +75,6 @@ "type": "github" } }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, - "locked": { - "lastModified": 1668450977, - "narHash": "sha256-cfLhMhnvXn6x1vPm+Jow3RiFAUSCw/l1utktCw5rVA4=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "d591857e9d7dd9ddbfba0ea02b43b927c3c0f1fa", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, "flake-utils": { "locked": { "lastModified": 1659877975, @@ -119,23 +90,40 @@ "type": "github" } }, + "mk-naked-shell": { + "flake": false, + "locked": { + "lastModified": 1676572903, + "narHash": "sha256-oQoDHHUTxNVSURfkFcYLuAK+btjs30T4rbEUtCUyKy8=", + "owner": "yusdacra", + "repo": "mk-naked-shell", + "rev": "aeca9f8aa592f5e8f71f407d081cb26fd30c5a57", + "type": "github" + }, + "original": { + "owner": "yusdacra", + "repo": "mk-naked-shell", + "type": "github" + } + }, "nci": { "inputs": { - "devshell": "devshell", "dream2nix": "dream2nix", + "mk-naked-shell": "mk-naked-shell", "nixpkgs": [ "nixpkgs" ], + "parts": "parts", "rust-overlay": [ "rust-overlay" ] }, "locked": { - "lastModified": 1671430291, - "narHash": "sha256-UIc7H8F3N8rK72J/Vj5YJdV72tvDvYjH+UPsOFvlcsE=", + "lastModified": 1677294491, + "narHash": "sha256-p09IOJqhUOM6egRJe4Ou1EXdTs/I9Pmm8e7pMYDlIWM=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "b1b0d38b8c3b0d0e6a38638d5bbe10b0bc67522c", + "rev": "a525ed36c440854f296cd958f4ebf574f0ebe22c", "type": "github" }, "original": { @@ -146,11 +134,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1671359686, - "narHash": "sha256-3MpC6yZo+Xn9cPordGz2/ii6IJpP2n8LE8e/ebUXLrs=", + "lastModified": 1677063315, + "narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "04f574a1c0fde90b51bf68198e2297ca4e7cccf4", + "rev": "988cc958c57ce4350ec248d2d53087777f9e1949", "type": "github" }, "original": { @@ -163,11 +151,11 @@ "nixpkgs-lib": { "locked": { "dir": "lib", - "lastModified": 1665349835, - "narHash": "sha256-UK4urM3iN80UXQ7EaOappDzcisYIuEURFRoGQ/yPkug=", + "lastModified": 1675183161, + "narHash": "sha256-Zq8sNgAxDckpn7tJo7V1afRSk2eoVbu3OjI1QklGLNg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "34c5293a71ffdb2fe054eb5288adc1882c1eb0b1", + "rev": "e1e1b192c1a5aab2960bf0a0bd53a2e8124fa18e", "type": "github" }, "original": { @@ -178,10 +166,50 @@ "type": "github" } }, + "parts": { + "inputs": { + "nixpkgs-lib": [ + "nci", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1675933616, + "narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "47478a4a003e745402acf63be7f9a092d51b83d7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1675933616, + "narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "47478a4a003e745402acf63be7f9a092d51b83d7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "root": { "inputs": { "nci": "nci", "nixpkgs": "nixpkgs", + "parts": "parts_2", "rust-overlay": "rust-overlay" } }, @@ -193,11 +221,11 @@ ] }, "locked": { - "lastModified": 1671416426, - "narHash": "sha256-kpSH1Jrxfk2qd0pRPJn1eQdIOseGv5JuE+YaOrqU9s4=", + "lastModified": 1677292251, + "narHash": "sha256-D+6q5Z2MQn3UFJtqsM5/AvVHi3NXKZTIMZt1JGq/spA=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "fbaaff24f375ac25ec64268b0a0d63f91e474b7d", + "rev": "34cdbf6ad480ce13a6a526f57d8b9e609f3d65dc", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 734ac78e2..66fb641d9 100644 --- a/flake.nix +++ b/flake.nix @@ -12,16 +12,10 @@ inputs.nixpkgs.follows = "nixpkgs"; inputs.rust-overlay.follows = "rust-overlay"; }; + parts.url = "github:hercules-ci/flake-parts"; }; - outputs = { - self, - nixpkgs, - nci, - ... - }: let - lib = nixpkgs.lib; - ncl = nci.lib.nci-lib; + outputs = inp: let mkRootPath = rel: builtins.path { path = "${toString ./.}/${rel}"; @@ -32,6 +26,12 @@ ".envrc" ".ignore" ".github" + ".gitignore" + "logo.svg" + "logo_dark.svg" + "logo_light.svg" + "rust-toolchain.toml" + "rustfmt.toml" "runtime" "screenshot.png" "book" @@ -46,6 +46,7 @@ "flake.lock" ]; ignorePaths = path: type: let + inherit (inp.nixpkgs) lib; # split the nix store path into its components components = lib.splitString "/" path; # drop off the `/nix/hash-source` section from the path @@ -61,123 +62,105 @@ # filter out unnecessary paths filter = ignorePaths; }; - outputs = nci.lib.makeOutputs { - root = ./.; - config = common: { - outputs = { - # rename helix-term to helix since it's our main package - rename = {"helix-term" = "helix";}; - # Set default app to hx (binary is from helix-term release build) - # Set default package to helix-term release build - defaults = { - app = "hx"; - package = "helix"; - }; - }; - cCompiler.package = with common.pkgs; - if stdenv.isLinux - then gcc - else clang; - shell = { - packages = with common.pkgs; - [lld_13 cargo-flamegraph rust-analyzer] - ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin) - ++ (lib.optional stdenv.isLinux lldb) - ++ (lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.CoreFoundation); - env = [ - { - name = "HELIX_RUNTIME"; - eval = "$PWD/runtime"; - } - { - name = "RUST_BACKTRACE"; - value = "1"; - } + in + inp.parts.lib.mkFlake {inputs = inp;} { + imports = [inp.nci.flakeModule]; + systems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + "aarch64-darwin" + "i686-linux" + ]; + perSystem = { + config, + pkgs, + lib, + ... + }: let + makeOverridableHelix = old: config: let + grammars = pkgs.callPackage ./grammars.nix config; + runtimeDir = pkgs.runCommand "helix-runtime" {} '' + mkdir -p $out + ln -s ${mkRootPath "runtime"}/* $out + rm -r $out/grammars + ln -s ${grammars} $out/grammars + ''; + helix-wrapped = + pkgs.runCommand + old.name { - name = "RUSTFLAGS"; - eval = - if common.pkgs.stdenv.isLinux - then "$RUSTFLAGS\" -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment\"" - else "$RUSTFLAGS"; + inherit (old) pname version; + meta = old.meta or {}; + passthru = + (old.passthru or {}) + // { + unwrapped = old; + }; + nativeBuildInputs = [pkgs.makeWrapper]; + makeWrapperArgs = config.makeWrapperArgs or []; } - ]; - }; - }; - pkgConfig = common: { - helix-term = let - # Wrap helix with runtime - wrapper = _: old: let - inherit (common) pkgs; - makeOverridableHelix = old: config: let - grammars = pkgs.callPackage ./grammars.nix config; - runtimeDir = pkgs.runCommand "helix-runtime" {} '' - mkdir -p $out - ln -s ${mkRootPath "runtime"}/* $out - rm -r $out/grammars - ln -s ${grammars} $out/grammars - ''; - helix-wrapped = - common.internal.pkgsSet.utils.wrapDerivation old - { - nativeBuildInputs = [pkgs.makeWrapper]; - makeWrapperArgs = config.makeWrapperArgs or []; - } - '' - rm -rf $out/bin - mkdir -p $out/bin - ln -sf ${old}/bin/* $out/bin/ - wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}" - ''; - in - helix-wrapped + '' + cp -rs --no-preserve=mode,ownership ${old} $out + wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}" + ''; + in + helix-wrapped + // { + override = makeOverridableHelix old; + passthru = + helix-wrapped.passthru // { - override = makeOverridableHelix old; - passthru = helix-wrapped.passthru // {wrapper = wrapper {};}; + wrapper = old: makeOverridableHelix old config; }; - in - makeOverridableHelix old {}; - in { - inherit wrapper; - overrides.fix-build.overrideAttrs = prev: { - src = filteredSource; - - # disable fetching and building of tree-sitter grammars in the helix-term build.rs - HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1"; - - buildInputs = ncl.addBuildInputs prev [common.config.cCompiler.package.cc.lib]; - - # link languages and theme toml files since helix-term expects them (for tests) - preConfigure = '' - ${prev.preConfigure or ""} - ${ - lib.concatMapStringsSep - "\n" - (path: "ln -sf ${mkRootPath path} ..") - ["languages.toml" "theme.toml" "base16_theme.toml"] - } - ''; - checkPhase = ":"; - - meta.mainProgram = "hx"; + }; + stdenv = + if pkgs.stdenv.isLinux + then pkgs.stdenv + else pkgs.clangStdenv; + rustFlagsEnv = + if stdenv.isLinux + then "$RUSTFLAGS\" -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment\"" + else "$RUSTFLAGS"; + in { + nci.projects."helix-project".relPath = ""; + nci.crates."helix-term" = { + overrides = { + add-meta.override = _: {meta.mainProgram = "hx";}; + add-inputs.overrideAttrs = prev: { + buildInputs = (prev.buildInputs or []) ++ [stdenv.cc.cc.lib]; + }; + disable-grammar-builds = { + # disable fetching and building of tree-sitter grammars in the helix-term build.rs + HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1"; + }; + disable-tests = {checkPhase = ":";}; + set-stdenv.override = _: {inherit stdenv;}; + set-filtered-src.override = _: {src = filteredSource;}; }; }; + + packages.helix-unwrapped = config.nci.outputs."helix-term".packages.release; + packages.helix-unwrapped-dev = config.nci.outputs."helix-term".packages.dev; + packages.helix = makeOverridableHelix config.packages.helix-unwrapped {}; + packages.helix-dev = makeOverridableHelix config.packages.helix-unwrapped-dev {}; + packages.default = config.packages.helix; + + devShells.default = config.nci.outputs."helix-project".devShell.overrideAttrs (old: { + nativeBuildInputs = + (old.nativeBuildInputs or []) + ++ (with pkgs; [lld_13 cargo-flamegraph rust-analyzer]) + ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin) + ++ (lib.optional stdenv.isLinux pkgs.lldb) + ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation); + shellHook = '' + export HELIX_RUNTIME="$PWD/runtime" + export RUST_BACKTRACE="1" + export RUSTFLAGS="${rustFlagsEnv}" + ''; + }); }; }; - in - outputs - // { - packages = - lib.mapAttrs - ( - system: packages: - packages - // { - helix-unwrapped = packages.helix.passthru.unwrapped; - helix-unwrapped-dev = packages.helix-dev.passthru.unwrapped; - } - ) - outputs.packages; - }; nixConfig = { extra-substituters = ["https://helix.cachix.org"]; diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 603f37d39..2d4ba436e 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -12,10 +12,6 @@ include = ["src/**/*", "README.md"] default-run = "hx" rust-version = "1.57" -[package.metadata.nix] -build = true -app = true - [features] default = ["git"] unicode-lines = ["helix-core/unicode-lines"] From 309735aa2d9516a734165ee066f2c9a080f9849a Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sat, 25 Feb 2023 07:06:30 +0300 Subject: [PATCH 02/81] build(nix): fix devshell --- flake.lock | 6 +++--- flake.nix | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index de72c612a..fa292273a 100644 --- a/flake.lock +++ b/flake.lock @@ -119,11 +119,11 @@ ] }, "locked": { - "lastModified": 1677294491, - "narHash": "sha256-p09IOJqhUOM6egRJe4Ou1EXdTs/I9Pmm8e7pMYDlIWM=", + "lastModified": 1677297103, + "narHash": "sha256-ArlJIbp9NGV9yvhZdV0SOUFfRlI/kHeKoCk30NbSiLc=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "a525ed36c440854f296cd958f4ebf574f0ebe22c", + "rev": "a79272a2cb0942392bb3a5bf9a3ec6bc568795b2", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 66fb641d9..2ac764888 100644 --- a/flake.nix +++ b/flake.nix @@ -120,9 +120,11 @@ else pkgs.clangStdenv; rustFlagsEnv = if stdenv.isLinux - then "$RUSTFLAGS\" -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment\"" + then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment'' else "$RUSTFLAGS"; in { + # by default NCI adds rust-analyzer component, but helix toolchain doesn't have rust-analyzer + nci.toolchains.shell.components = ["rust-src" "rustfmt" "clippy"]; nci.projects."helix-project".relPath = ""; nci.crates."helix-term" = { overrides = { From 0cbb61c3a45f605f685897027ec0dc606ec8bef5 Mon Sep 17 00:00:00 2001 From: Matthias Deiml Date: Sat, 25 Feb 2023 19:40:02 +0100 Subject: [PATCH 03/81] Improve markdown highlights and add latex injection (#6100) --- languages.toml | 4 ++-- runtime/queries/markdown.inline/injections.scm | 2 ++ runtime/queries/markdown/highlights.scm | 8 +++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/languages.toml b/languages.toml index aade6bf26..aa580dec5 100644 --- a/languages.toml +++ b/languages.toml @@ -1026,7 +1026,7 @@ indent = { tab-width = 2, unit = " " } [[grammar]] name = "markdown" -source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "a7de4be29783a6e25f3240c90afea52f2417faa3", subpath = "tree-sitter-markdown" } +source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "7e7aa9a25ca9729db9fe22912f8f47bdb403a979", subpath = "tree-sitter-markdown" } [[language]] name = "markdown.inline" @@ -1038,7 +1038,7 @@ grammar = "markdown_inline" [[grammar]] name = "markdown_inline" -source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "a7de4be29783a6e25f3240c90afea52f2417faa3", subpath = "tree-sitter-markdown-inline" } +source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "7e7aa9a25ca9729db9fe22912f8f47bdb403a979", subpath = "tree-sitter-markdown-inline" } [[language]] name = "dart" diff --git a/runtime/queries/markdown.inline/injections.scm b/runtime/queries/markdown.inline/injections.scm index 2dd149d90..c2e7012ca 100644 --- a/runtime/queries/markdown.inline/injections.scm +++ b/runtime/queries/markdown.inline/injections.scm @@ -1,2 +1,4 @@ ((html_tag) @injection.content (#set! injection.language "html") (#set! injection.include-unnamed-children)) + +((latex_block) @injection.content (#set! injection.language "latex") (#set! injection.include-unnamed-children)) diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm index 25f22ba73..80c9f9583 100644 --- a/runtime/queries/markdown/highlights.scm +++ b/runtime/queries/markdown/highlights.scm @@ -39,7 +39,7 @@ (list_marker_parenthesis) ] @markup.list.numbered -(thematic_break) @punctuation.delimiter +(thematic_break) @punctuation.special [ (block_continuation) @@ -51,3 +51,9 @@ ] @string.escape (block_quote) @markup.quote + +(pipe_table_row + "|" @punctuation.special) +(pipe_table_header + "|" @punctuation.special) +(pipe_table_delimiter_row) @punctuation.special From f69bb411691ba023951168e8ee865795328294bb Mon Sep 17 00:00:00 2001 From: Sophie Dankel <47993817+sdankel@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:47:54 -0800 Subject: [PATCH 04/81] Add language support for sway (#6023) --- book/src/generated/lang-support.md | 1 + languages.toml | 14 ++ 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 +++++ 7 files changed, 493 insertions(+) create mode 100644 runtime/queries/sway/highlights.scm create mode 100644 runtime/queries/sway/indents.scm create mode 100644 runtime/queries/sway/injections.scm create mode 100644 runtime/queries/sway/locals.scm create mode 100644 runtime/queries/sway/textobjects.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index cdecb9b04..3bd61d7a9 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -125,6 +125,7 @@ | sshclientconfig | ✓ | | | | | starlark | ✓ | ✓ | | | | svelte | ✓ | | | `svelteserver` | +| sway | ✓ | ✓ | ✓ | `forc` | | swift | ✓ | | | `sourcekit-lsp` | | tablegen | ✓ | ✓ | ✓ | | | task | ✓ | | | | diff --git a/languages.toml b/languages.toml index aa580dec5..b364de961 100644 --- a/languages.toml +++ b/languages.toml @@ -52,6 +52,20 @@ args = { attachCommands = [ "platform select remote-gdb-server", "platform conne name = "rust" source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" } +[[language]] +name = "sway" +scope = "source.sway" +injection-regex = "sway" +file-types = ["sw"] +language-server = { command = "forc", args = ["lsp"] } +roots = ["Forc.toml", "Forc.lock"] +indent = { tab-width = 4, unit = " " } +comment-token = "//" + +[[grammar]] +name = "sway" +source = { git = "https://github.com/FuelLabs/tree-sitter-sway", rev = "e491a005ee1d310f4c138bf215afd44cfebf959c" } + [[language]] name = "toml" scope = "source.toml" diff --git a/runtime/queries/sway/highlights.scm b/runtime/queries/sway/highlights.scm new file mode 100644 index 000000000..98f4d4493 --- /dev/null +++ b/runtime/queries/sway/highlights.scm @@ -0,0 +1,336 @@ +; ------- +; Tree-Sitter doesn't allow overrides in regards to captures, +; though it is possible to affect the child node of a captured +; node. Thus, the approach here is to flip the order so that +; overrides are unnecessary. +; ------- + +; ------- +; Types +; ------- + +; --- +; Primitives +; --- + +(escape_sequence) @constant.character.escape +(primitive_type) @type.builtin +(boolean_literal) @constant.builtin.boolean +(integer_literal) @constant.numeric.integer +(float_literal) @constant.numeric.float +(char_literal) @constant.character +[ + (string_literal) + (raw_string_literal) +] @string +[ + (line_comment) + (block_comment) +] @comment + +; --- +; Extraneous +; --- + +(self) @variable.builtin +(enum_variant (identifier) @type.enum.variant) + +(field_initializer + (field_identifier) @variable.other.member) +(shorthand_field_initializer + (identifier) @variable.other.member) +(shorthand_field_identifier) @variable.other.member + +(loop_label + "'" @label + (identifier) @label) + +; --- +; Punctuation +; --- + +[ + "::" + "." + ";" + "," +] @punctuation.delimiter + +[ + "(" + ")" + "[" + "]" + "{" + "}" + "#" +] @punctuation.bracket +(type_arguments + [ + "<" + ">" + ] @punctuation.bracket) +(type_parameters + [ + "<" + ">" + ] @punctuation.bracket) +(closure_parameters + "|" @punctuation.bracket) + +; --- +; Variables +; --- + +(let_declaration + pattern: [ + ((identifier) @variable) + ((tuple_pattern + (identifier) @variable)) + ]) + +; It needs to be anonymous to not conflict with `call_expression` further below. +(_ + value: (field_expression + value: (identifier)? @variable + field: (field_identifier) @variable.other.member)) + +(parameter + pattern: (identifier) @variable.parameter) +(closure_parameters + (identifier) @variable.parameter) + +; ------- +; Keywords +; ------- + +(for_expression + "for" @keyword.control.repeat) +((identifier) @keyword.control + (#match? @keyword.control "^yield$")) + +"in" @keyword.control + +[ + "match" + "if" + "else" +] @keyword.control.conditional + +[ + "while" +] @keyword.control.repeat + +[ + "break" + "continue" + "return" +] @keyword.control.return + +[ + "contract" + "script" + "predicate" +] @keyword.other + +"use" @keyword.control.import +(dep_item "dep" @keyword.control.import !body) +(use_as_clause "as" @keyword.control.import) + +(type_cast_expression "as" @keyword.operator) + +[ + "as" + "pub" + "dep" + + "abi" + "impl" + "where" + "trait" + "for" +] @keyword + +[ + "struct" + "enum" + "storage" + "configurable" +] @keyword.storage.type + +"let" @keyword.storage +"fn" @keyword.function +"abi" @keyword.function + +(mutable_specifier) @keyword.storage.modifier.mut + +(reference_type "&" @keyword.storage.modifier.ref) +(self_parameter "&" @keyword.storage.modifier.ref) + +[ + "const" + "ref" + "deref" + "move" +] @keyword.storage.modifier + +; TODO: variable.mut to highlight mutable identifiers via locals.scm + +; ------- +; Guess Other Types +; ------- + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]*$")) + +; --- +; PascalCase identifiers in call_expressions (e.g. `Ok()`) +; are assumed to be enum constructors. +; --- + +(call_expression + function: [ + ((identifier) @type.enum.variant + (#match? @type.enum.variant "^[A-Z]")) + (scoped_identifier + name: ((identifier) @type.enum.variant + (#match? @type.enum.variant "^[A-Z]"))) + ]) + +; --- +; Assume that types in match arms are enums and not +; tuple structs. Same for `if let` expressions. +; --- + +(match_pattern + (scoped_identifier + name: (identifier) @constructor)) +(tuple_struct_pattern + type: [ + ((identifier) @constructor) + (scoped_identifier + name: (identifier) @constructor) + ]) +(struct_pattern + type: [ + ((type_identifier) @constructor) + (scoped_type_identifier + name: (type_identifier) @constructor) + ]) + +; --- +; Other PascalCase identifiers are assumed to be structs. +; --- + +((identifier) @type + (#match? @type "^[A-Z]")) + +; ------- +; Functions +; ------- + +(call_expression + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function) + ]) +(generic_function + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function.method) + ]) + +(function_item + name: (identifier) @function) + +(function_signature_item + name: (identifier) @function) + +; ------- +; Operators +; ------- + +[ + "*" + "'" + "->" + "=>" + "<=" + "=" + "==" + "!" + "!=" + "%" + "%=" + "&" + "&=" + "&&" + "|" + "|=" + "||" + "^" + "^=" + "*" + "*=" + "-" + "-=" + "+" + "+=" + "/" + "/=" + ">" + "<" + ">=" + ">>" + "<<" + ">>=" + "<<=" + "@" + ".." + "..=" + "'" +] @operator + +; ------- +; Paths +; ------- + +(use_declaration + argument: (identifier) @namespace) +(use_wildcard + (identifier) @namespace) +(dep_item + name: (identifier) @namespace) +(scoped_use_list + path: (identifier)? @namespace) +(use_list + (identifier) @namespace) +(use_as_clause + path: (identifier)? @namespace + alias: (identifier) @namespace) + +; --- +; Remaining Paths +; --- + +(scoped_identifier + path: (identifier)? @namespace + name: (identifier) @namespace) +(scoped_type_identifier + path: (identifier) @namespace) + +; ------- +; Remaining Identifiers +; ------- + +"?" @special + +(type_identifier) @type +(identifier) @variable +(field_identifier) @variable.other.member diff --git a/runtime/queries/sway/indents.scm b/runtime/queries/sway/indents.scm new file mode 100644 index 000000000..e6902b62c --- /dev/null +++ b/runtime/queries/sway/indents.scm @@ -0,0 +1,71 @@ +[ + (use_list) + (block) + (match_block) + (arguments) + (parameters) + (declaration_list) + (field_declaration_list) + (field_initializer_list) + (struct_pattern) + (tuple_pattern) + (unit_expression) + (enum_variant_list) + (call_expression) + (binary_expression) + (field_expression) + (tuple_expression) + (array_expression) + (where_clause) + + (token_tree) +] @indent + +[ + "}" + "]" + ")" +] @outdent + +; Indent the right side of assignments. +; The #not-same-line? predicate is required to prevent an extra indent for e.g. +; an else-clause where the previous if-clause starts on the same line as the assignment. +(assignment_expression + . + (_) @expr-start + right: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(compound_assignment_expr + . + (_) @expr-start + right: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(let_declaration + . + (_) @expr-start + value: (_) @indent + alternative: (_)? @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(if_expression + . + (_) @expr-start + condition: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) + +; Some field expressions where the left part is a multiline expression are not +; indented by cargo fmt. +; Because this multiline expression might be nested in an arbitrary number of +; field expressions, this can only be matched using a Regex. +(field_expression + value: (_) @val + "." @outdent + (#match? @val "(\\A[^\\n\\r]+\\([\\t ]*(\\n|\\r).*)|(\\A[^\\n\\r]*\\{[\\t ]*(\\n|\\r))") +) diff --git a/runtime/queries/sway/injections.scm b/runtime/queries/sway/injections.scm new file mode 100644 index 000000000..e4509a5fd --- /dev/null +++ b/runtime/queries/sway/injections.scm @@ -0,0 +1,2 @@ +([(line_comment) (block_comment)] @injection.content + (#set! injection.language "comment")) diff --git a/runtime/queries/sway/locals.scm b/runtime/queries/sway/locals.scm new file mode 100644 index 000000000..262d609e9 --- /dev/null +++ b/runtime/queries/sway/locals.scm @@ -0,0 +1,17 @@ +; Scopes + +[ + (function_item) + (closure_expression) + (block) +] @local.scope + +; Definitions + +(parameter + (identifier) @local.definition) + +(closure_parameters (identifier) @local.definition) + +; References +(identifier) @local.reference diff --git a/runtime/queries/sway/textobjects.scm b/runtime/queries/sway/textobjects.scm new file mode 100644 index 000000000..15740bc85 --- /dev/null +++ b/runtime/queries/sway/textobjects.scm @@ -0,0 +1,52 @@ +(function_item + body: (_) @function.inside) @function.around(closure_expression body: (_) @function.inside) @function.around + +(struct_item + body: (_) @class.inside) @class.around + +(enum_item + body: (_) @class.inside) @class.around + +(trait_item + body: (_) @class.inside) @class.around + +(impl_item + body: (_) @class.inside) @class.around + +(parameters + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(type_parameters + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(type_arguments + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(closure_parameters + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(arguments + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +[ + (line_comment) + (block_comment) +] @comment.inside + +(line_comment)+ @comment.around + +(block_comment) @comment.around + +(; #[test] + (attribute_item + (attribute + (identifier) @_test_attribute)) + ; allow other attributes like #[should_panic] and comments + [ + (attribute_item) + (line_comment) + ]* + ; the test function + (function_item + body: (_) @test.inside) @test.around + (#eq? @_test_attribute "test")) From a4049e6f55144f502a1d6d1538b690f3e24524ef Mon Sep 17 00:00:00 2001 From: Matthew Toohey Date: Sat, 25 Feb 2023 13:53:37 -0500 Subject: [PATCH 05/81] feat: add nasm language (#6068) --- book/src/generated/lang-support.md | 1 + languages.toml | 13 +++ runtime/queries/nasm/highlights.scm | 126 +++++++++++++++++++++++++++ runtime/queries/nasm/injections.scm | 2 + runtime/queries/nasm/textobjects.scm | 15 ++++ 5 files changed, 157 insertions(+) create mode 100644 runtime/queries/nasm/highlights.scm create mode 100644 runtime/queries/nasm/injections.scm create mode 100644 runtime/queries/nasm/textobjects.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 3bd61d7a9..4011aa114 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -85,6 +85,7 @@ | meson | ✓ | | ✓ | | | mint | | | | `mint` | | msbuild | ✓ | | ✓ | | +| nasm | ✓ | ✓ | | | | nickel | ✓ | | ✓ | `nls` | | nix | ✓ | | | `nil` | | nu | ✓ | | | | diff --git a/languages.toml b/languages.toml index b364de961..d4ea9a867 100644 --- a/languages.toml +++ b/languages.toml @@ -2218,3 +2218,16 @@ comment-token = "#" [[grammar]] name = "po" source = { git = "https://github.com/erasin/tree-sitter-po", rev = "417cee9abb2053ed26b19e7de972398f2da9b29e" } + +[[language]] +name = "nasm" +scope = "source.nasm" +file-types = ["asm", "s", "S", "nasm"] +injection-regex = "n?asm" +roots = [] +comment-token = ";" +indent = { tab-width = 8, unit = " " } + +[[grammar]] +name = "nasm" +source = { git = "https://github.com/naclsn/tree-sitter-nasm", rev = "a0db15db6fcfb1bf2cc8702500e55e558825c48b" } diff --git a/runtime/queries/nasm/highlights.scm b/runtime/queries/nasm/highlights.scm new file mode 100644 index 000000000..5e3cfebe6 --- /dev/null +++ b/runtime/queries/nasm/highlights.scm @@ -0,0 +1,126 @@ +(comment) @comment + +(label) @label + +(preproc_expression) @keyword.directive + +[ + (line_here_token) + (section_here_token) +] @variable.builtin + +(unary_expression + operator: _ @operator) +(binary_expression + operator: _ @operator) +(conditional_expression + "?" @operator + ":" @operator) + +[ + ":" + "," +] @punctuation.delimiter + +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +(instruction_prefix) @keyword +(actual_instruction + instruction: (word) @function) + +(call_syntax_expression + base: (word) @function) + +(size_hint) @type +(struc_declaration + name: (word) @type) +(struc_instance + name: (word) @type) + +(effective_address + hint: _ @type) +(effective_address + segment: _ @constant.builtin) + +(register) @constant.builtin + +(number_literal) @constant.numeric.integer +(string_literal) @string +(float_literal) @constant.numeric.float +(packed_bcd_literal) @constant.numeric.integer + +((word) @constant + (#match? @constant "^[A-Z_][?A-Z_0-9]+$")) +((word) @constant.builtin + (#match? @constant.builtin "^__\\?[A-Z_a-z0-9]+\\?__$")) +(word) @variable + +(preproc_arg) @keyword.directive + +[ + (preproc_def) + (preproc_function_def) + (preproc_undef) + (preproc_alias) + (preproc_multiline_macro) + (preproc_multiline_unmacro) + (preproc_if) + (preproc_rotate) + (preproc_rep_loop) + (preproc_include) + (preproc_pathsearch) + (preproc_depend) + (preproc_use) + (preproc_push) + (preproc_pop) + (preproc_repl) + (preproc_arg) + (preproc_stacksize) + (preproc_local) + (preproc_reporting) + (preproc_pragma) + (preproc_line) + (preproc_clear) +] @keyword.directive +[ + (pseudo_instruction_dx) + (pseudo_instruction_resx) + (pseudo_instruction_incbin_command) + (pseudo_instruction_equ_command) + (pseudo_instruction_times_prefix) + (pseudo_instruction_alignx_macro) +] @function.special +[ + (assembl_directive_target) + (assembl_directive_defaults) + (assembl_directive_sections) + (assembl_directive_absolute) + (assembl_directive_symbols) + (assembl_directive_common) + (assembl_directive_symbolfixes) + (assembl_directive_cpu) + (assembl_directive_floathandling) + (assembl_directive_org) + (assembl_directive_sectalign) + + (assembl_directive_primitive_target) + (assembl_directive_primitive_defaults) + (assembl_directive_primitive_sections) + (assembl_directive_primitive_absolute) + (assembl_directive_primitive_symbols) + (assembl_directive_primitive_common) + (assembl_directive_primitive_symbolfixes) + (assembl_directive_primitive_cpu) + (assembl_directive_primitive_floathandling) + (assembl_directive_primitive_org) + (assembl_directive_primitive_sectalign) + (assembl_directive_primitive_warning) + (assembl_directive_primitive_map) +] @keyword diff --git a/runtime/queries/nasm/injections.scm b/runtime/queries/nasm/injections.scm new file mode 100644 index 000000000..2f0e58eb6 --- /dev/null +++ b/runtime/queries/nasm/injections.scm @@ -0,0 +1,2 @@ +((comment) @injection.content + (#set! injection.language "comment")) diff --git a/runtime/queries/nasm/textobjects.scm b/runtime/queries/nasm/textobjects.scm new file mode 100644 index 000000000..ddfbad78c --- /dev/null +++ b/runtime/queries/nasm/textobjects.scm @@ -0,0 +1,15 @@ +(preproc_multiline_macro + body: (body) @function.inside) @function.around +(struc_declaration + body: (struc_declaration_body) @class.inside) @class.around +(struc_instance + body: (struc_instance_body) @class.inside) @class.around + +(preproc_function_def_parameters + (word) @parameter.inside) +(call_syntax_arguments + (_) @parameter.inside) +(operand) @parameter.inside + +(comment) @comment.inside +(comment)+ @comment.around From 98a3d46912be7dcc650c54ea417d7f00ab6d05a3 Mon Sep 17 00:00:00 2001 From: Mathieu Agopian Date: Sat, 25 Feb 2023 19:55:44 +0100 Subject: [PATCH 06/81] Add elm treesitter textobjects (#6084) --- book/src/generated/lang-support.md | 2 +- runtime/queries/elm/textobjects.scm | 63 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 runtime/queries/elm/textobjects.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 4011aa114..e997b3e81 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -30,7 +30,7 @@ | eex | ✓ | | | | | ejs | ✓ | | | | | elixir | ✓ | ✓ | ✓ | `elixir-ls` | -| elm | ✓ | | | `elm-language-server` | +| elm | ✓ | ✓ | | `elm-language-server` | | elvish | ✓ | | | `elvish` | | env | ✓ | | | | | erb | ✓ | | | | diff --git a/runtime/queries/elm/textobjects.scm b/runtime/queries/elm/textobjects.scm new file mode 100644 index 000000000..d212e9c3b --- /dev/null +++ b/runtime/queries/elm/textobjects.scm @@ -0,0 +1,63 @@ +(line_comment) @comment.inside +(line_comment)+ @comment.around +(block_comment) @comment.inside +(block_comment)+ @comment.around + +((type_annotation)? + (value_declaration + (function_declaration_left (lower_case_identifier)) + (eq) + (_) @function.inside + ) +) @function.around + +(parenthesized_expr + (anonymous_function_expr + ( + (arrow) + (_) @function.inside + ) + ) +) @function.around + +(value_declaration + (function_declaration_left + (lower_pattern + (lower_case_identifier) @parameter.inside @parameter.around + ) + ) +) + +(value_declaration + (function_declaration_left + (pattern) @parameter.inside @parameter.around + ) +) + +(value_declaration + (function_declaration_left + (tuple_pattern + (pattern) @parameter.inside + ) @parameter.around + ) +) + +(value_declaration + (function_declaration_left + (record_pattern + (lower_pattern + (lower_case_identifier) @parameter.inside + ) + ) @parameter.around + ) +) + +(parenthesized_expr + (anonymous_function_expr + ( + (backslash) + (pattern) @parameter.inside + (arrow) + ) + ) +) From eb3086a5b3d37e871e1e752652617fbc2dc2a085 Mon Sep 17 00:00:00 2001 From: Adam Becker <47185607+adam-becker@users.noreply.github.com> Date: Sun, 26 Feb 2023 14:29:16 -0700 Subject: [PATCH 07/81] Fix diagnostic underline colors in catppuccin themes (#6107) --- runtime/themes/catppuccin_mocha.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/themes/catppuccin_mocha.toml b/runtime/themes/catppuccin_mocha.toml index 2504fec87..59fa430f8 100644 --- a/runtime/themes/catppuccin_mocha.toml +++ b/runtime/themes/catppuccin_mocha.toml @@ -103,10 +103,10 @@ "ui.menu" = { fg = "overlay2", bg = "surface0" } "ui.menu.selected" = { fg = "text", bg = "surface1", modifiers = ["bold"] } -"diagnostic.error" = { fg = "red", underline = { color = "red", style = "curl" } } -"diagnostic.warning" = { fg = "yellow", underline = { color = "yellow", style = "curl" } } -"diagnostic.info" = { fg = "sky", underline = { color = "sky", style = "curl" } } -"diagnostic.hint" = { fg = "teal", underline = { color = "teal", style = "curl" } } +"diagnostic.error" = { underline = { color = "red", style = "curl" } } +"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } } +"diagnostic.info" = { underline = { color = "sky", style = "curl" } } +"diagnostic.hint" = { underline = { color = "teal", style = "curl" } } error = "red" warning = "yellow" From cac4a3604cc9049f03054e964776c4fe53352696 Mon Sep 17 00:00:00 2001 From: luetage Date: Sun, 26 Feb 2023 22:34:46 +0100 Subject: [PATCH 08/81] Kanagawa: fix bufferline, theme wrap-indicators, cursor, menu, and syntax changes (#6085) --- runtime/themes/kanagawa.toml | 53 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/runtime/themes/kanagawa.toml b/runtime/themes/kanagawa.toml index 16e27362f..a7d33f3ee 100644 --- a/runtime/themes/kanagawa.toml +++ b/runtime/themes/kanagawa.toml @@ -14,9 +14,8 @@ "ui.linenr" = { fg = "sumiInk4" } "ui.linenr.selected" = { fg = "roninYellow" } +"ui.virtual" = "sumiInk4" "ui.virtual.ruler" = { bg = "sumiInk2" } -"ui.virtual.whitespace" = "waveBlue1" -"ui.virtual.indent-guide" = "sumiInk4" "ui.statusline" = { fg = "oldWhite", bg = "sumiInk0" } "ui.statusline.inactive" = { fg = "fujiGray", bg = "sumiInk0" } @@ -24,8 +23,9 @@ "ui.statusline.insert" = { fg = "sumiInk0", bg = "autumnGreen", modifiers = ["bold"] } "ui.statusline.select" = { fg = "sumiInk0", bg = "oniViolet", modifiers = ["bold"] } -"ui.bufferline" = { fg = "oldWhite", bg = "sumiInk0" } -"ui.bufferline.inactive" = { fg = "fujiGray", bg = "sumiInk0" } +"ui.bufferline" = { fg = "fujiGray", bg = "sumiInk0" } +"ui.bufferline.active" = { fg = "oldWhite", bg = "sumiInk0" } +"ui.bufferline.background" = { bg = "sumiInk0" } "ui.popup" = { fg = "fujiWhite", bg = "sumiInk0" } "ui.window" = { fg = "sumiInk0" } @@ -33,14 +33,16 @@ "ui.text" = "fujiWhite" "ui.text.focus" = { fg = "fujiWhite", bg = "waveBlue1", modifiers = ["bold"] } -"ui.cursor" = { fg = "waveBlue1", bg = "fujiWhite"} -"ui.cursor.primary" = { fg = "waveBlue1", bg = "seaFoam" } -"ui.cursor.match" = { fg = "seaFoam", modifiers = ["bold"] } +"ui.cursor" = { fg = "waveBlue1", bg = "waveAqua2"} +"ui.cursor.primary" = { fg = "waveBlue1", bg = "fujiWhite" } +"ui.cursor.match" = { fg = "waveRed", modifiers = ["bold"] } "ui.highlight" = { fg = "fujiWhite", bg = "waveBlue2" } -"ui.menu" = { fg = "fujiWhite", bg = "sumiInk0" } -"ui.menu.selected" = { fg = "fujiWhite", bg = "waveBlue1", modifiers = ["bold"] } +"ui.menu" = { fg = "fujiWhite", bg = "waveBlue1" } +"ui.menu.selected" = { fg = "fujiWhite", bg = "waveBlue2", modifiers = ["bold"] } +"ui.menu.scroll" = { fg = "oldWhite", bg = "waveBlue1" } "ui.cursorline.primary" = { bg = "sumiInk3"} +"ui.cursorcolumn.primary" = { bg = "sumiInk3" } "diagnostic.error" = { underline = { color = "samuraiRed", style = "curl" } } "diagnostic.warning" = { underline = { color = "roninYellow", style = "curl" } } @@ -58,12 +60,16 @@ hint = "dragonBlue" "diff.delta" = "autumnYellow" ## Syntax highlighting +"attribute" = "waveRed" "type" = "waveAqua2" +"constructor" = "springBlue" "constant" = "surimiOrange" "constant.numeric" = "sakuraPink" "constant.character.escape" = "springBlue" "string" = "springGreen" "string.regexp" = "boatYellow2" +"string.special.url" = "springBlue" +"string.special.symbol" = "oniViolet" "comment" = "fujiGray" "variable" = "fujiWhite" "variable.builtin" = "waveRed" @@ -71,37 +77,36 @@ hint = "dragonBlue" "variable.other.member" = "carpYellow" "label" = "springBlue" "punctuation" = "springViolet2" -"punctuation.delimiter" = "springViolet2" -"punctuation.bracket" = "springViolet2" "keyword" = "oniViolet" +"keyword.control.return" = "peachRed" +"keyword.control.exception" = "peachRed" "keyword.directive" = "peachRed" "operator" = "boatYellow2" "function" = "crystalBlue" -"function.builtin" = "peachRed" +"function.builtin" = "springBlue" "function.macro" = "waveRed" -"tag" = "springBlue" +"tag" = "waveAqua2" "namespace" = "surimiOrange" -"attribute" = "peachRed" -"constructor" = "springBlue" -"module" = "waveAqua2" "special" = "peachRed" ## Markup modifiers -"markup.heading.marker" = "fujiGray" +"markup.heading.marker" = "springViolet2" "markup.heading.1" = { fg = "surimiOrange", modifiers = ["bold"] } "markup.heading.2" = { fg = "carpYellow", modifiers = ["bold"] } "markup.heading.3" = { fg = "waveAqua2", modifiers = ["bold"] } -"markup.heading.4" = { fg = "springGreen", modifiers = ["bold"] } -"markup.heading.5" = { fg = "waveRed", modifiers = ["bold"] } -"markup.heading.6" = { fg = "autumnRed", modifiers = ["bold"] } -"markup.list" = "oniViolet" +"markup.heading.4" = { fg = "lightBlue", modifiers = ["bold"] } +"markup.heading.5" = { fg = "oniViolet", modifiers = ["bold"] } +"markup.heading.6" = { fg = "springViolet1", modifiers = ["bold"] } +"markup.list.numbered" = "sakuraPink" +"markup.list.unnumbered" = "waveRed" "markup.bold" = { modifiers = ["bold"] } "markup.italic" = { modifiers = ["italic"] } "markup.strikethrough" = { modifiers = ["crossed_out"] } -"markup.link.url" = { fg = "springBlue", modifiers = ["underlined"] } "markup.link.text" = "crystalBlue" -"markup.quote" = "seaFoam" -"markup.raw" = "seaFoam" +"markup.link.url" = { fg = "springBlue", underline.style = "line" } +"markup.link.label" = "surimiOrange" +"markup.quote" = "springViolet1" +"markup.raw" = "springGreen" [palette] seaFoam = "#C7CCD1" # custom lighter foreground From f02fdd2f7324121cd01b7cf51f462bce46819e4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 10:24:06 +0900 Subject: [PATCH 09/81] build(deps): bump tempfile from 3.3.0 to 3.4.0 (#6128) Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.3.0 to 3.4.0. - [Release notes](https://github.com/Stebalien/tempfile/releases) - [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS) - [Commits](https://github.com/Stebalien/tempfile/commits) --- updated-dependencies: - dependency-name: tempfile dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 69 ++++++++++++++++++++++++++++++++++--------- helix-term/Cargo.toml | 2 +- helix-vcs/Cargo.toml | 2 +- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f8302e28..cb3300a34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -407,6 +407,27 @@ dependencies = [ "encoding_rs", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "error-code" version = "2.3.1" @@ -1414,6 +1435,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.4" @@ -1460,6 +1491,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -1792,15 +1829,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "ropey" version = "1.6.0" @@ -1811,6 +1839,20 @@ dependencies = [ "str_indices", ] +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -2025,16 +2067,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys", ] [[package]] diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 2d4ba436e..4921db926 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -75,4 +75,4 @@ helix-loader = { version = "0.6", path = "../helix-loader" } [dev-dependencies] smallvec = "1.10" indoc = "2.0.0" -tempfile = "3.3.0" +tempfile = "3.4.0" diff --git a/helix-vcs/Cargo.toml b/helix-vcs/Cargo.toml index 19b660a60..c4d6eb45b 100644 --- a/helix-vcs/Cargo.toml +++ b/helix-vcs/Cargo.toml @@ -25,4 +25,4 @@ log = "0.4" git = ["git-repository"] [dev-dependencies] -tempfile = "3.3" \ No newline at end of file +tempfile = "3.4" \ No newline at end of file From 7b8daae3950e5c7f46f219cbd7d60107b4428f65 Mon Sep 17 00:00:00 2001 From: Isotoxal <62714538+IsotoxalDev@users.noreply.github.com> Date: Tue, 28 Feb 2023 08:12:58 +0530 Subject: [PATCH 10/81] theme: Add Everblush (#6086) --- runtime/themes/everblush.toml | 114 ++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 runtime/themes/everblush.toml diff --git a/runtime/themes/everblush.toml b/runtime/themes/everblush.toml new file mode 100644 index 000000000..edcdaf41b --- /dev/null +++ b/runtime/themes/everblush.toml @@ -0,0 +1,114 @@ +# Author: Isotoxal + +"attribute" = { fg = "blue" } +"comment" = { fg = "comment", modifiers = ["italic"] } +"constant" = { fg = "cyan" } +"constant.builtin.boolean" = { fg = "cyan" } +"constant.character" = { fg = "blue" } +"constant.numeric.float" = { fg = "black-light" } +"constant.builtin" = { fg = "blue" } +"constant.numeric" = { fg = "yellow" } +"constructor" = { fg = "blue" } +"function" = { fg = "red" } +"function.builtin" = { fg = "cyan-light" } +"function.macro" = { fg = "green" } +"function.method" = { fg = "blue-light" } +"keyword" = { fg = "blue" } +"keyword.function" = { fg = "blue" } +"keyword.operator" = { fg = "blue-light" } +"keyword.control.conditional" = { fg = "red" } +"keyword.control.import" = { fg = "red-light" } +"keyword.control.return" = { fg = "blue" } +"keyword.control.repeat" = { fg = "yellow-light" } +"keyword.control.exception" = { fg = "black-light" } +"label" = { fg = "blue" } +"namespace" = { fg = "red-light" } +"operator" = { fg = "white" } +#"parameter.reference" = { fg = "red-light" } +#"property" = { fg = "red" } +"punctuation.bracket" = { fg = "white" } +"punctuation.delimiter" = { fg = "white" } +"punctuation.special" = { fg = "white" } +"string" = { fg = "green" } +"string.escape" = { fg = "blue" } +"string.regex" = { fg = "green" } +"string.special" = { fg = "blue" } +"string.special.symbol" = { fg = "red" } +"tag" = { fg = "blue" } +"type" = { fg = "yellow" } +"type.builtin" = { fg = "yellow" } +"variable" = { fg = "white" } +"variable.builtin" = { fg = "blue" } +"variable.parameter" = { fg = "red" } +"variable.other.member" = { fg = "red" } + +"diff.plus" = { fg = "blue" } +"diff.delta" = { fg = "magenta" } +"diff.minus" = { fg = "red" } + +"ui.background" = { fg = "foreground", bg = "background" } +"ui.cursor" = { modifiers = ["reversed"] } +"ui.cursorline.primary" = { bg = "cursorline" } +"ui.help" = { fg = "foreground", bg = "contrast" } +"ui.linenr" = { fg = "comment" } +"ui.linenr.selected" = { fg = "foreground" } +"ui.menu" = { fg = "foreground", bg = "contrast" } +"ui.menu.selected" = { bg = "black" } +"ui.popup" = { fg = "foreground", bg = "contrast" } +"ui.selection" = { bg = "black" } +"ui.selection.primary" = { bg = "black" } +"ui.statusline" = { fg = "foreground", bg = "background" } +"ui.statusline.inactive" = { fg = "foreground", bg = "background" } +"ui.statusline.normal" = { fg = "white", bg = "background" } +"ui.statusline.insert" = { fg = "blue", bg = "background" } +"ui.statusline.select" = { fg = "cyan", bg = "magenta" } +"ui.text" = { fg = "foreground" } +"ui.text.focus" = { fg = "blue" } +"ui.virtual.ruler" = { bg = "cursorline" } +"ui.virtual.whitespace" = { fg = "comment" } +"ui.virtual.wrap" = { fg = "comment" } +"ui.virtual.indent-guide" = { fg = "comment" } +"ui.window" = { fg = "black" } + +"error" = { fg = "red" } +"hint" = { fg = "green" } +"warning" = { fg = "yellow" } +"info" = { fg = "blue" } +"diagnostic.error" = { underline = { style = "curl", color = "red" } } +"diagnostic.warning" = { underline = { style = "curl", color = "yellow" } } +"diagnostic.info" = { underline = { style = "curl", color = "blue" } } +"diagnostic.hint" = { underline = { style = "curl", color = "green" } } +"special" = { fg = "red-light" } + +"markup.heading" = { fg = "blue", modifiers = ["bold"] } +"markup.list" = { fg = "cyan" } +"markup.bold" = { fg = "magenta", modifiers = ["bold"] } +"markup.italic" = { fg = "yellow", modifiers = ["italic"] } +"markup.strikethrough" = { modifiers = ["crossed_out"] } +"markup.link.url" = { fg = "green" } +"markup.link.text" = { fg = "black-light" } +"markup.quote" = { fg = "yellow", modifiers = ["italic"] } +"markup.raw" = { fg = "cyan" } + +[palette] +black = "#232a2d" +red = "#e57474" +green = "#8ccf7e" +yellow = "#e5c76b" +blue = "#67b0e8" +magenta = "#c47fd5" +cyan = "#6cbfbf" +white = "#b3b9b8" +black-light = "#2d3437" +red-light = "#ef7e7e" +green-light = "#96d988" +yellow-light = "#f4d67a" +blue-light = "#71baf2" +magenta-light = "#ce89df" +cyan-light = "#67cbe7" +white-light = "#bdc3c2" +comment = "#404749" +contrast = "#161d1f" +background = "#141b1e" +foreground = "#dadada" +cursorline = "#2c3333" From 5ef3f5f59f37df04267fdebd64d9b51b5c4419a7 Mon Sep 17 00:00:00 2001 From: Mofiqul Date: Tue, 28 Feb 2023 08:14:46 +0530 Subject: [PATCH 11/81] theme: Add Adwaita Dark (#6042) --- runtime/themes/adwaita-dark.toml | 169 +++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 runtime/themes/adwaita-dark.toml diff --git a/runtime/themes/adwaita-dark.toml b/runtime/themes/adwaita-dark.toml new file mode 100644 index 000000000..f339e33c1 --- /dev/null +++ b/runtime/themes/adwaita-dark.toml @@ -0,0 +1,169 @@ +# Author: Mofiqul Islam + +"attribute" = "orange_4" + +"type" = "teal_2" +"type.builtin" = "teal_2" + +"constructor" = "blue_2" + +"constant" = "violet_2" +"constant.builtin" = { fg = "violet_2", modifiers = ["bold"] } +"constant.character" = "teal_3" +"constant.numeric" = { fg = "teal_3", modifiers = ["bold"] } +"constant.character.escape" = "violet_2" + +"string" = "teal_2" +"string.regexp" = "purple_2" +"string.special" = "blue_2" + +"comment" = "dark_2" + +"variable" = "light_4" +"variable.parameter" = "orange_2" +"variable.builtin" = "orange_2" +"variable.other" = "teal_2" +"variable.other.member" = "teal_2" + +"label" = "purple_2" + +"punctuation" = "light_4" +"punctuation.delimiter" = "light_4" +"punctuation.bracket" = "light_4" +"punctuation.special" = "red_3" + +"keyword" = { fg = "orange_2", modifiers = ["bold"] } +"keyword.control" = { fg = "orange_2", modifiers = ["bold"] } +"keyword.operator" = "purple_2" +"keyword.directive" = { fg = "orange_2", modifiers = ["bold"] } +"keyword.function" = "orange_2" +"keyword.storage" = { fg = "orange_2", modifiers = ["bold"] } + +"operator" = "purple_2" + +"function" = "blue_2" +"function.builtin" = "blue_2" +"function.macro" = { fg = "blue_2", modifiers = ["bold"] } +"function.special" = { fg = "blue_2", modifiers = ["bold"] } + +"tag" = "teal_2" + +"namespace" = "orange_2" + +"markup" = "light_4" +"markup.heading" = { fg = "teal_2", modifiers = ["bold"] } +"markup.list" = { fg = "orange_2", modifiers = ["bold"] } +"markup.bold" = { fg = "light_4", modifiers = ["bold"] } +"markup.italic" = { fg = "light_4", modifiers = ["italic"] } +"markup.link" = { fg = "blue_3", modifiers = ["underlined"] } +"markup.quote" = { fg = "light_3", modifiers = ["italic"] } +"diff.plus" = "teal_3" +"diff.minus" = "red_1" +"diff.delta" = "orange_3" +"diff.delta.moved" = "orange_2" + +"ui.background" = { fg = "light_4", bg = "libadwaita_dark" } +"ui.background.separator" = { fg = "split_and_borders", bg = "libadwaita_dark" } +"ui.cursor" = { fg = "libadwaita_dark", bg = "light_5" } +"ui.cursor.insert" = { fg = "libadwaita_dark", bg = "light_5" } +"ui.cursor.select" = { fg = "libadwaita_dark", bg = "light_5" } +"ui.cursor.match" = { fg = "libadwaita_dark", bg = "blue_2" } +"ui.cursor.primary" = { fg = "libadwaita_dark", bg = "light_7" } +"ui.linenr" = "dark_2" +"ui.linenr.selected" = { fg = "light_7", bg = "libadwaita_dark_alt", modifiers = [ + "bold", +] } +"ui.statusline" = { fg = "light_4", bg = "libadwaita_dark_alt" } +"ui.statusline.inactive" = { fg = "light_4", bg = "libadwaita_dark_alt" } +"ui.statusline.insert" = { fg = "light_4", bg = "teal_4" } +"ui.statusline.select" = { fg = "light_4", bg = "blue_4" } +"ui.popup" = { bg = "libadwaita_popup" } +"ui.window" = "split_and_borders" +"ui.help" = { bg = "libadwaita_dark_alt" } +"ui.text" = "light_4" +"ui.virtual" = "dark_1" +"ui.menu" = { fg = "light_4", bg = "libadwaita_popup" } +"ui.menu.selected" = { fg = "light_4", bg = "blue_5" } +"ui.menu.scroll" = { fg = "light_7", bg = "dark_3" } +"ui.selection" = { bg = "blue_7" } +"ui.selection.primary" = { bg = "blue_7" } +"ui.cursorline.primary" = { bg = "libadwaita_dark_alt" } + +"warning" = "yellow_2" +"error" = "red_4" +"info" = "purple_2" +"hint" = "blue_2" + +"diagnostic.hint" = { fg = "blue_2", modifiers = ["dim"] } +"diagnostic.info" = { fg = "purple_2", modifiers = ["dim"] } +"diagnostic.error" = { fg = "red_4", modifiers = ["underlined"] } +"diagnostic.warning" = { fg = "yellow_2", modifiers = ["underlined"] } + +[palette] +blue_1 = "#99C1F1" +blue_2 = "#62A0EA" +blue_3 = "#3584E4" +blue_4 = "#1C71D8" +blue_5 = "#1A5FB4" +blue_6 = "#1B497E" +blue_7 = "#193D66" +brown_1 = "#CDAB8F" +brown_2 = "#B5835A" +brown_3 = "#986A44" +brown_4 = "#865E3C" +brown_5 = "#63452C" +chameleon_3 = "#4E9A06" +dark_1 = "#77767B" +dark_2 = "#5E5C64" +dark_3 = "#504E55" +dark_4 = "#3D3846" +dark_5 = "#241F31" +dark_6 = "#000000" +dark_7 = "#1c1c1c" +green_1 = "#8FF0A4" +green_2 = "#57E389" +green_3 = "#33D17A" +green_4 = "#2EC27E" +green_5 = "#26A269" +green_6 = "#1F7F56" +green_7 = "#1C6849" +libadwaita_dark = "#1D1D1D" +libadwaita_dark_alt = "#303030" +libadwaita_popup = "#282828" +light_1 = "#FFFFFF" +light_2 = "#FCFCFC" +light_3 = "#F6F5F4" +light_4 = "#DEDDDA" +light_5 = "#C0BFBC" +light_6 = "#B0AFAC" +light_7 = "#9A9996" +orange_1 = "#FFBE6F" +orange_2 = "#FFA348" +orange_3 = "#FF7800" +orange_4 = "#E66100" +orange_5 = "#C64600" +purple_1 = "#DC8ADD" +purple_2 = "#C061CB" +purple_3 = "#9141AC" +purple_4 = "#813D9C" +purple_5 = "#613583" +red_1 = "#F66151" +red_2 = "#ED333B" +red_3 = "#E01B24" +red_4 = "#C01C28" +red_5 = "#A51D2D" +teal_1 = "#93DDC2" +teal_2 = "#5BC8AF" +teal_3 = "#33B2A4" +teal_4 = "#26A1A2" +teal_5 = "#218787" +violet_2 = "#7D8AC7" +violet_3 = "#6362C8" +violet_4 = "#4E57BA" +yellow_1 = "#F9F06B" +yellow_2 = "#F8E45C" +yellow_3 = "#F6D32D" +yellow_4 = "#F5C211" +yellow_5 = "#E5A50A" +yellow_6 = "#D38B09" +split_and_borders = "#4F4F4F" From a976786a4fa013150ec1b59ae55276361e61340f Mon Sep 17 00:00:00 2001 From: Mathieu Agopian Date: Tue, 28 Feb 2023 04:03:56 +0100 Subject: [PATCH 12/81] book: Document h and g (#6124) --- book/src/keymap.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 8864ab696..87867024b 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -268,28 +268,30 @@ Accessed by typing `Space` in [normal mode](#normal-mode). This layer is a kludge of mappings, mostly pickers. -| Key | Description | Command | -| ----- | ----------- | ------- | -| `f` | Open file picker | `file_picker` | -| `F` | Open file picker at current working directory | `file_picker_in_current_directory` | -| `b` | Open buffer picker | `buffer_picker` | -| `j` | Open jumplist picker | `jumplist_picker` | -| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` | -| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | -| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | -| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` | -| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` | -| `r` | Rename symbol (**LSP**) | `rename_symbol` | -| `a` | Apply code action (**LSP**) | `code_action` | -| `'` | Open last fuzzy picker | `last_picker` | -| `w` | Enter [window mode](#window-mode) | N/A | -| `p` | Paste system clipboard after selections | `paste_clipboard_after` | -| `P` | Paste system clipboard before selections | `paste_clipboard_before` | -| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` | -| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | -| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | -| `/` | Global search in workspace folder | `global_search` | -| `?` | Open command palette | `command_palette` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `f` | Open file picker | `file_picker` | +| `F` | Open file picker at current working directory | `file_picker_in_current_directory` | +| `b` | Open buffer picker | `buffer_picker` | +| `j` | Open jumplist picker | `jumplist_picker` | +| `g` | Debug (experimental) | N/A | +| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` | +| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | +| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | +| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` | +| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` | +| `r` | Rename symbol (**LSP**) | `rename_symbol` | +| `a` | Apply code action (**LSP**) | `code_action` | +| `h` | Select symbol references (**LSP**) | `select_references_to_symbol_under_cursor` | +| `'` | Open last fuzzy picker | `last_picker` | +| `w` | Enter [window mode](#window-mode) | N/A | +| `p` | Paste system clipboard after selections | `paste_clipboard_after` | +| `P` | Paste system clipboard before selections | `paste_clipboard_before` | +| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` | +| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | +| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | +| `/` | Global search in workspace folder | `global_search` | +| `?` | Open command palette | `command_palette` | > TIP: Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file. From 79bf5e3094e16a34637703b14c7bf090d2dcf155 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 10 Jan 2023 20:48:26 -0600 Subject: [PATCH 13/81] Update crossterm to 0.26.1 Crossterm 0.26.x includes a breaking change for the command to set the cursor shape. This commit includes a change which uses the new type. --- Cargo.lock | 4 ++-- helix-term/Cargo.toml | 2 +- helix-tui/Cargo.toml | 2 +- helix-tui/src/backend/crossterm.rs | 10 +++++----- helix-view/Cargo.toml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb3300a34..c74e8e40c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,9 +261,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" dependencies = [ "bitflags", "crossterm_winapi", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 4921db926..975d08712 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -37,7 +37,7 @@ which = "4.4" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.25", features = ["event-stream"] } +crossterm = { version = "0.26", features = ["event-stream"] } signal-hook = "0.3" tokio-stream = "0.1" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index a4a1c389f..ccd016f50 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -19,7 +19,7 @@ default = ["crossterm"] bitflags = "1.3" cassowary = "0.3" unicode-segmentation = "1.10" -crossterm = { version = "0.25", optional = true } +crossterm = { version = "0.26", optional = true } termini = "0.1" serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index c00e1f406..5305640cb 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -1,6 +1,6 @@ use crate::{backend::Backend, buffer::Cell}; use crossterm::{ - cursor::{CursorShape, Hide, MoveTo, SetCursorShape, Show}, + cursor::{Hide, MoveTo, SetCursorStyle, Show}, execute, queue, style::{ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, @@ -156,12 +156,12 @@ where fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { let shape = match kind { - CursorKind::Block => CursorShape::Block, - CursorKind::Bar => CursorShape::Line, - CursorKind::Underline => CursorShape::UnderScore, + CursorKind::Block => SetCursorStyle::SteadyBlock, + CursorKind::Bar => SetCursorStyle::SteadyBar, + CursorKind::Underline => SetCursorStyle::SteadyUnderScore, CursorKind::Hidden => unreachable!(), }; - map_error(execute!(self.buffer, Show, SetCursorShape(shape))) + map_error(execute!(self.buffer, Show, shape)) } fn get_cursor(&mut self) -> io::Result<(u16, u16)> { diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 17e07e9a2..54b679ade 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -20,7 +20,7 @@ helix-core = { version = "0.6", path = "../helix-core" } helix-loader = { version = "0.6", path = "../helix-loader" } helix-lsp = { version = "0.6", path = "../helix-lsp" } helix-dap = { version = "0.6", path = "../helix-dap" } -crossterm = { version = "0.25", optional = true } +crossterm = { version = "0.26", optional = true } helix-vcs = { version = "0.6", path = "../helix-vcs" } # Conversion traits From a066815833b322233dc11aeae38679bc8466ec56 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sat, 26 Nov 2022 13:04:27 -0600 Subject: [PATCH 14/81] Enable the enhanced keyboard protocol if supported --- helix-term/src/application.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index c8e8ecb1a..d78964194 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -40,7 +40,8 @@ use anyhow::{Context, Error}; use crossterm::{ event::{ DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, - EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, + EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, KeyboardEnhancementFlags, + PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, }, execute, terminal, tty::IsTty, @@ -111,6 +112,9 @@ fn restore_term() -> Result<(), Error> { let mut stdout = stdout(); // reset cursor shape write!(stdout, "\x1B[0 q")?; + if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { + execute!(stdout, PopKeyboardEnhancementFlags)?; + } // Ignore errors on disabling, this might trigger on windows if we call // disable without calling enable previously let _ = execute!(stdout, DisableMouseCapture); @@ -1062,6 +1066,19 @@ impl Application { if self.config.load().editor.mouse { execute!(stdout, EnableMouseCapture)?; } + if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { + log::debug!("The enhanced keyboard protocol is supported on this terminal"); + execute!( + stdout, + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS + ) + )?; + } else { + log::debug!("The enhanced keyboard protocol is not supported on this terminal"); + } + Ok(()) } From 8dab8a0a039fe1f3dd98fc62ac97d2f1c089793a Mon Sep 17 00:00:00 2001 From: lesleyrs <19632758+lesleyrs@users.noreply.github.com> Date: Tue, 29 Nov 2022 23:31:18 +0100 Subject: [PATCH 15/81] Add shift-backspace keybind alias for backspace (#4937) When the Kitty Keyboard Protocol is enabled, S-backspace is distinguished from backspace with no modifiers. This is awkward when typing because it's very easy to accidentally hold shift and press backspace temporarily when typing capital letters. Kakoune (which is also a Kitty Keyboard Protocol application) treats S-backspace as backspace too: https://github.com/mawww/kakoune/blob/3150e9b3cd8e61d9bc68245d67822614d4376cf4/src/input_handler.cc#L1275 --- book/src/keymap.md | 4 ++-- helix-term/src/keymap/default.rs | 2 +- helix-term/src/ui/prompt.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 87867024b..bc16aa1a7 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -352,7 +352,7 @@ experience. | `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` | | `Ctrl-u` | Delete to start of line | `kill_to_line_start` | | `Ctrl-k` | Delete to end of line | `kill_to_line_end` | -| `Ctrl-h`, `Backspace` | Delete previous char | `delete_char_backward` | +| `Ctrl-h`, `Backspace`, `Shift-Backspace` | Delete previous char | `delete_char_backward` | | `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` | | `Ctrl-j`, `Enter` | Insert new line | `insert_newline` | @@ -433,7 +433,7 @@ Keys to use within prompt, Remapping currently not supported. | `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word | | `Ctrl-u` | Delete to start of line | | `Ctrl-k` | Delete to end of line | -| `Backspace`, `Ctrl-h` | Delete previous char | +| `Backspace`, `Ctrl-h`, `Shift-Backspace` | Delete previous char | | `Delete`, `Ctrl-d` | Delete next char | | `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later | | `Ctrl-p`, `Up` | Select previous history | diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 01184f80e..7425c8155 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -363,7 +363,7 @@ pub fn default() -> HashMap { "A-d" | "A-del" => delete_word_forward, "C-u" => kill_to_line_start, "C-k" => kill_to_line_end, - "C-h" | "backspace" => delete_char_backward, + "C-h" | "backspace" | "S-backspace" => delete_char_backward, "C-d" | "del" => delete_char_forward, "C-j" | "ret" => insert_newline, "tab" => insert_tab, diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index f438231fa..35ae8c2a8 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -516,7 +516,7 @@ impl Component for Prompt { alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx.editor), ctrl!('k') => self.kill_to_end_of_line(cx.editor), ctrl!('u') => self.kill_to_start_of_line(cx.editor), - ctrl!('h') | key!(Backspace) => { + ctrl!('h') | key!(Backspace) | shift!(Backspace) => { self.delete_char_backwards(cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } From 27211abf0688794f9bf7a395a8c47846b4f7fd41 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 28 Feb 2023 19:26:02 -0600 Subject: [PATCH 16/81] Ignore key-release keyboard events (#6139) Since crossterm 0.26.x, we receive press/release keyboard events on Windows always. We can ignore the release events though to emulate the behavior of keyboard input on Windows on crossterm 0.25.x. --- helix-term/src/application.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index d78964194..ee2a438c0 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -638,6 +638,11 @@ impl Application { self.compositor .handle_event(&Event::Resize(width, height), &mut cx) } + // Ignore keyboard release events. + CrosstermEvent::Key(crossterm::event::KeyEvent { + kind: crossterm::event::KeyEventKind::Release, + .. + }) => false, event => self.compositor.handle_event(&event.into(), &mut cx), }; From c082ef28632e8a92a91926f3714808942238098b Mon Sep 17 00:00:00 2001 From: NomisIV <47303199+NomisIV@users.noreply.github.com> Date: Wed, 1 Mar 2023 07:45:27 +0100 Subject: [PATCH 17/81] Fix indentation lines (#6134) (#6136) --- helix-term/src/ui/document.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index ed4b1de90..4f615f7bd 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -322,7 +322,7 @@ pub struct TextRenderer<'a> { pub nbsp: String, pub space: String, pub tab: String, - pub tab_width: u16, + pub indent_width: u16, pub starting_indent: usize, pub draw_indent_guides: bool, pub col_offset: usize, @@ -370,16 +370,19 @@ impl<'a> TextRenderer<'a> { let text_style = theme.get("ui.text"); + let indent_width = doc.indent_style.indent_width(tab_width) as u16; + TextRenderer { surface, indent_guide_char: editor_config.indent_guides.character.into(), newline, nbsp, space, - tab_width: tab_width as u16, tab, whitespace_style: theme.get("ui.virtual.whitespace"), - starting_indent: (col_offset / tab_width) + indent_width, + starting_indent: col_offset / indent_width as usize + + (col_offset % indent_width as usize != 0) as usize + editor_config.indent_guides.skip_levels as usize, indent_guide_style: text_style.patch( theme @@ -461,14 +464,14 @@ impl<'a> TextRenderer<'a> { // Don't draw indent guides outside of view let end_indent = min( indent_level, - // Add tab_width - 1 to round up, since the first visible + // Add indent_width - 1 to round up, since the first visible // indent might be a bit after offset.col - self.col_offset + self.viewport.width as usize + (self.tab_width - 1) as usize, - ) / self.tab_width as usize; + self.col_offset + self.viewport.width as usize + (self.indent_width as usize - 1), + ) / self.indent_width as usize; for i in self.starting_indent..end_indent { - let x = - (self.viewport.x as usize + (i * self.tab_width as usize) - self.col_offset) as u16; + let x = (self.viewport.x as usize + (i * self.indent_width as usize) - self.col_offset) + as u16; let y = self.viewport.y + row; debug_assert!(self.surface.in_bounds(x, y)); self.surface From 0625f410ebdab22e54af10f934de41bdba911069 Mon Sep 17 00:00:00 2001 From: Andrey Grebenyk <35263631+x318@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:59:16 +0300 Subject: [PATCH 18/81] Add graphql schema file type (#6159) Co-authored-by: Andrey Grebenyk --- languages.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages.toml b/languages.toml index d4ea9a867..006db633c 100644 --- a/languages.toml +++ b/languages.toml @@ -1190,7 +1190,7 @@ source = { git = "https://github.com/shunsambongi/tree-sitter-gitignore", rev = name = "graphql" scope = "source.graphql" injection-regex = "graphql" -file-types = ["gql", "graphql"] +file-types = ["gql", "graphql", "graphqls"] roots = [] indent = { tab-width = 2, unit = " " } From 6e7dcb33170e50088de6734a046480917a386049 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 2 Mar 2023 17:01:47 -0600 Subject: [PATCH 19/81] CI: Update cachix/install-nix-action to v20 (#6163) This fixes an issue with installing Nix 1.14 which causes the cachix/cachix-action in the next step to fail. --- .github/workflows/cachix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cachix.yml b/.github/workflows/cachix.yml index bcdea3197..7d2f734aa 100644 --- a/.github/workflows/cachix.yml +++ b/.github/workflows/cachix.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - name: Install nix - uses: cachix/install-nix-action@v19 + uses: cachix/install-nix-action@v20 - name: Authenticate with Cachix uses: cachix/cachix-action@v12 From ddc5bf4e606230dd8bc20c59b1cc114e93bbf1c0 Mon Sep 17 00:00:00 2001 From: nuid32 <91177333+nuid32@users.noreply.github.com> Date: Fri, 3 Mar 2023 11:50:26 +0500 Subject: [PATCH 20/81] Fix 'attempt to divide by zero' panic (#6155) --- helix-term/src/ui/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/text.rs b/helix-term/src/ui/text.rs index c318052b2..a379536f8 100644 --- a/helix-term/src/ui/text.rs +++ b/helix-term/src/ui/text.rs @@ -58,7 +58,7 @@ pub fn required_size(text: &tui::text::Text, max_text_width: u16) -> (u16, u16) let content_width = content.width() as u16; if content_width > max_text_width { text_width = max_text_width; - height += content_width / max_text_width; + height += content_width.checked_div(max_text_width).unwrap_or(0); } else if content_width > text_width { text_width = content_width; } From 2d5577dbe6b353bd266154ab693ffedc521afee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Donk=C3=B3?= Date: Fri, 3 Mar 2023 17:03:03 +0100 Subject: [PATCH 21/81] Extend the set of tags highlighted in comments (#6143) --- runtime/queries/comment/highlights.scm | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/runtime/queries/comment/highlights.scm b/runtime/queries/comment/highlights.scm index 88685d59a..3b25531a8 100644 --- a/runtime/queries/comment/highlights.scm +++ b/runtime/queries/comment/highlights.scm @@ -5,17 +5,33 @@ ":" @punctuation.delimiter +; Hint level tags +((tag (name) @hint) + (#match? @hint "^(HINT|MARK)$")) + +("text" @hint + (#match? @hint "^(HINT|MARK)$")) + +; Info level tags +((tag (name) @info) + (#match? @info "^(INFO|NOTE|TODO)$")) + +("text" @info + (#match? @info "^(INFO|NOTE|TODO)$")) + +; Warning level tags ((tag (name) @warning) - (#match? @warning "^(TODO|HACK|WARNING)$")) + (#match? @warning "^(HACK|WARN|WARNING)$")) ("text" @warning - (#match? @warning "^(TODO|HACK|WARNING)$")) + (#match? @warning "^(HACK|WARN|WARNING)$")) +; Error level tags ((tag (name) @error) - (match? @error "^(FIXME|XXX|BUG)$")) + (match? @error "^(BUG|FIXME|ISSUE|XXX)$")) ("text" @error - (match? @error "^(FIXME|XXX|BUG)$")) + (match? @error "^(BUG|FIXME|ISSUE|XXX)$")) (tag (name) @ui.text From 5c716af7a2c2fff36080d51be3cb9fa30aa36bc7 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Fri, 3 Mar 2023 17:05:40 +0100 Subject: [PATCH 22/81] Fix scrolloff at view bottom (#6142) Fixes a regression introduced in #5420 where a scrolloff of `x - 1` was used instead if `x` at the bottom of the screen. This was especially problematic if the scrolloff was set to `0` in that case the scrolloff behaved as tough set to `-1` and the cursor disappeared from the view if scrolled to the botoom. --- helix-view/src/view.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index f793cbe36..7bfbb2418 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -242,7 +242,7 @@ impl View { at_top = true; true } - Some((visual_pos, _)) if visual_pos.row >= vertical_viewport_end - scrolloff => { + Some((visual_pos, _)) if visual_pos.row + scrolloff + 1 >= vertical_viewport_end => { if CENTERING && visual_pos.row >= vertical_viewport_end { // cursor out of view return None; @@ -257,7 +257,7 @@ impl View { let v_off = if at_top { scrolloff as isize } else { - viewport.height as isize - scrolloff as isize + viewport.height as isize - scrolloff as isize - 1 }; (offset.anchor, offset.vertical_offset) = char_idx_at_visual_offset(doc_text, cursor, -v_off, 0, &text_fmt, &annotations); From 2bd8bc8d8484a4a8852280f16888a819acbfde7a Mon Sep 17 00:00:00 2001 From: Matthias Q <35303817+matthias-Q@users.noreply.github.com> Date: Fri, 3 Mar 2023 20:12:37 +0100 Subject: [PATCH 23/81] feat(prql): add prql support (#6126) --- book/src/generated/lang-support.md | 1 + languages.toml | 13 +++ runtime/queries/prql/highlights.scm | 136 ++++++++++++++++++++++++++++ runtime/queries/prql/injections.scm | 8 ++ 4 files changed, 158 insertions(+) create mode 100644 runtime/queries/prql/highlights.scm create mode 100644 runtime/queries/prql/injections.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index e997b3e81..cf8e50236 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -104,6 +104,7 @@ | prisma | ✓ | | | `prisma-language-server` | | prolog | | | | `swipl` | | protobuf | ✓ | | ✓ | | +| prql | ✓ | | | | | purescript | ✓ | | | `purescript-language-server` | | python | ✓ | ✓ | ✓ | `pylsp` | | qml | ✓ | | ✓ | `qmlls` | diff --git a/languages.toml b/languages.toml index 006db633c..b0a34be0d 100644 --- a/languages.toml +++ b/languages.toml @@ -2208,6 +2208,19 @@ indent = { tab-width = 2, unit = " " } name = "yuck" source = { git = "https://github.com/Philipp-M/tree-sitter-yuck", rev = "9e97da5773f82123a8c8cccf8f7e795d140ed7d1" } +[[language]] +name = "prql" +scope = "source.prql" +injection-regex = "prql" +file-types = ["prql"] +roots = [] +comment-token = "#" +indent = { tab-width = 4, unit = " " } + +[[grammar]] +name = "prql" +source = { git = "https://github.com/PRQL/tree-sitter-prql", rev = "3f27cac466f030ee7d985d91eba5470e01dd21ea" } + [[language]] name = "po" scope = "source.po" diff --git a/runtime/queries/prql/highlights.scm b/runtime/queries/prql/highlights.scm new file mode 100644 index 000000000..5cfedee48 --- /dev/null +++ b/runtime/queries/prql/highlights.scm @@ -0,0 +1,136 @@ +[ + (keyword_from) + (keyword_filter) + (keyword_derive) + (keyword_group) + (keyword_aggregate) + (keyword_sort) + (keyword_take) + (keyword_window) + (keyword_join) + (keyword_select) + (keyword_switch) + (keyword_append) + (keyword_remove) + (keyword_intersect) + (keyword_rolling) + (keyword_rows) + (keyword_expanding) + (keyword_let) + (keyword_prql) + (keyword_from_text) +] @keyword + +(literal) @string + +(assignment + alias: (field) @variable.other.member) + +alias: (identifier) @variable.other.member + +(f_string) @string.special +(s_string) @string.special + +(comment) @comment + +(keyword_func) @keyword.function + +(function_call + (identifier) @function) + +[ + "+" + "-" + "*" + "/" + "=" + "==" + "<" + "<=" + "!=" + ">=" + ">" + "->" + (bang) +] @operator + +[ + "(" + ")" + "[" + "]" +] @punctuation.bracket + +[ + "," + "." + (pipe) +] @punctuation.delimiter + +(literal + (integer) @constant.numeric.integer) + +(integer) @constant.numeric.integer + +(literal + (decimal_number) @constant.numeric.float) + +(decimal_number) @constant.numeric.float + +[ + (keyword_min) + (keyword_max) + (keyword_count) + (keyword_count_distinct) + (keyword_average) + (keyword_avg) + (keyword_sum) + (keyword_stddev) + (keyword_count) +] @function + +[ + (keyword_side) + (keyword_version) + (keyword_target) + (keyword_null) + (keyword_format) +] @attribute + +(target) @function.builtin + + [ + (date) + (time) + (timestamp) +] @string.special + +[ + (keyword_left) + (keyword_inner) + (keyword_right) + (keyword_full) + (keyword_csv) + (keyword_json) +] @function.method + +[ + (keyword_true) + (keyword_false) +] @constant.builtin.boolean + +[ + (keyword_and) + (keyword_or) +] @keyword.operator + +(function_definition + (keyword_func) + name: (identifier) @function) + +(parameter + (identifier) @variable.parameter) + +(variable + (keyword_let) + name: (identifier) @constant) diff --git a/runtime/queries/prql/injections.scm b/runtime/queries/prql/injections.scm new file mode 100644 index 000000000..02a8919f8 --- /dev/null +++ b/runtime/queries/prql/injections.scm @@ -0,0 +1,8 @@ +((s_string) @injection.content + (#set! injection.language "sql")) + +(from_text + (keyword_from_text) + (keyword_json) + (literal) @injection.content + (#set! injection.language "json")) From bf872366fdc9d31383cc84a35bd8d30b1a9b9da6 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 4 Mar 2023 23:04:17 +0530 Subject: [PATCH 24/81] Document the file-modification-indicator statusline element (#6036) --- book/src/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book/src/configuration.md b/book/src/configuration.md index 7514a3d0f..0b9ebe966 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -98,6 +98,7 @@ The following statusline elements can be configured: | `spinner` | A progress spinner indicating LSP activity | | `file-name` | The path/name of the opened file | | `file-base-name` | The basename of the opened file | +| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) | | `file-encoding` | The encoding of the opened file if it differs from UTF-8 | | `file-line-ending` | The file line endings (CRLF or LF) | | `total-line-numbers` | The total line numbers of the opened file | From a2e54167d8f9ad8764ba586488d3995aeb33a559 Mon Sep 17 00:00:00 2001 From: Alexander Brevig Date: Sun, 5 Mar 2023 02:52:20 +0100 Subject: [PATCH 25/81] fix: Handle signals before crossterm events (#6170) This is a workaround for a freeze when suspending Helix with C-z on non-Windows systems. The check for the keyboard enhancement protocol locks up crossterm's internal event reading/polling system by trying to set up multiple concurrent readers. `input_stream.next()` sets up one reader looking for regular crossterm events while the `supports_keyboard_enhancement` query sets up another looking for internal events. The latter hangs for two seconds or until the former yields an event. By handling signals first we don't lock up the mutex by trying to read keyboard events. --- helix-term/src/application.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index ee2a438c0..df6d9da6c 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -345,12 +345,12 @@ impl Application { tokio::select! { biased; - Some(event) = input_stream.next() => { - self.handle_terminal_events(event).await; - } Some(signal) = self.signals.next() => { self.handle_signals(signal).await; } + Some(event) = input_stream.next() => { + self.handle_terminal_events(event).await; + } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render().await; From cf153080d7712f9a03e5e2c30bbae10eade1dc74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sun, 5 Mar 2023 03:21:31 +0100 Subject: [PATCH 26/81] Theme: Papercolor: Add ui.highlight (#6162) Using the picker with syntax highlighting the fallback `ui.selection` makes a lot of text, especially for the light variant, hard to read. Instead, use a lighter background for highlights --- runtime/themes/papercolor-dark.toml | 1 + runtime/themes/papercolor-light.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/runtime/themes/papercolor-dark.toml b/runtime/themes/papercolor-dark.toml index 088658e9e..eaaa36dcf 100644 --- a/runtime/themes/papercolor-dark.toml +++ b/runtime/themes/papercolor-dark.toml @@ -7,6 +7,7 @@ "ui.text.focus" = { fg = "selection_background", modifiers = ["bold"]} "ui.selection" = {bg="selection_background", fg="selection_foreground"} "ui.cursorline" = {bg="cursorline_background"} +"ui.highlight" = {bg="cursorline_background"} "ui.statusline" = {bg="paper_bar_bg", fg="regular0"} "ui.statusline.select" = {bg="background", fg="bright7"} "ui.statusline.normal" = {bg="background", fg="bright3"} diff --git a/runtime/themes/papercolor-light.toml b/runtime/themes/papercolor-light.toml index c44c67091..63671e1b3 100644 --- a/runtime/themes/papercolor-light.toml +++ b/runtime/themes/papercolor-light.toml @@ -6,6 +6,7 @@ "ui.text" = "foreground" "ui.text.focus" = { fg = "selection_background", modifiers = ["bold"]} "ui.selection" = {bg="selection_background", fg="selection_foreground"} +"ui.highlight" = {bg="cursorline_background"} "ui.cursorline" = {bg="cursorline_background"} "ui.statusline" = {bg="paper_bar_bg", fg="regular0"} "ui.statusline.select" = {bg="background", fg="bright7"} From 725d9aecf08262e83553e54aa57d9bbec4841c80 Mon Sep 17 00:00:00 2001 From: Roberto Vidal Date: Sun, 5 Mar 2023 03:36:01 +0100 Subject: [PATCH 27/81] Add support for reStructuredText (#6180) --- book/src/generated/lang-support.md | 1 + languages.toml | 11 +++++++++ runtime/queries/rst/highlights.scm | 38 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 runtime/queries/rst/highlights.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index cf8e50236..48cb66f1f 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -114,6 +114,7 @@ | rescript | ✓ | ✓ | | `rescript-language-server` | | rmarkdown | ✓ | | ✓ | `R` | | ron | ✓ | | ✓ | | +| rst | ✓ | | | | | ruby | ✓ | ✓ | ✓ | `solargraph` | | rust | ✓ | ✓ | ✓ | `rust-analyzer` | | sage | ✓ | ✓ | | | diff --git a/languages.toml b/languages.toml index b0a34be0d..e9a77b5a4 100644 --- a/languages.toml +++ b/languages.toml @@ -2244,3 +2244,14 @@ indent = { tab-width = 8, unit = " " } [[grammar]] name = "nasm" source = { git = "https://github.com/naclsn/tree-sitter-nasm", rev = "a0db15db6fcfb1bf2cc8702500e55e558825c48b" } + +[[language]] +name = "rst" +scope = "source.rst" +comment-token = ".." +file-types = ["rst"] +roots = [] + +[[grammar]] +name = "rst" +source = { git = "https://github.com/stsewd/tree-sitter-rst", rev = "25e6328872ac3a764ba8b926aea12719741103f1" } diff --git a/runtime/queries/rst/highlights.scm b/runtime/queries/rst/highlights.scm new file mode 100644 index 000000000..73c0def43 --- /dev/null +++ b/runtime/queries/rst/highlights.scm @@ -0,0 +1,38 @@ +(comment) @comment + +[ + (title) +] @markup.heading.1 + +[ + "adornment" +] @markup.heading.marker + +[ + (target) + (reference) +] @markup.link.url + +[ + "bullet" +] @markup.list.unnumbered + +(strong) @markup.bold +(emphasis) @markup.italic +(literal) @markup.raw.inline + +(list_item + (term) @markup.bold + (classifier)? @markup.italic) + +(directive + [".." (type) "::"] @function +) + +(field + [":" (field_name) ":"] @variable.other.member +) + +(interpreted_text) @markup.raw.inline + +(interpreted_text (role)) @keyword From ac9e0b39f2d217f1c40a7e536e15009b21423610 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 20 Feb 2023 16:48:13 +0100 Subject: [PATCH 28/81] upgrade `git-repository` to `gix` 0.36.1; up min. rustc version to 1.64 This fixes breakage when installing `helix` due to an incorrect usage of `as_ref()` when interacting with `bstr` in the `gitoxide` codebase. However, this upgrade also requires a higher rustc version, as `gitoxide` recently updated its `windows` crate version. --- Cargo.lock | 575 ++++++++++++++++++++----------------------- helix-vcs/Cargo.toml | 4 +- helix-vcs/src/git.rs | 21 +- rust-toolchain.toml | 2 +- 4 files changed, 283 insertions(+), 319 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c74e8e40c..1c4549b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", "once_cell", @@ -383,6 +383,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + [[package]] name = "either" version = "1.8.0" @@ -561,75 +567,118 @@ dependencies = [ ] [[package]] -name = "git-actor" -version = "0.17.0" +name = "gix" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d5dbcb1efbee862cdc851a23377e1a8a5c1a8971740b4933d4ce022a0889a8" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-discover", + "gix-features", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-index", + "gix-lock", + "gix-mailmap", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-prompt", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-sec", + "gix-tempfile", + "gix-traverse", + "gix-url", + "gix-validate", + "gix-worktree", + "log", + "once_cell", + "prodash", + "signal-hook", + "smallvec", + "thiserror", + "unicode-normalization", +] + +[[package]] +name = "gix-actor" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9e5fd7bc63ad527d64584f8d01f99b89c051f5fbb8144b58ae5f812775065cf" +checksum = "381153ea93b9d8a5c6894a5c734b2e9c15d623063adfd2bda4342ecf90f9a5f8" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "btoi", - "git-date", + "gix-date", "itoa", "nom", "quick-error", ] [[package]] -name = "git-attributes" -version = "0.8.0" +name = "gix-attributes" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8013dfce47c1e29236d732308933e2c77af5355ec5105755d26faf7764d3f7b" +checksum = "df09b20424fd4cee04c43b50df954c4b119c45b769639b60d80ee8bb6d84e0aa" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "compact_str", - "git-features", - "git-glob", - "git-path", - "git-quote", + "gix-features", + "gix-glob", + "gix-path", + "gix-quote", "thiserror", "unicode-bom", ] [[package]] -name = "git-bitmap" -version = "0.2.0" +name = "gix-bitmap" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44304093ac66a0ada1b243c15c3a503a165a1d0f50bec748f4e5a9b84a0d0722" +checksum = "5229fd26e288f417c8dd2385c5bc740415eb55aba4d6f529db7ad4b526771e06" dependencies = [ "quick-error", ] [[package]] -name = "git-chunk" -version = "0.4.0" +name = "gix-chunk" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3090baa2f4a3fe488a9b3e31090b83259aaf930bf0634af34c18117274f8f1a8" +checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978" dependencies = [ "thiserror", ] [[package]] -name = "git-command" -version = "0.2.1" +name = "gix-command" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "215145cc1686a45bc6f9872b153a0d3f3c40a1b94173a928325e1b53dfa5e2af" +checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", ] [[package]] -name = "git-config" -version = "0.15.0" +name = "gix-config" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9da662fd64ac69772158dcf04777da6266f0f36bc9a310b3eb2d805bb696315" +checksum = "398b5003d5e4991355528e8fbb4a9d532050c8327df790522735a711db82fcd0" dependencies = [ - "bstr 1.0.1", - "git-config-value", - "git-features", - "git-glob", - "git-path", - "git-ref", - "git-sec", + "bstr 1.3.0", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", "memchr", "nom", "once_cell", @@ -639,81 +688,82 @@ dependencies = [ ] [[package]] -name = "git-config-value" -version = "0.10.0" +name = "gix-config-value" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989a90c1c630513a153c685b4249b96fdf938afc75bf7ef2ae1ccbd3d799f5db" +checksum = "693d4a4ba0531e46fe558459557a5b29fb86c3e4b2666c1c0861d93c7c678331" dependencies = [ "bitflags", - "bstr 1.0.1", - "git-path", + "bstr 1.3.0", + "gix-path", "libc", "thiserror", ] [[package]] -name = "git-credentials" -version = "0.9.0" +name = "gix-credentials" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cd6bbe001afd6356b35ef13f2a6b0f0abc0133d1b2ecaec1033bdd769616d6" +checksum = "5d1536399f70146825bd10321adc5307032e3de93f4954a3c54184281f2e6955" dependencies = [ - "bstr 1.0.1", - "git-command", - "git-config-value", - "git-path", - "git-prompt", - "git-sec", - "git-url", + "bstr 1.3.0", + "gix-command", + "gix-config-value", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-url", "thiserror", ] [[package]] -name = "git-date" -version = "0.4.0" +name = "gix-date" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "412c9b89026505bd24d5f8acafa578de6eea3b271ece307a73b8e646e671302a" +checksum = "b96271912ce39822501616f177dea7218784e6c63be90d5f36322ff3a722aae2" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "itoa", "thiserror", "time", ] [[package]] -name = "git-diff" -version = "0.26.0" +name = "gix-diff" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca87474422d26d606d04cec6bedfabcd92a0a74102cd7936785358ced6a4a25a" +checksum = "2ec3351a6cec2ddca29c1124afef8b4f3fad0b617dce8916148153541468117c" dependencies = [ - "git-hash", - "git-object", + "gix-hash", + "gix-object", "imara-diff", "thiserror", ] [[package]] -name = "git-discover" -version = "0.12.0" +name = "gix-discover" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9e26e0bc434643228cd418185bd28ca5c7cf831bde1da434807391c27ac40e" +checksum = "38029783886cb46fbe63e61b02a70404aa04cfeacfb53ed336832c20fcb1e281" dependencies = [ - "bstr 1.0.1", - "git-hash", - "git-path", - "git-ref", - "git-sec", + "bstr 1.3.0", + "dunce", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", "thiserror", ] [[package]] -name = "git-features" -version = "0.26.0" +name = "gix-features" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff74064fa007c5beefa89a64bb72834f32b3c497750a56c79c6802bbdb311f9" +checksum = "3402b831ea4bb3af36369d61dbf250eb0e1a8577d3cb77b9719c11a82485bfe9" dependencies = [ "crc32fast", "flate2", - "git-hash", + "gix-hash", "libc", "once_cell", "prodash", @@ -723,51 +773,51 @@ dependencies = [ ] [[package]] -name = "git-glob" -version = "0.5.1" +name = "gix-glob" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3908404c9b76ac7b3f636a104142378d3eaa78623cbc6eb7c7f0651979d48e8a" +checksum = "93e43efd776bc543f46f0fd0ca3d920c37af71a764a16f2aebd89765e9ff2993" dependencies = [ "bitflags", - "bstr 1.0.1", + "bstr 1.3.0", ] [[package]] -name = "git-hash" -version = "0.10.1" +name = "gix-hash" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1532d82bf830532f8d545c5b7b568e311e3593f16cf7ee9dd0ce03c74b12b99d" +checksum = "0c0c5a9f4d621d4f4ea046bb331df5c746ca735b8cae5b234cc2be70ee4dbef0" dependencies = [ "hex", "thiserror", ] [[package]] -name = "git-hashtable" -version = "0.1.0" +name = "gix-hashtable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52b625ad8cc360a0b7f426266f21fb07bd49b8f4ccf1b3ca7bc89424db1dec4" +checksum = "1a256cceeea0f0d7f42a0c3ac649535644a04395d9f415518f4008ef6bb331b5" dependencies = [ - "git-hash", + "gix-hash", "hashbrown 0.13.2", ] [[package]] -name = "git-index" -version = "0.12.1" +name = "gix-index" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "485da97dd4f69c7d9a8dc238cd6f4a726387ffc34573489e8e0d2bee266e3454" +checksum = "decb345476c25434a202f1cf8a24aa71133c567b7b502c549fd57211c51ed78a" dependencies = [ "atoi", "bitflags", - "bstr 1.0.1", + "bstr 1.3.0", "filetime", - "git-bitmap", - "git-features", - "git-hash", - "git-lock", - "git-object", - "git-traverse", + "gix-bitmap", + "gix-features", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", "itoa", "memmap2", "smallvec", @@ -775,39 +825,39 @@ dependencies = [ ] [[package]] -name = "git-lock" -version = "3.0.0" +name = "gix-lock" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e4f05b8a68c3a5dd83a6651c76be384e910fe283072184fdab9d77f87ccec2" +checksum = "e5fe84f09afadec78a7227d80f58cb5412d216dbae4b7fa060b619c0ce62b55d" dependencies = [ "fastrand", - "git-tempfile", + "gix-tempfile", "quick-error", ] [[package]] -name = "git-mailmap" -version = "0.9.0" +name = "gix-mailmap" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0316b4346f3e162ade368209efb8a609b587793c74aa3b8de0ec01a4f3580120" +checksum = "a28214e75835ab33d34210a18981110642728bf169f5e339dbfb6f6380b94318" dependencies = [ - "bstr 1.0.1", - "git-actor", + "bstr 1.3.0", + "gix-actor", "quick-error", ] [[package]] -name = "git-object" -version = "0.26.0" +name = "gix-object" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8563e2d6f524d7053f3106714f99ecdc3adbba2cb7108c09d71a02579f2e19" +checksum = "de3b04e3028ddab838d005104f234f4d2c26ecd51f2d72d96747c878094c4619" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "btoi", - "git-actor", - "git-features", - "git-hash", - "git-validate", + "gix-actor", + "gix-features", + "gix-hash", + "gix-validate", "hex", "itoa", "nom", @@ -816,41 +866,41 @@ dependencies = [ ] [[package]] -name = "git-odb" -version = "0.40.0" +name = "gix-odb" +version = "0.40.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616115a0e3daff6e08842758d24547b37a6eb6d0e2eedd95a740c3aaa2750333" +checksum = "0bd81ab7cd13c0f78bd619f967509953094f415288f8693dbb63a084e5bb39c4" dependencies = [ "arc-swap", - "git-features", - "git-hash", - "git-object", - "git-pack", - "git-path", - "git-quote", + "gix-features", + "gix-hash", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", "parking_lot 0.12.1", "tempfile", "thiserror", ] [[package]] -name = "git-pack" -version = "0.30.0" +name = "gix-pack" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd16b88f4b66041f41ca510c28bd81c4ee7363c5a544b3d62b4170432965871" +checksum = "26143c5c8bc145a39e9b335cc74504f2eba2ce68b1724661d8e6cb4484ab187e" dependencies = [ "bytesize", "clru", "dashmap", - "git-chunk", - "git-diff", - "git-features", - "git-hash", - "git-hashtable", - "git-object", - "git-path", - "git-tempfile", - "git-traverse", + "gix-chunk", + "gix-diff", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-traverse", "memmap2", "parking_lot 0.12.1", "smallvec", @@ -858,147 +908,104 @@ dependencies = [ ] [[package]] -name = "git-path" -version = "0.7.0" +name = "gix-path" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40e68481a06da243d3f4dfd86a4be39c24eefb535017a862e845140dcdb878a" +checksum = "f6c104a66dec149cb8f7aaafc6ab797654cf82d67f050fd0cb7e7294e328354b" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "thiserror", ] [[package]] -name = "git-prompt" -version = "0.3.0" +name = "gix-prompt" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3612a486e507dd431ef0f7108eeaafc8fd1ed7bd0f205a88554f6f91fe5dccbf" +checksum = "a20cebf73229debaa82574c4fd20dcaf00fa8d4bfce823a862c4e990d7a0b5b4" dependencies = [ - "git-command", - "git-config-value", + "gix-command", + "gix-config-value", "nix", "parking_lot 0.12.1", "thiserror", ] [[package]] -name = "git-quote" -version = "0.4.0" +name = "gix-quote" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd11f4e7f251ab297545faa4c5a4517f4985a43b9c16bf96fa49107f58e837f" +checksum = "e34cffcf5dd0ddf06a768b697a0f29319284deffba970e4355b51b0fee61ffa2" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "btoi", "quick-error", ] [[package]] -name = "git-ref" -version = "0.23.0" +name = "gix-ref" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6767925a6fc4af5c5a81e348d1d851c1b3ab2b512bd7f562ac11be37c14468" +checksum = "93e85abee11aa093f24da7336bf0a8ad598f15da396b28cf1270ab1091137d35" dependencies = [ - "git-actor", - "git-features", - "git-hash", - "git-lock", - "git-object", - "git-path", - "git-tempfile", - "git-validate", + "gix-actor", + "gix-features", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-validate", "memmap2", "nom", "thiserror", ] [[package]] -name = "git-refspec" -version = "0.7.0" +name = "gix-refspec" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf310ed5f2829ac0af96e7d4aebd4ae4b89f0718a7ae3666d09b02b2c5a1dfd" +checksum = "ac80b201eeeb3bc554583fd0127cb6bc9e20981cabb085149c9740329f8a2319" dependencies = [ - "bstr 1.0.1", - "git-hash", - "git-revision", - "git-validate", - "smallvec", - "thiserror", -] - -[[package]] -name = "git-repository" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993277960cb7e2d3991a11c1ec6951c1d142de052c26a18d2db64304e52d3741" -dependencies = [ - "git-actor", - "git-attributes", - "git-config", - "git-credentials", - "git-date", - "git-diff", - "git-discover", - "git-features", - "git-glob", - "git-hash", - "git-hashtable", - "git-index", - "git-lock", - "git-mailmap", - "git-object", - "git-odb", - "git-pack", - "git-path", - "git-prompt", - "git-ref", - "git-refspec", - "git-revision", - "git-sec", - "git-tempfile", - "git-traverse", - "git-url", - "git-validate", - "git-worktree", - "log", - "once_cell", - "prodash", - "signal-hook", + "bstr 1.3.0", + "gix-hash", + "gix-revision", + "gix-validate", "smallvec", "thiserror", - "unicode-normalization", ] [[package]] -name = "git-revision" -version = "0.10.0" +name = "gix-revision" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9a6bd28c9d1676bb96f428cd09614ae18a0087d7cea1cebfd177e25f99b2af" +checksum = "107a10d92379a797bea0f1d0eceded58e08913e0a706c8d436592673c6c6503f" dependencies = [ - "bstr 1.0.1", - "git-date", - "git-hash", - "git-hashtable", - "git-object", + "bstr 1.3.0", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", "thiserror", ] [[package]] -name = "git-sec" -version = "0.6.0" +name = "gix-sec" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1802e8252fa223b0ad89a393aed461132174ced1e6842a41f56dc92a3fc14f" +checksum = "e8ffa5bf0772f9b01de501c035b6b084cf9b8bb07dec41e3afc6a17336a65f47" dependencies = [ "bitflags", "dirs", - "git-path", + "gix-path", "libc", "windows", ] [[package]] -name = "git-tempfile" -version = "3.0.0" +name = "gix-tempfile" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6bb4dee86c8cae5a078cfaac3b004ef99c31548ed86218f23a7ff9b4b74f3be" +checksum = "48590cb5de0b8feadee42466a90028877ba67b9fd894c5493b4b64f5e3217c17" dependencies = [ "dashmap", "libc", @@ -1009,55 +1016,55 @@ dependencies = [ ] [[package]] -name = "git-traverse" -version = "0.22.0" +name = "gix-traverse" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd356da21ec00f69b9d4f105df4cb85543c746b18f4b7fc81529ce77713cdb29" +checksum = "f7ee7eee98b6e196fba1f34751d4399e0daa4e61892a78f634d0901e52dd739b" dependencies = [ - "git-hash", - "git-hashtable", - "git-object", + "gix-hash", + "gix-hashtable", + "gix-object", "thiserror", ] [[package]] -name = "git-url" -version = "0.13.0" +name = "gix-url" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85af407ed0dbb8d8da2a7241827d2fd5681186d9dab3570fc8dd8d6152ec48f" +checksum = "4d6e3e05267f7873099b3e510ab8eebdfc28920a915ab2e3d549493abe0fd9f0" dependencies = [ - "bstr 1.0.1", - "git-features", - "git-path", + "bstr 1.3.0", + "gix-features", + "gix-path", "home", "thiserror", "url", ] [[package]] -name = "git-validate" -version = "0.7.1" +name = "gix-validate" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431cf9352c596dc7c8ec9066ee551ce54e63c86c3c767e5baf763f6019ff3c2" +checksum = "b69ddb780ea1465255e66818d75b7098371c58dbc9560da4488a44b9f5c7e443" dependencies = [ - "bstr 1.0.1", + "bstr 1.3.0", "thiserror", ] [[package]] -name = "git-worktree" -version = "0.12.0" +name = "gix-worktree" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3bc63878f134e08ed52dba5d82422798c01a3f2e48c38ae9a2f7ff9194f362" +checksum = "da7ddd5b042c85cfe768d5ea97bb204cf1ed2b9413148f482146f4e831ca172e" dependencies = [ - "bstr 1.0.1", - "git-attributes", - "git-features", - "git-glob", - "git-hash", - "git-index", - "git-object", - "git-path", + "bstr 1.3.0", + "gix-attributes", + "gix-features", + "gix-glob", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", "io-close", "thiserror", ] @@ -1269,7 +1276,7 @@ dependencies = [ name = "helix-vcs" version = "0.6.0" dependencies = [ - "git-repository", + "gix", "helix-core", "imara-diff", "log", @@ -2472,17 +2479,17 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.40.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30acc718a52fb130fec72b1cb5f55ffeeec9253e1b785e94db222178a6acaa1" +checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" dependencies = [ - "windows_aarch64_gnullvm 0.40.0", - "windows_aarch64_msvc 0.40.0", - "windows_i686_gnu 0.40.0", - "windows_i686_msvc 0.40.0", - "windows_x86_64_gnu 0.40.0", - "windows_x86_64_gnullvm 0.40.0", - "windows_x86_64_msvc 0.40.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -2491,93 +2498,51 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.0", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm 0.42.0", - "windows_x86_64_msvc 0.42.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3caa4a1a16561b714323ca6b0817403738583033a6a92e04c5d10d4ba37ca10" - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_msvc" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328973c62dfcc50fb1aaa8e7100676e0b642fe56bac6bafff3327902db843ab4" - [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_i686_gnu" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa5b09fad70f0df85dea2ac2a525537e415e2bf63ee31cf9b8e263645ee9f3c1" - [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_msvc" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1ad4031c1a98491fa195d8d43d7489cb749f135f2e5c4eed58da094bd0d876" - [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_x86_64_gnu" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520ff37edd72da8064b49d2281182898e17f0688ae9f4070bca27e4b5c162ac7" - [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046e5b82215102c44fd75f488f1b9158973d02aa34d06ed85c23d6f5520a2853" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_msvc" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0c9c6df55dd1bfa76e131cef44bdd8ec9c819ef3611f04dfe453fd5bfeda28" - [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/helix-vcs/Cargo.toml b/helix-vcs/Cargo.toml index c4d6eb45b..ad8005d1a 100644 --- a/helix-vcs/Cargo.toml +++ b/helix-vcs/Cargo.toml @@ -16,13 +16,13 @@ helix-core = { version = "0.6", path = "../helix-core" } tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] } parking_lot = "0.12" -git-repository = { version = "0.32", default-features = false , optional = true } +gix= { version = "0.36.1", default-features = false , optional = true } imara-diff = "0.1.5" log = "0.4" [features] -git = ["git-repository"] +git = ["gix"] [dev-dependencies] tempfile = "3.4" \ No newline at end of file diff --git a/helix-vcs/src/git.rs b/helix-vcs/src/git.rs index 432159b6c..2a540c8d1 100644 --- a/helix-vcs/src/git.rs +++ b/helix-vcs/src/git.rs @@ -1,9 +1,8 @@ use std::path::Path; -use git::objs::tree::EntryMode; -use git::sec::trust::DefaultForLevel; -use git::{Commit, ObjectId, Repository, ThreadSafeRepository}; -use git_repository as git; +use gix::objs::tree::EntryMode; +use gix::sec::trust::DefaultForLevel; +use gix::{Commit, ObjectId, Repository, ThreadSafeRepository}; use crate::DiffProvider; @@ -15,13 +14,13 @@ pub struct Git; impl Git { fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Option { // custom open options - let mut git_open_opts_map = git::sec::trust::Mapping::::default(); + let mut git_open_opts_map = gix::sec::trust::Mapping::::default(); // On windows various configuration options are bundled as part of the installations // This path depends on the install location of git and therefore requires some overhead to lookup // This is basically only used on windows and has some overhead hence it's disabled on other platforms. // `gitoxide` doesn't use this as default - let config = git::permissions::Config { + let config = gix::permissions::Config { system: true, git: true, user: true, @@ -30,16 +29,16 @@ impl Git { git_binary: cfg!(windows), }; // change options for config permissions without touching anything else - git_open_opts_map.reduced = git_open_opts_map.reduced.permissions(git::Permissions { + git_open_opts_map.reduced = git_open_opts_map.reduced.permissions(gix::Permissions { config, - ..git::Permissions::default_for_level(git::sec::Trust::Reduced) + ..gix::Permissions::default_for_level(gix::sec::Trust::Reduced) }); - git_open_opts_map.full = git_open_opts_map.full.permissions(git::Permissions { + git_open_opts_map.full = git_open_opts_map.full.permissions(gix::Permissions { config, - ..git::Permissions::default_for_level(git::sec::Trust::Full) + ..gix::Permissions::default_for_level(gix::sec::Trust::Full) }); - let mut open_options = git::discover::upwards::Options::default(); + let mut open_options = gix::discover::upwards::Options::default(); if let Some(ceiling_dir) = ceiling_dir { open_options.ceiling_dirs = vec![ceiling_dir.to_owned()]; } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ace4f5f96..c72b136d4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.63.0" +channel = "1.64.0" components = ["rustfmt", "rust-src"] From 5b4e73f37dea1b199e30610f255731f110a4a605 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 23 Feb 2023 07:59:41 +0100 Subject: [PATCH 29/81] Update helix-vcs/Cargo.toml Co-authored-by: Ivan Tham --- helix-vcs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-vcs/Cargo.toml b/helix-vcs/Cargo.toml index ad8005d1a..d7eef9db3 100644 --- a/helix-vcs/Cargo.toml +++ b/helix-vcs/Cargo.toml @@ -16,7 +16,7 @@ helix-core = { version = "0.6", path = "../helix-core" } tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] } parking_lot = "0.12" -gix= { version = "0.36.1", default-features = false , optional = true } +gix = { version = "0.36.1", default-features = false , optional = true } imara-diff = "0.1.5" log = "0.4" From ccdb1446652662e2577fb7405fee9ccd49c56180 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Sat, 4 Mar 2023 21:53:54 +0100 Subject: [PATCH 30/81] update MSRV to 1.65 --- .github/workflows/build.yml | 6 +- Cargo.lock | 236 ++++++++++------------------------ helix-term/Cargo.toml | 2 +- helix-term/src/commands.rs | 5 +- helix-term/src/ui/document.rs | 13 +- helix-vcs/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- 7 files changed, 77 insertions(+), 189 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44d267884..d7d7d47e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.63 + uses: dtolnay/rust-toolchain@1.65 - uses: Swatinem/rust-cache@v2 @@ -66,7 +66,7 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.63 + uses: dtolnay/rust-toolchain@1.65 with: components: rustfmt, clippy @@ -91,7 +91,7 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.63 + uses: dtolnay/rust-toolchain@1.65 - uses: Swatinem/rust-cache@v2 diff --git a/Cargo.lock b/Cargo.lock index 1c4549b28..090b5f027 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,15 +61,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -107,9 +98,9 @@ dependencies = [ [[package]] name = "btoi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c0869a9faa81f8bbf8102371105d6d0a7b79167a04c340b04ab16892246a11" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" dependencies = [ "num-traits", ] @@ -132,27 +123,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "bytesize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" - [[package]] name = "cassowary" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.0.79" @@ -215,17 +191,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "compact_str" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5138945395949e7dfba09646dc9e766b548ff48e23deb5246890e6b64ae9e1b9" -dependencies = [ - "castaway", - "itoa", - "ryu", -] - [[package]] name = "content_inspector" version = "0.2.4" @@ -270,7 +235,7 @@ dependencies = [ "futures-core", "libc", "mio", - "parking_lot 0.12.1", + "parking_lot", "signal-hook", "signal-hook-mio", "winapi", @@ -329,19 +294,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dashmap" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" -dependencies = [ - "cfg-if", - "hashbrown 0.12.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.4", -] - [[package]] name = "dirs" version = "4.0.0" @@ -568,9 +520,9 @@ dependencies = [ [[package]] name = "gix" -version = "0.36.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d5dbcb1efbee862cdc851a23377e1a8a5c1a8971740b4933d4ce022a0889a8" +checksum = "dabfac58aecb4a38cdd2568de66eb1f0d968fd6726f5a80cb8bea7944ef10cc0" dependencies = [ "gix-actor", "gix-attributes", @@ -602,7 +554,6 @@ dependencies = [ "gix-worktree", "log", "once_cell", - "prodash", "signal-hook", "smallvec", "thiserror", @@ -611,26 +562,25 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.17.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381153ea93b9d8a5c6894a5c734b2e9c15d623063adfd2bda4342ecf90f9a5f8" +checksum = "dc22b0cdc52237667c301dd7cdc6ead8f8f73c9f824e9942c8ebd6b764f6c0bf" dependencies = [ "bstr 1.3.0", "btoi", "gix-date", "itoa", "nom", - "quick-error", + "thiserror", ] [[package]] name = "gix-attributes" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df09b20424fd4cee04c43b50df954c4b119c45b769639b60d80ee8bb6d84e0aa" +checksum = "2231a25934a240d0a4b6f4478401c73ee81d8be52de0293eedbc172334abf3e1" dependencies = [ "bstr 1.3.0", - "compact_str", "gix-features", "gix-glob", "gix-path", @@ -641,11 +591,11 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5229fd26e288f417c8dd2385c5bc740415eb55aba4d6f529db7ad4b526771e06" +checksum = "024bca0c7187517bda5ea24ab148c9ca8208dd0c3e2bea88cdb2008f91791a6d" dependencies = [ - "quick-error", + "thiserror", ] [[package]] @@ -668,9 +618,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.16.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398b5003d5e4991355528e8fbb4a9d532050c8327df790522735a711db82fcd0" +checksum = "52c62e26ce11f607712e4f49a0a192ed87675d30187fd61be070abbd607d12f1" dependencies = [ "bstr 1.3.0", "gix-config-value", @@ -702,9 +652,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.9.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1536399f70146825bd10321adc5307032e3de93f4954a3c54184281f2e6955" +checksum = "5be32b5fe339a31b8e53fa854081dc914c45020dcb64637f3c21baf69c96fc1b" dependencies = [ "bstr 1.3.0", "gix-command", @@ -730,9 +680,9 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.26.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ec3351a6cec2ddca29c1124afef8b4f3fad0b617dce8916148153541468117c" +checksum = "585b0834d4b6791a848637c4e109545fda9b0f29b591ba55edb33ceda6e7856b" dependencies = [ "gix-hash", "gix-object", @@ -742,9 +692,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.13.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38029783886cb46fbe63e61b02a70404aa04cfeacfb53ed336832c20fcb1e281" +checksum = "91c204adba5ebd211c74735cbb65817d277e154486bac0dffa3701f163b80350" dependencies = [ "bstr 1.3.0", "dunce", @@ -757,9 +707,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.26.5" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3402b831ea4bb3af36369d61dbf250eb0e1a8577d3cb77b9719c11a82485bfe9" +checksum = "5e6a9dfa7b3c1a99315203e8b97f8f99f3bd95731590607abeaa5ca31bc41fe3" dependencies = [ "crc32fast", "flate2", @@ -767,8 +717,8 @@ dependencies = [ "libc", "once_cell", "prodash", - "quick-error", "sha1_smol", + "thiserror", "walkdir", ] @@ -794,23 +744,24 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a256cceeea0f0d7f42a0c3ac649535644a04395d9f415518f4008ef6bb331b5" +checksum = "9609c1b8f36f12968e6a6098f7cdb52004f7d42d570f47a2d6d7c16612f19acb" dependencies = [ "gix-hash", "hashbrown 0.13.2", + "parking_lot", ] [[package]] name = "gix-index" -version = "0.12.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decb345476c25434a202f1cf8a24aa71133c567b7b502c549fd57211c51ed78a" +checksum = "c12caf7886c7ba06f2b28835cdc2be1dca86bd047d00299d2d49e707ce1c2616" dependencies = [ - "atoi", "bitflags", "bstr 1.3.0", + "btoi", "filetime", "gix-bitmap", "gix-features", @@ -826,31 +777,31 @@ dependencies = [ [[package]] name = "gix-lock" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fe84f09afadec78a7227d80f58cb5412d216dbae4b7fa060b619c0ce62b55d" +checksum = "66119ff8a4a395d0ea033fef718bc85f8b4f0855874f4ce1e005fc16cfe1f66e" dependencies = [ "fastrand", "gix-tempfile", - "quick-error", + "thiserror", ] [[package]] name = "gix-mailmap" -version = "0.9.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28214e75835ab33d34210a18981110642728bf169f5e339dbfb6f6380b94318" +checksum = "2b66aea5e52875cd4915f4957a6f4b75831a36981e2ec3f5fad9e370e444fe1a" dependencies = [ "bstr 1.3.0", "gix-actor", - "quick-error", + "thiserror", ] [[package]] name = "gix-object" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3b04e3028ddab838d005104f234f4d2c26ecd51f2d72d96747c878094c4619" +checksum = "8df068db9180ee935fbb70504848369e270bdcb576b05c0faa8b9fd3b86fc017" dependencies = [ "bstr 1.3.0", "btoi", @@ -867,9 +818,9 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.40.2" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd81ab7cd13c0f78bd619f967509953094f415288f8693dbb63a084e5bb39c4" +checksum = "e9a5f9e1afbd509761977a2ea02869cedaaba500b4e783deb2e4de5179a55a80" dependencies = [ "arc-swap", "gix-features", @@ -878,20 +829,18 @@ dependencies = [ "gix-pack", "gix-path", "gix-quote", - "parking_lot 0.12.1", + "parking_lot", "tempfile", "thiserror", ] [[package]] name = "gix-pack" -version = "0.30.3" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26143c5c8bc145a39e9b335cc74504f2eba2ce68b1724661d8e6cb4484ab187e" +checksum = "e51db84e1459a8022e518d40a8778028d793dbb28e4d35c9a5eaf92658fb0775" dependencies = [ - "bytesize", "clru", - "dashmap", "gix-chunk", "gix-diff", "gix-features", @@ -902,7 +851,7 @@ dependencies = [ "gix-tempfile", "gix-traverse", "memmap2", - "parking_lot 0.12.1", + "parking_lot", "smallvec", "thiserror", ] @@ -926,26 +875,26 @@ dependencies = [ "gix-command", "gix-config-value", "nix", - "parking_lot 0.12.1", + "parking_lot", "thiserror", ] [[package]] name = "gix-quote" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34cffcf5dd0ddf06a768b697a0f29319284deffba970e4355b51b0fee61ffa2" +checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb" dependencies = [ "bstr 1.3.0", "btoi", - "quick-error", + "thiserror", ] [[package]] name = "gix-ref" -version = "0.24.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e85abee11aa093f24da7336bf0a8ad598f15da396b28cf1270ab1091137d35" +checksum = "90a0ed29e581f04b904ecd0c32b11f33b8209b5a0af9c43f415249a4f2fba632" dependencies = [ "gix-actor", "gix-features", @@ -962,9 +911,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac80b201eeeb3bc554583fd0127cb6bc9e20981cabb085149c9740329f8a2319" +checksum = "aba332462bda2e8efeae4302b39a6ed01ad56ef772fd5b7ef197cf2798294d65" dependencies = [ "bstr 1.3.0", "gix-hash", @@ -976,9 +925,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.10.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a10d92379a797bea0f1d0eceded58e08913e0a706c8d436592673c6c6503f" +checksum = "ed98e4a0254953c64bc913bd23146a1de662067d5cf974cbdde396958b39e5b0" dependencies = [ "bstr 1.3.0", "gix-date", @@ -1003,13 +952,13 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "3.0.2" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48590cb5de0b8feadee42466a90028877ba67b9fd894c5493b4b64f5e3217c17" +checksum = "a8e0227bd284cd16105e8479602bb8af6bddcb800427e881c1feee4806310a31" dependencies = [ - "dashmap", "libc", "once_cell", + "parking_lot", "signal-hook", "signal-hook-registry", "tempfile", @@ -1017,9 +966,9 @@ dependencies = [ [[package]] name = "gix-traverse" -version = "0.22.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ee7eee98b6e196fba1f34751d4399e0daa4e61892a78f634d0901e52dd739b" +checksum = "dd9a4a07bb22168dc79c60e1a6a41919d198187ca83d8a5940ad8d7122a45df3" dependencies = [ "gix-hash", "gix-hashtable", @@ -1029,9 +978,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.13.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6e3e05267f7873099b3e510ab8eebdfc28920a915ab2e3d549493abe0fd9f0" +checksum = "044072b7ce8601b62dcec841b92129f5cc677072823324121b395d766ac5f528" dependencies = [ "bstr 1.3.0", "gix-features", @@ -1053,9 +1002,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.12.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7ddd5b042c85cfe768d5ea97bb204cf1ed2b9413148f482146f4e831ca172e" +checksum = "b7cb9af6e56152953d8fe113c4f9d7cf60cf7a982362711e9200a255579b49cb" dependencies = [ "bstr 1.3.0", "gix-attributes", @@ -1280,7 +1229,7 @@ dependencies = [ "helix-core", "imara-diff", "log", - "parking_lot 0.12.1", + "parking_lot", "tempfile", "tokio", ] @@ -1339,12 +1288,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "human_format" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86cce260d758a9aa3d7c4b99d55c815a540f8a37514ba6046ab6be402a157cb0" - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1653,17 +1596,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -1671,21 +1603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -1730,15 +1648,9 @@ dependencies = [ [[package]] name = "prodash" -version = "23.0.0" +version = "23.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8c414345b4a98cbcd0e8d8829c8f54b47a7ed4fb771c45b7c5c6c0ae23dc4c" -dependencies = [ - "bytesize", - "dashmap", - "human_format", - "parking_lot 0.11.2", -] +checksum = "d73c6b64cb5b99eb63ca97d378685712617ec0172ff5c04cd47a489d3e2c51f8" [[package]] name = "pulldown-cmark" @@ -1751,12 +1663,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quickcheck" version = "1.0.3" @@ -1860,12 +1766,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - [[package]] name = "ryu" version = "1.0.11" @@ -2208,7 +2108,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 975d08712..b1d63a04a 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/helix-editor/helix" homepage = "https://helix-editor.com" include = ["src/**/*", "README.md"] default-run = "hx" -rust-version = "1.57" +rust-version = "1.65" [features] default = ["git"] diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fb55ca2a8..7b9b59434 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3061,10 +3061,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) { .prev_hunk(cursor_line) .map(|idx| idx.saturating_sub(count)), }; - // TODO refactor with let..else once MSRV reaches 1.65 - let hunk_idx = if let Some(hunk_idx) = hunk_idx { - hunk_idx - } else { + let Some(hunk_idx) = hunk_idx else { return range; }; let hunk = hunks.nth_hunk(hunk_idx); diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index 4f615f7bd..28a52f74d 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -202,10 +202,7 @@ pub fn render_text<'t>( // formattter.line_pos returns to line index of the next grapheme // so it must be called before formatter.next let doc_line = formatter.line_pos(); - // TODO refactor with let .. else once MSRV reaches 1.65 - let (grapheme, mut pos) = if let Some(it) = formatter.next() { - it - } else { + let Some((grapheme, mut pos)) = formatter.next() else { let mut last_pos = formatter.visual_pos(); if last_pos.row >= row_off { last_pos.col -= 1; @@ -226,7 +223,6 @@ pub fn render_text<'t>( // skip any graphemes on visual lines before the block start if pos.row < row_off { if char_pos >= style_span.1 { - // TODO refactor using let..else once MSRV reaches 1.65 style_span = if let Some(style_span) = styles.next() { style_span } else { @@ -266,12 +262,7 @@ pub fn render_text<'t>( // aquire the correct grapheme style if char_pos >= style_span.1 { - // TODO refactor using let..else once MSRV reaches 1.65 - style_span = if let Some(style_span) = styles.next() { - style_span - } else { - (Style::default(), usize::MAX) - } + style_span = styles.next().unwrap_or((Style::default(), usize::MAX)); } char_pos += grapheme.doc_chars(); diff --git a/helix-vcs/Cargo.toml b/helix-vcs/Cargo.toml index d7eef9db3..789ee7951 100644 --- a/helix-vcs/Cargo.toml +++ b/helix-vcs/Cargo.toml @@ -16,7 +16,7 @@ helix-core = { version = "0.6", path = "../helix-core" } tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] } parking_lot = "0.12" -gix = { version = "0.36.1", default-features = false , optional = true } +gix = { version = "0.39.0", default-features = false , optional = true } imara-diff = "0.1.5" log = "0.4" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c72b136d4..2abc56652 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.64.0" +channel = "1.65.0" components = ["rustfmt", "rust-src"] From def26966d22dfc4b9a74e51542db246ba52f3dc9 Mon Sep 17 00:00:00 2001 From: nuid32 <91177333+nuid32@users.noreply.github.com> Date: Sun, 5 Mar 2023 16:43:24 +0500 Subject: [PATCH 31/81] Fix lacking space panic (#6109) * Fix lack of space for popup crash * Fix saturating -> wrapping * Fix wrapping -> saturating (I am an idiot) * Remove useless "mut" in helix-tui/src/buffer.rs Co-authored-by: Michael Davis * Remove redundant bound-check * Return bound-check back * Add bound-check for set_style * Remove set_style bound-check * Revert bound-check --------- Co-authored-by: Michael Davis --- helix-term/src/ui/markdown.rs | 5 +---- helix-view/src/graphics.rs | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 923dd73a1..87136992c 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -342,13 +342,10 @@ impl Component for Markdown { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { let padding = 2; - if padding >= viewport.1 || padding >= viewport.0 { - return None; - } let contents = self.parse(None); // TODO: account for tab width - let max_text_width = (viewport.0 - padding).min(120); + let max_text_width = (viewport.0.saturating_sub(padding)).min(120); let (width, height) = crate::ui::text::required_size(&contents, max_text_width); Some((width + padding, height + padding)) diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index a0b645fae..e813fb560 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -237,8 +237,8 @@ impl Rect { Rect { x: x1, y: y1, - width: x2 - x1, - height: y2 - y1, + width: x2.saturating_sub(x1), + height: y2.saturating_sub(y1), } } From e6597bc9927c73800d31d4a45dc695a4abe77ec8 Mon Sep 17 00:00:00 2001 From: Erasin Wang Date: Mon, 6 Mar 2023 02:41:18 +0800 Subject: [PATCH 32/81] Update queries for godot4 (#6186) --- languages.toml | 2 +- runtime/queries/gdscript/highlights.scm | 88 +++++++++++++------ runtime/queries/gdscript/indents.scm | 2 +- runtime/queries/godot-resource/injections.scm | 16 ++++ 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/languages.toml b/languages.toml index e9a77b5a4..c34135244 100644 --- a/languages.toml +++ b/languages.toml @@ -1463,7 +1463,7 @@ indent = { tab-width = 4, unit = "\t" } [[grammar]] name = "gdscript" -source = { git = "https://github.com/PrestonKnopp/tree-sitter-gdscript", rev = "a56a6fcec3fb63ec3324bf9373aae7298861eb30" } +source = { git = "https://github.com/PrestonKnopp/tree-sitter-gdscript", rev = "a4b57cc3bcbfc24550e858159647e9238e7ad1ac" } [[language]] name = "godot-resource" diff --git a/runtime/queries/gdscript/highlights.scm b/runtime/queries/gdscript/highlights.scm index f36f4e35c..88f2a1875 100644 --- a/runtime/queries/gdscript/highlights.scm +++ b/runtime/queries/gdscript/highlights.scm @@ -1,7 +1,8 @@ ; Identifier naming conventions -((identifier) @constant - (#match? @constant "^[A-Z][A-Z_]*$")) +( + (identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]+$")) ; class (class_name_statement (name) @type) @@ -11,32 +12,35 @@ ; Function calls (attribute_call (identifier) @function) - (base_call (identifier) @function) - (call (identifier) @function) ; Function definitions (function_definition (name) @function) - (constructor_definition "_init" @function) + ;; Literals -(integer) @constant.numeric.integer -(float) @constant.numeric.float (comment) @comment (string) @string -(escape_sequence) @constant.character.escape -(identifier) @variable + (type) @type +(expression_statement (array (identifier) @type)) +(binary_operator (identifier) @type) -;; Literals +(variable_statement (identifier) @variable) +(get_node) @label + +(const_statement (name) @constant) +(integer) @constant.numeric.integer +(float) @constant.numeric.float +(escape_sequence) @constant.character.escape [ (true) (false) - (null) -] @constant.builtin +] @constant.builtin.boolean +(null) @constant.builtin [ "+" @@ -62,37 +66,67 @@ "~" "<<" ">>" - "and" - "or" - "not" ] @operator +(annotation (identifier) @keyword.storage.modifier) + [ - (static_keyword) - (remote_keyword) - (tool_statement) - "var" - "func" - "setget" - "in" - "is" - "as" "if" "else" "elif" +] @keyword.control.conditional + +[ "while" "for" +] @keyword.control.repeat + +[ "return" + "pass" "break" "continue" - "pass" +] @keyword.control.return + +[ + "func" +] @keyword.control.function + +[ + "export" +] @keyword.control.import + +[ + "in" + "is" + "as" "match" + "and" + "or" + "not" +] @keyword.operator + +[ + "var" "class" "class_name" "enum" +] @keyword.storage.type + + +[ + (remote_keyword) + (static_keyword) + "const" "signal" + "@" +] @keyword.storage.modifier + +[ + "setget" "onready" - "export" "extends" - "const" + "set" + "get" ] @keyword + diff --git a/runtime/queries/gdscript/indents.scm b/runtime/queries/gdscript/indents.scm index 1ab41a44d..c969eb7cf 100644 --- a/runtime/queries/gdscript/indents.scm +++ b/runtime/queries/gdscript/indents.scm @@ -12,6 +12,7 @@ (dictionary (_)) (array (_)) + (setget) ] @indent [ @@ -25,7 +26,6 @@ (class_definition) ] @extend - [ (return_statement) (break_statement) diff --git a/runtime/queries/godot-resource/injections.scm b/runtime/queries/godot-resource/injections.scm index 321c90add..7929d63cd 100644 --- a/runtime/queries/godot-resource/injections.scm +++ b/runtime/queries/godot-resource/injections.scm @@ -1,2 +1,18 @@ ((comment) @injection.content (#set! injection.language "comment")) + +; ((section) @injection.content +; (#set! injection.language "comment")) + +((section + (attribute + (identifier) @_type + (string) @_is_shader) + (property + (path) @_is_code + (string) @injection.content)) + (#match? @_type "type") + (#match? @_is_shader "Shader") + (#eq? @_is_code "code") + (#set! injection.language "glsl") +) From 39d5fb0e593b0da1bf6e2659c67a7914edcd75a6 Mon Sep 17 00:00:00 2001 From: Santiago Vrancovich <103597690+santiagovrancovich@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:41:49 -0300 Subject: [PATCH 33/81] Remove centering view from Unimpaired commands (#6193) Remove `align_view` calls from `goto_*_diag` as per issue #6177 --- helix-term/src/commands.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7b9b59434..cd7626ebd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2941,7 +2941,6 @@ fn goto_first_diag(cx: &mut Context) { None => return, }; doc.set_selection(view.id, selection); - align_view(doc, view, Align::Center); } fn goto_last_diag(cx: &mut Context) { @@ -2951,7 +2950,6 @@ fn goto_last_diag(cx: &mut Context) { None => return, }; doc.set_selection(view.id, selection); - align_view(doc, view, Align::Center); } fn goto_next_diag(cx: &mut Context) { @@ -2973,7 +2971,6 @@ fn goto_next_diag(cx: &mut Context) { None => return, }; doc.set_selection(view.id, selection); - align_view(doc, view, Align::Center); } fn goto_prev_diag(cx: &mut Context) { @@ -2998,7 +2995,6 @@ fn goto_prev_diag(cx: &mut Context) { None => return, }; doc.set_selection(view.id, selection); - align_view(doc, view, Align::Center); } fn goto_first_change(cx: &mut Context) { From 376c19e06bedf54c8a897068f25ff7b9a8e75198 Mon Sep 17 00:00:00 2001 From: Filip Dutescu Date: Mon, 6 Mar 2023 11:19:53 +0200 Subject: [PATCH 34/81] feat(dap): implement Restart request (#5651) Add a restart debug session command, which would issue a [Restart Request][1], if the debugger supports it and a session is running. It uses the same arguments and requests used to start the initial session, when recreating it. It builds upon #5532, making use of the changes to the termination workflow of a session. [1]: https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Restart Closes: #5594 Signed-off-by: Filip Dutescu --- helix-dap/src/client.rs | 17 +++++++++++++++++ helix-dap/src/types.rs | 13 +++++++++++-- helix-term/src/commands.rs | 1 + helix-term/src/commands/dap.rs | 30 ++++++++++++++++++++++++++++++ helix-term/src/keymap/default.rs | 1 + 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/helix-dap/src/client.rs b/helix-dap/src/client.rs index f6d8a0696..ff727d00a 100644 --- a/helix-dap/src/client.rs +++ b/helix-dap/src/client.rs @@ -33,6 +33,7 @@ pub struct Client { server_tx: UnboundedSender, request_counter: AtomicU64, connection_type: Option, + starting_request_args: Option, pub caps: Option, // thread_id -> frames pub stack_frames: HashMap>, @@ -87,6 +88,7 @@ impl Client { request_counter: AtomicU64::new(0), caps: None, connection_type: None, + starting_request_args: None, stack_frames: HashMap::new(), thread_states: HashMap::new(), thread_id: None, @@ -158,6 +160,10 @@ impl Client { ) } + pub fn starting_request_args(&self) -> &Option { + &self.starting_request_args + } + pub async fn tcp_process( cmd: &str, args: Vec<&str>, @@ -356,14 +362,25 @@ impl Client { pub fn launch(&mut self, args: serde_json::Value) -> impl Future> { self.connection_type = Some(ConnectionType::Launch); + self.starting_request_args = Some(args.clone()); self.call::(args) } pub fn attach(&mut self, args: serde_json::Value) -> impl Future> { self.connection_type = Some(ConnectionType::Attach); + self.starting_request_args = Some(args.clone()); self.call::(args) } + pub fn restart(&self) -> impl Future> { + let args = if let Some(args) = &self.starting_request_args { + args.clone() + } else { + Value::Null + }; + self.call::(args) + } + pub async fn set_breakpoints( &self, file: PathBuf, diff --git a/helix-dap/src/types.rs b/helix-dap/src/types.rs index c598790b2..bbaf53a60 100644 --- a/helix-dap/src/types.rs +++ b/helix-dap/src/types.rs @@ -378,7 +378,7 @@ pub mod requests { impl Request for Launch { type Arguments = Value; - type Result = Value; + type Result = (); const COMMAND: &'static str = "launch"; } @@ -387,7 +387,7 @@ pub mod requests { impl Request for Attach { type Arguments = Value; - type Result = Value; + type Result = (); const COMMAND: &'static str = "attach"; } @@ -402,6 +402,15 @@ pub mod requests { pub suspend_debuggee: Option, } + #[derive(Debug)] + pub enum Restart {} + + impl Request for Restart { + type Arguments = Value; + type Result = (); + const COMMAND: &'static str = "restart"; + } + #[derive(Debug)] pub enum Disconnect {} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cd7626ebd..c76e9f2bd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -430,6 +430,7 @@ impl MappableCommand { goto_next_paragraph, "Goto next paragraph", goto_prev_paragraph, "Goto previous paragraph", dap_launch, "Launch debug target", + dap_restart, "Restart debugging session", dap_toggle_breakpoint, "Toggle breakpoint", dap_continue, "Continue program execution", dap_pause, "Pause program execution", diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 023ed3779..dac1e9d52 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -289,6 +289,36 @@ pub fn dap_launch(cx: &mut Context) { )))); } +pub fn dap_restart(cx: &mut Context) { + let debugger = match &cx.editor.debugger { + Some(debugger) => debugger, + None => { + cx.editor.set_error("Debugger is not running"); + return; + } + }; + if !debugger + .capabilities() + .supports_restart_request + .unwrap_or(false) + { + cx.editor + .set_error("Debugger does not support session restarts"); + return; + } + if debugger.starting_request_args().is_none() { + cx.editor + .set_error("No arguments found with which to restart the sessions"); + return; + } + + dap_callback( + cx.jobs, + debugger.restart(), + |editor, _compositor, _resp: ()| editor.set_status("Debugging session restarted"), + ); +} + fn debug_parameter_prompt( completions: Vec, config_name: String, diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 7425c8155..9bd002809 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -223,6 +223,7 @@ pub fn default() -> HashMap { "'" => last_picker, "g" => { "Debug (experimental)" sticky=true "l" => dap_launch, + "r" => dap_restart, "b" => dap_toggle_breakpoint, "c" => dap_continue, "h" => dap_pause, From 5ebe1014ac1dffaab19f8f9f0ebe55df3202fbf9 Mon Sep 17 00:00:00 2001 From: cinerea0 <49368915+cinerea0@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:20:24 +0000 Subject: [PATCH 35/81] docs: describe tab-width and unit subkeys (#5862) --- book/src/languages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/languages.md b/book/src/languages.md index 0646b9af9..74d090eb7 100644 --- a/book/src/languages.md +++ b/book/src/languages.md @@ -56,7 +56,7 @@ These configuration keys are available: | `auto-format` | Whether to autoformat this language when saving | | `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) | | `comment-token` | The token to use as a comment-token | -| `indent` | The indent to use. Has sub keys `tab-width` and `unit` | +| `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) | | `language-server` | The Language Server to run. See the Language Server configuration section below. | | `config` | Language Server configuration | | `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) | From 707457c632e3b79f71d6b7ad2f62716e98080af2 Mon Sep 17 00:00:00 2001 From: David Else <12832280+David-Else@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:27:17 +0000 Subject: [PATCH 36/81] Rewrite and refactor all documentation (#5534) * Rewrite and refactor all documentation * Rewrite and refactor the guides * update runtime directory instructions for windows * Update the Ubuntu 3rd party repo section with 22.10 * Merge from upstream * Rewrite and refactor all documentation * Apply suggestions from code review Apply the suggestions that can be committed from the GitHub web interface. Co-authored-by: Michael Davis * Add Windows themes folder Co-authored-by: digidoor <37601466+digidoor@users.noreply.github.com> * Apply the rest of the suggestions from the code review * Revert "Apply the rest of the suggestions from the code review" This reverts commit 498be1b7a1aec3ff567b95130148628beeef9b77. * Revert "Merge branch 'rewrite-and-refactor-all-documentation' of github.com:David-Else/helix into rewrite-and-refactor-all-documentation" This reverts commit 7c8404248ffef73b80b9051d5a4359c5bcfa5d1a, reversing changes made to d932969cfc9fadda12a74cc01665919dee7152fb. * Apply code review suggestions * Changes after re-reading all documents * Missed a full stop * Code review suggestions and remove macOS and Windows specific sections * Add OpenBSD to heading * Add back macOS and Windows sections and further simplify and improve * Change wording to nightly * Remove README installation section and turn into a link * Simplify building from source and follow code review suggestions * Code review revisions * Fix copy paste mistake * Apply the latest code review suggestions * More small code review items * Change minor modes for code review * Fix link and typos * Add note that you need a c++ compiler to install the tree-sitter grammars * Add pacman example * Make sure all headings are lower case * Revert to the original passage adding a reference to Windows that was missing * Update book/src/guides/adding_languages.md Fix grammar typo Co-authored-by: Michael Davis * Update book/src/install.md Fix tree sitter typo Co-authored-by: Michael Davis * Remove TOC links to main heading --------- Co-authored-by: CptPotato <3957610+CptPotato@users.noreply.github.com> Co-authored-by: Michael Davis Co-authored-by: digidoor <37601466+digidoor@users.noreply.github.com> --- README.md | 84 +-------- book/src/SUMMARY.md | 10 +- book/src/commands.md | 2 +- book/src/configuration.md | 89 +++++----- book/src/guides/README.md | 2 +- book/src/guides/adding_languages.md | 77 ++++---- book/src/guides/indent.md | 4 +- book/src/guides/textobject.md | 16 +- book/src/install.md | 261 ++++++++++++++++------------ book/src/keymap.md | 40 ++--- book/src/lang-support.md | 4 +- book/src/languages.md | 24 +-- book/src/remapping.md | 19 +- book/src/themes.md | 98 ++++++----- book/src/usage.md | 206 ++++++++++++---------- 15 files changed, 465 insertions(+), 471 deletions(-) diff --git a/README.md b/README.md index a26673219..cba52a7a1 100644 --- a/README.md +++ b/README.md @@ -45,92 +45,10 @@ Note: Only certain languages have indentation definitions at the moment. Check # Installation -Packages are available for various distributions (see [Installation docs](https://docs.helix-editor.com/install.html)). - -If you would like to build from source: - -```shell -git clone https://github.com/helix-editor/helix -cd helix -cargo install --locked --path helix-term -``` - -This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`. - -Helix needs its runtime files so make sure to copy/symlink the `runtime/` directory into the -config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows). - -| OS | Command | -| -------------------- | ------------------------------------------------ | -| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` | -| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` | -| Linux / macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` | - -Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires -elevated privileges - i.e. PowerShell or Cmd must be run as administrator. - -**PowerShell:** - -```powershell -New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime" -``` -Note: "runtime" must be absolute path to the runtime directory. - -**Cmd:** - -```cmd -cd %appdata%\helix -mklink /D runtime "\runtime" -``` - -The runtime location can be overridden via the `HELIX_RUNTIME` environment variable. - -> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --locked --path helix-term`, -> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`. - -If you plan on keeping the repo locally, an alternative to copying/symlinking -runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime` -(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory). - -Packages already solve this for you by wrapping the `hx` binary with a wrapper -that sets the variable to the install dir. - -> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically -> detect the `runtime` directory in the project root. - -If you want to customize your `languages.toml` config, -tree-sitter grammars may be manually fetched and built with `hx --grammar fetch` and `hx --grammar build`. - -In order to use LSP features like auto-complete, you will need to -[install the appropriate Language Server](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) -for a language. +[Installation documentation](https://docs.helix-editor.com/install.html). [![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions) -## Adding Helix to your desktop environment - -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: - -```bash -cp contrib/Helix.desktop ~/.local/share/applications -cp contrib/helix.png ~/.local/share/icons -``` - -To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`: - -```bash -sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop -sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop -``` - -## macOS - -Helix can be installed on macOS through homebrew: - -``` -brew install helix -``` - # Contributing Contributing guidelines can be found [here](./docs/CONTRIBUTING.md). diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index eaf0c4f48..6e780b87f 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -6,13 +6,13 @@ - [Usage](./usage.md) - [Keymap](./keymap.md) - [Commands](./commands.md) - - [Language Support](./lang-support.md) + - [Language support](./lang-support.md) - [Migrating from Vim](./from-vim.md) - [Configuration](./configuration.md) - [Themes](./themes.md) - - [Key Remapping](./remapping.md) + - [Key remapping](./remapping.md) - [Languages](./languages.md) - [Guides](./guides/README.md) - - [Adding Languages](./guides/adding_languages.md) - - [Adding Textobject Queries](./guides/textobject.md) - - [Adding Indent Queries](./guides/indent.md) + - [Adding languages](./guides/adding_languages.md) + - [Adding textobject queries](./guides/textobject.md) + - [Adding indent queries](./guides/indent.md) diff --git a/book/src/commands.md b/book/src/commands.md index d9a113866..047a30a91 100644 --- a/book/src/commands.md +++ b/book/src/commands.md @@ -1,5 +1,5 @@ # Commands -Command mode can be activated by pressing `:`, similar to Vim. Built-in commands: +Command mode can be activated by pressing `:`. The built-in commands are: {{#include ./generated/typable-cmd.md}} diff --git a/book/src/configuration.md b/book/src/configuration.md index 0b9ebe966..5410024ba 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -2,10 +2,10 @@ To override global configuration parameters, create a `config.toml` file located in your config directory: -* Linux and Mac: `~/.config/helix/config.toml` -* Windows: `%AppData%\helix\config.toml` +- Linux and Mac: `~/.config/helix/config.toml` +- Windows: `%AppData%\helix\config.toml` -> Hint: You can easily open the config file by typing `:config-open` within Helix normal mode. +> 💡 You can easily open the config file by typing `:config-open` within Helix normal mode. Example config: @@ -25,12 +25,10 @@ select = "underline" hidden = false ``` -You may also specify a file to use for configuration with the `-c` or -`--config` CLI argument: `hx -c path/to/custom-config.toml`. - -It is also possible to trigger configuration file reloading by sending the `USR1` -signal to the helix process, e.g. via `pkill -USR1 hx`. This is only supported -on unix operating systems. +You can use a custom configuration file by specifying it with the `-c` or +`--config` command line argument, for example `hx -c path/to/custom-config.toml`. +Additionally, you can reload the configuration file by sending the USR1 +signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`. ## Editor @@ -38,23 +36,23 @@ on unix operating systems. | Key | Description | Default | |--|--|---------| -| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `5` | -| `mouse` | Enable mouse mode. | `true` | -| `middle-click-paste` | Middle click paste support. | `true` | -| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` | -| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`
Windows: `["cmd", "/C"]` | -| `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` | -| `cursorline` | Highlight all lines with a cursor. | `false` | -| `cursorcolumn` | Highlight all columns with a cursor. | `false` | +| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` | +| `mouse` | Enable mouse mode | `true` | +| `middle-click-paste` | Middle click paste support | `true` | +| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` | +| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`
Windows: `["cmd", "/C"]` | +| `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` | +| `cursorline` | Highlight all lines with a cursor | `false` | +| `cursorcolumn` | Highlight all columns with a cursor | `false` | | `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"]` | -| `auto-completion` | Enable automatic pop up of auto-completion. | `true` | -| `auto-format` | Enable automatic formatting on save. | `true` | -| `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` | -| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | +| `auto-completion` | Enable automatic pop up of auto-completion | `true` | +| `auto-format` | Enable automatic formatting on save | `true` | +| `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` | +| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | -| `auto-info` | Whether to display infoboxes | `true` | -| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` | -| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` | +| `auto-info` | Whether to display info boxes | `true` | +| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` | +| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` | | `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` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | @@ -125,10 +123,12 @@ The following statusline elements can be configured: ### `[editor.cursor-shape]` Section -Defines the shape of cursor in each mode. Note that due to limitations -of the terminal environment, only the primary cursor can change shape. +Defines the shape of cursor in each mode. Valid values for these options are `block`, `bar`, `underline`, or `hidden`. +> 💡 Due to limitations of the terminal environment, only the primary cursor can +> change shape. + | Key | Description | Default | | --- | ----------- | ------- | | `normal` | Cursor shape in [normal mode][normal mode] | `block` | @@ -141,25 +141,22 @@ Valid values for these options are `block`, `bar`, `underline`, or `hidden`. ### `[editor.file-picker]` Section -Sets options for file picker and global search. All but the last key listed in -the default file-picker configuration below are IgnoreOptions: whether hidden -files and files listed within ignore files are ignored by (not visible in) the -helix file picker and global search. There is also one other key, `max-depth` -available, which is not defined by default. +Set options for file picker and global search. Ignoring a file means it is +not visible in the Helix file picker and global search. All git related options are only enabled in a git repository. | Key | Description | Default | |--|--|---------| -|`hidden` | Enables ignoring hidden files. | true +|`hidden` | Enables ignoring hidden files | true |`follow-links` | Follow symlinks instead of ignoring them | true |`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true -|`parents` | Enables reading ignore files from parent directories. | true -|`ignore` | Enables reading `.ignore` files. | true -|`git-ignore` | Enables reading `.gitignore` files. | true -|`git-global` | Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. | true -|`git-exclude` | Enables reading `.git/info/exclude` files. | true -|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`. +|`parents` | Enables reading ignore files from parent directories | true +|`ignore` | Enables reading `.ignore` files | true +|`git-ignore` | Enables reading `.gitignore` files | true +|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option | true +|`git-exclude` | Enables reading `.git/info/exclude` files | true +|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`. ### `[editor.auto-pairs]` Section @@ -211,7 +208,7 @@ Search specific options. | Key | Description | Default | |--|--|---------| -| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` | +| `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` | | `wrap-around`| Whether the search should wrap after depleting the matches | `true` | ### `[editor.whitespace]` Section @@ -220,7 +217,7 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r | Key | Description | Default | |-----|-------------|---------| -| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline`. | `"none"` | +| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline` | `"none"` | | `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below | Example @@ -248,7 +245,7 @@ Options for rendering vertical indent guides. | Key | Description | Default | | --- | --- | --- | -| `render` | Whether to render indent guides. | `false` | +| `render` | Whether to render indent guides | `false` | | `character` | Literal character to use for rendering the indent guide | `│` | | `skip-levels` | Number of indent levels to skip | `0` | @@ -273,7 +270,7 @@ gutters = ["diff", "diagnostics", "line-numbers", "spacer"] To customize the behavior of gutters, the `[editor.gutters]` section must be used. This section contains top level settings, as well as settings for -specific gutter components as sub-sections. +specific gutter components as subsections. | Key | Description | Default | | --- | --- | --- | @@ -315,13 +312,13 @@ Currently unused ### `[editor.soft-wrap]` Section -Options for soft wrapping lines that exceed the view width +Options for soft wrapping lines that exceed the view width: | Key | Description | Default | | --- | --- | --- | -| `enable` | Whether soft wrapping is enabled. | `false` | -| `max-wrap` | Maximum free space left at the end of the line. | `20` | -| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` | +| `enable` | Whether soft wrapping is enabled | `false` | +| `max-wrap` | Maximum free space left at the end of the line | `20` | +| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line | `40` | | `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` | Example: diff --git a/book/src/guides/README.md b/book/src/guides/README.md index e0c44ce7d..c25768e68 100644 --- a/book/src/guides/README.md +++ b/book/src/guides/README.md @@ -1,4 +1,4 @@ # Guides This section contains guides for adding new language server configurations, -tree-sitter grammars, textobject queries, etc. +tree-sitter grammars, textobject queries, and other similar items. diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md index 6598b9bf7..b92af4028 100644 --- a/book/src/guides/adding_languages.md +++ b/book/src/guides/adding_languages.md @@ -1,45 +1,52 @@ -# Adding languages +# Adding new languages to Helix + +In order to add a new language to Helix, you will need to follow the steps +below. ## Language configuration -To add a new language, you need to add a `[[language]]` entry to the -`languages.toml` (see the [language configuration section]). +1. Add a new `[[language]]` entry in the `languages.toml` file and provide the + necessary configuration for the new language. For more information on + language configuration, refer to the + [language configuration section](../languages.md) of the documentation. +2. If you are adding a new language or updating an existing language server + configuration, run the command `cargo xtask docgen` to update the + [Language Support](../lang-support.md) documentation. -When adding a new language or Language Server configuration for an existing -language, run `cargo xtask docgen` to add the new configuration to the -[Language Support][lang-support] docs before creating a pull request. -When adding a Language Server configuration, be sure to update the -[Language Server Wiki][install-lsp-wiki] with installation notes. +> 💡 If you are adding a new Language Server configuration, make sure to update +> the +> [Language Server Wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) +> with the installation instructions. ## Grammar configuration -If a tree-sitter grammar is available for the language, add a new `[[grammar]]` -entry to `languages.toml`. - -You may use the `source.path` key rather than `source.git` with an absolute path -to a locally available grammar for testing, but switch to `source.git` before -submitting a pull request. +1. If a tree-sitter grammar is available for the new language, add a new + `[[grammar]]` entry to the `languages.toml` file. +2. If you are testing the grammar locally, you can use the `source.path` key + with an absolute path to the grammar. However, before submitting a pull + request, make sure to switch to using `source.git`. ## Queries -For a language to have syntax-highlighting and indentation among -other things, you have to add queries. Add a directory for your -language with the path `runtime/queries//`. The tree-sitter -[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries) -gives more info on how to write queries. - -> NOTE: When evaluating queries, the first matching query takes -precedence, which is different from other editors like Neovim where -the last matching query supersedes the ones before it. See -[this issue][neovim-query-precedence] for an example. - -## Common Issues - -- 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. - -- If a parser is segfaulting or you want to remove the parser, make sure to remove the compiled parser in `runtime/grammar/.so` - -[language configuration section]: ../languages.md -[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090 -[install-lsp-wiki]: https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers -[lang-support]: ../lang-support.md +1. In order to provide syntax highlighting and indentation for the new language, + you will need to add queries. +2. Create a new directory for the language with the path + `runtime/queries//`. +3. Refer to the + [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries) + for more information on writing queries. + +> 💡 In Helix, the first matching query takes precedence when evaluating +> queries, which is different from other editors such as Neovim where the last +> matching query supersedes the ones before it. See +> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090) +> for an example. + +## Common issues + +- If you encounter errors when running Helix after switching branches, you may + need to update the tree-sitter grammars. Run the command `hx --grammar fetch` + to fetch the grammars and `hx --grammar build` to build any out-of-date + grammars. +- If a parser is causing a segfault, or you want to remove it, make sure to + remove the compiled parser located at `runtime/grammars/.so`. diff --git a/book/src/guides/indent.md b/book/src/guides/indent.md index 0e2592897..b660d7857 100644 --- a/book/src/guides/indent.md +++ b/book/src/guides/indent.md @@ -1,4 +1,4 @@ -# Adding Indent Queries +# Adding indent queries Helix uses tree-sitter to correctly indent new lines. This requires a tree-sitter grammar and an `indent.scm` query file placed in @@ -36,7 +36,7 @@ changed by using a `#set!` declaration anywhere in the pattern: (#set! "scope" "all")) ``` -## Capture Types +## Capture types - `@indent` (default scope `tail`): Increase the indent level by 1. Multiple occurrences in the same line diff --git a/book/src/guides/textobject.md b/book/src/guides/textobject.md index 8a2173547..405f11c1b 100644 --- a/book/src/guides/textobject.md +++ b/book/src/guides/textobject.md @@ -1,14 +1,14 @@ -# Adding Textobject Queries +# Adding textobject queries -Textobjects that are language specific ([like functions, classes, etc][textobjects]) -require an accompanying tree-sitter grammar and a `textobjects.scm` query file +Helix supports textobjects that are language specific, such as functions, classes, etc. +These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file to work properly. Tree-sitter allows us to query the source code syntax tree and capture specific parts of it. The queries are written in a lisp dialect. More information on how to write queries can be found in the [official tree-sitter documentation][tree-sitter-queries]. Query files should be placed in `runtime/queries/{language}/textobjects.scm` -when contributing. Note that to test the query files locally you should put +when contributing to Helix. Note that to test the query files locally you should put them under your local runtime directory (`~/.config/helix/runtime` on Linux for example). @@ -28,9 +28,9 @@ The following [captures][tree-sitter-captures] are recognized: [Example query files][textobject-examples] can be found in the helix GitHub repository. -## Queries for Textobject Based Navigation +## Queries for textobject based navigation -[Tree-sitter based navigation][textobjects-nav] is done using captures in the +Tree-sitter based navigation in Helix is done using captures in the following order: - `object.movement` @@ -38,12 +38,10 @@ following order: - `object.inside` For example if a `function.around` capture has been already defined for a language -in it's `textobjects.scm` file, function navigation should also work automatically. +in its `textobjects.scm` file, function navigation should also work automatically. `function.movement` should be defined only if the node captured by `function.around` doesn't make sense in a navigation context. -[textobjects]: ../usage.md#textobjects -[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation [tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax [tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes [textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l= diff --git a/book/src/install.md b/book/src/install.md index 7df9e6c77..f9cf9a3ba 100644 --- a/book/src/install.md +++ b/book/src/install.md @@ -1,180 +1,223 @@ -# Installation - -We provide pre-built binaries on the [GitHub Releases page](https://github.com/helix-editor/helix/releases). +# Installing Helix + + +- [Pre-built binaries](#pre-built-binaries) +- [Linux, macOS, Windows and OpenBSD packaging status](#linux-macos-windows-and-openbsd-packaging-status) +- [Linux](#linux) + - [Ubuntu](#ubuntu) + - [Fedora/RHEL](#fedorarhel) + - [Arch Linux community](#arch-linux-community) + - [NixOS](#nixos) +- [macOS](#macos) + - [Homebrew Core](#homebrew-core) +- [Windows](#windows) + - [Scoop](#scoop) + - [Chocolatey](#chocolatey) + - [MSYS2](#msys2) +- [Building from source](#building-from-source) + - [Configuring Helix's runtime files](#configuring-helixs-runtime-files) + - [Validating the installation](#validating-the-installation) + - [Configure the desktop shortcut](#configure-the-desktop-shortcut) + + +To install Helix, follow the instructions specific to your operating system. +Note that: + +- To get the latest nightly version of Helix, you need to + [build from source](#building-from-source). + +- To take full advantage of Helix, install the language servers for your + preferred programming languages. See the + [wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) + for instructions. + +## Pre-built binaries + +Download pre-built binaries from the +[GitHub Releases page](https://github.com/helix-editor/helix/releases). Add the binary to your system's `$PATH` to use it from the command +line. + +## Linux, macOS, Windows and OpenBSD packaging status + +Helix is available for Linux, macOS and Windows via the official repositories listed below. [![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions) -## OSX +## Linux -Helix is available in homebrew-core: +The following third party repositories are available: -``` -brew install helix -``` +### Ubuntu -## Linux +Helix is available via [Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor): -### NixOS +```sh +sudo add-apt-repository ppa:maveonair/helix-editor +sudo apt update +sudo apt install helix +``` -A [flake](https://nixos.wiki/wiki/Flakes) containing the package is available in -the project root. The flake can also be used to spin up a reproducible development -shell for working on Helix with `nix develop`. +### Fedora/RHEL -Flake outputs are cached for each push to master using -[Cachix](https://www.cachix.org/). The flake is configured to -automatically make use of this cache assuming the user accepts -the new settings on first use. +Helix is available via `copr`: -If you are using a version of Nix without flakes enabled you can -[install Cachix cli](https://docs.cachix.org/installation); `cachix use helix` will -configure Nix to use cached outputs when possible. +```sh +sudo dnf copr enable varlad/helix +sudo dnf install helix +``` -### Arch Linux +### Arch Linux community -Releases are available in the `community` repository. +Releases are available in the `community` repository: -A [helix-git](https://aur.archlinux.org/packages/helix-git/) package is also available on the AUR, which builds the master branch. +```sh +sudo pacman -S helix +``` +Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available +in the AUR, which builds the master branch. -### Fedora Linux +### NixOS -You can install the COPR package for Helix via +Helix is available as a [flake](https://nixos.wiki/wiki/Flakes) in the project +root. Use `nix develop` to spin up a reproducible development shell. Outputs are +cached for each push to master using [Cachix](https://www.cachix.org/). The +flake is configured to automatically make use of this cache assuming the user +accepts the new settings on first use. -``` -sudo dnf copr enable varlad/helix -sudo dnf install helix -``` +If you are using a version of Nix without flakes enabled, +[install Cachix CLI](https://docs.cachix.org/installation) and use +`cachix use helix` to configure Nix to use cached outputs when possible. + +## macOS -### Void Linux +### Homebrew Core -``` -sudo xbps-install helix +```sh +brew install helix ``` ## Windows -Helix can be installed using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/) +Install on Windows using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/) or [MSYS2](https://msys2.org/). -**Scoop:** +### Scoop -``` +```sh scoop install helix ``` -**Chocolatey:** +### Chocolatey -``` +```sh choco install helix ``` -**MSYS2:** - -Choose the [proper command](https://www.msys2.org/docs/package-naming/) for your system from below: - - - For 32 bit Windows 7 or above: - -``` -pacman -S mingw-w64-i686-helix -``` - - - For 64 bit Windows 7 or above: - -``` -pacman -S mingw-w64-x86_64-helix -``` +### MSYS2 - - For 64 bit Windows 8.1 or above: +For 64-bit Windows 8.1 or above: -``` +```sh pacman -S mingw-w64-ucrt-x86_64-helix ``` -## Build from source +## Building from source -``` +Clone the repository: + +```sh git clone https://github.com/helix-editor/helix cd helix -cargo install --path helix-term --locked ``` -This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`. - -If you are using the musl-libc instead of glibc the following environment variable must be set during the build -to ensure tree sitter grammars can be loaded correctly: +Compile from source: +```sh +cargo install --path helix-term --locked ``` -RUSTFLAGS="-C target-feature=-crt-static" -``` - -Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the -config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overridden -via the `HELIX_RUNTIME` environment variable. +This command will create the `hx` executable and construct the tree-sitter +grammars either in the `runtime` folder, or in the folder specified in `HELIX_RUNTIME` +(as described below). To build the tree-sitter grammars requires a c++ compiler to be installed, for example `gcc-c++`. -| OS | Command | -| -------------------- | ------------------------------------------------ | -| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` | -| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` | -| Linux / macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` | +> 💡 If you are using the musl-libc instead of glibc the following environment variable must be set during the build +> to ensure tree-sitter grammars can be loaded correctly: +> +> ```sh +> RUSTFLAGS="-C target-feature=-crt-static" +> ``` -Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires -elevated privileges - i.e. PowerShell or Cmd must be run as administrator. +> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch +> grammars with `hx --grammar fetch` (requires `git`) and compile them with +> `hx --grammar build` (requires a C++ compiler). -**PowerShell:** +### Configuring Helix's runtime files -```powershell -New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime" -``` -Note: "runtime" must be the absolute path to the runtime directory. +- **Linux and macOS** -**Cmd:** +Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent: -```cmd -cd %appdata%\helix -mklink /D runtime "\runtime" +```sh +HELIX_RUNTIME=/home/user-name/src/helix/runtime ``` -The runtime location can be overridden via the `HELIX_RUNTIME` environment variable. +Or, create a symlink in `~/.config/helix` that links to the source code directory: -> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --path helix-term --locked`, -> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`. +```sh +ln -s $PWD/runtime ~/.config/helix/runtime +``` -If you plan on keeping the repo locally, an alternative to copying/symlinking -runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime` -(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory). +- **Windows** -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: +Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for +`Edit environment variables for your account`) or use the `setx` command in +Cmd: -```bash -cp contrib/Helix.desktop ~/.local/share/applications +```sh +setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime" ``` -To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`: +> 💡 `%userprofile%` resolves to your user directory like +> `C:\Users\Your-Name\` for example. -```bash -sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop -sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop -``` +Or, create a symlink in `%appdata%\helix\` that links to the source code directory: -Please note: there is no icon for Helix yet, so the system default will be used. + | Method | Command | + | ---------- | -------------------------------------------------------------------------------------- | + | PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` | + | Cmd | `cd %appdata%\helix`
`mklink /D runtime "%userprofile%\src\helix\runtime"` | -## Finishing up the installation + > 💡 On Windows, creating a symbolic link may require running PowerShell or + > Cmd as an administrator. -To make sure everything is set up as expected you should finally run the helix healthcheck via +### Validating the installation -``` +To make sure everything is set up as expected you should run the Helix health +check: + +```sh hx --health ``` -For more information on the information displayed in the health check results refer to [Healthcheck](https://github.com/helix-editor/helix/wiki/Healthcheck). +For more information on the health check results refer to +[Health check](https://github.com/helix-editor/helix/wiki/Healthcheck). -### Building tree-sitter grammars +### Configure the desktop shortcut -Tree-sitter grammars must be fetched and compiled if not pre-packaged. -Fetch grammars with `hx --grammar fetch` (requires `git`) and compile them -with `hx --grammar build` (requires a C++ compiler). +If your desktop environment supports the +[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html) +you can configure Helix to show up in the application menu by copying the +provided `.desktop` and icon files to their correct folders: -### Installing language servers +```sh +cp contrib/Helix.desktop ~/.local/share/applications +cp contrib/helix.png ~/.icons # or ~/.local/share/icons +``` + +To use another terminal than the system default, you can modify the `.desktop` +file. For example, to use `kitty`: -Language servers can optionally be installed if you want their features (auto-complete, diagnostics etc.). -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. +```sh +sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop +sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop +``` diff --git a/book/src/keymap.md b/book/src/keymap.md index bc16aa1a7..173728f27 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -14,14 +14,14 @@ - [Space mode](#space-mode) - [Popup](#popup) - [Unimpaired](#unimpaired) -- [Insert Mode](#insert-mode) -- [Select / extend mode](#select--extend-mode) +- [Insert mode](#insert-mode) +- [Select / extend mode](#select-extend-mode) - [Picker](#picker) - [Prompt](#prompt) > 💡 Mappings marked (**LSP**) require an active language server for the file. -> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the filetype. +> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the file type. ## Normal mode @@ -109,7 +109,7 @@ | Key | Description | Command | | ----- | ----------- | ------- | | `s` | Select all regex matches inside selections | `select_regex` | -| `S` | Split selection into subselections on regex matches | `split_selection` | +| `S` | Split selection into sub selections on regex matches | `split_selection` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` | | `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` | | `&` | Align selection in columns | `align_selections` | @@ -141,7 +141,7 @@ ### Search -Search commands all operate on the `/` register by default. Use `"` to operate on a different one. +Search commands all operate on the `/` register by default. To use a different register, use `"`. | Key | Description | Command | | ----- | ----------- | ------- | @@ -175,9 +175,8 @@ Accessed by typing `z` in [normal mode](#normal-mode). View mode is intended for scrolling and manipulating the view without changing the selection. The "sticky" variant of this mode (accessed by typing `Z` in -normal mode) is persistent; use the Escape key to return to normal mode after -usage (useful when you're simply looking over text and not actively editing -it). +normal mode) is persistent and can be exited using the escape key. This is +useful when you're simply looking over text and not actively editing it. | Key | Description | Command | @@ -225,7 +224,7 @@ Jumps to various locations. Accessed by typing `m` in [normal mode](#normal-mode). See the relevant section in [Usage](./usage.md) for an explanation about -[surround](./usage.md#surround) and [textobject](./usage.md#textobjects) usage. +[surround](./usage.md#surround) and [textobject](./usage.md#navigating-using-tree-sitter-textobjects) usage. | Key | Description | Command | | ----- | ----------- | ------- | @@ -242,7 +241,7 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`). Accessed by typing `Ctrl-w` in [normal mode](#normal-mode). -This layer is similar to Vim keybindings as Kakoune does not support window. +This layer is similar to Vim keybindings as Kakoune does not support windows. | Key | Description | Command | | ----- | ------------- | ------- | @@ -293,7 +292,7 @@ This layer is a kludge of mappings, mostly pickers. | `/` | Global search in workspace folder | `global_search` | | `?` | Open command palette | `command_palette` | -> TIP: Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file. +> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file. ##### Popup @@ -306,7 +305,7 @@ Displays documentation for item under cursor. #### Unimpaired -Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired). +These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired). | Key | Description | Command | | ----- | ----------- | ------- | @@ -335,12 +334,13 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire ## Insert mode -Insert mode bindings are somewhat minimal by default. Helix is designed to +Insert mode bindings are minimal by default. Helix is designed to be a modal editor, and this is reflected in the user experience and internal -mechanics. For example, changes to the text are only saved for undos when -escaping from insert mode to normal mode. For this reason, new users are -strongly encouraged to learn the modal editing paradigm to get the smoothest -experience. +mechanics. Changes to the text are only saved for undos when +escaping from insert mode to normal mode. + +> 💡 New users are strongly encouraged to learn the modal editing paradigm +> to get the smoothest experience. | Key | Description | Command | | ----- | ----------- | ------- | @@ -370,8 +370,8 @@ with modal editors. | `Home` | Move to line start | `goto_line_start` | | `End` | Move to line end | `goto_line_end_newline` | -If you want to disable them in insert mode as you become more comfortable with modal editing, you can use -the following in your `config.toml`: +As you become more comfortable with modal editing, you may want to disable some +insert mode bindings. You can do this by editing your `config.toml` file. ```toml [keys.insert] @@ -387,7 +387,7 @@ end = "no_op" ## Select / extend mode -This mode echoes Normal mode, but changes any movements to extend +Select mode echoes Normal mode, but changes any movements to extend selections rather than replace them. Goto motions are also changed to extend, so that `vgl` for example extends the selection to the end of the line. diff --git a/book/src/lang-support.md b/book/src/lang-support.md index 6a08cd699..3f96673bc 100644 --- a/book/src/lang-support.md +++ b/book/src/lang-support.md @@ -1,10 +1,10 @@ # Language Support -The following languages and Language Servers are supported. In order to use +The following languages and Language Servers are supported. To use Language Server features, you must first [install][lsp-install-wiki] the appropriate Language Server. -Check the language support in your installed helix version with `hx --health`. +You can check the language support in your installed helix version with `hx --health`. Also see the [Language Configuration][lang-config] docs and the [Adding Languages][adding-languages] guide for more language configuration information. diff --git a/book/src/languages.md b/book/src/languages.md index 74d090eb7..8a8f3bb61 100644 --- a/book/src/languages.md +++ b/book/src/languages.md @@ -5,13 +5,15 @@ in `languages.toml` files. ## `languages.toml` files -There are three possible `languages.toml` files. The first is compiled into -Helix and lives in the [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml). -This provides the default configurations for languages and language servers. +There are three possible locations for a `languages.toml` file: -You may define a `languages.toml` in your [configuration directory](./configuration.md) -which overrides values from the built-in language configuration. For example -to disable auto-LSP-formatting in Rust: +1. In the Helix source code, this lives in the + [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml). + It provides the default configurations for languages and language servers. + +2. In your [configuration directory](./configuration.md). This overrides values + from the built-in language configuration. For example to disable + auto-LSP-formatting in Rust: ```toml # in /helix/languages.toml @@ -21,10 +23,10 @@ name = "rust" auto-format = false ``` -Language configuration may also be overridden local to a project by creating -a `languages.toml` file under a `.helix` directory. Its settings will be merged -with the language configuration in the configuration directory and the built-in -configuration. +3. In a `.helix` folder in your project. Language configuration may also be + overridden local to a project by creating a `languages.toml` file in a + `.helix` folder. Its settings will be merged with the language configuration + in the configuration directory and the built-in configuration. ## Language configuration @@ -65,7 +67,7 @@ These configuration keys are available: ### File-type detection and the `file-types` key -Helix determines which language configuration to use with the `file-types` key +Helix determines which language configuration to use based on the `file-types` key from the above section. `file-types` is a list of strings or tables, for example: diff --git a/book/src/remapping.md b/book/src/remapping.md index 8339e05fc..d762c6add 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -1,18 +1,18 @@ -# Key Remapping +# Key remapping -One-way key remapping is temporarily supported via a simple TOML configuration +Helix currently supports one-way key remapping through a simple TOML configuration file. (More powerful solutions such as rebinding via commands will be available in the future). -To remap keys, write a `config.toml` file in your `helix` configuration -directory (default `~/.config/helix` in Linux systems) with a structure like +To remap keys, create a `config.toml` file in your `helix` configuration +directory (default `~/.config/helix` on Linux systems) with a structure like this: ```toml # At most one section each of 'keys.normal', 'keys.insert' and 'keys.select' [keys.normal] -C-s = ":w" # Maps the Ctrl-s to the typable command :w which is an alias for :write (save file) -C-o = ":open ~/.config/helix/config.toml" # Maps the Ctrl-o to opening of the helix config file +C-s = ":w" # Maps Ctrl-s to the typable command :w which is an alias for :write (save file) +C-o = ":open ~/.config/helix/config.toml" # Maps Ctrl-o to opening of the helix config file a = "move_char_left" # Maps the 'a' key to the move_char_left command w = "move_line_up" # Maps the 'w' key move_line_up "C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line @@ -20,10 +20,9 @@ g = { a = "code_action" } # Maps `ga` to show possible code actions "ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode [keys.insert] -"A-x" = "normal_mode" # Maps Alt-X to enter normal mode +"A-x" = "normal_mode" # Maps Alt-X to enter normal mode j = { k = "normal_mode" } # Maps `jk` to exit insert mode ``` -> NOTE: Typable commands can also be remapped, remember to keep the `:` prefix to indicate it's a typable command. ## Minor modes @@ -76,5 +75,5 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes Keys can be disabled by binding them to the `no_op` command. -Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands. -> 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`. +A list of commands is available in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation + 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`. diff --git a/book/src/themes.md b/book/src/themes.md index 9b7e97a1c..af238e949 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -1,14 +1,15 @@ # Themes -To use a theme add `theme = ""` to your [`config.toml`](./configuration.md) at the very top of the file before the first section or select it during runtime using `:theme `. +To use a theme add `theme = ""` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme `. ## Creating a theme -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. +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. -The names "default" and "base16_default" are reserved for the builtin themes and cannot be overridden by user defined themes. +> 💡 The names "default" and "base16_default" are reserved for built-in themes +> and cannot be overridden by user-defined themes. -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). +### Overview Each line in the theme file is specified as below: @@ -16,7 +17,7 @@ Each line in the theme file is specified as below: key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] } ``` -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. +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. To specify only the foreground color: @@ -24,15 +25,30 @@ To specify only the foreground color: key = "#ffffff" ``` -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). +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). ```toml "key.key" = "#ffffff" ``` +For inspiration, you can find the default `theme.toml` +[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). + +### Using the linter + +Use the supplied linting tool to check for errors and missing scopes: + +```sh +cargo xtask themelint onedark # replace onedark with +``` + +## The details of theme creation + ### Color palettes -It's recommended define a palette of named colors, and refer to them from the +It's recommended to define a palette of named colors, and refer to them in the configuration values in your theme. To do this, add a table called `palette` to your theme file: @@ -45,8 +61,8 @@ white = "#ffffff" black = "#000000" ``` -Remember that the `[palette]` table includes all keys after its header, -so you should define the palette after normal theme options. +Keep in mind that the `[palette]` table includes all keys after its header, +so it should be defined after the normal theme options. The default palette uses the terminal's default 16 colors, and the colors names are listed below. The `[palette]` section in the config file takes precedence @@ -73,9 +89,8 @@ over it and is merged into the default palette. ### Modifiers -The following values may be used as modifiers. - -Less common modifiers might not be supported by your terminal emulator. +The following values may be used as modifier, provided they are supported by +your terminal emulator. | Modifier | | --- | @@ -89,14 +104,13 @@ Less common modifiers might not be supported by your terminal emulator. | `hidden` | | `crossed_out` | -> Note: The `underlined` modifier is deprecated and only available for backwards compatibility. +> 💡 The `underlined` modifier is deprecated and only available for backwards compatibility. > Its behavior is equivalent to setting `underline.style="line"`. -### Underline Style - -One of the following values may be used as a value for `underline.style`. +### Underline style -Some styles might not be supported by your terminal emulator. +One of the following values may be used as a value for `underline.style`, providing it is +supported by your terminal emulator. | Modifier | | --- | @@ -109,7 +123,7 @@ Some styles might not be supported by your terminal emulator. ### Inheritance -Extend upon other themes by setting the `inherits` property to an existing theme. +Extend other themes by setting the `inherits` property to an existing theme. ```toml inherits = "boo_berry" @@ -124,19 +138,19 @@ berry = "#2A2A4D" ### Scopes -The following is a list of scopes available to use for styling. +The following is a list of scopes available to use for styling: #### Syntax highlighting These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). -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`. +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`. We use a similar set of scopes as -[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also +[Sublime Text](https://www.sublimetext.com/docs/scope_naming.html). See also [TextMate](https://macromates.com/manual/en/language_grammars) scopes. -- `attribute` - Class attributes, html tag attributes +- `attribute` - Class attributes, HTML tag attributes - `type` - Types - `builtin` - Primitive types provided by the language (`int`, `usize`) @@ -144,7 +158,7 @@ We use a similar set of scopes as - `variant` - `constructor` -- `constant` (TODO: constant.other.placeholder for %v) +- `constant` (TODO: constant.other.placeholder for `%v`) - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc) - `boolean` - `character` @@ -162,11 +176,11 @@ We use a similar set of scopes as - `comment` - Code comments - `line` - Single line comments (`//`) - - `block` - Block comments (e.g. (`/* */`) + - `block` - Block comments (e.g. (`/* */`) - `documentation` - Documentation comments (e.g. `///` in Rust) - `variable` - Variables - - `builtin` - Reserved language variables (`self`, `this`, `super`, etc) + - `builtin` - Reserved language variables (`self`, `this`, `super`, etc.) - `parameter` - Function parameters - `other` - `member` - Fields of composite data types (e.g. structs, unions) @@ -186,10 +200,10 @@ We use a similar set of scopes as - `return` - `exception` - `operator` - `or`, `in` - - `directive` - Preprocessor directives (`#if` in C) + - `directive` - Preprocessor directives (`#if` in C) - `function` - `fn`, `func` - `storage` - Keywords describing how things are stored - - `type` - The type of something, `class`, `function`, `var`, `let`, etc. + - `type` - The type of something, `class`, `function`, `var`, `let`, etc. - `modifier` - Storage modifiers like `static`, `mut`, `const`, `ref`, etc. - `operator` - `||`, `+=`, `>` @@ -216,9 +230,9 @@ We use a similar set of scopes as - `bold` - `italic` - `link` - - `url` - urls pointed to by links - - `label` - non-url link references - - `text` - url and image descriptions in links + - `url` - URLs pointed to by links + - `label` - non-URL link references + - `text` - URL and image descriptions in links - `quote` - `raw` - `inline` @@ -232,19 +246,19 @@ We use a similar set of scopes as #### Interface -These scopes are used for theming the editor interface. +These scopes are used for theming the editor interface: - `markup` - `normal` - - `completion` - for completion doc popup ui - - `hover` - for hover popup ui + - `completion` - for completion doc popup UI + - `hover` - for hover popup UI - `heading` - - `completion` - for completion doc popup ui - - `hover` - for hover popup ui + - `completion` - for completion doc popup UI + - `hover` - for hover popup UI - `raw` - `inline` - - `completion` - for completion doc popup ui - - `hover` - for hover popup ui + - `completion` - for completion doc popup UI + - `hover` - for hover popup UI | Key | Notes | @@ -270,9 +284,9 @@ These scopes are used for theming the editor interface. | `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.separator` | Separator character in statusline | -| `ui.popup` | Documentation popups (e.g Space + k) | +| `ui.popup` | Documentation popups (e.g. Space + k) | | `ui.popup.info` | Prompt for multiple key options | -| `ui.window` | Border lines separating splits | +| `ui.window` | Borderlines separating splits | | `ui.help` | Description box for commands | | `ui.text` | Command prompts, popup text, etc. | | `ui.text.focus` | | @@ -301,10 +315,4 @@ These scopes are used for theming the editor interface. | `diagnostic.warning` | Diagnostics warning (editing area) | | `diagnostic.error` | Diagnostics error (editing area) | -You can check compliance to spec with - -```shell -cargo xtask themelint onedark # replace onedark with -``` - [editor-section]: ./configuration.md#editor-section diff --git a/book/src/usage.md b/book/src/usage.md index a6eb9ec1d..81cf83725 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -1,22 +1,43 @@ -# Usage +# Using Helix -(Currently not fully documented, see the [keymappings](./keymap.md) list for more.) + +- [Registers](#registers) + - [User-defined registers](#user-defined-registers) + - [Special registers](#special-registers) +- [Surround](#surround) +- [Selecting and manipulating text with textobjects](#selecting-and-manipulating-text-with-textobjects) +- [Navigating using tree-sitter textobjects](#navigating-using-tree-sitter-textobjects) +- [Moving the selection with syntax-aware motions](#moving-the-selection-with-syntax-aware-motions) + -See [tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) (accessible via `hx --tutor` or `:tutor`) for a vimtutor-like introduction. +For a full interactive introduction to Helix, refer to the +[tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) which +can be accessed via the command `hx --tutor` or `:tutor`. + +> 💡 Currently, not all functionality is fully documented, please refer to the +> [key mappings](./keymap.md) list. ## Registers -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: +In Helix, registers are storage locations for text and other data, such as the +result of a search. Registers can be used to cut, copy, and paste text, similar +to the clipboard in other text editors. Usage is similar to Vim, with `"` being +used to select a register. + +### User-defined registers + +Helix allows you to create your own named registers for storing text, for +example: - `"ay` - Yank the current selection to register `a`. - `"op` - Paste the text in register `o` after the selection. -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: +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: - `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode). - `"md` - Store the selection in register `m` and delete it. -### Special Registers +### Special registers | Register character | Contains | | --- | --- | @@ -25,41 +46,90 @@ If there is a selected register before invoking a change or delete command, the | `"` | Last yanked text | | `_` | Black hole | -> 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. -> The black hole register works as a no-op register, meaning no data will be written to / read from it. +The system clipboard is not directly supported by a special register. Instead, special commands and keybindings are provided. Refer to the +[key map](keymap.md#space-mode) for more details. + +The black hole register is a no-op register, meaning that no data will be read or written to it. ## Surround -Functionality similar to [vim-surround](https://github.com/tpope/vim-surround) is built into -helix. The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich): +Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround). +The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich): -![surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif) +![Surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif) -- `ms` - Add surround characters -- `mr` - Replace surround characters -- `md` - Delete surround characters +| Key Sequence | Action | +| --------------------------------- | --------------------------------------- | +| `ms` (after selecting text) | Add surround characters to selection | +| `mr` | Replace the closest surround characters | +| `md` | Delete the closest surround characters | -`ms` acts on a selection, so select the text first and use `ms`. `mr` and `md` work -on the closest pairs found and selections are not required; use counts to act in outer pairs. +You can use counts to act on outer pairs. -It can also act on multiple selections (yay!). For example, to change every occurrence of `(use)` to `[use]`: +Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`: -- `%` to select the whole file -- `s` to split the selections on a search term -- Input `use` and hit Enter -- `mr([` to replace the parens with square brackets +1. `%` to select the whole file +2. `s` to split the selections on a search term +3. Input `use` and hit Enter +4. `mr([` to replace the parentheses with square brackets -Multiple characters are currently not supported, but planned. +Multiple characters are currently not supported, but planned for future release. -## Syntax-tree Motions +## Selecting and manipulating text with textobjects -`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) move the primary -selection according to the selection's place in the syntax tree. Let's walk -through an example to get familiar with them. Many languages have a syntax like -so for function calls: +In Helix, textobjects are a way to select, manipulate and operate on a piece of +text in a structured way. They allow you to refer to blocks of text based on +their structure or purpose, such as a word, sentence, paragraph, or even a +function or block of code. -``` -func(arg1, arg2, arg3) +![Textobject demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif) +![Textobject tree-sitter demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif) + +- `ma` - Select around the object (`va` in Vim, `` in Kakoune) +- `mi` - Select inside the object (`vi` in Vim, `` in Kakoune) + +| Key after `mi` or `ma` | Textobject selected | +| --- | --- | +| `w` | Word | +| `W` | WORD | +| `p` | Paragraph | +| `(`, `[`, `'`, etc. | Specified surround pairs | +| `m` | The closest surround pair | +| `f` | Function | +| `c` | Class | +| `a` | Argument/parameter | +| `o` | Comment | +| `t` | Test | +| `g` | Change | + +> 💡 `f`, `c`, etc. need a tree-sitter grammar active for the current +document and a special tree-sitter query file to work properly. [Only +some grammars][lang-support] currently have the query file implemented. +Contributions are welcome! + +## Navigating using tree-sitter textobjects + +Navigating between functions, classes, parameters, and other elements is +possible using tree-sitter and textobject queries. For +example to move to the next function use `]f`, to move to previous +class use `[c`, and so on. + +![Tree-sitter-nav-demo][tree-sitter-nav-demo] + +For the full reference see the [unimpaired][unimpaired-keybinds] section of the key bind +documentation. + +> 💡 This feature relies on tree-sitter textobjects +> and requires the corresponding query file to work properly. + +## Moving the selection with syntax-aware motions + +`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the +selection according to its location in the syntax tree. For example, many languages have the +following syntax for function calls: + +```js +func(arg1, arg2, arg3); ``` A function call might be parsed by tree-sitter into a tree like the following. @@ -93,77 +163,29 @@ a more intuitive tree format: └──────────┘ └──────────┘ └──────────┘ ``` -Say we have a selection that wraps `arg1`. The selection is on the `arg1` leaf -in the tree above. +If you have a selection that wraps `arg1` (see the tree above), and you use +`Alt-n`, it will select the next sibling in the syntax tree: `arg2`. -``` +```js +// before func([arg1], arg2, arg3) +// after +func(arg1, [arg2], arg3); ``` -Using `Alt-n` would select the next sibling in the syntax tree: `arg2`. +Similarly, `Alt-o` will expand the selection to the parent node, in this case, the +arguments node. -``` -func(arg1, [arg2], arg3) -``` - -While `Alt-o` would expand the selection to the parent node. In the tree above we -can see that we would select the `arguments` node. - -``` -func[(arg1, arg2, arg3)] +```js +func[(arg1, arg2, arg3)]; ``` There is also some nuanced behavior that prevents you from getting stuck on a -node with no sibling. If we have a selection on `arg1`, `Alt-p` would bring us -to the previous child node. Since `arg1` doesn't have a sibling to its left, -though, we climb the syntax tree and then take the previous selection. So -`Alt-p` will move the selection over to the "func" `identifier`. - -``` -[func](arg1, arg2, arg3) -``` - -## Textobjects - -![textobject-demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif) -![textobject-treesitter-demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif) - -- `ma` - Select around the object (`va` in Vim, `` in Kakoune) -- `mi` - Select inside the object (`vi` in Vim, `` in Kakoune) - -| Key after `mi` or `ma` | Textobject selected | -| --- | --- | -| `w` | Word | -| `W` | WORD | -| `p` | Paragraph | -| `(`, `[`, `'`, etc | Specified surround pairs | -| `m` | Closest surround pair | -| `f` | Function | -| `c` | Class | -| `a` | Argument/parameter | -| `o` | Comment | -| `t` | Test | -| `g` | Change | - -> NOTE: `f`, `c`, etc need a tree-sitter grammar active for the current -document and a special tree-sitter query file to work properly. [Only -some grammars][lang-support] currently have the query file implemented. -Contributions are welcome! - -## Tree-sitter Textobject Based Navigation - -Navigating between functions, classes, parameters, etc is made -possible by leveraging tree-sitter and textobjects queries. For -example to move to the next function use `]f`, to move to previous -class use `[c`, and so on. - -![tree-sitter-nav-demo][tree-sitter-nav-demo] - -See the [unimpaired][unimpaired-keybinds] section of the keybind -documentation for the full reference. - -> NOTE: This feature is dependent on tree-sitter based textobjects -and therefore requires the corresponding query file to work properly. +node with no sibling. When using `Alt-p` with a selection on `arg1`, the previous +child node will be selected. In the event that `arg1` does not have a previous +sibling, the selection will move up the syntax tree and select the previous +element. As a result, using `Alt-p` with a selection on `arg1` will move the +selection to the "func" `identifier`. [lang-support]: ./lang-support.md [unimpaired-keybinds]: ./keymap.md#unimpaired From cfb9986d84764c7f8b3a903f2b092ba0a61dd951 Mon Sep 17 00:00:00 2001 From: Erasin Wang Date: Mon, 6 Mar 2023 23:29:06 +0800 Subject: [PATCH 37/81] Update onelight theme (#6192) --- runtime/themes/onelight.toml | 211 ++++++++++++++++++++++------------- 1 file changed, 133 insertions(+), 78 deletions(-) diff --git a/runtime/themes/onelight.toml b/runtime/themes/onelight.toml index 7c2669793..c2d4e16e3 100644 --- a/runtime/themes/onelight.toml +++ b/runtime/themes/onelight.toml @@ -1,129 +1,184 @@ -# One Light # Author : erasin "attribute" = { fg = "yellow" } -"comment" = { fg = "gray", modifiers = ["italic"] } +"constructor" = { fg = "brown" } +"label" = { fg = "cyan" } +"operator" = { fg = "red" } +"tag" = { fg = "cyan" } +"namespace" = { fg = "blue" } +"special" = { fg = "deep-purple" } +"property" = { fg = "purple" } +"module" = { fg = "cyan" } -"constant" = { fg = "cyan" } +"type" = { fg = "gold" } +"type.builtin" = { fg = "light-blue" } +"type.enum" = { fg = "cyan" } +"type.enum.variant" = { fg = "cyan" } + +"constant" = { fg = "cyan", modifiers = ["bold"] } +"constant.builtin" = { fg = "deep-purple" } +"constant.builtin.boolean" = { fg = "deep-purple" } +"constant.character" = { fg = "green" } +"constant.character.escape" = { fg = "brown" } "constant.numeric" = { fg = "gold" } -"constant.builtin" = { fg = "gold" } -"constant.character.escape" = { fg = "gold" } +"constant.numeric.integer" = { fg = "gold" } +"constant.numeric.float" = { fg = "gold" } -"constructor" = { fg = "yellow" } +"string" = { fg = "green" } +"string.regexp" = { fg = "purple" } +"string.special" = { fg = "green" } +"string.special.path" = { fg = "blue" } +"string.special.url" = { fg = "light-blue" } +"string.special.symbol" = { fg = "pink" } + +"comment" = { fg = "grey", modifiers = ["italic"] } +"comment.line" = { fg = "grey", modifiers = ["italic"] } +"comment.block" = { fg = "grey", modifiers = ["italic"] } +"comment.block.documentation" = { fg = "grey", modifiers = ["italic"] } + +# "variable" = { fg = "black" } +"variable.builtin" = { fg = "light-blue" } +"variable.parameter" = { fg = "red" } +"variable.other" = { fg = "pink" } +"variable.other.member" = { fg = "pink" } -"function" = { fg = "blue" } -"function.builtin" = { fg = "cyan" } -"function.macro" = { fg = "red" } +"punctuation" = { fg = "black" } +"punctuation.delimiter" = { fg = "purple" } +"punctuation.bracket" = { fg = "brown" } +"punctuation.special" = { fg = "brown" } "keyword" = { fg = "purple" } -"keyword.function" = { fg = "purple" } "keyword.control" = { fg = "purple" } -"keyword.control.import" = { fg = "purple" } +"keyword.control.conditional" = { fg = "red", modifiers = ["bold"] } +"keyword.control.repeat" = { fg = "pink", modifiers = ["bold"] } +"keyword.control.import" = { fg = "red" } +"keyword.control.return" = { fg = "deep-purple", modifiers = ["bold"] } +"keyword.control.exception" = { fg = "purple" } +"keyword.operator" = { fg = "red" } "keyword.directive" = { fg = "purple" } -"keyword.operator" = { fg = "purple" } +"keyword.function" = { fg = "purple" } +"keyword.storage" = { fg = "purple" } "keyword.storage.type" = { fg = "purple" } +"keyword.storage.modifier" = { fg = "purple", modifiers = ["bold"] } -"tag" = "cyan" -"label" = { fg = "cyan" } -"namespace" = { fg = "red" } -"operator" = { fg = "red" } -"special" = { fg = "purple" } -"string" = { fg = "green" } -"module" = { fg = "cyan" } - -"type" = { fg = "yellow" } -"type.builtin" = { fg = "purple" } - -"punctuation" = { fg = "gray" } -"punctuation.delimiter" = { fg = "black" } -"punctuation.bracket" = { fg = "gray" } - -"variable" = { fg = "black" } -"variable.builtin" = { fg = "light-blue" } -"variable.parameter" = { fg = "red" } -"variable.other.member" = { fg = "red" } +"function" = { fg = "blue" } +"function.builtin" = { fg = "cyan" } +"function.method" = { fg = "light-blue" } +"function.macro" = { fg = "pink", modifiers = ["bold"] } +"function.special" = { fg = "cyan" } "markup.heading" = { fg = "red" } -"markup.raw" = { fg = "gray" } -"markup.raw.inline" = { fg = "green", bg = "grey-200" } -"markup.bold" = { fg = "yellow", modifiers = ["bold"] } -"markup.italic" = { fg = "purple", modifiers = ["italic"] } -"markup.strikethrough" = { modifiers = ["crossed_out"] } -"markup.list" = { fg = "light-blue" } -"markup.quote" = { fg = "gray" } -"markup.link.url" = { fg = "cyan", modifiers = ["underlined"] } -"markup.link.text" = { fg = "light-blue" } +"markup.heading.marker" = { fg = "red" } "markup.heading.1" = { fg = "red", modifiers = ["bold"] } -"markup.heading.2" = { fg = "gold", modifiers = ["bold"] } +"markup.heading.2" = { fg = "gold", modifiers = [ + "bold", +], underline = { style = "line" } } "markup.heading.3" = { fg = "yellow", modifiers = ["bold"] } "markup.heading.4" = { fg = "green", modifiers = ["bold"] } "markup.heading.5" = { fg = "blue", modifiers = ["bold"] } "markup.heading.6" = { fg = "purple", modifiers = ["bold"] } +"markup.list" = { fg = "light-blue" } +"markup.list.unnumbered" = { fg = "light-blue" } +"markup.list.numbered" = { fg = "light-blue" } +"markup.bold" = { fg = "yellow", modifiers = ["bold"] } +"markup.italic" = { fg = "purple", modifiers = ["italic"] } +"markup.link" = { fg = "light-blue" } +"markup.link.url" = { fg = "cyan", modifiers = ["underlined"] } +"markup.link.text" = { fg = "light-blue" } +"markup.quote" = { fg = "grey" } +"markup.raw" = { fg = "brown" } +"markup.raw.inline" = { fg = "green" } +"markup.raw.block" = { fg = "grey" } -"diff.plus" = "green" -"diff.delta" = "gold" -"diff.minus" = "red" - -"diagnostic.info".underline = { color = "blue", style = "curl" } -"diagnostic.hint".underline = { color = "green", style = "curl" } -"diagnostic.warning".underline = { color = "yellow", style = "curl" } -"diagnostic.error".underline = { color = "red", style = "curl" } - -"info" = { fg = "blue", modifiers = ["bold"] } -"hint" = { fg = "green", modifiers = ["bold"] } -"warning" = { fg = "yellow", modifiers = ["bold"] } -"error" = { fg = "red", modifiers = ["bold"] } +"diff" = { fg = "red" } +"diff.plus" = { fg = "green" } +"diff.minus" = { fg = "red" } +"diff.delta" = { fg = "cyan" } +"diff.delta.moved" = { fg = "cyan" } "ui.background" = { bg = "white" } +"ui.background.separator" = { bg = "white" } -"ui.cursor" = { fg = "white", bg = "gray" } +"ui.cursor" = { fg = "white", bg = "grey" } +"ui.cursor.normal" = { fg = "white", bg = "grey" } +"ui.cursor.insert" = { fg = "white", bg = "grey" } +"ui.cursor.select" = { fg = "white", bg = "grey" } +"ui.cursor.match" = { bg = "light-white", modifiers = ["bold"] } "ui.cursor.primary" = { fg = "white", bg = "black" } -"ui.cursor.match" = { bg = "light-gray" } +"ui.cursor.primary.normal" = { fg = "white", bg = "black" } +"ui.cursor.primary.insert" = { fg = "red", bg = "black" } +"ui.cursor.primary.select" = { fg = "white", bg = "black" } -"ui.cursorline.primary" = { fg = "white", bg = "grey-100" } -# "ui.cursorline.secondary" = { fg = "white", bg = "grey-200" } - -"ui.highlight" = { bg = "light-white" } - -"ui.selection" = { bg = "light-white", modifiers = ["dim"] } -"ui.selection.primary" = { bg = "light-white" } - -"ui.virtual" = { fg = "light-white" } -"ui.virtual.indent-guide" = { fg = "grey-500" } -"ui.virtual.ruler" = { bg = "light-white" } -"ui.virtual.whitespace" = { fg = "light-white" } +"ui.gutter" = { fg = "grey-500" } +"ui.gutter.selected" = { fg = "black" } "ui.linenr" = { fg = "grey-500" } -"ui.linenr.selected" = { fg = "black", modifiers = ["dim"] } +"ui.linenr.selected" = { fg = "black", modifiers = ["bold"] } "ui.statusline" = { fg = "black", bg = "light-white" } -"ui.statusline.inactive" = { fg = "gray", bg = "light-white" } +"ui.statusline.inactive" = { fg = "grey", bg = "grey-200" } "ui.statusline.normal" = { fg = "light-white", bg = "light-blue" } "ui.statusline.insert" = { fg = "light-white", bg = "green" } "ui.statusline.select" = { fg = "light-white", bg = "purple" } +"ui.popup" = { fg = "black", bg = "grey-200" } +"ui.popup.info" = { fg = "black", bg = "grey-200" } +"ui.window" = { fg = "grey-500", bg = "grey-100" } +"ui.help" = { fg = "black", bg = "grey-200" } + "ui.text" = { fg = "black" } "ui.text.focus" = { fg = "red", bg = "light-white", modifiers = ["bold"] } +"ui.text.inactive" = { fg = "grey" } +"ui.text.info" = { fg = "black" } + +"ui.virtual" = { fg = "light-white" } +"ui.virtual.ruler" = { bg = "light-white" } +"ui.virtual.wrap" = { bg = "light-white" } +"ui.virtual.whitespace" = { fg = "light-white" } +"ui.virtual.indent-guide" = { fg = "grey-500" } +"ui.virtual.inlay-hint" = { fg = "grey" } -"ui.help" = { fg = "black", bg = "grey-200" } -"ui.popup" = { fg = "black", bg = "grey-200" } -"ui.window" = { fg = "black", bg = "light-white" } "ui.menu" = { fg = "black", bg = "light-white" } "ui.menu.selected" = { fg = "white", bg = "light-blue" } +"ui.menu.scroll" = { fg = "light-blue", bg = "white" } + +"ui.selection" = { bg = "light-white", modifiers = ["dim"] } +"ui.selection.primary" = { bg = "light-white" } + +"ui.cursorline.primary" = { fg = "white", bg = "grey-100" } +"ui.cursorline.secondary" = { fg = "white", bg = "grey-200" } + +"ui.cursorcolumn.primary" = { fg = "white", bg = "grey-100" } +"ui.cursorcolumn.secondary" = { fg = "white", bg = "grey-200" } + +"ui.highlight" = { bg = "light-white" } + +"diagnostic.info" = { underline = { color = "blue", style = "dotted" } } +"diagnostic.hint" = { underline = { color = "green", style = "dashed" } } +"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } } +"diagnostic.error" = { underline = { color = "red", style = "curl" } } + +"info" = { fg = "blue", modifiers = ["bold"] } +"hint" = { fg = "green", modifiers = ["bold"] } +"warning" = { fg = "yellow", modifiers = ["bold"] } +"error" = { fg = "red", modifiers = ["bold"] } [palette] white = "#FAFAFA" -yellow = "#A06600" +yellow = "#FF6F00" +gold = "#D35400" +brown = "#4E342E" blue = "#0061FF" -light-blue = "#1877F2" -red = "#DC003F" +light-blue = "#0091EA" +red = "#D50000" +pink = "#C2185B" purple = "#B500A9" +deep-purple = "#651FFF" green = "#24A443" -gold = "#D35400" cyan = "#0086C1" black = "#282C34" light-white = "#E3E3E3" -gray = "#5C6370" +grey = "#5C6370" grey-100 = "#F3F3F3" grey-200 = "#EDEDED" grey-500 = "#9E9E9E" From bc50502b1eef4971d3d3a5518386888bf43ccf8b Mon Sep 17 00:00:00 2001 From: Erasin Wang Date: Tue, 7 Mar 2023 00:15:03 +0800 Subject: [PATCH 38/81] Update highlight for ecma/js/ts (#6205) --- runtime/queries/ecma/highlights.scm | 59 +++++++++++++++++++---------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/runtime/queries/ecma/highlights.scm b/runtime/queries/ecma/highlights.scm index 212bb8754..7285ab960 100644 --- a/runtime/queries/ecma/highlights.scm +++ b/runtime/queries/ecma/highlights.scm @@ -167,55 +167,76 @@ ] @punctuation.bracket [ - "as" "async" "debugger" "delete" "extends" "from" - "function" "get" - "in" - "instanceof" "new" - "of" "set" - "static" "target" - "try" "typeof" + "instanceof" "void" "with" ] @keyword +[ + "of" + "as" + "in" +] @keyword.operator + +[ + "function" +] @keyword.function + [ "class" "let" - "const" "var" ] @keyword.storage.type [ - "switch" - "case" + "const" + "static" +] @keyword.storage.modifier + +[ "default" - "if" - "else" "yield" - "throw" "finally" - "return" - "catch" - "continue" - "while" - "break" - "for" "do" "await" ] @keyword.control +[ + "if" + "else" + "switch" + "case" + "while" +] @keyword.control.conditional + +[ + "for" +] @keyword.control.repeat + [ "import" "export" ] @keyword.control.import +[ + "return" + "break" + "continue" +] @keyword.control.return + +[ + "throw" + "try" + "catch" +] @keyword.control.exception + From 77d6ed150c746098020cc7104943fcb64453f388 Mon Sep 17 00:00:00 2001 From: workingj <19818596+workingj@users.noreply.github.com> Date: Tue, 7 Mar 2023 00:35:32 +0100 Subject: [PATCH 39/81] feat(theme): Update pop-dark for color-modes (#6208) --- runtime/themes/pop-dark.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/themes/pop-dark.toml b/runtime/themes/pop-dark.toml index 57db2d2c5..d2010ed62 100644 --- a/runtime/themes/pop-dark.toml +++ b/runtime/themes/pop-dark.toml @@ -28,8 +28,10 @@ error = { fg = 'brownD', bg = 'redE', modifiers = ['bold'] } 'ui.linenr' = { bg = 'brownU', fg = 'greyL' } 'ui.linenr.selected' = { fg = 'orangeH' } 'ui.cursorline' = { bg = 'brownH' } -'ui.statusline' = { fg = 'greyT', bg = 'brownD' } 'ui.statusline.inactive' = { fg = 'greyT', bg = 'brownN' } +'ui.statusline.normal' = { fg = 'greyT', bg = 'brownD', modifiers = ['bold'] } +'ui.statusline.select' = { bg = 'blueL', fg = 'brownD', modifiers = ['bold'] } +'ui.statusline.insert' = { bg = 'orangeL', fg = 'brownD', modifiers = ['bold'] } 'ui.help' = { fg = 'greyT', bg = 'brownD' } 'ui.highlight' = { bg = 'brownH' } 'ui.virtual' = { fg = 'brownV' } From c00baf7da6f0119a2c58defb5d25a6333183e59c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:18:45 -0600 Subject: [PATCH 40/81] build(deps): bump grep-searcher from 0.1.10 to 0.1.11 (#6213) Bumps [grep-searcher](https://github.com/BurntSushi/ripgrep) from 0.1.10 to 0.1.11. - [Release notes](https://github.com/BurntSushi/ripgrep/releases) - [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-searcher-0.1.10...0.1.11) --- updated-dependencies: - dependency-name: grep-searcher dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 10 +++++----- helix-term/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 090b5f027..a282992dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1033,9 +1033,9 @@ dependencies = [ [[package]] name = "grep-matcher" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d27563c33062cd33003b166ade2bb4fd82db1fd6a86db764dfdad132d46c1cc" +checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319" dependencies = [ "memchr", ] @@ -1057,11 +1057,11 @@ dependencies = [ [[package]] name = "grep-searcher" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48852bd08f9b4eb3040ecb6d2f4ade224afe880a9a0909c5563cc59fa67932cc" +checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f" dependencies = [ - "bstr 0.2.17", + "bstr 1.3.0", "bytecount", "encoding_rs", "encoding_rs_io", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index b1d63a04a..ae0d08ab0 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -64,7 +64,7 @@ serde = { version = "1.0", features = ["derive"] } # ripgrep for global search grep-regex = "0.1.10" -grep-searcher = "0.1.10" +grep-searcher = "0.1.11" [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } From b1e7d4d9a0f28bea3ab0b5e8ecb438d8234ddb57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:19:10 -0600 Subject: [PATCH 41/81] build(deps): bump serde_json from 1.0.93 to 1.0.94 (#6211) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.93 to 1.0.94. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.93...v1.0.94) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a282992dd..c7968384f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1815,9 +1815,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", From 53c8dcea5bc2e77706a1272cf0562121005bf54f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:20:51 -0600 Subject: [PATCH 42/81] build(deps): bump ignore from 0.4.18 to 0.4.20 (#6214) Bumps [ignore](https://github.com/BurntSushi/ripgrep) from 0.4.18 to 0.4.20. - [Release notes](https://github.com/BurntSushi/ripgrep/releases) - [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/ripgrep/compare/ignore-0.4.18...ignore-0.4.20) --- updated-dependencies: - dependency-name: ignore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7968384f..7ac8144d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,15 +215,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossterm" version = "0.26.1" @@ -1020,12 +1011,12 @@ dependencies = [ [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", - "bstr 0.2.17", + "bstr 1.3.0", "fnv", "log", "regex", @@ -1324,11 +1315,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils", "globset", "lazy_static", "log", From 2417ac8a4b11b4614724b0d42214992a0178a8ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:43:24 -0600 Subject: [PATCH 43/81] build(deps): bump thiserror from 1.0.38 to 1.0.39 (#6216) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.38 to 1.0.39. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.38...1.0.39) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ac8144d8..90ce177f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,18 +2006,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", From 8228fb0cf7634a627f2eb79289d105ac941361b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 09:53:10 +0900 Subject: [PATCH 44/81] build(deps): bump tokio from 1.25.0 to 1.26.0 (#6212) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.25.0 to 1.26.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.25.0...tokio-1.26.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 70 +++++++++++++++++++++++++++++--------------- helix-lsp/Cargo.toml | 2 +- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90ce177f8..e25c47d6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,7 +425,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1382,7 +1382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1508,7 +1508,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1606,7 +1606,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1753,7 +1753,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1972,7 +1972,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2088,9 +2088,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.25.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -2103,7 +2103,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -2397,47 +2397,71 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "xtask" diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 133ead298..c1f091107 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -23,6 +23,6 @@ lsp-types = { version = "0.94" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.25", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } +tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } tokio-stream = "0.1.12" which = "4.4" From 84be5cd52c61a53f8d47be60b33bbaaed63652e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 19:18:28 -0600 Subject: [PATCH 45/81] build(deps): bump grep-regex from 0.1.10 to 0.1.11 (#6215) Bumps [grep-regex](https://github.com/BurntSushi/ripgrep) from 0.1.10 to 0.1.11. - [Release notes](https://github.com/BurntSushi/ripgrep/releases) - [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-regex-0.1.10...0.1.11) --- updated-dependencies: - dependency-name: grep-regex dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 59 ++++++++++++++++++------------------------- helix-term/Cargo.toml | 2 +- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e25c47d6f..a184cf3a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,17 +73,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", -] - [[package]] name = "bstr" version = "1.3.0" @@ -557,7 +546,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc22b0cdc52237667c301dd7cdc6ead8f8f73c9f824e9942c8ebd6b764f6c0bf" dependencies = [ - "bstr 1.3.0", + "bstr", "btoi", "gix-date", "itoa", @@ -571,7 +560,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2231a25934a240d0a4b6f4478401c73ee81d8be52de0293eedbc172334abf3e1" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-features", "gix-glob", "gix-path", @@ -604,7 +593,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab" dependencies = [ - "bstr 1.3.0", + "bstr", ] [[package]] @@ -613,7 +602,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52c62e26ce11f607712e4f49a0a192ed87675d30187fd61be070abbd607d12f1" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-config-value", "gix-features", "gix-glob", @@ -635,7 +624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d4a4ba0531e46fe558459557a5b29fb86c3e4b2666c1c0861d93c7c678331" dependencies = [ "bitflags", - "bstr 1.3.0", + "bstr", "gix-path", "libc", "thiserror", @@ -647,7 +636,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be32b5fe339a31b8e53fa854081dc914c45020dcb64637f3c21baf69c96fc1b" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-command", "gix-config-value", "gix-path", @@ -663,7 +652,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b96271912ce39822501616f177dea7218784e6c63be90d5f36322ff3a722aae2" dependencies = [ - "bstr 1.3.0", + "bstr", "itoa", "thiserror", "time", @@ -687,7 +676,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91c204adba5ebd211c74735cbb65817d277e154486bac0dffa3701f163b80350" dependencies = [ - "bstr 1.3.0", + "bstr", "dunce", "gix-hash", "gix-path", @@ -720,7 +709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93e43efd776bc543f46f0fd0ca3d920c37af71a764a16f2aebd89765e9ff2993" dependencies = [ "bitflags", - "bstr 1.3.0", + "bstr", ] [[package]] @@ -751,7 +740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c12caf7886c7ba06f2b28835cdc2be1dca86bd047d00299d2d49e707ce1c2616" dependencies = [ "bitflags", - "bstr 1.3.0", + "bstr", "btoi", "filetime", "gix-bitmap", @@ -783,7 +772,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b66aea5e52875cd4915f4957a6f4b75831a36981e2ec3f5fad9e370e444fe1a" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-actor", "thiserror", ] @@ -794,7 +783,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df068db9180ee935fbb70504848369e270bdcb576b05c0faa8b9fd3b86fc017" dependencies = [ - "bstr 1.3.0", + "bstr", "btoi", "gix-actor", "gix-features", @@ -853,7 +842,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6c104a66dec149cb8f7aaafc6ab797654cf82d67f050fd0cb7e7294e328354b" dependencies = [ - "bstr 1.3.0", + "bstr", "thiserror", ] @@ -876,7 +865,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb" dependencies = [ - "bstr 1.3.0", + "bstr", "btoi", "thiserror", ] @@ -906,7 +895,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aba332462bda2e8efeae4302b39a6ed01ad56ef772fd5b7ef197cf2798294d65" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-hash", "gix-revision", "gix-validate", @@ -920,7 +909,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed98e4a0254953c64bc913bd23146a1de662067d5cf974cbdde396958b39e5b0" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-date", "gix-hash", "gix-hashtable", @@ -973,7 +962,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "044072b7ce8601b62dcec841b92129f5cc677072823324121b395d766ac5f528" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-features", "gix-path", "home", @@ -987,7 +976,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b69ddb780ea1465255e66818d75b7098371c58dbc9560da4488a44b9f5c7e443" dependencies = [ - "bstr 1.3.0", + "bstr", "thiserror", ] @@ -997,7 +986,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7cb9af6e56152953d8fe113c4f9d7cf60cf7a982362711e9200a255579b49cb" dependencies = [ - "bstr 1.3.0", + "bstr", "gix-attributes", "gix-features", "gix-glob", @@ -1016,7 +1005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", - "bstr 1.3.0", + "bstr", "fnv", "log", "regex", @@ -1033,12 +1022,12 @@ dependencies = [ [[package]] name = "grep-regex" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1345f8d33c89f2d5b081f2f2a41175adef9fd0bed2fea6a26c96c2deb027e58e" +checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f" dependencies = [ "aho-corasick", - "bstr 0.2.17", + "bstr", "grep-matcher", "log", "regex", @@ -1052,7 +1041,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f" dependencies = [ - "bstr 1.3.0", + "bstr", "bytecount", "encoding_rs", "encoding_rs_io", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index ae0d08ab0..bca567c28 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -63,7 +63,7 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } # ripgrep for global search -grep-regex = "0.1.10" +grep-regex = "0.1.11" grep-searcher = "0.1.11" [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 From 136d1164e06c8ae6f23d611e8fcc2c3e53b9bd80 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 6 Mar 2023 20:46:46 -0600 Subject: [PATCH 46/81] Pin tree-sitter at git master (#6218) Tree-sitter has some unreleased improvements that can speed up small queries and prevent hangs due to error recovery in some parsers. This change pins tree-sitter to the latest master. Neovim also pins tree-sitter to a commit on master. --- Cargo.lock | 3 +-- Cargo.toml | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a184cf3a6..96c9fcf96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2154,8 +2154,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4423c784fe11398ca91e505cdc71356b07b1a924fc8735cfab5333afe3e18bc" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14#c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index c7e254728..016267fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,6 @@ inherits = "test" package.helix-core.opt-level = 2 package.helix-tui.opt-level = 2 package.helix-term.opt-level = 2 + +[patch.crates-io] +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" } From 622f90a157cd3cdac71437851d4a2a2bd38e0a79 Mon Sep 17 00:00:00 2001 From: Erasin Wang Date: Wed, 8 Mar 2023 08:28:44 +0800 Subject: [PATCH 47/81] Update highlight for PHP (#6203) - update tree-sitter-php - add basic types, operator - refine keyword --- languages.toml | 2 +- runtime/queries/php/highlights.scm | 267 ++++++++++++++++++++++++----- 2 files changed, 228 insertions(+), 41 deletions(-) diff --git a/languages.toml b/languages.toml index c34135244..d8b57e976 100644 --- a/languages.toml +++ b/languages.toml @@ -571,7 +571,7 @@ indent = { tab-width = 4, unit = " " } [[grammar]] name = "php" -source = { git = "https://github.com/tree-sitter/tree-sitter-php", rev = "57f855461aeeca73bd4218754fb26b5ac143f98f" } +source = { git = "https://github.com/tree-sitter/tree-sitter-php", rev = "f860e598194f4a71747f91789bf536b393ad4a56" } [[language]] name = "twig" diff --git a/runtime/queries/php/highlights.scm b/runtime/queries/php/highlights.scm index 77c9424fe..4bf313d84 100644 --- a/runtime/queries/php/highlights.scm +++ b/runtime/queries/php/highlights.scm @@ -2,15 +2,63 @@ "?>" @tag ; Types +[ + (primitive_type) + (cast_type) +] @type.builtin + +(named_type + [ (name) @type + (qualified_name (name) @type)]) + +(base_clause + [ (name) @type + (qualified_name (name) @type)]) + +(enum_declaration + name: (name) @type.enum) -(primitive_type) @type.builtin -(cast_type) @type.builtin -(named_type (name) @type) @type -(named_type (qualified_name) @type) @type +(interface_declaration + name: (name) @constructor) + +(class_declaration + name: (name) @constructor) + +(trait_declaration + name:(name) @constructor) (namespace_definition name: (namespace_name (name) @namespace)) +(namespace_name_as_prefix + (namespace_name (name) @namespace)) + +(namespace_use_clause + [ (name) @namespace + (qualified_name (name) @type) ]) + +(namespace_aliasing_clause (name) @namespace) + +(class_interface_clause + [(name) @type + (qualified_name (name) @type)]) + +(scoped_call_expression + scope: [(name) @type + (qualified_name (name) @type)]) + +(class_constant_access_expression + . [(name) @constructor + (qualified_name (name) @constructor)] + (name) @constant) + +(use_declaration (name) @type) + +(binary_expression + operator: "instanceof" + right: [(name) @type + (qualified_name (name) @type)]) + ; Superglobals (subscript_expression (variable_name(name) @constant.builtin @@ -36,6 +84,21 @@ (function_definition name: (name) @function) +(nullsafe_member_call_expression + name: (name) @function.method) + +(object_creation_expression + [(name) @constructor + (qualified_name (name) @constructor)]) + +; Parameters +[ + (simple_parameter) + (variadic_parameter) +] @variable.parameter + +(argument + (name) @variable.parameter) ; Member @@ -62,68 +125,192 @@ (variable_name) @variable +; Attributes +(attribute_list) @attribute + ; Basic tokens -(string) @string -(heredoc) @string +[ + (string) + (encapsed_string) + (heredoc_body) + (nowdoc_body) + (shell_command_expression) +] @string +(escape_sequence) @constant.character.escape + (boolean) @constant.builtin.boolean (null) @constant.builtin (integer) @constant.numeric.integer (float) @constant.numeric.float (comment) @comment -"$" @operator +(goto_statement (name) @label) +(named_label_statement (name) @label) ; Keywords [ - "abstract" - "as" - "break" - "case" - "catch" - "class" - "const" - "continue" - "declare" "default" - "do" "echo" - "else" - "elseif" - "enddeclare" - "endforeach" - "endif" - "endswitch" - "endwhile" "enum" "extends" "final" - "finally" - "foreach" - "fn" - "function" + "goto" "global" - "if" "implements" - "include_once" - "include" "insteadof" - "interface" - "match" - "namespace" "new" "private" "protected" "public" +] @keyword + +[ + "if" + "else" + "elseif" + "endif" + "switch" + "endswitch" + "case" + "match" + "declare" + "enddeclare" + "??" +] @keyword.control.conditional + +[ + "for" + "endfor" + "foreach" + "endforeach" + "while" + "endwhile" + "do" +] @keyword.control.repeat + +[ + + "include_once" + "include" "require_once" "require" + "use" +] @keyword.control.import + +[ "return" - "static" - "switch" + "break" + "continue" + "yield" +] @keyword.control.return + +[ "throw" - "trait" "try" - "use" - "while" -] @keyword + "catch" + "finally" +] @keyword.control.exception + +[ + "as" + "or" + "xor" + "and" + "instanceof" +] @keyword.operator + +[ + "fn" + "function" +] @keyword.function + +[ + "namespace" + "class" + "interface" + "trait" + "abstract" +] @keyword.storage.type + +[ + "static" + "const" +] @keyword.storage.modifier + +[ + "," + ";" + ":" + "\\" + ] @punctuation.delimiter + +[ + (php_tag) + "?>" + "(" + ")" + "[" + "]" + "{" + "}" + "#[" +] @punctuation.bracket + +[ + "=" + + "." + "-" + "*" + "/" + "+" + "%" + "**" + + "~" + "|" + "^" + "&" + "<<" + ">>" + + "->" + "?->" + + "=>" + + "<" + "<=" + ">=" + ">" + "<>" + "==" + "!=" + "===" + "!==" + + "!" + "&&" + "||" + + ".=" + "-=" + "+=" + "*=" + "/=" + "%=" + "**=" + "&=" + "|=" + "^=" + "<<=" + ">>=" + "??=" + "--" + "++" + + "@" + "::" +] @operator From 0e5a4e55a497c58f68859edb48fd85854403b866 Mon Sep 17 00:00:00 2001 From: Erasin Wang Date: Wed, 8 Mar 2023 08:33:13 +0800 Subject: [PATCH 48/81] Update highlights for golang (#6204) - update tree-sitter-go - refine keywords - set package as namespace - add label --- languages.toml | 2 +- runtime/queries/go/highlights.scm | 75 ++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/languages.toml b/languages.toml index d8b57e976..0fd37a006 100644 --- a/languages.toml +++ b/languages.toml @@ -324,7 +324,7 @@ args = { mode = "local", processId = "{0}" } [[grammar]] name = "go" -source = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "05900faa3cdb5d2d8c8bd5e77ee698487e0a8611" } +source = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "64457ea6b73ef5422ed1687178d4545c3e91334a" } [[language]] name = "gomod" diff --git a/runtime/queries/go/highlights.scm b/runtime/queries/go/highlights.scm index 927bd95b0..b2d81e45d 100644 --- a/runtime/queries/go/highlights.scm +++ b/runtime/queries/go/highlights.scm @@ -19,6 +19,9 @@ (method_declaration name: (field_identifier) @function.method) +(method_spec + name: (field_identifier) @function.method) + ; Identifiers ((identifier) @constant (match? @constant "^[A-Z][A-Z\\d_]+$")) @@ -32,10 +35,19 @@ (match? @type.builtin "^(any|bool|byte|comparable|complex128|complex64|error|float32|float64|int|int16|int32|int64|int8|rune|string|uint|uint16|uint32|uint64|uint8|uintptr)$")) (type_identifier) @type +(type_spec + name: (type_identifier) @constructor) (field_identifier) @variable.other.member (identifier) @variable -(package_identifier) @variable +(package_identifier) @namespace + +(parameter_declaration (identifier) @variable.parameter) +(variadic_parameter_declaration (identifier) @variable.parameter) +(label_name) @label + +(const_spec + name: (identifier) @constant) ; Operators @@ -82,36 +94,57 @@ ; Keywords [ - "break" - "case" - "chan" - "const" - "continue" "default" - "defer" + "type" +] @keyword + +[ + "if" "else" - "fallthrough" + "switch" + "select" + "case" +] @keyword.control.conditional + +[ "for" - "func" - "go" - "goto" - "if" - "interface" - "map" "range" - "return" - "select" - "struct" - "switch" - "type" - "var" -] @keyword +] @keyword.control.repeat [ "import" "package" ] @keyword.control.import +[ + "return" + "continue" + "break" + "fallthrough" +] @keyword.control.return + +[ + "func" +] @keyword.function + +[ + "var" + "chan" + "interface" + "map" + "struct" +] @keyword.storage.type + +[ + "const" +] @keyword.storage.modifier + +[ + "defer" + "goto" + "go" +] @function.macro + ; Delimiters [ From f976c004e2efa4cb583b06827b44fef84bf925f5 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 8 Mar 2023 01:34:31 +0100 Subject: [PATCH 49/81] Allow LSP server to be stopped (#5964) --- book/src/generated/typable-cmd.md | 1 + helix-lsp/src/lib.rs | 10 ++++++++ helix-term/src/commands/typed.rs | 38 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index ab36997c7..badadc43d 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -49,6 +49,7 @@ | `:update` | Write changes only if the file has been modified. | | `:lsp-workspace-command` | Open workspace command picker | | `:lsp-restart` | Restarts the Language Server that is in use by the current doc | +| `:lsp-stop` | Stops the Language Server that is in use by the current doc | | `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. | | `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. | | `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. | diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 341d4a547..ca9d17ace 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -476,6 +476,16 @@ impl Registry { } } + pub fn stop(&mut self, language_config: &LanguageConfiguration) { + let scope = language_config.scope.clone(); + + if let Some((_, client)) = self.inner.remove(&scope) { + tokio::spawn(async move { + let _ = client.force_shutdown().await; + }); + } + } + pub fn get( &mut self, language_config: &LanguageConfiguration, diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index b0fd18a76..0ddca6df7 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1354,6 +1354,37 @@ fn lsp_restart( Ok(()) } +fn lsp_stop( + cx: &mut compositor::Context, + _args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let doc = doc!(cx.editor); + + let ls_id = doc + .language_server() + .map(|ls| ls.id()) + .context("LSP not running for the current document")?; + + let config = doc + .language_config() + .context("LSP not defined for the current document")?; + cx.editor.language_servers.stop(config); + + for doc in cx.editor.documents_mut() { + if doc.language_server().map_or(false, |ls| ls.id() == ls_id) { + doc.set_language_server(None); + doc.set_diagnostics(Default::default()); + } + } + + Ok(()) +} + fn tree_sitter_scopes( cx: &mut compositor::Context, _args: &[Cow], @@ -2349,6 +2380,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: lsp_restart, completer: None, }, + TypableCommand { + name: "lsp-stop", + aliases: &[], + doc: "Stops the Language Server that is in use by the current doc", + fun: lsp_stop, + completer: None, + }, TypableCommand { name: "tree-sitter-scopes", aliases: &[], From c8e6857affdd286a5aff1e8f72fc428ea216076e Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 7 Feb 2023 09:18:44 +0100 Subject: [PATCH 50/81] Add a parser-combinator crate Parser-combinators are one of the simpler tools for building ad-hoc parsers. They're a good fit because they are... * Small: each parser / parser-combinator is around 10 LOC. * Functional: helix_core strives to be a functional set of utilities usable throughout the rest of the editor. * Flexible: use them to build any sort of ad-hoc parser. In the child commit, we'll parse LSP Snippet syntax using these new parser combinators. Why not use an existing parser-combinator crate? Existing popular parser-combinator crates have histories of making breaking changes (for example nom and combine). > Implementation note: I tried to not introduce a new trait since the > types can be expressed in terms of `impl Fn`s. The trait is necessary > to build `seq` implementations without a proc macro though, and also > allows us to use `&'static str`s very conveniently: see the trait > implementation for `&'static str`. --- Cargo.lock | 7 + Cargo.toml | 1 + helix-parsec/Cargo.toml | 14 + helix-parsec/src/lib.rs | 560 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 582 insertions(+) create mode 100644 helix-parsec/Cargo.toml create mode 100644 helix-parsec/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 96c9fcf96..affc6bd90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1149,6 +1149,13 @@ dependencies = [ "which", ] +[[package]] +name = "helix-parsec" +version = "0.6.0" +dependencies = [ + "regex", +] + [[package]] name = "helix-term" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 016267fe3..aaa21659a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "helix-dap", "helix-loader", "helix-vcs", + "helix-parsec", "xtask", ] diff --git a/helix-parsec/Cargo.toml b/helix-parsec/Cargo.toml new file mode 100644 index 000000000..562df8ddd --- /dev/null +++ b/helix-parsec/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "helix-parsec" +version = "0.6.0" +authors = ["Blaž Hrastnik "] +edition = "2021" +license = "MPL-2.0" +description = "Parser combinators for Helix" +categories = ["editor"] +repository = "https://github.com/helix-editor/helix" +homepage = "https://helix-editor.com" +include = ["src/**/*", "README.md"] + +[dependencies] +regex = "1" diff --git a/helix-parsec/src/lib.rs b/helix-parsec/src/lib.rs new file mode 100644 index 000000000..c86a1a056 --- /dev/null +++ b/helix-parsec/src/lib.rs @@ -0,0 +1,560 @@ +//! Parser-combinator functions +//! +//! This module provides parsers and parser combinators which can be used +//! together to build parsers by functional composition. + +use regex::Regex; + +// This module implements parser combinators following https://bodil.lol/parser-combinators/. +// `sym` (trait implementation for `&'static str`), `map`, `pred` (filter), `one_or_more`, +// `zero_or_more`, as well as the `Parser` trait originate mostly from that post. +// The remaining parsers and parser combinators are either based on +// https://github.com/archseer/snippets.nvim/blob/a583da6ef130d2a4888510afd8c4e5ffd62d0dce/lua/snippet/parser.lua#L5-L138 +// or are novel. + +// When a parser matches the input successfully, it returns `Ok((next_input, some_value))` +// where the type of the returned value depends on the parser. If the parser fails to match, +// it returns `Err(input)`. +type ParseResult<'a, Output> = Result<(&'a str, Output), &'a str>; + +/// A parser or parser-combinator. +/// +/// Parser-combinators compose multiple parsers together to parse input. +/// For example, two basic parsers (`&'static str`s) may be combined with +/// a parser-combinator like [or] to produce a new parser. +/// +/// ``` +/// use helix_parsec::{or, Parser}; +/// let foo = "foo"; // matches "foo" literally +/// let bar = "bar"; // matches "bar" literally +/// let foo_or_bar = or(foo, bar); // matches either "foo" or "bar" +/// assert_eq!(Ok(("", "foo")), foo_or_bar.parse("foo")); +/// assert_eq!(Ok(("", "bar")), foo_or_bar.parse("bar")); +/// assert_eq!(Err("baz"), foo_or_bar.parse("baz")); +/// ``` +pub trait Parser<'a> { + type Output; + + fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output>; +} + +// Most parser-combinators are written as higher-order functions which take some +// parser(s) as input and return a new parser: a function that takes input and returns +// a parse result. The underlying implementation of [Parser::parse] for these functions +// is simply application. +#[doc(hidden)] +impl<'a, F, T> Parser<'a> for F +where + F: Fn(&'a str) -> ParseResult, +{ + type Output = T; + + fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> { + self(input) + } +} + +/// A parser which matches the string literal exactly. +/// +/// This parser succeeds if the next characters in the input are equal to the given +/// string literal. +/// +/// Note that [str::parse] interferes with calling [Parser::parse] on string literals +/// directly; this trait implementation works when used within any parser combinator +/// but does not work on its own. To call [Parser::parse] on a parser for a string +/// literal, use the [token] parser. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{or, Parser}; +/// let parser = or("foo", "bar"); +/// assert_eq!(Ok(("", "foo")), parser.parse("foo")); +/// assert_eq!(Ok(("", "bar")), parser.parse("bar")); +/// assert_eq!(Err("baz"), parser.parse("baz")); +/// ``` +impl<'a> Parser<'a> for &'static str { + type Output = &'a str; + + fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> { + match input.get(0..self.len()) { + Some(actual) if actual == *self => Ok((&input[self.len()..], &input[0..self.len()])), + _ => Err(input), + } + } +} + +// Parsers + +/// A parser which matches the given string literally. +/// +/// This function is a convenience for interpreting string literals as parsers +/// and is only necessary to avoid conflict with [str::parse]. See the documentation +/// for the `&'static str` implementation of [Parser]. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{token, Parser}; +/// let parser = token("foo"); +/// assert_eq!(Ok(("", "foo")), parser.parse("foo")); +/// assert_eq!(Err("bar"), parser.parse("bar")); +/// ``` +pub fn token<'a>(literal: &'static str) -> impl Parser<'a, Output = &'a str> { + literal +} + +/// A parser which matches the pattern described by the given regular expression. +/// +/// The pattern must match from the beginning of the input as if the regular expression +/// included the `^` anchor. Using a `^` anchor in the regular expression is +/// recommended in order to reduce any work done by the regex on non-matching input. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{pattern, Parser}; +/// use regex::Regex; +/// let regex = Regex::new(r"Hello, \w+!").unwrap(); +/// let parser = pattern(®ex); +/// assert_eq!(Ok(("", "Hello, world!")), parser.parse("Hello, world!")); +/// assert_eq!(Err("Hey, you!"), parser.parse("Hey, you!")); +/// assert_eq!(Err("Oh Hello, world!"), parser.parse("Oh Hello, world!")); +/// ``` +pub fn pattern<'a>(regex: &'a Regex) -> impl Parser<'a, Output = &'a str> { + move |input: &'a str| match regex.find(input) { + Some(match_) if match_.start() == 0 => { + Ok((&input[match_.end()..], &input[0..match_.end()])) + } + _ => Err(input), + } +} + +/// A parser which matches all values until the specified pattern is found. +/// +/// If the pattern is not found, this parser does not match. The input up to the +/// character which returns `true` is returned but not that character itself. +/// +/// If the pattern function returns true on the first input character, this +/// parser fails. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{take_until, Parser}; +/// let parser = take_until(|c| c == '.'); +/// assert_eq!(Ok((".bar", "foo")), parser.parse("foo.bar")); +/// assert_eq!(Err(".foo"), parser.parse(".foo")); +/// assert_eq!(Err("foo"), parser.parse("foo")); +/// ``` +pub fn take_until<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str> +where + F: Fn(char) -> bool, +{ + move |input: &'a str| match input.find(&pattern) { + Some(index) if index != 0 => Ok((&input[index..], &input[0..index])), + _ => Err(input), + } +} + +// Variadic parser combinators + +/// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion. +/// +/// The returned value is a tuple containing the outputs of all parsers in order. Each +/// parser in the sequence may be typed differently. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{seq, Parser}; +/// let parser = seq!("<", "a", ">"); +/// assert_eq!(Ok(("", ("<", "a", ">"))), parser.parse("")); +/// assert_eq!(Err(""), parser.parse("")); +/// ``` +#[macro_export] +macro_rules! seq { + ($($parsers: expr),+ $(,)?) => { + ($($parsers),+) + } +} + +// Seq is implemented using trait-implementations of Parser for various size tuples. +// This allows sequences to be typed heterogeneously. +macro_rules! seq_impl { + ($($parser:ident),+) => { + #[allow(non_snake_case)] + impl<'a, $($parser),+> Parser<'a> for ($($parser),+) + where + $($parser: Parser<'a>),+ + { + type Output = ($($parser::Output),+); + + fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> { + let ($($parser),+) = self; + seq_body_impl!(input, input, $($parser),+ ; ) + } + } + } +} + +macro_rules! seq_body_impl { + ($input:expr, $next_input:expr, $head:ident, $($tail:ident),+ ; $(,)? $($acc:ident),*) => { + match $head.parse($next_input) { + Ok((next_input, $head)) => seq_body_impl!($input, next_input, $($tail),+ ; $($acc),*, $head), + Err(_) => Err($input), + } + }; + ($input:expr, $next_input:expr, $last:ident ; $(,)? $($acc:ident),*) => { + match $last.parse($next_input) { + Ok((next_input, last)) => Ok((next_input, ($($acc),+, last))), + Err(_) => Err($input), + } + } +} + +seq_impl!(A, B); +seq_impl!(A, B, C); +seq_impl!(A, B, C, D); +seq_impl!(A, B, C, D, E); +seq_impl!(A, B, C, D, E, F); +seq_impl!(A, B, C, D, E, F, G); +seq_impl!(A, B, C, D, E, F, G, H); +seq_impl!(A, B, C, D, E, F, G, H, I); +seq_impl!(A, B, C, D, E, F, G, H, I, J); + +/// A parser combinator which chooses the first of the input parsers which matches +/// successfully. +/// +/// All input parsers must have the same output type. This is a variadic form for [or]. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{choice, or, Parser}; +/// let parser = choice!("foo", "bar", "baz"); +/// assert_eq!(Ok(("", "foo")), parser.parse("foo")); +/// assert_eq!(Ok(("", "bar")), parser.parse("bar")); +/// assert_eq!(Err("quiz"), parser.parse("quiz")); +/// ``` +#[macro_export] +macro_rules! choice { + ($parser: expr $(,)?) => { + $parser + }; + ($parser: expr, $($rest: expr),+ $(,)?) => { + or($parser, choice!($($rest),+)) + } +} + +// Ordinary parser combinators + +/// A parser combinator which takes a parser as input and maps the output using the +/// given transformation function. +/// +/// This corresponds to [Result::map]. The value is only mapped if the input parser +/// matches against input. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{map, Parser}; +/// let parser = map("123", |s| s.parse::().unwrap()); +/// assert_eq!(Ok(("", 123)), parser.parse("123")); +/// assert_eq!(Err("abc"), parser.parse("abc")); +/// ``` +pub fn map<'a, P, F, T>(parser: P, map_fn: F) -> impl Parser<'a, Output = T> +where + P: Parser<'a>, + F: Fn(P::Output) -> T, +{ + move |input| { + parser + .parse(input) + .map(|(next_input, result)| (next_input, map_fn(result))) + } +} + +/// A parser combinator which succeeds if the given parser matches the input and +/// the given `filter_map_fn` returns `Some`. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{filter_map, take_until, Parser}; +/// let parser = filter_map(take_until(|c| c == '.'), |s| s.parse::().ok()); +/// assert_eq!(Ok((".456", 123)), parser.parse("123.456")); +/// assert_eq!(Err("abc.def"), parser.parse("abc.def")); +/// ``` +pub fn filter_map<'a, P, F, T>(parser: P, filter_map_fn: F) -> impl Parser<'a, Output = T> +where + P: Parser<'a>, + F: Fn(P::Output) -> Option, +{ + move |input| match parser.parse(input) { + Ok((next_input, value)) => match filter_map_fn(value) { + Some(value) => Ok((next_input, value)), + None => Err(input), + }, + Err(_) => Err(input), + } +} + +/// A parser combinator which succeeds if the first given parser matches the input and +/// the second given parse also matches. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{reparse_as, take_until, one_or_more, Parser}; +/// let parser = reparse_as(take_until(|c| c == '/'), one_or_more("a")); +/// assert_eq!(Ok(("/bb", vec!["a", "a"])), parser.parse("aa/bb")); +/// ``` +pub fn reparse_as<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T> +where + P1: Parser<'a, Output = &'a str>, + P2: Parser<'a, Output = T>, +{ + filter_map(parser1, move |str| { + parser2.parse(str).map(|(_, value)| value).ok() + }) +} + +/// A parser combinator which only matches the input when the predicate function +/// returns true. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{filter, take_until, Parser}; +/// let parser = filter(take_until(|c| c == '.'), |s| s == &"123"); +/// assert_eq!(Ok((".456", "123")), parser.parse("123.456")); +/// assert_eq!(Err("456.123"), parser.parse("456.123")); +/// ``` +pub fn filter<'a, P, F, T>(parser: P, pred_fn: F) -> impl Parser<'a, Output = T> +where + P: Parser<'a, Output = T>, + F: Fn(&P::Output) -> bool, +{ + move |input| { + if let Ok((next_input, value)) = parser.parse(input) { + if pred_fn(&value) { + return Ok((next_input, value)); + } + } + Err(input) + } +} + +/// A parser combinator which matches either of the input parsers. +/// +/// Both parsers must have the same output type. For a variadic form which +/// can take any number of parsers, use `choice!`. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{or, Parser}; +/// let parser = or("foo", "bar"); +/// assert_eq!(Ok(("", "foo")), parser.parse("foo")); +/// assert_eq!(Ok(("", "bar")), parser.parse("bar")); +/// assert_eq!(Err("baz"), parser.parse("baz")); +/// ``` +pub fn or<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T> +where + P1: Parser<'a, Output = T>, + P2: Parser<'a, Output = T>, +{ + move |input| match parser1.parse(input) { + ok @ Ok(_) => ok, + Err(_) => parser2.parse(input), + } +} + +/// A parser combinator which attempts to match the given parser, returning a +/// `None` output value if the parser does not match. +/// +/// The parser produced with this combinator always succeeds. If the given parser +/// succeeds, `Some(value)` is returned where `value` is the output of the given +/// parser. Otherwise, `None`. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{optional, Parser}; +/// let parser = optional("foo"); +/// assert_eq!(Ok(("bar", Some("foo"))), parser.parse("foobar")); +/// assert_eq!(Ok(("bar", None)), parser.parse("bar")); +/// ``` +pub fn optional<'a, P, T>(parser: P) -> impl Parser<'a, Output = Option> +where + P: Parser<'a, Output = T>, +{ + move |input| match parser.parse(input) { + Ok((next_input, value)) => Ok((next_input, Some(value))), + Err(_) => Ok((input, None)), + } +} + +/// A parser combinator which runs the given parsers in sequence and returns the +/// value of `left` if both are matched. +/// +/// This is useful for two-element sequences in which you only want the output +/// value of the `left` parser. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{left, Parser}; +/// let parser = left("foo", "bar"); +/// assert_eq!(Ok(("", "foo")), parser.parse("foobar")); +/// ``` +pub fn left<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T> +where + L: Parser<'a, Output = T>, + R: Parser<'a>, +{ + map(seq!(left, right), |(left_value, _)| left_value) +} + +/// A parser combinator which runs the given parsers in sequence and returns the +/// value of `right` if both are matched. +/// +/// This is useful for two-element sequences in which you only want the output +/// value of the `right` parser. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{right, Parser}; +/// let parser = right("foo", "bar"); +/// assert_eq!(Ok(("", "bar")), parser.parse("foobar")); +/// ``` +pub fn right<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T> +where + L: Parser<'a>, + R: Parser<'a, Output = T>, +{ + map(seq!(left, right), |(_, right_value)| right_value) +} + +/// A parser combinator which matches the given parser against the input zero or +/// more times. +/// +/// This parser always succeeds and returns the empty Vec when it matched zero +/// times. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{zero_or_more, Parser}; +/// let parser = zero_or_more("a"); +/// assert_eq!(Ok(("", vec![])), parser.parse("")); +/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a")); +/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa")); +/// assert_eq!(Ok(("bb", vec![])), parser.parse("bb")); +/// ``` +pub fn zero_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec> +where + P: Parser<'a, Output = T>, +{ + move |mut input| { + let mut values = Vec::new(); + + while let Ok((next_input, value)) = parser.parse(input) { + input = next_input; + values.push(value); + } + + Ok((input, values)) + } +} + +/// A parser combinator which matches the given parser against the input one or +/// more times. +/// +/// This parser combinator acts the same as [zero_or_more] but must match at +/// least once. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{one_or_more, Parser}; +/// let parser = one_or_more("a"); +/// assert_eq!(Err(""), parser.parse("")); +/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a")); +/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa")); +/// assert_eq!(Err("bb"), parser.parse("bb")); +/// ``` +pub fn one_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec> +where + P: Parser<'a, Output = T>, +{ + move |mut input| { + let mut values = Vec::new(); + + match parser.parse(input) { + Ok((next_input, value)) => { + input = next_input; + values.push(value); + } + Err(err) => return Err(err), + } + + while let Ok((next_input, value)) = parser.parse(input) { + input = next_input; + values.push(value); + } + + Ok((input, values)) + } +} + +/// A parser combinator which matches one or more instances of the given parser +/// interspersed with the separator parser. +/// +/// Output values of the separator parser are discarded. +/// +/// This is typically used to parse function arguments or list items. +/// +/// # Examples +/// +/// ```rust +/// use helix_parsec::{sep, Parser}; +/// let parser = sep("a", ","); +/// assert_eq!(Ok(("", vec!["a", "a", "a"])), parser.parse("a,a,a")); +/// ``` +pub fn sep<'a, P, S, T>(parser: P, separator: S) -> impl Parser<'a, Output = Vec> +where + P: Parser<'a, Output = T>, + S: Parser<'a>, +{ + move |mut input| { + let mut values = Vec::new(); + + match parser.parse(input) { + Ok((next_input, value)) => { + input = next_input; + values.push(value); + } + Err(err) => return Err(err), + } + + loop { + match separator.parse(input) { + Ok((next_input, _)) => input = next_input, + Err(_) => break, + } + + match parser.parse(input) { + Ok((next_input, value)) => { + input = next_input; + values.push(value); + } + Err(_) => break, + } + } + + Ok((input, values)) + } +} From 9c12e0fb765f065b43788da670ffb98159d64a5f Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 3 Oct 2022 20:41:31 -0500 Subject: [PATCH 51/81] Add parser for LSP snippet --- Cargo.lock | 1 + helix-lsp/Cargo.toml | 2 + helix-lsp/src/lib.rs | 1 + helix-lsp/src/snippet.rs | 367 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 371 insertions(+) create mode 100644 helix-lsp/src/snippet.rs diff --git a/Cargo.lock b/Cargo.lock index affc6bd90..d069cf41d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,6 +1141,7 @@ dependencies = [ "helix-loader", "log", "lsp-types", + "once_cell", "serde", "serde_json", "thiserror", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index c1f091107..7c71fa9f2 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -14,6 +14,7 @@ homepage = "https://helix-editor.com" [dependencies] helix-core = { version = "0.6", path = "../helix-core" } helix-loader = { version = "0.6", path = "../helix-loader" } +helix-parsec = { version = "0.6", path = "../helix-parsec" } anyhow = "1.0" futures-executor = "0.3" @@ -26,3 +27,4 @@ thiserror = "1.0" tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } tokio-stream = "0.1.12" which = "4.4" +once_cell = "1.15" diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index ca9d17ace..cce848ab1 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -1,5 +1,6 @@ mod client; pub mod jsonrpc; +pub mod snippet; mod transport; pub use client::Client; diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs new file mode 100644 index 000000000..529c3b975 --- /dev/null +++ b/helix-lsp/src/snippet.rs @@ -0,0 +1,367 @@ +use anyhow::{anyhow, Result}; + +use crate::{util::lsp_pos_to_pos, OffsetEncoding}; + +#[derive(Debug, PartialEq, Eq)] +pub enum CaseChange { + Upcase, + Downcase, + Capitalize, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum FormatItem<'a> { + Text(&'a str), + Capture(usize), + CaseChange(usize, CaseChange), + Conditional(usize, Option<&'a str>, Option<&'a str>), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Regex<'a> { + value: &'a str, + replacement: Vec>, + options: Option<&'a str>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SnippetElement<'a> { + Tabstop { + tabstop: usize, + }, + Placeholder { + tabstop: usize, + value: Box>, + }, + Choice { + tabstop: usize, + choices: Vec<&'a str>, + }, + Variable { + name: &'a str, + default: Option<&'a str>, + regex: Option>, + }, + Text(&'a str), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Snippet<'a> { + elements: Vec>, +} + +pub fn parse<'a>(s: &'a str) -> Result> { + parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest)) +} + +mod parser { + use helix_core::regex; + use once_cell::sync::Lazy; + + use helix_parsec::*; + + use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement}; + + /* + https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax + + any ::= tabstop | placeholder | choice | variable | text + tabstop ::= '$' int | '${' int '}' + placeholder ::= '${' int ':' any '}' + choice ::= '${' int '|' text (',' text)* '|}' + variable ::= '$' var | '${' var }' + | '${' var ':' any '}' + | '${' var '/' regex '/' (format | text)+ '/' options '}' + format ::= '$' int | '${' int '}' + | '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}' + | '${' int ':+' if '}' + | '${' int ':?' if ':' else '}' + | '${' int ':-' else '}' | '${' int ':' else '}' + regex ::= Regular Expression value (ctor-string) + options ::= Regular Expression option (ctor-options) + var ::= [_a-zA-Z] [_a-zA-Z0-9]* + int ::= [0-9]+ + text ::= .* + if ::= text + else ::= text + */ + + static DIGIT: Lazy = Lazy::new(|| regex::Regex::new(r"^[0-9]+").unwrap()); + static VARIABLE: Lazy = + Lazy::new(|| regex::Regex::new(r"^[_a-zA-Z][_a-zA-Z0-9]*").unwrap()); + static TEXT: Lazy = Lazy::new(|| regex::Regex::new(r"^[^\$]+").unwrap()); + + fn var<'a>() -> impl Parser<'a, Output = &'a str> { + pattern(&VARIABLE) + } + + fn digit<'a>() -> impl Parser<'a, Output = usize> { + filter_map(pattern(&DIGIT), |s| s.parse().ok()) + } + + fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> { + use CaseChange::*; + + choice!( + map("upcase", |_| Upcase), + map("downcase", |_| Downcase), + map("capitalize", |_| Capitalize), + ) + } + + fn format<'a>() -> impl Parser<'a, Output = FormatItem<'a>> { + use FormatItem::*; + + choice!( + // '$' int + map(right("$", digit()), Capture), + // '${' int '}' + map(seq!("${", digit(), "}"), |seq| Capture(seq.1)), + // '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}' + map(seq!("${", digit(), ":/", case_change(), "}"), |seq| { + CaseChange(seq.1, seq.3) + }), + // '${' int ':+' if '}' + map( + seq!("${", digit(), ":+", take_until(|c| c == '}'), "}"), + |seq| { Conditional(seq.1, Some(seq.3), None) } + ), + // '${' int ':?' if ':' else '}' + map( + seq!( + "${", + digit(), + ":?", + take_until(|c| c == ':'), + ":", + take_until(|c| c == '}'), + "}" + ), + |seq| { Conditional(seq.1, Some(seq.3), Some(seq.5)) } + ), + // '${' int ':-' else '}' | '${' int ':' else '}' + map( + seq!( + "${", + digit(), + ":", + optional("-"), + take_until(|c| c == '}'), + "}" + ), + |seq| { Conditional(seq.1, None, Some(seq.4)) } + ), + // Any text + map(pattern(&TEXT), Text), + ) + } + + fn regex<'a>() -> impl Parser<'a, Output = Regex<'a>> { + let replacement = reparse_as(take_until(|c| c == '/'), one_or_more(format())); + + map( + seq!( + "/", + take_until(|c| c == '/'), + "/", + replacement, + "/", + optional(take_until(|c| c == '}')), + ), + |(_, value, _, replacement, _, options)| Regex { + value, + replacement, + options, + }, + ) + } + + fn tabstop<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { + map( + or( + right("$", digit()), + map(seq!("${", digit(), "}"), |values| values.1), + ), + |digit| SnippetElement::Tabstop { tabstop: digit }, + ) + } + + fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { + // TODO: why doesn't parse_as work? + // let value = reparse_as(take_until(|c| c == '}'), anything()); + let value = filter_map(take_until(|c| c == '}'), |s| { + anything().parse(s).map(|parse_result| parse_result.1).ok() + }); + + map(seq!("${", digit(), ":", value, "}"), |seq| { + SnippetElement::Placeholder { + tabstop: seq.1, + value: Box::new(seq.3), + } + }) + } + + fn choice<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { + map( + seq!( + "${", + digit(), + "|", + sep(take_until(|c| c == ',' || c == '|'), ","), + "|}", + ), + |seq| SnippetElement::Choice { + tabstop: seq.1, + choices: seq.3, + }, + ) + } + + fn variable<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { + choice!( + // $var + map(right("$", var()), |name| SnippetElement::Variable { + name, + default: None, + regex: None, + }), + // ${var:default} + map( + seq!("${", var(), ":", take_until(|c| c == '}'), "}",), + |values| SnippetElement::Variable { + name: values.1, + default: Some(values.3), + regex: None, + } + ), + // ${var/value/format/options} + map(seq!("${", var(), regex(), "}"), |values| { + SnippetElement::Variable { + name: values.1, + default: None, + regex: Some(values.2), + } + }), + ) + } + + fn text<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { + map(pattern(&TEXT), SnippetElement::Text) + } + + fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { + choice!(tabstop(), placeholder(), choice(), variable(), text()) + } + + fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> { + map(one_or_more(anything()), |parts| Snippet { elements: parts }) + } + + pub fn parse(s: &str) -> Result { + snippet().parse(s).map(|(_input, elements)| elements) + } + + #[cfg(test)] + mod test { + use super::SnippetElement::*; + use super::*; + + #[test] + fn empty_string_is_error() { + assert_eq!(Err(""), parse("")); + } + + #[test] + fn parse_placeholders_in_function_call() { + assert_eq!( + Ok(Snippet { + elements: vec![ + Text("match("), + Placeholder { + tabstop: 1, + value: Box::new(Text("Arg1")), + }, + Text(")") + ] + }), + parse("match(${1:Arg1})") + ) + } + + #[test] + fn parse_placeholders_in_statement() { + assert_eq!( + Ok(Snippet { + elements: vec![ + Text("local "), + Placeholder { + tabstop: 1, + value: Box::new(Text("var")), + }, + Text(" = "), + Placeholder { + tabstop: 1, + value: Box::new(Text("value")), + }, + ] + }), + parse("local ${1:var} = ${1:value}") + ) + } + + #[test] + fn parse_all() { + assert_eq!( + Ok(Snippet { + elements: vec![ + Text("hello "), + Tabstop { tabstop: 1 }, + Tabstop { tabstop: 2 }, + Text(" "), + Choice { + tabstop: 1, + choices: vec!["one", "two", "three"] + }, + Text(" "), + Variable { + name: "name", + default: Some("foo"), + regex: None + }, + Text(" "), + Variable { + name: "var", + default: None, + regex: None + }, + Text(" "), + Variable { + name: "TM", + default: None, + regex: None + }, + ] + }), + parse("hello $1${2} ${1|one,two,three|} ${name:foo} $var $TM") + ); + } + + #[test] + fn regex_capture_replace() { + assert_eq!( + Ok(Snippet { + elements: vec![Variable { + name: "TM_FILENAME", + default: None, + regex: Some(Regex { + value: "(.*).+$", + replacement: vec![FormatItem::Capture(1)], + options: None, + }), + }] + }), + parse("${TM_FILENAME/(.*).+$/$1/}") + ); + } + } +} From e973b71c83dcefbdb3a748f28ce3bfd51e9cd842 Mon Sep 17 00:00:00 2001 From: Urgau Date: Tue, 7 Feb 2023 20:15:39 +0100 Subject: [PATCH 52/81] Optimize LSP snippet parsing --- Cargo.lock | 1 - helix-lsp/Cargo.toml | 1 - helix-lsp/src/snippet.rs | 45 +++++++++++++++++++++++++--------------- helix-parsec/src/lib.rs | 29 ++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d069cf41d..affc6bd90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,7 +1141,6 @@ dependencies = [ "helix-loader", "log", "lsp-types", - "once_cell", "serde", "serde_json", "thiserror", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 7c71fa9f2..9d76822dc 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -27,4 +27,3 @@ thiserror = "1.0" tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } tokio-stream = "0.1.12" which = "4.4" -once_cell = "1.15" diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index 529c3b975..27b103d5d 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -50,14 +50,11 @@ pub struct Snippet<'a> { elements: Vec>, } -pub fn parse<'a>(s: &'a str) -> Result> { +pub fn parse(s: &str) -> Result> { parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest)) } mod parser { - use helix_core::regex; - use once_cell::sync::Lazy; - use helix_parsec::*; use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement}; @@ -86,17 +83,34 @@ mod parser { else ::= text */ - static DIGIT: Lazy = Lazy::new(|| regex::Regex::new(r"^[0-9]+").unwrap()); - static VARIABLE: Lazy = - Lazy::new(|| regex::Regex::new(r"^[_a-zA-Z][_a-zA-Z0-9]*").unwrap()); - static TEXT: Lazy = Lazy::new(|| regex::Regex::new(r"^[^\$]+").unwrap()); - fn var<'a>() -> impl Parser<'a, Output = &'a str> { - pattern(&VARIABLE) + // var = [_a-zA-Z][_a-zA-Z0-9]* + move |input: &'a str| match input + .char_indices() + .take_while(|(p, c)| { + *c == '_' + || if *p == 0 { + c.is_ascii_alphabetic() + } else { + c.is_ascii_alphanumeric() + } + }) + .last() + { + Some((index, c)) if index >= 1 => { + let index = index + c.len_utf8(); + Ok((&input[index..], &input[0..index])) + } + _ => Err(input), + } + } + + fn text<'a>() -> impl Parser<'a, Output = &'a str> { + take_while(|c| c != '$') } fn digit<'a>() -> impl Parser<'a, Output = usize> { - filter_map(pattern(&DIGIT), |s| s.parse().ok()) + filter_map(take_while(|c| c.is_ascii_digit()), |s| s.parse().ok()) } fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> { @@ -152,7 +166,7 @@ mod parser { |seq| { Conditional(seq.1, None, Some(seq.4)) } ), // Any text - map(pattern(&TEXT), Text), + map(text(), Text), ) } @@ -245,12 +259,9 @@ mod parser { ) } - fn text<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { - map(pattern(&TEXT), SnippetElement::Text) - } - fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { - choice!(tabstop(), placeholder(), choice(), variable(), text()) + let text = map(text(), SnippetElement::Text); + choice!(tabstop(), placeholder(), choice(), variable(), text) } fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> { diff --git a/helix-parsec/src/lib.rs b/helix-parsec/src/lib.rs index c86a1a056..bfa981e58 100644 --- a/helix-parsec/src/lib.rs +++ b/helix-parsec/src/lib.rs @@ -157,6 +157,35 @@ where } } +/// A parser which matches all values until the specified pattern no longer match. +/// +/// This parser only ever fails if the input has a length of zero. +/// +/// # Examples +/// +/// ``` +/// use helix_parsec::{take_while, Parser}; +/// let parser = take_while(|c| c == '1'); +/// assert_eq!(Ok(("2", "11")), parser.parse("112")); +/// assert_eq!(Err("22"), parser.parse("22")); +/// ``` +pub fn take_while<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str> +where + F: Fn(char) -> bool, +{ + move |input: &'a str| match input + .char_indices() + .take_while(|(_p, c)| pattern(*c)) + .last() + { + Some((index, c)) => { + let index = index + c.len_utf8(); + Ok((&input[index..], &input[0..index])) + } + _ => Err(input), + } +} + // Variadic parser combinators /// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion. From 3f90dafa3c4875cb33f404392552edc2381e6bf7 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 16 Feb 2023 11:21:53 +0100 Subject: [PATCH 53/81] Remove now unused the pattern combinator --- Cargo.lock | 3 --- helix-parsec/Cargo.toml | 1 - helix-parsec/src/lib.rs | 28 ---------------------------- 3 files changed, 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index affc6bd90..eec2a9766 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1152,9 +1152,6 @@ dependencies = [ [[package]] name = "helix-parsec" version = "0.6.0" -dependencies = [ - "regex", -] [[package]] name = "helix-term" diff --git a/helix-parsec/Cargo.toml b/helix-parsec/Cargo.toml index 562df8ddd..505a4247e 100644 --- a/helix-parsec/Cargo.toml +++ b/helix-parsec/Cargo.toml @@ -11,4 +11,3 @@ homepage = "https://helix-editor.com" include = ["src/**/*", "README.md"] [dependencies] -regex = "1" diff --git a/helix-parsec/src/lib.rs b/helix-parsec/src/lib.rs index bfa981e58..e09814b81 100644 --- a/helix-parsec/src/lib.rs +++ b/helix-parsec/src/lib.rs @@ -3,8 +3,6 @@ //! This module provides parsers and parser combinators which can be used //! together to build parsers by functional composition. -use regex::Regex; - // This module implements parser combinators following https://bodil.lol/parser-combinators/. // `sym` (trait implementation for `&'static str`), `map`, `pred` (filter), `one_or_more`, // `zero_or_more`, as well as the `Parser` trait originate mostly from that post. @@ -104,32 +102,6 @@ pub fn token<'a>(literal: &'static str) -> impl Parser<'a, Output = &'a str> { literal } -/// A parser which matches the pattern described by the given regular expression. -/// -/// The pattern must match from the beginning of the input as if the regular expression -/// included the `^` anchor. Using a `^` anchor in the regular expression is -/// recommended in order to reduce any work done by the regex on non-matching input. -/// -/// # Examples -/// -/// ``` -/// use helix_parsec::{pattern, Parser}; -/// use regex::Regex; -/// let regex = Regex::new(r"Hello, \w+!").unwrap(); -/// let parser = pattern(®ex); -/// assert_eq!(Ok(("", "Hello, world!")), parser.parse("Hello, world!")); -/// assert_eq!(Err("Hey, you!"), parser.parse("Hey, you!")); -/// assert_eq!(Err("Oh Hello, world!"), parser.parse("Oh Hello, world!")); -/// ``` -pub fn pattern<'a>(regex: &'a Regex) -> impl Parser<'a, Output = &'a str> { - move |input: &'a str| match regex.find(input) { - Some(match_) if match_.start() == 0 => { - Ok((&input[match_.end()..], &input[0..match_.end()])) - } - _ => Err(input), - } -} - /// A parser which matches all values until the specified pattern is found. /// /// If the pattern is not found, this parser does not match. The input up to the From b9b1ec22084cfe82ced7e34412dab0351828fc53 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sat, 22 Oct 2022 09:52:25 -0500 Subject: [PATCH 54/81] Apply snippets as transactions --- Cargo.lock | 1 + helix-lsp/src/snippet.rs | 108 ++++++++++++++++++++++++++++++++ helix-term/src/ui/completion.rs | 46 +++++++++++--- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eec2a9766..cc7265f33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,6 +1139,7 @@ dependencies = [ "futures-util", "helix-core", "helix-loader", + "helix-parsec", "log", "lsp-types", "serde", diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index 27b103d5d..f74237496 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use anyhow::{anyhow, Result}; use crate::{util::lsp_pos_to_pos, OffsetEncoding}; @@ -54,6 +56,112 @@ pub fn parse(s: &str) -> Result> { parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest)) } +pub fn into_transaction<'a>( + snippet: Snippet<'a>, + doc: &helix_core::Rope, + selection: &helix_core::Selection, + edit: &lsp_types::TextEdit, + line_ending: &str, + offset_encoding: OffsetEncoding, +) -> helix_core::Transaction { + use helix_core::{smallvec, Range, Selection, Transaction}; + use SnippetElement::*; + + let text = doc.slice(..); + let primary_cursor = selection.primary().cursor(text); + + let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) { + Some(start) => start as i128 - primary_cursor as i128, + None => return Transaction::new(doc), + }; + let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) { + Some(end) => end as i128 - primary_cursor as i128, + None => return Transaction::new(doc), + }; + + let newline_with_offset = format!( + "{line_ending}{blank:width$}", + width = edit.range.start.character as usize, + blank = "" + ); + + let mut insert = String::new(); + let mut offset = (primary_cursor as i128 + start_offset) as usize; + let mut tabstops: Vec = Vec::new(); + + for element in snippet.elements { + match element { + Text(text) => { + // small optimization to avoid calling replace when it's unnecessary + let text = if text.contains('\n') { + Cow::Owned(text.replace('\n', &newline_with_offset)) + } else { + Cow::Borrowed(text) + }; + offset += text.chars().count(); + insert.push_str(&text); + } + Variable { + name: _name, + regex: None, + r#default, + } => { + // TODO: variables. For now, fall back to the default, which defaults to "". + let text = r#default.unwrap_or_default(); + offset += text.chars().count(); + insert.push_str(text); + } + Tabstop { .. } => { + // TODO: tabstop indexing: 0 is final cursor position. 1,2,.. are positions. + // TODO: merge tabstops with the same index + tabstops.push(Range::point(offset)); + } + Placeholder { + tabstop: _tabstop, + value, + } => match value.as_ref() { + // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html + // would make this a bit nicer + Text(text) => { + let len_chars = text.chars().count(); + tabstops.push(Range::new(offset, offset + len_chars + 1)); + offset += len_chars; + insert.push_str(text); + } + other => { + log::error!( + "Discarding snippet: generating a transaction for placeholder contents {:?} is unimplemented.", + other + ); + return Transaction::new(doc); + } + }, + other => { + log::error!( + "Discarding snippet: generating a transaction for {:?} is unimplemented.", + other + ); + return Transaction::new(doc); + } + } + } + + let transaction = Transaction::change_by_selection(doc, selection, |range| { + let cursor = range.cursor(text); + ( + (cursor as i128 + start_offset) as usize, + (cursor as i128 + end_offset) as usize, + Some(insert.clone().into()), + ) + }); + + if let Some(first) = tabstops.first() { + transaction.with_selection(Selection::new(smallvec![*first], 0)) + } else { + transaction + } +} + mod parser { use helix_parsec::*; diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index a24da20a9..6897305de 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -119,7 +119,9 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Transaction { - let transaction = if let Some(edit) = &item.text_edit { + use helix_lsp::snippet; + + if let Some(edit) = &item.text_edit { let edit = match edit { lsp::CompletionTextEdit::Edit(edit) => edit.clone(), lsp::CompletionTextEdit::InsertAndReplace(item) => { @@ -128,12 +130,38 @@ impl Completion { } }; - util::generate_transaction_from_completion_edit( - doc.text(), - doc.selection(view_id), - edit, - offset_encoding, // TODO: should probably transcode in Client - ) + if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET)) + || matches!( + item.insert_text_format, + Some(lsp::InsertTextFormat::SNIPPET) + ) + { + match snippet::parse(&edit.new_text) { + Ok(snippet) => snippet::into_transaction( + snippet, + doc.text(), + doc.selection(view_id), + &edit, + doc.line_ending.as_str(), + offset_encoding, + ), + Err(err) => { + log::error!( + "Failed to parse snippet: {:?}, remaining output: {}", + &edit.new_text, + err + ); + Transaction::new(doc.text()) + } + } + } else { + util::generate_transaction_from_completion_edit( + doc.text(), + doc.selection(view_id), + edit, + offset_encoding, // TODO: should probably transcode in Client + ) + } } else { let text = item.insert_text.as_ref().unwrap_or(&item.label); // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯ @@ -157,9 +185,7 @@ impl Completion { (cursor, cursor, Some(text.into())) }) - }; - - transaction + } } fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec { From d2af31b916114dd7ebbc3390eb44c0d72e06ec50 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 30 Oct 2022 16:39:27 -0500 Subject: [PATCH 55/81] LSP: Advertise snippet support --- helix-lsp/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 3f88b3523..95f3ea348 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -320,7 +320,7 @@ impl Client { text_document: Some(lsp::TextDocumentClientCapabilities { completion: Some(lsp::CompletionClientCapabilities { completion_item: Some(lsp::CompletionItemCapability { - snippet_support: Some(false), + snippet_support: Some(true), resolve_support: Some(lsp::CompletionItemCapabilityResolveSupport { properties: vec![ String::from("documentation"), From ded4381728bcbbcee3d3fec1b861038229b806d3 Mon Sep 17 00:00:00 2001 From: Urgau Date: Wed, 8 Feb 2023 09:36:15 +0100 Subject: [PATCH 56/81] Implement LSP snippet tabstops sorting and merging --- helix-lsp/src/snippet.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index f74237496..87c839f94 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use anyhow::{anyhow, Result}; +use helix_core::SmallVec; use crate::{util::lsp_pos_to_pos, OffsetEncoding}; @@ -87,7 +88,7 @@ pub fn into_transaction<'a>( let mut insert = String::new(); let mut offset = (primary_cursor as i128 + start_offset) as usize; - let mut tabstops: Vec = Vec::new(); + let mut tabstops: Vec<(usize, Range)> = Vec::new(); for element in snippet.elements { match element { @@ -111,20 +112,15 @@ pub fn into_transaction<'a>( offset += text.chars().count(); insert.push_str(text); } - Tabstop { .. } => { - // TODO: tabstop indexing: 0 is final cursor position. 1,2,.. are positions. - // TODO: merge tabstops with the same index - tabstops.push(Range::point(offset)); + Tabstop { tabstop } => { + tabstops.push((tabstop, Range::point(offset))); } - Placeholder { - tabstop: _tabstop, - value, - } => match value.as_ref() { + Placeholder { tabstop, value } => match value.as_ref() { // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html // would make this a bit nicer Text(text) => { let len_chars = text.chars().count(); - tabstops.push(Range::new(offset, offset + len_chars + 1)); + tabstops.push((tabstop, Range::new(offset, offset + len_chars + 1))); offset += len_chars; insert.push_str(text); } @@ -155,8 +151,25 @@ pub fn into_transaction<'a>( ) }); - if let Some(first) = tabstops.first() { - transaction.with_selection(Selection::new(smallvec![*first], 0)) + // sort in ascending order (except for 0, which should always be the last one (per lsp doc)) + tabstops.sort_unstable_by_key(|(n, _range)| if *n == 0 { usize::MAX } else { *n }); + + // merge tabstops with the same index (we take advantage of the fact that we just sorted them + // above to simply look backwards) + let mut ntabstops = Vec::>::new(); + let mut prev = None; + for (tabstop, range) in tabstops { + if prev == Some(tabstop) { + let len_1 = ntabstops.len() - 1; + ntabstops[len_1].push(range); + } else { + prev = Some(tabstop); + ntabstops.push(smallvec![range]); + } + } + + if let Some(first) = ntabstops.first() { + transaction.with_selection(Selection::new(first.clone(), 0)) } else { transaction } From ba24cfe9125eda97346e3ceee42686fb9f46046f Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Sat, 11 Feb 2023 14:20:49 +0100 Subject: [PATCH 57/81] Delete snippet placeholders when accepting completion When accepting a snippet completion we automatically delete the placeholders for now as doing so manual is quite cumbersome. In the future we should keep these as a mark + virtual text that is automatically removed once the cursor moves there. --- helix-lsp/src/snippet.rs | 13 +++++++++---- helix-term/src/ui/completion.rs | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index 87c839f94..441c419f8 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -64,6 +64,7 @@ pub fn into_transaction<'a>( edit: &lsp_types::TextEdit, line_ending: &str, offset_encoding: OffsetEncoding, + include_placeholer: bool, ) -> helix_core::Transaction { use helix_core::{smallvec, Range, Selection, Transaction}; use SnippetElement::*; @@ -119,10 +120,14 @@ pub fn into_transaction<'a>( // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html // would make this a bit nicer Text(text) => { - let len_chars = text.chars().count(); - tabstops.push((tabstop, Range::new(offset, offset + len_chars + 1))); - offset += len_chars; - insert.push_str(text); + if include_placeholer { + let len_chars = text.chars().count(); + tabstops.push((tabstop, Range::new(offset, offset + len_chars + 1))); + offset += len_chars; + insert.push_str(text); + } else { + tabstops.push((tabstop, Range::point(offset))); + } } other => { log::error!( diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 6897305de..c7955a3dd 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -118,6 +118,7 @@ impl Completion { offset_encoding: helix_lsp::OffsetEncoding, start_offset: usize, trigger_offset: usize, + include_placeholder: bool, ) -> Transaction { use helix_lsp::snippet; @@ -144,6 +145,7 @@ impl Completion { &edit, doc.line_ending.as_str(), offset_encoding, + include_placeholder, ), Err(err) => { log::error!( @@ -216,6 +218,7 @@ impl Completion { offset_encoding, start_offset, trigger_offset, + true, ); // initialize a savepoint @@ -238,6 +241,7 @@ impl Completion { offset_encoding, start_offset, trigger_offset, + false, ); doc.apply(&transaction, view.id); From ec6e575a408372400b7789b90cdf6ac271f51182 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 11 Feb 2023 18:45:30 +0100 Subject: [PATCH 58/81] Correctly handle multiple cursors with LSP snippets --- helix-core/src/selection.rs | 10 ++++++++ helix-lsp/src/snippet.rs | 48 ++++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 0ac2c6802..0db7634c9 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -578,6 +578,16 @@ impl Selection { self.normalize() } + /// Takes a closure and maps each `Range` over the closure to multiple `Range`s. + pub fn transform_iter(mut self, f: F) -> Self + where + F: FnMut(Range) -> I, + I: Iterator, + { + self.ranges = self.ranges.into_iter().flat_map(f).collect(); + self.normalize() + } + // Ensures the selection adheres to the following invariants: // 1. All ranges are grapheme aligned. // 2. All ranges are at least 1 character wide, unless at the diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index 441c419f8..ab0f406d0 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -66,7 +66,7 @@ pub fn into_transaction<'a>( offset_encoding: OffsetEncoding, include_placeholer: bool, ) -> helix_core::Transaction { - use helix_core::{smallvec, Range, Selection, Transaction}; + use helix_core::{smallvec, Range, Transaction}; use SnippetElement::*; let text = doc.slice(..); @@ -87,9 +87,9 @@ pub fn into_transaction<'a>( blank = "" ); + let mut offset = 0; let mut insert = String::new(); - let mut offset = (primary_cursor as i128 + start_offset) as usize; - let mut tabstops: Vec<(usize, Range)> = Vec::new(); + let mut tabstops: Vec<(usize, usize, usize)> = Vec::new(); for element in snippet.elements { match element { @@ -114,7 +114,7 @@ pub fn into_transaction<'a>( insert.push_str(text); } Tabstop { tabstop } => { - tabstops.push((tabstop, Range::point(offset))); + tabstops.push((tabstop, offset, offset)); } Placeholder { tabstop, value } => match value.as_ref() { // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html @@ -122,11 +122,11 @@ pub fn into_transaction<'a>( Text(text) => { if include_placeholer { let len_chars = text.chars().count(); - tabstops.push((tabstop, Range::new(offset, offset + len_chars + 1))); + tabstops.push((tabstop, offset, offset + len_chars + 1)); offset += len_chars; insert.push_str(text); } else { - tabstops.push((tabstop, Range::point(offset))); + tabstops.push((tabstop, offset, offset)); } } other => { @@ -157,24 +157,38 @@ pub fn into_transaction<'a>( }); // sort in ascending order (except for 0, which should always be the last one (per lsp doc)) - tabstops.sort_unstable_by_key(|(n, _range)| if *n == 0 { usize::MAX } else { *n }); + tabstops.sort_unstable_by_key(|(n, _o1, _o2)| if *n == 0 { usize::MAX } else { *n }); // merge tabstops with the same index (we take advantage of the fact that we just sorted them // above to simply look backwards) - let mut ntabstops = Vec::>::new(); - let mut prev = None; - for (tabstop, range) in tabstops { - if prev == Some(tabstop) { - let len_1 = ntabstops.len() - 1; - ntabstops[len_1].push(range); - } else { - prev = Some(tabstop); - ntabstops.push(smallvec![range]); + let mut ntabstops = Vec::>::new(); + { + let mut prev = None; + for (tabstop, o1, o2) in tabstops { + if prev == Some(tabstop) { + let len_1 = ntabstops.len() - 1; + ntabstops[len_1].push((o1, o2)); + } else { + prev = Some(tabstop); + ntabstops.push(smallvec![(o1, o2)]); + } } } if let Some(first) = ntabstops.first() { - transaction.with_selection(Selection::new(first.clone(), 0)) + let cursor_offset = insert.chars().count() as i128 - (end_offset - start_offset); + let mut extra_offset = start_offset; + transaction.with_selection(selection.clone().transform_iter(|range| { + let cursor = range.cursor(text); + let iter = first.iter().map(move |first| { + Range::new( + (cursor as i128 + first.0 as i128 + extra_offset) as usize, + (cursor as i128 + first.1 as i128 + extra_offset) as usize, + ) + }); + extra_offset += cursor_offset; + iter + })) } else { transaction } From 1866b43cd355ff6d41d579b4b710a0f602aa79d1 Mon Sep 17 00:00:00 2001 From: Andrii Grynenko Date: Fri, 17 Feb 2023 07:51:00 -0800 Subject: [PATCH 59/81] Render every LSP snippets for every cursor This refactors the snippet logic to be largely unaware of the rest of the document. The completion application logic is moved into generate_transaction_from_snippet which is extended to support dynamically computing replacement text. --- helix-lsp/src/lib.rs | 79 ++++++++++++++ helix-lsp/src/snippet.rs | 187 ++++++++++++++------------------ helix-term/src/ui/completion.rs | 8 +- 3 files changed, 166 insertions(+), 108 deletions(-) diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index cce848ab1..5b4f7ee4e 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -60,6 +60,7 @@ pub mod util { use super::*; use helix_core::line_ending::{line_end_byte_index, line_end_char_index}; use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction}; + use helix_core::{smallvec, SmallVec}; /// Converts a diagnostic in the document to [`lsp::Diagnostic`]. /// @@ -282,6 +283,84 @@ pub mod util { }) } + /// Creates a [Transaction] from the [snippet::Snippet] in a completion response. + /// The transaction applies the edit to all cursors. + pub fn generate_transaction_from_snippet( + doc: &Rope, + selection: &Selection, + edit_range: &lsp::Range, + snippet: snippet::Snippet, + line_ending: &str, + include_placeholder: bool, + offset_encoding: OffsetEncoding, + ) -> Transaction { + let text = doc.slice(..); + let primary_cursor = selection.primary().cursor(text); + + let start_offset = match lsp_pos_to_pos(doc, edit_range.start, offset_encoding) { + Some(start) => start as i128 - primary_cursor as i128, + None => return Transaction::new(doc), + }; + let end_offset = match lsp_pos_to_pos(doc, edit_range.end, offset_encoding) { + Some(end) => end as i128 - primary_cursor as i128, + None => return Transaction::new(doc), + }; + + // For each cursor store offsets for the first tabstop + let mut cursor_tabstop_offsets = Vec::>::new(); + let transaction = Transaction::change_by_selection(doc, selection, |range| { + let cursor = range.cursor(text); + let replacement_start = (cursor as i128 + start_offset) as usize; + let replacement_end = (cursor as i128 + end_offset) as usize; + let newline_with_offset = format!( + "{line_ending}{blank:width$}", + line_ending = line_ending, + width = replacement_start - doc.line_to_char(doc.char_to_line(replacement_start)), + blank = "" + ); + + let (replacement, tabstops) = + snippet::render(&snippet, newline_with_offset, include_placeholder); + + let replacement_len = replacement.chars().count(); + cursor_tabstop_offsets.push( + tabstops + .first() + .unwrap_or(&smallvec![(replacement_len, replacement_len)]) + .iter() + .map(|(from, to)| -> (i128, i128) { + ( + *from as i128 - replacement_len as i128, + *to as i128 - replacement_len as i128, + ) + }) + .collect(), + ); + + (replacement_start, replacement_end, Some(replacement.into())) + }); + + // Create new selection based on the cursor tabstop from above + let mut cursor_tabstop_offsets_iter = cursor_tabstop_offsets.iter(); + let selection = selection + .clone() + .map(transaction.changes()) + .transform_iter(|range| { + cursor_tabstop_offsets_iter + .next() + .unwrap() + .iter() + .map(move |(from, to)| { + Range::new( + (range.anchor as i128 + *from) as usize, + (range.anchor as i128 + *to) as usize, + ) + }) + }); + + transaction.with_selection(selection) + } + pub fn generate_transaction_from_edits( doc: &Rope, mut edits: Vec, diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index ab0f406d0..63054cdbb 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -1,9 +1,7 @@ use std::borrow::Cow; use anyhow::{anyhow, Result}; -use helix_core::SmallVec; - -use crate::{util::lsp_pos_to_pos, OffsetEncoding}; +use helix_core::{SmallVec, smallvec}; #[derive(Debug, PartialEq, Eq)] pub enum CaseChange { @@ -34,7 +32,7 @@ pub enum SnippetElement<'a> { }, Placeholder { tabstop: usize, - value: Box>, + value: Vec>, }, Choice { tabstop: usize, @@ -57,141 +55,108 @@ pub fn parse(s: &str) -> Result> { parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest)) } -pub fn into_transaction<'a>( - snippet: Snippet<'a>, - doc: &helix_core::Rope, - selection: &helix_core::Selection, - edit: &lsp_types::TextEdit, - line_ending: &str, - offset_encoding: OffsetEncoding, +fn render_elements( + snippet_elements: &[SnippetElement<'_>], + insert: &mut String, + offset: &mut usize, + tabstops: &mut Vec<(usize, (usize, usize))>, + newline_with_offset: &String, include_placeholer: bool, -) -> helix_core::Transaction { - use helix_core::{smallvec, Range, Transaction}; +) { use SnippetElement::*; - let text = doc.slice(..); - let primary_cursor = selection.primary().cursor(text); - - let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) { - Some(start) => start as i128 - primary_cursor as i128, - None => return Transaction::new(doc), - }; - let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) { - Some(end) => end as i128 - primary_cursor as i128, - None => return Transaction::new(doc), - }; - - let newline_with_offset = format!( - "{line_ending}{blank:width$}", - width = edit.range.start.character as usize, - blank = "" - ); - - let mut offset = 0; - let mut insert = String::new(); - let mut tabstops: Vec<(usize, usize, usize)> = Vec::new(); - - for element in snippet.elements { + for element in snippet_elements { match element { - Text(text) => { + &Text(text) => { // small optimization to avoid calling replace when it's unnecessary let text = if text.contains('\n') { - Cow::Owned(text.replace('\n', &newline_with_offset)) + Cow::Owned(text.replace('\n', newline_with_offset)) } else { Cow::Borrowed(text) }; - offset += text.chars().count(); + *offset += text.chars().count(); insert.push_str(&text); } - Variable { - name: _name, - regex: None, + &Variable { + name: _, + regex: _, r#default, } => { // TODO: variables. For now, fall back to the default, which defaults to "". let text = r#default.unwrap_or_default(); - offset += text.chars().count(); + *offset += text.chars().count(); insert.push_str(text); } - Tabstop { tabstop } => { - tabstops.push((tabstop, offset, offset)); + &Tabstop { tabstop } => { + tabstops.push((tabstop, (*offset, *offset))); } - Placeholder { tabstop, value } => match value.as_ref() { - // https://doc.rust-lang.org/beta/unstable-book/language-features/box-patterns.html - // would make this a bit nicer - Text(text) => { - if include_placeholer { - let len_chars = text.chars().count(); - tabstops.push((tabstop, offset, offset + len_chars + 1)); - offset += len_chars; - insert.push_str(text); - } else { - tabstops.push((tabstop, offset, offset)); - } - } - other => { - log::error!( - "Discarding snippet: generating a transaction for placeholder contents {:?} is unimplemented.", - other + Placeholder { + tabstop, + value: inner_snippet_elements, + } => { + let start_offset = *offset; + if include_placeholer { + render_elements( + inner_snippet_elements, + insert, + offset, + tabstops, + newline_with_offset, + include_placeholer, ); - return Transaction::new(doc); } - }, - other => { - log::error!( - "Discarding snippet: generating a transaction for {:?} is unimplemented.", - other - ); - return Transaction::new(doc); + tabstops.push((*tabstop, (start_offset, *offset))); + } + &Choice { + tabstop, + choices: _, + } => { + // TODO: choices + tabstops.push((tabstop, (*offset, *offset))); } } } +} - let transaction = Transaction::change_by_selection(doc, selection, |range| { - let cursor = range.cursor(text); - ( - (cursor as i128 + start_offset) as usize, - (cursor as i128 + end_offset) as usize, - Some(insert.clone().into()), - ) - }); +#[allow(clippy::type_complexity)] // only used one time +pub fn render( + snippet: &Snippet<'_>, + newline_with_offset: String, + include_placeholer: bool, +) -> (String, Vec>) { + let mut insert = String::new(); + let mut tabstops = Vec::new(); + let mut offset = 0; + + render_elements( + &snippet.elements, + &mut insert, + &mut offset, + &mut tabstops, + &newline_with_offset, + include_placeholer, + ); // sort in ascending order (except for 0, which should always be the last one (per lsp doc)) - tabstops.sort_unstable_by_key(|(n, _o1, _o2)| if *n == 0 { usize::MAX } else { *n }); + tabstops.sort_unstable_by_key(|(n, _)| if *n == 0 { usize::MAX } else { *n }); // merge tabstops with the same index (we take advantage of the fact that we just sorted them // above to simply look backwards) let mut ntabstops = Vec::>::new(); { let mut prev = None; - for (tabstop, o1, o2) in tabstops { + for (tabstop, r) in tabstops { if prev == Some(tabstop) { let len_1 = ntabstops.len() - 1; - ntabstops[len_1].push((o1, o2)); + ntabstops[len_1].push(r); } else { prev = Some(tabstop); - ntabstops.push(smallvec![(o1, o2)]); + ntabstops.push(smallvec![r]); } } } - if let Some(first) = ntabstops.first() { - let cursor_offset = insert.chars().count() as i128 - (end_offset - start_offset); - let mut extra_offset = start_offset; - transaction.with_selection(selection.clone().transform_iter(|range| { - let cursor = range.cursor(text); - let iter = first.iter().map(move |first| { - Range::new( - (cursor as i128 + first.0 as i128 + extra_offset) as usize, - (cursor as i128 + first.1 as i128 + extra_offset) as usize, - ) - }); - extra_offset += cursor_offset; - iter - })) - } else { - transaction - } + (insert, ntabstops) } mod parser { @@ -343,14 +308,15 @@ mod parser { fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { // TODO: why doesn't parse_as work? // let value = reparse_as(take_until(|c| c == '}'), anything()); + // TODO: fix this to parse nested placeholders (take until terminates too early) let value = filter_map(take_until(|c| c == '}'), |s| { - anything().parse(s).map(|parse_result| parse_result.1).ok() + snippet().parse(s).map(|parse_result| parse_result.1).ok() }); map(seq!("${", digit(), ":", value, "}"), |seq| { SnippetElement::Placeholder { tabstop: seq.1, - value: Box::new(seq.3), + value: seq.3.elements, } }) } @@ -430,7 +396,7 @@ mod parser { Text("match("), Placeholder { tabstop: 1, - value: Box::new(Text("Arg1")), + value: vec!(Text("Arg1")), }, Text(")") ] @@ -447,12 +413,12 @@ mod parser { Text("local "), Placeholder { tabstop: 1, - value: Box::new(Text("var")), + value: vec!(Text("var")), }, Text(" = "), Placeholder { tabstop: 1, - value: Box::new(Text("value")), + value: vec!(Text("value")), }, ] }), @@ -460,6 +426,19 @@ mod parser { ) } + #[test] + fn parse_tabstop_nested_in_placeholder() { + assert_eq!( + Ok(Snippet { + elements: vec![Placeholder { + tabstop: 1, + value: vec!(Text("var, "), Tabstop { tabstop: 2 },), + },] + }), + parse("${1:var, $2}") + ) + } + #[test] fn parse_all() { assert_eq!( diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c7955a3dd..e7815e12d 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -138,14 +138,14 @@ impl Completion { ) { match snippet::parse(&edit.new_text) { - Ok(snippet) => snippet::into_transaction( - snippet, + Ok(snippet) => util::generate_transaction_from_snippet( doc.text(), doc.selection(view_id), - &edit, + &edit.range, + snippet, doc.line_ending.as_str(), - offset_encoding, include_placeholder, + offset_encoding, ), Err(err) => { log::error!( From 0d924255e4ea3d5d5c4be9b11a337f4316550e32 Mon Sep 17 00:00:00 2001 From: Andrii Grynenko Date: Mon, 20 Feb 2023 22:04:24 -0800 Subject: [PATCH 60/81] Add nested placeholder parsing for LSP snippets And fix `text` over-parsing, inspired by https://github.com/neovim/neovim/blob/d18f8d5c2d82b209093b2feaa8921a4792b71d59/runtime/lua/vim/lsp/_snippet.lua --- helix-lsp/src/snippet.rs | 70 ++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/helix-lsp/src/snippet.rs b/helix-lsp/src/snippet.rs index 63054cdbb..b27077e70 100644 --- a/helix-lsp/src/snippet.rs +++ b/helix-lsp/src/snippet.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use anyhow::{anyhow, Result}; -use helix_core::{SmallVec, smallvec}; +use helix_core::{smallvec, SmallVec}; #[derive(Debug, PartialEq, Eq)] pub enum CaseChange { @@ -210,8 +210,8 @@ mod parser { } } - fn text<'a>() -> impl Parser<'a, Output = &'a str> { - take_while(|c| c != '$') + fn text<'a, const SIZE: usize>(cs: [char; SIZE]) -> impl Parser<'a, Output = &'a str> { + take_while(move |c| cs.into_iter().all(|c1| c != c1)) } fn digit<'a>() -> impl Parser<'a, Output = usize> { @@ -270,13 +270,15 @@ mod parser { ), |seq| { Conditional(seq.1, None, Some(seq.4)) } ), - // Any text - map(text(), Text), ) } fn regex<'a>() -> impl Parser<'a, Output = Regex<'a>> { - let replacement = reparse_as(take_until(|c| c == '/'), one_or_more(format())); + let text = map(text(['$', '/']), FormatItem::Text); + let replacement = reparse_as( + take_until(|c| c == '/'), + one_or_more(choice!(format(), text)), + ); map( seq!( @@ -306,19 +308,20 @@ mod parser { } fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { - // TODO: why doesn't parse_as work? - // let value = reparse_as(take_until(|c| c == '}'), anything()); - // TODO: fix this to parse nested placeholders (take until terminates too early) - let value = filter_map(take_until(|c| c == '}'), |s| { - snippet().parse(s).map(|parse_result| parse_result.1).ok() - }); - - map(seq!("${", digit(), ":", value, "}"), |seq| { - SnippetElement::Placeholder { + let text = map(text(['$', '}']), SnippetElement::Text); + map( + seq!( + "${", + digit(), + ":", + one_or_more(choice!(anything(), text)), + "}" + ), + |seq| SnippetElement::Placeholder { tabstop: seq.1, - value: seq.3.elements, - } - }) + value: seq.3, + }, + ) } fn choice<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { @@ -366,12 +369,18 @@ mod parser { } fn anything<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> { - let text = map(text(), SnippetElement::Text); - choice!(tabstop(), placeholder(), choice(), variable(), text) + // The parser has to be constructed lazily to avoid infinite opaque type recursion + |input: &'a str| { + let parser = choice!(tabstop(), placeholder(), choice(), variable()); + parser.parse(input) + } } fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> { - map(one_or_more(anything()), |parts| Snippet { elements: parts }) + let text = map(text(['$']), SnippetElement::Text); + map(one_or_more(choice!(anything(), text)), |parts| Snippet { + elements: parts, + }) } pub fn parse(s: &str) -> Result { @@ -439,6 +448,25 @@ mod parser { ) } + #[test] + fn parse_placeholder_nested_in_placeholder() { + assert_eq!( + Ok(Snippet { + elements: vec![Placeholder { + tabstop: 1, + value: vec!( + Text("foo "), + Placeholder { + tabstop: 2, + value: vec!(Text("bar")), + }, + ), + },] + }), + parse("${1:foo ${2:bar}}") + ) + } + #[test] fn parse_all() { assert_eq!( From 8c2e447b16e4d11db411b18f2fbe3ac2bc031d89 Mon Sep 17 00:00:00 2001 From: Andrii Grynenko Date: Thu, 2 Mar 2023 21:41:06 -0800 Subject: [PATCH 61/81] Handle snippets for LSPs not providing offsets for completion --- helix-lsp/src/lib.rs | 33 +++--------- helix-term/src/ui/completion.rs | 95 +++++++++++++++++++-------------- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 5b4f7ee4e..147b381c2 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -252,26 +252,17 @@ pub mod util { pub fn generate_transaction_from_completion_edit( doc: &Rope, selection: &Selection, - edit: lsp::TextEdit, - offset_encoding: OffsetEncoding, + start_offset: i128, + end_offset: i128, + new_text: String, ) -> Transaction { - let replacement: Option = if edit.new_text.is_empty() { + let replacement: Option = if new_text.is_empty() { None } else { - Some(edit.new_text.into()) + Some(new_text.into()) }; let text = doc.slice(..); - let primary_cursor = selection.primary().cursor(text); - - let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) { - Some(start) => start as i128 - primary_cursor as i128, - None => return Transaction::new(doc), - }; - let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) { - Some(end) => end as i128 - primary_cursor as i128, - None => return Transaction::new(doc), - }; Transaction::change_by_selection(doc, selection, |range| { let cursor = range.cursor(text); @@ -288,23 +279,13 @@ pub mod util { pub fn generate_transaction_from_snippet( doc: &Rope, selection: &Selection, - edit_range: &lsp::Range, + start_offset: i128, + end_offset: i128, snippet: snippet::Snippet, line_ending: &str, include_placeholder: bool, - offset_encoding: OffsetEncoding, ) -> Transaction { let text = doc.slice(..); - let primary_cursor = selection.primary().cursor(text); - - let start_offset = match lsp_pos_to_pos(doc, edit_range.start, offset_encoding) { - Some(start) => start as i128 - primary_cursor as i128, - None => return Transaction::new(doc), - }; - let end_offset = match lsp_pos_to_pos(doc, edit_range.end, offset_encoding) { - Some(end) => end as i128 - primary_cursor as i128, - None => return Transaction::new(doc), - }; // For each cursor store offsets for the first tabstop let mut cursor_tabstop_offsets = Vec::>::new(); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index e7815e12d..85931fe32 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -121,8 +121,9 @@ impl Completion { include_placeholder: bool, ) -> Transaction { use helix_lsp::snippet; + let selection = doc.selection(view_id); - if let Some(edit) = &item.text_edit { + let (start_offset, end_offset, new_text) = if let Some(edit) = &item.text_edit { let edit = match edit { lsp::CompletionTextEdit::Edit(edit) => edit.clone(), lsp::CompletionTextEdit::InsertAndReplace(item) => { @@ -130,46 +131,27 @@ impl Completion { lsp::TextEdit::new(item.replace, item.new_text.clone()) } }; - - if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET)) - || matches!( - item.insert_text_format, - Some(lsp::InsertTextFormat::SNIPPET) - ) - { - match snippet::parse(&edit.new_text) { - Ok(snippet) => util::generate_transaction_from_snippet( - doc.text(), - doc.selection(view_id), - &edit.range, - snippet, - doc.line_ending.as_str(), - include_placeholder, - offset_encoding, - ), - Err(err) => { - log::error!( - "Failed to parse snippet: {:?}, remaining output: {}", - &edit.new_text, - err - ); - Transaction::new(doc.text()) - } - } - } else { - util::generate_transaction_from_completion_edit( - doc.text(), - doc.selection(view_id), - edit, - offset_encoding, // TODO: should probably transcode in Client - ) - } + let text = doc.text().slice(..); + let primary_cursor = selection.primary().cursor(text); + + let start_offset = + match util::lsp_pos_to_pos(doc.text(), edit.range.start, offset_encoding) { + Some(start) => start as i128 - primary_cursor as i128, + None => return Transaction::new(doc.text()), + }; + let end_offset = + match util::lsp_pos_to_pos(doc.text(), edit.range.end, offset_encoding) { + Some(end) => end as i128 - primary_cursor as i128, + None => return Transaction::new(doc.text()), + }; + + (start_offset, end_offset, edit.new_text) } else { - let text = item.insert_text.as_ref().unwrap_or(&item.label); + let new_text = item.insert_text.as_ref().unwrap_or(&item.label); // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯ // in these cases we need to check for a common prefix and remove it let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset)); - let text = text.trim_start_matches::<&str>(&prefix); + let new_text = new_text.trim_start_matches::<&str>(&prefix); // TODO: this needs to be true for the numbers to work out correctly // in the closure below. It's passed in to a callback as this same @@ -182,11 +164,42 @@ impl Completion { == trigger_offset ); - Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| { - let cursor = range.cursor(doc.text().slice(..)); + (0, 0, new_text.into()) + }; - (cursor, cursor, Some(text.into())) - }) + if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET)) + || matches!( + item.insert_text_format, + Some(lsp::InsertTextFormat::SNIPPET) + ) + { + match snippet::parse(&new_text) { + Ok(snippet) => util::generate_transaction_from_snippet( + doc.text(), + selection, + start_offset, + end_offset, + snippet, + doc.line_ending.as_str(), + include_placeholder, + ), + Err(err) => { + log::error!( + "Failed to parse snippet: {:?}, remaining output: {}", + &new_text, + err + ); + Transaction::new(doc.text()) + } + } + } else { + util::generate_transaction_from_completion_edit( + doc.text(), + selection, + start_offset, + end_offset, + new_text, + ) } } From 48b6aa9a699df0680a6d31e9611ebd1ca9909de4 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Wed, 8 Mar 2023 02:49:14 +0100 Subject: [PATCH 62/81] Add command for resetting diff hunks (#5736) --- book/src/generated/typable-cmd.md | 1 + helix-term/src/commands.rs | 22 +++++------ helix-term/src/commands/typed.rs | 65 +++++++++++++++++++++++++++++++ helix-vcs/src/diff.rs | 58 +++++++++++++++++++-------- helix-vcs/src/diff/line_cache.rs | 8 ++++ helix-vcs/src/diff/worker.rs | 15 ++++--- helix-vcs/src/diff/worker/test.rs | 6 +-- helix-view/src/gutter.rs | 2 +- 8 files changed, 140 insertions(+), 37 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index badadc43d..8b367aad8 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -76,3 +76,4 @@ | `:pipe` | Pipe each selection to the shell command. | | `:pipe-to` | Pipe each selection to the shell command, ignoring output. | | `:run-shell-command`, `:sh` | Run a shell command | +| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c76e9f2bd..ebdfdfde2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3011,13 +3011,13 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) { let (view, doc) = current!(editor); if let Some(handle) = doc.diff_handle() { let hunk = { - let hunks = handle.hunks(); + let diff = handle.load(); let idx = if reverse { - hunks.len().saturating_sub(1) + diff.len().saturating_sub(1) } else { 0 }; - hunks.nth_hunk(idx) + diff.nth_hunk(idx) }; if hunk != Hunk::NONE { let range = hunk_range(hunk, doc.text().slice(..)); @@ -3049,19 +3049,19 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) { let selection = doc.selection(view.id).clone().transform(|range| { let cursor_line = range.cursor_line(doc_text) as u32; - let hunks = diff_handle.hunks(); + let diff = diff_handle.load(); let hunk_idx = match direction { - Direction::Forward => hunks + Direction::Forward => diff .next_hunk(cursor_line) - .map(|idx| (idx + count).min(hunks.len() - 1)), - Direction::Backward => hunks + .map(|idx| (idx + count).min(diff.len() - 1)), + Direction::Backward => diff .prev_hunk(cursor_line) .map(|idx| idx.saturating_sub(count)), }; let Some(hunk_idx) = hunk_idx else { return range; }; - let hunk = hunks.nth_hunk(hunk_idx); + let hunk = diff.nth_hunk(hunk_idx); let new_range = hunk_range(hunk, doc_text); if editor.mode == Mode::Select { let head = if new_range.head < range.anchor { @@ -4721,14 +4721,14 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { let textobject_change = |range: Range| -> Range { let diff_handle = doc.diff_handle().unwrap(); - let hunks = diff_handle.hunks(); + let diff = diff_handle.load(); let line = range.cursor_line(text); - let hunk_idx = if let Some(hunk_idx) = hunks.hunk_at(line as u32, false) { + let hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) { hunk_idx } else { return range; }; - let hunk = hunks.nth_hunk(hunk_idx).after; + let hunk = diff.nth_hunk(hunk_idx).after; let start = text.line_to_char(hunk.start as usize); let end = text.line_to_char(hunk.end as usize); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 0ddca6df7..77c143212 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2033,6 +2033,64 @@ fn run_shell_command( Ok(()) } +fn reset_diff_change( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + ensure!(args.is_empty(), ":reset-diff-change takes no arguments"); + + let editor = &mut cx.editor; + let scrolloff = editor.config().scrolloff; + + let (view, doc) = current!(editor); + // TODO refactor to use let..else once MSRV is raised to 1.65 + let handle = match doc.diff_handle() { + Some(handle) => handle, + None => bail!("Diff is not available in the current buffer"), + }; + + let diff = handle.load(); + let doc_text = doc.text().slice(..); + let line = doc.selection(view.id).primary().cursor_line(doc_text); + + // TODO refactor to use let..else once MSRV is raised to 1.65 + let hunk_idx = match diff.hunk_at(line as u32, true) { + Some(hunk_idx) => hunk_idx, + None => bail!("There is no change at the cursor"), + }; + let hunk = diff.nth_hunk(hunk_idx); + let diff_base = diff.diff_base(); + let before_start = diff_base.line_to_char(hunk.before.start as usize); + let before_end = diff_base.line_to_char(hunk.before.end as usize); + let text: Tendril = diff + .diff_base() + .slice(before_start..before_end) + .chunks() + .collect(); + let anchor = doc_text.line_to_char(hunk.after.start as usize); + let transaction = Transaction::change( + doc.text(), + [( + anchor, + doc_text.line_to_char(hunk.after.end as usize), + (!text.is_empty()).then_some(text), + )] + .into_iter(), + ); + drop(diff); // make borrow check happy + doc.apply(&transaction, view.id); + // select inserted text + let text_len = before_end - before_start; + doc.set_selection(view.id, Selection::single(anchor, anchor + text_len)); + doc.append_changes_to_history(view); + view.ensure_cursor_in_view(doc, scrolloff); + Ok(()) +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -2569,6 +2627,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: run_shell_command, completer: Some(completers::filename), }, + TypableCommand { + name: "reset-diff-change", + aliases: &["diffget", "diffg"], + doc: "Reset the diff change at the cursor position.", + fun: reset_diff_change, + completer: None, + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy> = diff --git a/helix-vcs/src/diff.rs b/helix-vcs/src/diff.rs index 9c6a362f7..ca33dda44 100644 --- a/helix-vcs/src/diff.rs +++ b/helix-vcs/src/diff.rs @@ -28,11 +28,18 @@ struct Event { render_lock: Option, } +#[derive(Clone, Debug, Default)] +struct DiffInner { + diff_base: Rope, + doc: Rope, + hunks: Vec, +} + #[derive(Clone, Debug)] pub struct DiffHandle { channel: UnboundedSender, render_lock: Arc>, - hunks: Arc>>, + diff: Arc>, inverted: bool, } @@ -47,10 +54,10 @@ impl DiffHandle { redraw_handle: RedrawHandle, ) -> (DiffHandle, JoinHandle<()>) { let (sender, receiver) = unbounded_channel(); - let hunks: Arc>> = Arc::default(); + let diff: Arc> = Arc::default(); let worker = DiffWorker { channel: receiver, - hunks: hunks.clone(), + diff: diff.clone(), new_hunks: Vec::default(), redraw_notify: redraw_handle.0, diff_finished_notify: Arc::default(), @@ -58,7 +65,7 @@ impl DiffHandle { let handle = tokio::spawn(worker.run(diff_base, doc)); let differ = DiffHandle { channel: sender, - hunks, + diff, inverted: false, render_lock: redraw_handle.1, }; @@ -69,9 +76,9 @@ impl DiffHandle { self.inverted = !self.inverted; } - pub fn hunks(&self) -> FileHunks { - FileHunks { - hunks: self.hunks.lock(), + pub fn load(&self) -> Diff { + Diff { + diff: self.diff.lock(), inverted: self.inverted, } } @@ -168,12 +175,28 @@ impl Hunk { /// A list of changes in a file sorted in ascending /// non-overlapping order #[derive(Debug)] -pub struct FileHunks<'a> { - hunks: MutexGuard<'a, Vec>, +pub struct Diff<'a> { + diff: MutexGuard<'a, DiffInner>, inverted: bool, } -impl FileHunks<'_> { +impl Diff<'_> { + pub fn diff_base(&self) -> &Rope { + if self.inverted { + &self.diff.doc + } else { + &self.diff.diff_base + } + } + + pub fn doc(&self) -> &Rope { + if self.inverted { + &self.diff.diff_base + } else { + &self.diff.doc + } + } + pub fn is_inverted(&self) -> bool { self.inverted } @@ -181,7 +204,7 @@ impl FileHunks<'_> { /// Returns the `Hunk` for the `n`th change in this file. /// if there is no `n`th change `Hunk::NONE` is returned instead. pub fn nth_hunk(&self, n: u32) -> Hunk { - match self.hunks.get(n as usize) { + match self.diff.hunks.get(n as usize) { Some(hunk) if self.inverted => hunk.invert(), Some(hunk) => hunk.clone(), None => Hunk::NONE, @@ -189,7 +212,7 @@ impl FileHunks<'_> { } pub fn len(&self) -> u32 { - self.hunks.len() as u32 + self.diff.hunks.len() as u32 } pub fn is_empty(&self) -> bool { @@ -204,19 +227,20 @@ impl FileHunks<'_> { }; let res = self + .diff .hunks .binary_search_by_key(&line, |hunk| hunk_range(hunk).start); match res { // Search found a hunk that starts exactly at this line, return the next hunk if it exists. - Ok(pos) if pos + 1 == self.hunks.len() => None, + Ok(pos) if pos + 1 == self.diff.hunks.len() => None, Ok(pos) => Some(pos as u32 + 1), // No hunk starts exactly at this line, so the search returns // the position where a hunk starting at this line should be inserted. // That position is exactly the position of the next hunk or the end // of the list if no such hunk exists - Err(pos) if pos == self.hunks.len() => None, + Err(pos) if pos == self.diff.hunks.len() => None, Err(pos) => Some(pos as u32), } } @@ -228,6 +252,7 @@ impl FileHunks<'_> { |hunk: &Hunk| hunk.after.clone() }; let res = self + .diff .hunks .binary_search_by_key(&line, |hunk| hunk_range(hunk).end); @@ -237,7 +262,7 @@ impl FileHunks<'_> { // which represents a pure removal. // Removals are technically empty but are still shown as single line hunks // and as such we must jump to the previous hunk (if it exists) if we are already inside the removal - Ok(pos) if !hunk_range(&self.hunks[pos]).is_empty() => Some(pos as u32), + Ok(pos) if !hunk_range(&self.diff.hunks[pos]).is_empty() => Some(pos as u32), // No hunk ends exactly at this line, so the search returns // the position where a hunk ending at this line should be inserted. @@ -255,6 +280,7 @@ impl FileHunks<'_> { }; let res = self + .diff .hunks .binary_search_by_key(&line, |hunk| hunk_range(hunk).start); @@ -267,7 +293,7 @@ impl FileHunks<'_> { // The previous hunk contains this hunk if it exists and doesn't end before this line Err(0) => None, Err(pos) => { - let hunk = hunk_range(&self.hunks[pos - 1]); + let hunk = hunk_range(&self.diff.hunks[pos - 1]); if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() { Some(pos as u32 - 1) } else { diff --git a/helix-vcs/src/diff/line_cache.rs b/helix-vcs/src/diff/line_cache.rs index c3ee5daa3..8e48250f1 100644 --- a/helix-vcs/src/diff/line_cache.rs +++ b/helix-vcs/src/diff/line_cache.rs @@ -43,6 +43,14 @@ impl InternedRopeLines { res } + pub fn doc(&self) -> Rope { + self.doc.clone() + } + + pub fn diff_base(&self) -> Rope { + self.diff_base.clone() + } + /// Updates the `diff_base` and optionally the document if `doc` is not None pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option) { self.interned.clear(); diff --git a/helix-vcs/src/diff/worker.rs b/helix-vcs/src/diff/worker.rs index f4bb4dbfb..5406446fd 100644 --- a/helix-vcs/src/diff/worker.rs +++ b/helix-vcs/src/diff/worker.rs @@ -10,7 +10,7 @@ use tokio::sync::Notify; use tokio::time::{timeout, timeout_at, Duration}; use crate::diff::{ - Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC, + DiffInner, Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC, }; use super::line_cache::InternedRopeLines; @@ -21,7 +21,7 @@ mod test; pub(super) struct DiffWorker { pub channel: UnboundedReceiver, - pub hunks: Arc>>, + pub diff: Arc>, pub new_hunks: Vec, pub redraw_notify: Arc, pub diff_finished_notify: Arc, @@ -46,7 +46,7 @@ impl DiffWorker { if let Some(lines) = interner.interned_lines() { self.perform_diff(lines); } - self.apply_hunks(); + self.apply_hunks(interner.diff_base(), interner.doc()); while let Some(event) = self.channel.recv().await { let (doc, diff_base) = self.accumulate_events(event).await; @@ -70,15 +70,18 @@ impl DiffWorker { #[cfg(not(test))] tokio::task::block_in_place(process_accumulated_events); - self.apply_hunks(); + self.apply_hunks(interner.diff_base(), interner.doc()); } } /// update the hunks (used by the gutter) by replacing it with `self.new_hunks`. /// `self.new_hunks` is always empty after this function runs. /// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs` - fn apply_hunks(&mut self) { - swap(&mut *self.hunks.lock(), &mut self.new_hunks); + fn apply_hunks(&mut self, diff_base: Rope, doc: Rope) { + let mut diff = self.diff.lock(); + diff.diff_base = diff_base; + diff.doc = doc; + swap(&mut diff.hunks, &mut self.new_hunks); self.diff_finished_notify.notify_waiters(); self.new_hunks.clear(); } diff --git a/helix-vcs/src/diff/worker/test.rs b/helix-vcs/src/diff/worker/test.rs index 144424265..6a68d987c 100644 --- a/helix-vcs/src/diff/worker/test.rs +++ b/helix-vcs/src/diff/worker/test.rs @@ -12,12 +12,12 @@ impl DiffHandle { ) } async fn into_diff(self, handle: JoinHandle<()>) -> Vec { - let hunks = self.hunks; + let diff = self.diff; // dropping the channel terminates the task drop(self.channel); handle.await.unwrap(); - let hunks = hunks.lock(); - Vec::clone(&*hunks) + let diff = diff.lock(); + Vec::clone(&diff.hunks) } } diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index cb9e43336..36e8e16a4 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -100,7 +100,7 @@ pub fn diff<'doc>( let deleted = theme.get("diff.minus"); let modified = theme.get("diff.delta"); if let Some(diff_handle) = doc.diff_handle() { - let hunks = diff_handle.hunks(); + let hunks = diff_handle.load(); let mut hunk_i = 0; let mut hunk = hunks.nth_hunk(hunk_i); Box::new( From 3d850247177f61601296fe5b1cdada8819137783 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 5 Mar 2023 12:06:12 -0600 Subject: [PATCH 63/81] Move terminal claim/restore code to helix-tui This moves the `Application::claim_term` and `helix-term::application::restore_term` functions into the helix-tui crate. How the terminal should be claimed and restored is a TUI concern and is implemented differently through different TUI backends. This cleans out a lot of crossterm and TUI code in Application and makes it easier to modify claim/restore based on information we query from the terminal host. The child commit will take advantage of this to cache the check for whether the host terminal supports the keyboard enhancement protocol. Without this change, caching that information takes much more code which is not easily reusable for anything else. The code to restore the terminal is somewhat duplicated by this patch: we want to restore the terminal in cases of panics. Panic handler hooks must live for `'static` and the Application's terminal does not. --- Cargo.lock | 1 + helix-term/src/application.rs | 98 +++++++----------------------- helix-tui/Cargo.toml | 1 + helix-tui/src/backend/crossterm.rs | 70 ++++++++++++++++++++- helix-tui/src/backend/mod.rs | 5 +- helix-tui/src/backend/test.rs | 13 ++++ helix-tui/src/terminal.rs | 22 +++++++ 7 files changed, 131 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc7265f33..231273956 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,7 @@ dependencies = [ "crossterm", "helix-core", "helix-view", + "log", "serde", "termini", "unicode-segmentation", diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index df6d9da6c..2487a02f8 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -30,22 +30,14 @@ use crate::{ use log::{debug, error, warn}; use std::{ - io::{stdin, stdout, Write}, + io::{stdin, stdout}, sync::Arc, time::{Duration, Instant}, }; use anyhow::{Context, Error}; -use crossterm::{ - event::{ - DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, - EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, KeyboardEnhancementFlags, - PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, - }, - execute, terminal, - tty::IsTty, -}; +use crossterm::{event::Event as CrosstermEvent, tty::IsTty}; #[cfg(not(windows))] use { signal_hook::{consts::signal, low_level}, @@ -63,10 +55,12 @@ use tui::backend::CrosstermBackend; use tui::backend::TestBackend; #[cfg(not(feature = "integration"))] -type Terminal = tui::terminal::Terminal>; +type TerminalBackend = CrosstermBackend; #[cfg(feature = "integration")] -type Terminal = tui::terminal::Terminal; +type TerminalBackend = TestBackend; + +type Terminal = tui::terminal::Terminal; pub struct Application { compositor: Compositor, @@ -108,26 +102,6 @@ fn setup_integration_logging() { .apply(); } -fn restore_term() -> Result<(), Error> { - let mut stdout = stdout(); - // reset cursor shape - write!(stdout, "\x1B[0 q")?; - if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { - execute!(stdout, PopKeyboardEnhancementFlags)?; - } - // Ignore errors on disabling, this might trigger on windows if we call - // disable without calling enable previously - let _ = execute!(stdout, DisableMouseCapture); - execute!( - stdout, - DisableBracketedPaste, - DisableFocusChange, - terminal::LeaveAlternateScreen - )?; - terminal::disable_raw_mode()?; - Ok(()) -} - impl Application { pub fn new( args: Args, @@ -472,13 +446,7 @@ impl Application { pub async fn handle_signals(&mut self, signal: i32) { match signal { signal::SIGTSTP => { - // restore cursor - use helix_view::graphics::CursorKind; - self.terminal - .backend_mut() - .show_cursor(CursorKind::Block) - .ok(); - restore_term().unwrap(); + self.restore_term().unwrap(); low_level::emulate_default_handler(signal::SIGTSTP).unwrap(); } signal::SIGCONT => { @@ -1054,37 +1022,19 @@ impl Application { } } - async fn claim_term(&mut self) -> Result<(), Error> { - use helix_view::graphics::CursorKind; - terminal::enable_raw_mode()?; - if self.terminal.cursor_kind() == CursorKind::Hidden { - self.terminal.backend_mut().hide_cursor().ok(); - } - let mut stdout = stdout(); - execute!( - stdout, - terminal::EnterAlternateScreen, - EnableBracketedPaste, - EnableFocusChange - )?; - execute!(stdout, terminal::Clear(terminal::ClearType::All))?; - if self.config.load().editor.mouse { - execute!(stdout, EnableMouseCapture)?; - } - if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { - log::debug!("The enhanced keyboard protocol is supported on this terminal"); - execute!( - stdout, - PushKeyboardEnhancementFlags( - KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES - | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS - ) - )?; - } else { - log::debug!("The enhanced keyboard protocol is not supported on this terminal"); - } + async fn claim_term(&mut self) -> std::io::Result<()> { + let terminal_config = self.config.load().editor.clone().into(); + self.terminal.claim(terminal_config) + } - Ok(()) + fn restore_term(&mut self) -> std::io::Result<()> { + let terminal_config = self.config.load().editor.clone().into(); + use helix_view::graphics::CursorKind; + self.terminal + .backend_mut() + .show_cursor(CursorKind::Block) + .ok(); + self.terminal.restore(terminal_config) } pub async fn run(&mut self, input_stream: &mut S) -> Result @@ -1099,7 +1049,7 @@ impl Application { // We can't handle errors properly inside this closure. And it's // probably not a good idea to `unwrap()` inside a panic handler. // So we just ignore the `Result`. - let _ = restore_term(); + let _ = TerminalBackend::force_restore(); hook(info); })); @@ -1107,13 +1057,7 @@ impl Application { let close_errs = self.close().await; - // restore cursor - use helix_view::graphics::CursorKind; - self.terminal - .backend_mut() - .show_cursor(CursorKind::Block) - .ok(); - restore_term()?; + self.restore_term()?; for err in close_errs { self.editor.exit_code = 1; diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index ccd016f50..3ca7e044e 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -22,5 +22,6 @@ unicode-segmentation = "1.10" crossterm = { version = "0.26", optional = true } termini = "0.1" serde = { version = "1", "optional" = true, features = ["derive"]} +log = "~0.4" helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-core = { version = "0.6", path = "../helix-core" } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 5305640cb..e81c1e008 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -1,6 +1,11 @@ -use crate::{backend::Backend, buffer::Cell}; +use crate::{backend::Backend, buffer::Cell, terminal::Config}; use crossterm::{ cursor::{Hide, MoveTo, SetCursorStyle, Show}, + event::{ + DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, + EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, + PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, + }, execute, queue, style::{ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, @@ -83,6 +88,69 @@ impl Backend for CrosstermBackend where W: Write, { + fn claim(&mut self, config: Config) -> io::Result<()> { + terminal::enable_raw_mode()?; + execute!( + self.buffer, + terminal::EnterAlternateScreen, + EnableBracketedPaste, + EnableFocusChange + )?; + execute!(self.buffer, terminal::Clear(terminal::ClearType::All))?; + if config.enable_mouse_capture { + execute!(self.buffer, EnableMouseCapture)?; + } + if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { + log::debug!("The enhanced keyboard protocol is supported on this terminal"); + execute!( + self.buffer, + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS + ) + )?; + } else { + log::debug!("The enhanced keyboard protocol is not supported on this terminal"); + } + Ok(()) + } + + fn restore(&mut self, config: Config) -> io::Result<()> { + // reset cursor shape + write!(self.buffer, "\x1B[0 q")?; + if config.enable_mouse_capture { + execute!(self.buffer, DisableMouseCapture)?; + } + if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { + execute!(self.buffer, PopKeyboardEnhancementFlags)?; + } + execute!( + self.buffer, + DisableBracketedPaste, + DisableFocusChange, + terminal::LeaveAlternateScreen + )?; + terminal::disable_raw_mode() + } + + fn force_restore() -> io::Result<()> { + let mut stdout = io::stdout(); + + // reset cursor shape + write!(stdout, "\x1B[0 q")?; + // Ignore errors on disabling, this might trigger on windows if we call + // disable without calling enable previously + let _ = execute!(stdout, DisableMouseCapture); + let _ = execute!(stdout, PopKeyboardEnhancementFlags); + execute!( + stdout, + DisableBracketedPaste, + DisableFocusChange, + terminal::LeaveAlternateScreen + )?; + terminal::disable_raw_mode() + } + fn draw<'a, I>(&mut self, content: I) -> io::Result<()> where I: Iterator, diff --git a/helix-tui/src/backend/mod.rs b/helix-tui/src/backend/mod.rs index c6c11019d..6d7c38942 100644 --- a/helix-tui/src/backend/mod.rs +++ b/helix-tui/src/backend/mod.rs @@ -1,6 +1,6 @@ use std::io; -use crate::buffer::Cell; +use crate::{buffer::Cell, terminal::Config}; use helix_view::graphics::{CursorKind, Rect}; @@ -13,6 +13,9 @@ mod test; pub use self::test::TestBackend; pub trait Backend { + fn claim(&mut self, config: Config) -> Result<(), io::Error>; + fn restore(&mut self, config: Config) -> Result<(), io::Error>; + fn force_restore() -> Result<(), io::Error>; fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error> where I: Iterator; diff --git a/helix-tui/src/backend/test.rs b/helix-tui/src/backend/test.rs index 52474148e..ff133ff3e 100644 --- a/helix-tui/src/backend/test.rs +++ b/helix-tui/src/backend/test.rs @@ -1,6 +1,7 @@ use crate::{ backend::Backend, buffer::{Buffer, Cell}, + terminal::Config, }; use helix_core::unicode::width::UnicodeWidthStr; use helix_view::graphics::{CursorKind, Rect}; @@ -106,6 +107,18 @@ impl TestBackend { } impl Backend for TestBackend { + fn claim(&mut self, _config: Config) -> Result<(), io::Error> { + Ok(()) + } + + fn restore(&mut self, _config: Config) -> Result<(), io::Error> { + Ok(()) + } + + fn force_restore() -> Result<(), io::Error> { + Ok(()) + } + fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error> where I: Iterator, diff --git a/helix-tui/src/terminal.rs b/helix-tui/src/terminal.rs index 22e9232f3..802a8c1d9 100644 --- a/helix-tui/src/terminal.rs +++ b/helix-tui/src/terminal.rs @@ -1,4 +1,5 @@ use crate::{backend::Backend, buffer::Buffer}; +use helix_view::editor::Config as EditorConfig; use helix_view::graphics::{CursorKind, Rect}; use std::io; @@ -16,6 +17,19 @@ pub struct Viewport { resize_behavior: ResizeBehavior, } +#[derive(Debug)] +pub struct Config { + pub enable_mouse_capture: bool, +} + +impl From for Config { + fn from(config: EditorConfig) -> Self { + Self { + enable_mouse_capture: config.mouse, + } + } +} + impl Viewport { /// UNSTABLE pub fn fixed(area: Rect) -> Viewport { @@ -98,6 +112,14 @@ where }) } + pub fn claim(&mut self, config: Config) -> io::Result<()> { + self.backend.claim(config) + } + + pub fn restore(&mut self, config: Config) -> io::Result<()> { + self.backend.restore(config) + } + // /// Get a Frame object which provides a consistent view into the terminal state for rendering. // pub fn get_frame(&mut self) -> Frame { // Frame { From 611701c36290b81c3c51ed30c49245f341a580e8 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 5 Mar 2023 12:13:11 -0600 Subject: [PATCH 64/81] tui: Cache the keyboard enhancement check Wether the host terminal supports keyboard enhancement can be cached for the lifetime of a Helix session. Caching this lookup prevents a potential lockup within crossterm's event reading system where the query for the keyboard enhancement support waits on the next keyboard event, which can happen if the crossterm event stream is checked by `tokio::select!` in another thread. --- Cargo.lock | 1 + helix-tui/Cargo.toml | 1 + helix-tui/src/backend/crossterm.rs | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 231273956..a03f9c921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,6 +1202,7 @@ dependencies = [ "helix-core", "helix-view", "log", + "once_cell", "serde", "termini", "unicode-segmentation", diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 3ca7e044e..8a6d5367d 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -22,6 +22,7 @@ unicode-segmentation = "1.10" crossterm = { version = "0.26", optional = true } termini = "0.1" serde = { version = "1", "optional" = true, features = ["derive"]} +once_cell = "1.17" log = "~0.4" helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-core = { version = "0.6", path = "../helix-core" } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index e81c1e008..7c512ce3e 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -15,6 +15,7 @@ use crossterm::{ Command, }; use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle}; +use once_cell::sync::OnceCell; use std::{ fmt, io::{self, Write}, @@ -57,6 +58,7 @@ impl Capabilities { pub struct CrosstermBackend { buffer: W, capabilities: Capabilities, + supports_keyboard_enhancement_protocol: OnceCell, } impl CrosstermBackend @@ -67,8 +69,16 @@ where CrosstermBackend { buffer, capabilities: Capabilities::from_env_or_default(), + supports_keyboard_enhancement_protocol: OnceCell::new(), } } + + #[inline] + fn supports_keyboard_enhancement_protocol(&self) -> io::Result { + self.supports_keyboard_enhancement_protocol + .get_or_try_init(terminal::supports_keyboard_enhancement) + .copied() + } } impl Write for CrosstermBackend @@ -100,7 +110,7 @@ where if config.enable_mouse_capture { execute!(self.buffer, EnableMouseCapture)?; } - if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { + if self.supports_keyboard_enhancement_protocol()? { log::debug!("The enhanced keyboard protocol is supported on this terminal"); execute!( self.buffer, @@ -121,7 +131,7 @@ where if config.enable_mouse_capture { execute!(self.buffer, DisableMouseCapture)?; } - if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) { + if self.supports_keyboard_enhancement_protocol()? { execute!(self.buffer, PopKeyboardEnhancementFlags)?; } execute!( From 563ac1a3cb7e909b345cf5c892c3bcf39b2e32a4 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 7 Mar 2023 10:33:46 -0600 Subject: [PATCH 65/81] tui: Log keyboard enhancement query time In my testing this takes around 3-4ms in terminals that support the enhanced keyboard protocol (Kitty, WezTerm) and a few hundred microseconds in terminals that don't (st, Alacritty). --- helix-tui/src/backend/crossterm.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 7c512ce3e..fba1f029c 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -76,7 +76,18 @@ where #[inline] fn supports_keyboard_enhancement_protocol(&self) -> io::Result { self.supports_keyboard_enhancement_protocol - .get_or_try_init(terminal::supports_keyboard_enhancement) + .get_or_try_init(|| { + use std::time::Instant; + + let now = Instant::now(); + let support = terminal::supports_keyboard_enhancement(); + log::debug!( + "The keyboard enhancement protocol is {}supported in this terminal (checked in {:?})", + if matches!(support, Ok(true)) { "" } else { "not " }, + Instant::now().duration_since(now) + ); + support + }) .copied() } } @@ -111,7 +122,6 @@ where execute!(self.buffer, EnableMouseCapture)?; } if self.supports_keyboard_enhancement_protocol()? { - log::debug!("The enhanced keyboard protocol is supported on this terminal"); execute!( self.buffer, PushKeyboardEnhancementFlags( @@ -119,8 +129,6 @@ where | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS ) )?; - } else { - log::debug!("The enhanced keyboard protocol is not supported on this terminal"); } Ok(()) } From 170593161cb23edc433d937eb62a9b8fd76bd339 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 7 Mar 2023 19:50:57 -0600 Subject: [PATCH 66/81] LSP: Send replies for malformed and unhandled RPC requests (#6058) Previously we did not respond to malformed or unhandled LSP requests. The JSONRPC spec says that all non-notification requests must have responses: > When a rpc call is made, the Server MUST reply with a Response, > except for in the case of Notifications (Note that Helix is the "Server" in this case. Also from the spec: "The Server is defined as the origin of Response objects and the handler of Request objects.") So this change sends error replies for requests which can't be parsed or handled. Request IDs are also now added to the log messages for unhandled requests. --- helix-term/src/application.rs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 2487a02f8..d56e7c884 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -928,24 +928,32 @@ impl Application { Call::MethodCall(helix_lsp::jsonrpc::MethodCall { method, params, id, .. }) => { - let call = match MethodCall::parse(&method, params) { - Ok(call) => call, + let reply = match MethodCall::parse(&method, params) { Err(helix_lsp::Error::Unhandled) => { - error!("Language Server: Method not found {}", method); - return; + error!( + "Language Server: Method {} not found in request {}", + method, id + ); + Err(helix_lsp::jsonrpc::Error { + code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound, + message: format!("Method not found: {}", method), + data: None, + }) } Err(err) => { log::error!( - "received malformed method call from Language Server: {}: {}", + "Language Server: Received malformed method call {} in request {}: {}", method, + id, err ); - return; + Err(helix_lsp::jsonrpc::Error { + code: helix_lsp::jsonrpc::ErrorCode::ParseError, + message: format!("Malformed method call: {}", method), + data: None, + }) } - }; - - let reply = match call { - MethodCall::WorkDoneProgressCreate(params) => { + Ok(MethodCall::WorkDoneProgressCreate(params)) => { self.lsp_progress.create(server_id, params.token); let editor_view = self @@ -959,7 +967,7 @@ impl Application { Ok(serde_json::Value::Null) } - MethodCall::ApplyWorkspaceEdit(params) => { + Ok(MethodCall::ApplyWorkspaceEdit(params)) => { apply_workspace_edit( &mut self.editor, helix_lsp::OffsetEncoding::Utf8, @@ -972,13 +980,13 @@ impl Application { failed_change: None, })) } - MethodCall::WorkspaceFolders => { + Ok(MethodCall::WorkspaceFolders) => { let language_server = self.editor.language_servers.get_by_id(server_id).unwrap(); Ok(json!(language_server.workspace_folders())) } - MethodCall::WorkspaceConfiguration(params) => { + Ok(MethodCall::WorkspaceConfiguration(params)) => { let result: Vec<_> = params .items .iter() From 6dc017b9e23be4f9331bfac8d10f39cdfa54e7bd Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 7 Mar 2023 19:51:29 -0600 Subject: [PATCH 67/81] Jump to symbol ranges in LSP goto commands (#5986) This follows prior changes like 42ad1a9e: we select the range given by the language server rather than the starting point. --- helix-term/src/commands/lsp.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 3b94c9bd5..d59eebdbe 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -5,7 +5,7 @@ use helix_lsp::{ self, CodeAction, CodeActionOrCommand, CodeActionTriggerKind, DiagnosticSeverity, NumberOrString, }, - util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range}, + util::{diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range}, OffsetEncoding, }; use tui::{ @@ -196,15 +196,15 @@ fn jump_to_location( } } let (view, doc) = current!(editor); - let definition_pos = location.range.start; // TODO: convert inside server - let new_pos = if let Some(new_pos) = lsp_pos_to_pos(doc.text(), definition_pos, offset_encoding) - { - new_pos - } else { - return; - }; - doc.set_selection(view.id, Selection::point(new_pos)); + let new_range = + if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) { + new_range + } else { + log::warn!("lsp position out of bounds - {:?}", location.range); + return; + }; + doc.set_selection(view.id, Selection::single(new_range.anchor, new_range.head)); align_view(doc, view, Align::Center); } From 0c6d25acae86cf87356fd3b3b59a25904226e41b Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 7 Mar 2023 19:51:52 -0600 Subject: [PATCH 68/81] DynamicPicker: Recalculate column widths for new options (#6004) This fixes blank row text in a DynamicPicker which is initially given no options. This can happen for language servers which respond to the workspace symbol request for an empty query with an empty list of symbols, and that behavior is somewhat common since returning all symbols as the spec suggests is very expensive. For empty options, `Picker::new` calculated the widths of each column as 0. We can recalculate the column widths when the new options are set to fix this. This refactor is also a good opportunity to formalize setting new options on a picker: besides setting the new options and calculating column widths we also want to reset the cursor and rescore the options. --- helix-term/src/ui/picker.rs | 62 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 803e2d65b..ec8b1c7f4 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -435,26 +435,6 @@ impl Picker { |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {}, ); - let n = options - .first() - .map(|option| option.format(&editor_data).cells.len()) - .unwrap_or_default(); - let max_lens = options.iter().fold(vec![0; n], |mut acc, option| { - let row = option.format(&editor_data); - // maintain max for each column - for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { - let width = cell.content.width(); - if width > *acc { - *acc = width; - } - } - acc - }); - let widths = max_lens - .into_iter() - .map(|len| Constraint::Length(len as u16)) - .collect(); - let mut picker = Self { options, editor_data, @@ -467,10 +447,12 @@ impl Picker { show_preview: true, callback_fn: Box::new(callback_fn), completion_height: 0, - widths, + widths: Vec::new(), }; - // scoring on empty input: + picker.calculate_column_widths(); + + // scoring on empty input // TODO: just reuse score() picker .matches @@ -486,6 +468,38 @@ impl Picker { picker } + pub fn set_options(&mut self, new_options: Vec) { + self.options = new_options; + self.cursor = 0; + self.force_score(); + self.calculate_column_widths(); + } + + /// Calculate the width constraints using the maximum widths of each column + /// for the current options. + fn calculate_column_widths(&mut self) { + let n = self + .options + .first() + .map(|option| option.format(&self.editor_data).cells.len()) + .unwrap_or_default(); + let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| { + let row = option.format(&self.editor_data); + // maintain max for each column + for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { + let width = cell.content.width(); + if width > *acc { + *acc = width; + } + } + acc + }); + self.widths = max_lens + .into_iter() + .map(|len| Constraint::Length(len as u16)) + .collect(); + } + pub fn score(&mut self) { let pattern = self.prompt.line(); @@ -931,9 +945,7 @@ impl Component for DynamicPicker { Some(overlay) => &mut overlay.content.file_picker.picker, None => return, }; - picker.options = new_options; - picker.cursor = 0; - picker.force_score(); + picker.set_options(new_options); editor.reset_idle_timer(); })); anyhow::Ok(callback) From f4bdbe4674e6d023a63d2e64aec48c8c8598be67 Mon Sep 17 00:00:00 2001 From: Kyle Smith Date: Tue, 7 Mar 2023 20:53:31 -0500 Subject: [PATCH 69/81] Do not add intermediate lines to jumplist with : command. (#5751) * Do not add intermediate lines to jumplist with : command. * Revert jumplist index changes. * Reduce calculations during update cycle. * Use jumplist for undo, set jumplist before preview. * remove some debug logging * Revert "remove some debug logging" This reverts commit 5772c4327e7121c53ea0726a4d7333ae1c413ffb. * Revert "Use jumplist for undo, set jumplist before preview." This reverts commit f73a1b29824feaf16477b9df547fb28d9db81923. * Add last_selection, update implementation. * @pascalkuthe initial feedback * Ensure ":goto 123" keybinding works as expected. * fix clippies, prefer expect() for expect last_selection state --- helix-term/src/commands.rs | 10 ++-- helix-term/src/commands/typed.rs | 80 ++++++++++++++++++++------------ helix-view/src/editor.rs | 11 +++-- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ebdfdfde2..e09a1c5bb 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2828,10 +2828,15 @@ fn push_jump(view: &mut View, doc: &Document) { } fn goto_line(cx: &mut Context) { - goto_line_impl(cx.editor, cx.count) + if cx.count.is_some() { + let (view, doc) = current!(cx.editor); + push_jump(view, doc); + + goto_line_without_jumplist(cx.editor, cx.count); + } } -fn goto_line_impl(editor: &mut Editor, count: Option) { +fn goto_line_without_jumplist(editor: &mut Editor, count: Option) { if let Some(count) = count { let (view, doc) = current!(editor); let text = doc.text().slice(..); @@ -2848,7 +2853,6 @@ fn goto_line_impl(editor: &mut Editor, count: Option) { .clone() .transform(|range| range.put_cursor(text, pos, editor.mode == Mode::Select)); - push_jump(view, doc); doc.set_selection(view.id, selection); } } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 77c143212..7588b708c 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1572,47 +1572,67 @@ fn tutor( Ok(()) } +fn abort_goto_line_number_preview(cx: &mut compositor::Context) { + if let Some(last_selection) = cx.editor.last_selection.take() { + let scrolloff = cx.editor.config().scrolloff; + + let (view, doc) = current!(cx.editor); + doc.set_selection(view.id, last_selection); + view.ensure_cursor_in_view(doc, scrolloff); + } +} + +fn update_goto_line_number_preview( + cx: &mut compositor::Context, + args: &[Cow], +) -> anyhow::Result<()> { + cx.editor.last_selection.get_or_insert_with(|| { + let (view, doc) = current!(cx.editor); + doc.selection(view.id).clone() + }); + + let scrolloff = cx.editor.config().scrolloff; + let line = args[0].parse::()?; + goto_line_without_jumplist(cx.editor, NonZeroUsize::new(line)); + + let (view, doc) = current!(cx.editor); + view.ensure_cursor_in_view(doc, scrolloff); + + Ok(()) +} + pub(super) fn goto_line_number( cx: &mut compositor::Context, args: &[Cow], event: PromptEvent, ) -> anyhow::Result<()> { match event { - PromptEvent::Abort => { - if let Some(line_number) = cx.editor.last_line_number { - goto_line_impl(cx.editor, NonZeroUsize::new(line_number)); - let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, line_number); - cx.editor.last_line_number = None; - } - return Ok(()); - } + PromptEvent::Abort => abort_goto_line_number_preview(cx), PromptEvent::Validate => { ensure!(!args.is_empty(), "Line number required"); - cx.editor.last_line_number = None; - } - PromptEvent::Update => { - if args.is_empty() { - if let Some(line_number) = cx.editor.last_line_number { - // When a user hits backspace and there are no numbers left, - // we can bring them back to their original line - goto_line_impl(cx.editor, NonZeroUsize::new(line_number)); - let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, line_number); - cx.editor.last_line_number = None; - } - return Ok(()); - } + + // If we are invoked directly via a keybinding, Validate is + // sent without any prior Update events. Ensure the cursor + // is moved to the appropriate location. + update_goto_line_number_preview(cx, args)?; + + let last_selection = cx + .editor + .last_selection + .take() + .expect("update_goto_line_number_preview should always set last_selection"); + let (view, doc) = current!(cx.editor); - let text = doc.text().slice(..); - let line = doc.selection(view.id).primary().cursor_line(text); - cx.editor.last_line_number.get_or_insert(line + 1); + view.jumps.push((doc.id(), last_selection)); } + + // When a user hits backspace and there are no numbers left, + // we can bring them back to their original selection. If they + // begin typing numbers again, we'll start a new preview session. + PromptEvent::Update if args.is_empty() => abort_goto_line_number_preview(cx), + PromptEvent::Update => update_goto_line_number_preview(cx, args)?, } - let line = args[0].parse::()?; - goto_line_impl(cx.editor, NonZeroUsize::new(line)); - let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, line); + Ok(()) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 50da3ddea..b96eec8de 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -40,12 +40,12 @@ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; -use helix_core::Position; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig}, Change, }; +use helix_core::{Position, Selection}; use helix_dap as dap; use helix_lsp::lsp; @@ -848,7 +848,12 @@ pub struct Editor { /// The currently applied editor theme. While previewing a theme, the previewed theme /// is set here. pub theme: Theme, - pub last_line_number: Option, + + /// The primary Selection prior to starting a goto_line_number preview. This is + /// restored when the preview is aborted, or added to the jumplist when it is + /// confirmed. + pub last_selection: Option, + pub status_msg: Option<(Cow<'static, str>, Severity)>, pub autoinfo: Option, @@ -964,7 +969,7 @@ impl Editor { syn_loader, theme_loader, last_theme: None, - last_line_number: None, + last_selection: None, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), status_msg: None, From 8dd1ab48997fe774165b3aee5d366833c9660710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Delafargue?= Date: Wed, 8 Mar 2023 03:02:11 +0100 Subject: [PATCH 70/81] Softwrapping improvements (#5893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use max_line_width + 1 during softwrap to account for newline char Helix softwrap implementation always wraps lines so that the newline character doesn't get cut off so he line wraps one chars earlier then in other editors. This is necessary, because newline chars are always selecatble in helix and must never be hidden. However That means that `max_line_width` currently wraps one char earlier than expected. The typical definition of line width does not include the newline character and other helix commands like `:reflow` also don't count the newline character here. This commit makes softwrap use `max_line_width + 1` instead of `max_line_width` to correct the impedance missmatch. * fix typos Co-authored-by: Jonathan Lebon * Add text-width to config.toml * text-width: update setting documentation * rename leftover config item * remove leftover max-line-length occurrences * Make `text-width` optional in editor config When it was only used for `:reflow` it made sense to have a default value set to `80`, but now that soft-wrapping uses this setting, keeping a default set to `80` would make soft-wrapping behave more aggressively. * Allow softwrapping to ignore `text-width` Softwrapping wraps by default to the viewport width or a configured `text-width` (whichever's smaller). In some cases we only want to set `text-width` to use for hard-wrapping and let longer lines flow if they have enough space. This setting allows that. * Revert "Make `text-width` optional in editor config" This reverts commit b247d526d69adf41434b6fd9c4983369c785aa22. * soft-wrap: allow per-language overrides * Update book/src/configuration.md Co-authored-by: Pascal Kuthe * Update book/src/languages.md Co-authored-by: Pascal Kuthe * Update book/src/configuration.md Co-authored-by: Pascal Kuthe --------- Co-authored-by: Pascal Kuthe Co-authored-by: Jonathan Lebon Co-authored-by: Alex Boehm Co-authored-by: Blaž Hrastnik --- book/src/configuration.md | 14 ++++---- book/src/languages.md | 2 +- helix-core/src/syntax.rs | 30 ++++++++++++++++- helix-core/src/wrap.rs | 4 +-- helix-term/src/commands/typed.rs | 20 +++++------- helix-view/src/document.rs | 55 ++++++++++++++++++++++++++------ helix-view/src/editor.rs | 42 +++--------------------- languages.toml | 2 +- 8 files changed, 99 insertions(+), 70 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 5410024ba..aebf5ff0f 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -55,6 +55,7 @@ signal to the Helix process on Unix operating systems, such as by using the comm | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` | | `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` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | +| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap_at_text_width` is set | `80` | ### `[editor.statusline]` Section @@ -314,12 +315,13 @@ Currently unused Options for soft wrapping lines that exceed the view width: -| Key | Description | Default | -| --- | --- | --- | -| `enable` | Whether soft wrapping is enabled | `false` | -| `max-wrap` | Maximum free space left at the end of the line | `20` | -| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line | `40` | -| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` | +| Key | Description | Default | +| --- | --- | --- | +| `enable` | Whether soft wrapping is enabled. | `false` | +| `max-wrap` | Maximum free space left at the end of the line. | `20` | +| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` | +| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` | +| `wrap-at-text-width` | Soft wrap at `text-width` instead of using the full viewport size. | `false` | Example: diff --git a/book/src/languages.md b/book/src/languages.md index 8a8f3bb61..5ed69505d 100644 --- a/book/src/languages.md +++ b/book/src/languages.md @@ -63,7 +63,7 @@ These configuration keys are available: | `config` | Language Server configuration | | `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) | | `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 | -| `max-line-length` | Maximum line length. Used for the `:reflow` command and soft-wrapping | +| `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` | ### File-type detection and the `file-types` key diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 1b6c1b1da..941e3ba7b 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -82,7 +82,8 @@ pub struct LanguageConfiguration { pub shebangs: Vec, // interpreter(s) associated with language pub roots: Vec, // these indicate project roots <.git, Cargo.toml> pub comment_token: Option, - pub max_line_length: Option, + pub text_width: Option, + pub soft_wrap: Option, #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")] pub config: Option, @@ -546,6 +547,33 @@ impl LanguageConfiguration { .ok() } } +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +pub struct SoftWrap { + /// Soft wrap lines that exceed viewport width. Default to off + pub enable: Option, + /// Maximum space left free at the end of the line. + /// This space is used to wrap text at word boundaries. If that is not possible within this limit + /// the word is simply split at the end of the line. + /// + /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views. + /// + /// Default to 20 + pub max_wrap: Option, + /// Maximum number of indentation that can be carried over from the previous line when softwrapping. + /// If a line is indented further then this limit it is rendered at the start of the viewport instead. + /// + /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views. + /// + /// Default to 40 + pub max_indent_retain: Option, + /// Indicator placed at the beginning of softwrapped lines + /// + /// Defaults to ↪ + pub wrap_indicator: Option, + /// Softwrap at `text_width` instead of viewport width if it is shorter + pub wrap_at_text_width: Option, +} // Expose loader as Lazy<> global since it's always static? diff --git a/helix-core/src/wrap.rs b/helix-core/src/wrap.rs index eabc47d47..2ba8d173e 100644 --- a/helix-core/src/wrap.rs +++ b/helix-core/src/wrap.rs @@ -2,6 +2,6 @@ use smartstring::{LazyCompact, SmartString}; /// Given a slice of text, return the text re-wrapped to fit it /// within the given width. -pub fn reflow_hard_wrap(text: &str, max_line_len: usize) -> SmartString { - textwrap::refill(text, max_line_len).into() +pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString { + textwrap::refill(text, text_width).into() } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 7588b708c..5ea611086 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1840,30 +1840,26 @@ fn reflow( } let scrolloff = cx.editor.config().scrolloff; + let cfg_text_width: usize = cx.editor.config().text_width; let (view, doc) = current!(cx.editor); - const DEFAULT_MAX_LEN: usize = 79; - - // Find the max line length by checking the following sources in order: + // Find the text_width by checking the following sources in order: // - The passed argument in `args` - // - The configured max_line_len for this language in languages.toml - // - The const default we set above - let max_line_len: usize = args + // - The configured text-width for this language in languages.toml + // - The configured text-width in the config.toml + let text_width: usize = args .get(0) .map(|num| num.parse::()) .transpose()? - .or_else(|| { - doc.language_config() - .and_then(|config| config.max_line_length) - }) - .unwrap_or(DEFAULT_MAX_LEN); + .or_else(|| doc.language_config().and_then(|config| config.text_width)) + .unwrap_or(cfg_text_width); let rope = doc.text(); let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(rope, selection, |range| { let fragment = range.fragment(rope.slice(..)); - let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, max_line_len); + let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, text_width); (range.from(), range.to(), Some(reflowed_text)) }); diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 579c67250..4d3586f1a 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1238,24 +1238,61 @@ impl Document { } pub fn text_format(&self, mut viewport_width: u16, theme: Option<&Theme>) -> TextFormat { - if let Some(max_line_len) = self + let config = self.config.load(); + let text_width = self .language_config() - .and_then(|config| config.max_line_length) - { - viewport_width = viewport_width.min(max_line_len as u16) + .and_then(|config| config.text_width) + .unwrap_or(config.text_width); + let soft_wrap_at_text_width = self + .language_config() + .and_then(|config| { + config + .soft_wrap + .as_ref() + .and_then(|soft_wrap| soft_wrap.wrap_at_text_width) + }) + .or(config.soft_wrap.wrap_at_text_width) + .unwrap_or(false); + if soft_wrap_at_text_width { + // We increase max_line_len by 1 because softwrap considers the newline character + // as part of the line length while the "typical" expectation is that this is not the case. + // In particular other commands like :reflow do not count the line terminator. + // This is technically inconsistent for the last line as that line never has a line terminator + // but having the last visual line exceed the width by 1 seems like a rare edge case. + viewport_width = viewport_width.min(text_width as u16 + 1) } let config = self.config.load(); - let soft_wrap = &config.soft_wrap; + let editor_soft_wrap = &config.soft_wrap; + let language_soft_wrap = self + .language + .as_ref() + .and_then(|config| config.soft_wrap.as_ref()); + let enable_soft_wrap = language_soft_wrap + .and_then(|soft_wrap| soft_wrap.enable) + .or(editor_soft_wrap.enable) + .unwrap_or(false); + let max_wrap = language_soft_wrap + .and_then(|soft_wrap| soft_wrap.max_wrap) + .or(config.soft_wrap.max_wrap) + .unwrap_or(20); + let max_indent_retain = language_soft_wrap + .and_then(|soft_wrap| soft_wrap.max_indent_retain) + .or(editor_soft_wrap.max_indent_retain) + .unwrap_or(40); + let wrap_indicator = language_soft_wrap + .and_then(|soft_wrap| soft_wrap.wrap_indicator.clone()) + .or_else(|| config.soft_wrap.wrap_indicator.clone()) + .unwrap_or_else(|| "↪ ".into()); let tab_width = self.tab_width() as u16; TextFormat { - soft_wrap: soft_wrap.enable && viewport_width > 10, + soft_wrap: enable_soft_wrap && viewport_width > 10, tab_width, - max_wrap: soft_wrap.max_wrap.min(viewport_width / 4), - max_indent_retain: soft_wrap.max_indent_retain.min(viewport_width * 2 / 5), + max_wrap: max_wrap.min(viewport_width / 4), + max_indent_retain: max_indent_retain.min(viewport_width * 2 / 5), // avoid spinning forever when the window manager // sets the size to something tiny viewport_width, - wrap_indicator: soft_wrap.wrap_indicator.clone().into_boxed_str(), + wrap_indicator: wrap_indicator.into_boxed_str(), wrap_indicator_highlight: theme .and_then(|theme| theme.find_scope_index("ui.virtual.wrap")) .map(Highlight), diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b96eec8de..5b819b333 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -42,7 +42,7 @@ pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; use helix_core::{ auto_pairs::AutoPairs, - syntax::{self, AutoPairConfig}, + syntax::{self, AutoPairConfig, SoftWrap}, Change, }; use helix_core::{Position, Selection}; @@ -241,6 +241,8 @@ pub struct Config { pub auto_format: bool, /// Automatic save on focus lost. Defaults to false. pub auto_save: bool, + /// Set a global text_width + pub text_width: usize, /// Time in milliseconds since last keypress before idle timers trigger. /// Used for autocompletion, set to 0 for instant. Defaults to 400ms. #[serde( @@ -276,43 +278,6 @@ pub struct Config { pub soft_wrap: SoftWrap, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] -pub struct SoftWrap { - /// Soft wrap lines that exceed viewport width. Default to off - pub enable: bool, - /// Maximum space left free at the end of the line. - /// This space is used to wrap text at word boundaries. If that is not possible within this limit - /// the word is simply split at the end of the line. - /// - /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views. - /// - /// Default to 20 - pub max_wrap: u16, - /// Maximum number of indentation that can be carried over from the previous line when softwrapping. - /// If a line is indented further then this limit it is rendered at the start of the viewport instead. - /// - /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views. - /// - /// Default to 40 - pub max_indent_retain: u16, - /// Indicator placed at the beginning of softwrapped lines - /// - /// Defaults to ↪ - pub wrap_indicator: String, -} - -impl Default for SoftWrap { - fn default() -> Self { - SoftWrap { - enable: false, - max_wrap: 20, - max_indent_retain: 40, - wrap_indicator: "↪ ".into(), - } - } -} - #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case", deny_unknown_fields)] pub struct TerminalConfig { @@ -772,6 +737,7 @@ impl Default for Config { indent_guides: IndentGuidesConfig::default(), color_modes: false, soft_wrap: SoftWrap::default(), + text_width: 80, } } } diff --git a/languages.toml b/languages.toml index 0fd37a006..8697f9fcc 100644 --- a/languages.toml +++ b/languages.toml @@ -1104,7 +1104,7 @@ file-types = ["COMMIT_EDITMSG"] comment-token = "#" indent = { tab-width = 2, unit = " " } rulers = [50, 72] -max-line-length = 72 +text-width = 72 [[grammar]] name = "git-commit" From 3849ca4c2abdb1c6076d391eab4c3f43dda30234 Mon Sep 17 00:00:00 2001 From: Kyle Smith Date: Tue, 7 Mar 2023 21:10:55 -0500 Subject: [PATCH 71/81] Add test cases for existing pair matching logic. (#6027) * Add test cases for existing pair matching logic. * fix clippy --- helix-core/src/surround.rs | 145 +++++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 29 deletions(-) diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs index 64d48c13a..f430aee8a 100644 --- a/helix-core/src/surround.rs +++ b/helix-core/src/surround.rs @@ -275,51 +275,138 @@ mod test { #[test] fn test_get_surround_pos() { - let doc = Rope::from("(some) (chars)\n(newline)"); - let slice = doc.slice(..); - let selection = Selection::new( - SmallVec::from_slice(&[Range::point(2), Range::point(9), Range::point(20)]), - 0, - ); + #[rustfmt::skip] + let (doc, selection, expectations) = + rope_with_selections_and_expectations( + "(some) (chars)\n(newline)", + "_ ^ _ _ ^ _\n_ ^ _" + ); - // cursor on s[o]me, c[h]ars, newl[i]ne assert_eq!( - get_surround_pos(slice, &selection, Some('('), 1) - .unwrap() - .as_slice(), - &[0, 5, 7, 13, 15, 23] + get_surround_pos(doc.slice(..), &selection, Some('('), 1).unwrap(), + expectations ); } #[test] - fn test_get_surround_pos_bail() { - let doc = Rope::from("[some]\n(chars)xx\n(newline)"); - let slice = doc.slice(..); + fn test_get_surround_pos_bail_different_surround_chars() { + #[rustfmt::skip] + let (doc, selection, _) = + rope_with_selections_and_expectations( + "[some]\n(chars)xx\n(newline)", + " ^ \n ^ \n " + ); - let selection = - Selection::new(SmallVec::from_slice(&[Range::point(2), Range::point(9)]), 0); - // cursor on s[o]me, c[h]ars assert_eq!( - get_surround_pos(slice, &selection, Some('('), 1), - Err(Error::PairNotFound) // different surround chars + get_surround_pos(doc.slice(..), &selection, Some('('), 1), + Err(Error::PairNotFound) ); + } + + #[test] + fn test_get_surround_pos_bail_overlapping_surround_chars() { + #[rustfmt::skip] + let (doc, selection, _) = + rope_with_selections_and_expectations( + "[some]\n(chars)xx\n(newline)", + " \n ^ \n ^ " + ); - let selection = Selection::new( - SmallVec::from_slice(&[Range::point(14), Range::point(24)]), - 0, - ); - // cursor on [x]x, newli[n]e assert_eq!( - get_surround_pos(slice, &selection, Some('('), 1), + get_surround_pos(doc.slice(..), &selection, Some('('), 1), Err(Error::PairNotFound) // overlapping surround chars ); + } + + #[test] + fn test_get_surround_pos_bail_cursor_overlap() { + #[rustfmt::skip] + let (doc, selection, _) = + rope_with_selections_and_expectations( + "[some]\n(chars)xx\n(newline)", + " ^^ \n \n " + ); - let selection = - Selection::new(SmallVec::from_slice(&[Range::point(2), Range::point(3)]), 0); - // cursor on s[o][m]e assert_eq!( - get_surround_pos(slice, &selection, Some('['), 1), + get_surround_pos(doc.slice(..), &selection, Some('['), 1), Err(Error::CursorOverlap) ); } + + #[test] + fn test_find_nth_pairs_pos_quote_success() { + #[rustfmt::skip] + let (doc, selection, expectations) = + rope_with_selections_and_expectations( + "some 'quoted text' on this 'line'\n'and this one'", + " _ ^ _ \n " + ); + + assert_eq!(2, expectations.len()); + assert_eq!( + find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1) + .expect("find should succeed"), + (expectations[0], expectations[1]) + ) + } + + #[test] + fn test_find_nth_pairs_pos_nested_quote_success() { + #[rustfmt::skip] + let (doc, selection, expectations) = + rope_with_selections_and_expectations( + "some 'nested 'quoted' text' on this 'line'\n'and this one'", + " _ ^ _ \n " + ); + + assert_eq!(2, expectations.len()); + assert_eq!( + find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 2) + .expect("find should succeed"), + (expectations[0], expectations[1]) + ) + } + + #[test] + fn test_find_nth_pairs_pos_inside_quote_ambiguous() { + #[rustfmt::skip] + let (doc, selection, _) = + rope_with_selections_and_expectations( + "some 'nested 'quoted' text' on this 'line'\n'and this one'", + " ^ \n " + ); + + assert_eq!( + find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1), + Err(Error::CursorOnAmbiguousPair) + ) + } + + // Create a Rope and a matching Selection using a specification language. + // ^ is a single-point selection. + // _ is an expected index. These are returned as a Vec for use in assertions. + fn rope_with_selections_and_expectations( + text: &str, + spec: &str, + ) -> (Rope, Selection, Vec) { + if text.len() != spec.len() { + panic!("specification must match text length -- are newlines aligned?"); + } + + let rope = Rope::from(text); + + let selections: SmallVec<[Range; 1]> = spec + .match_indices('^') + .into_iter() + .map(|(i, _)| Range::point(i)) + .collect(); + + let expectations: Vec = spec + .match_indices('_') + .into_iter() + .map(|(i, _)| i) + .collect(); + + (rope, Selection::new(selections, 0), expectations) + } } From 44ff8a1df1f69733bc40ea866674fcfd7e0cdded Mon Sep 17 00:00:00 2001 From: Kyle Smith Date: Tue, 7 Mar 2023 21:11:43 -0500 Subject: [PATCH 72/81] LSP: Support textDocument/prepareRename (#6103) * LSP: Support textDocument/prepareRename 'textDocument/prepareRename' can be used by the client to ask the server the range of the symbol under the cursor which would be changed by a subsequent call to 'textDocument/rename' with that position. We can use this information to fill the prompt with an accurate prefill which can improve the UX for renaming symbols when the symbol doesn't align with the "word" textobject. (We currently use the "word" textobject as a default value for the prompt.) Co-authored-by: Michael Davis * clippy fixes * rustfmt * Update helix-term/src/commands/lsp.rs Co-authored-by: Michael Davis * Update helix-term/src/commands/lsp.rs Co-authored-by: Michael Davis * fix clippy from suggestions * Update helix-term/src/commands/lsp.rs Co-authored-by: Michael Davis --------- Co-authored-by: Michael Davis --- helix-lsp/src/client.rs | 25 +++++- helix-term/src/commands/lsp.rs | 136 ++++++++++++++++++++++++--------- 2 files changed, 125 insertions(+), 36 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 95f3ea348..9fa118fbd 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -359,7 +359,7 @@ impl Client { }), rename: Some(lsp::RenameClientCapabilities { dynamic_registration: Some(false), - prepare_support: Some(false), + prepare_support: Some(true), prepare_support_default_behavior: None, honors_change_annotations: Some(false), }), @@ -1034,6 +1034,29 @@ impl Client { Some(self.call::(params)) } + pub fn prepare_rename( + &self, + text_document: lsp::TextDocumentIdentifier, + position: lsp::Position, + ) -> Option>> { + let capabilities = self.capabilities.get().unwrap(); + + match capabilities.rename_provider { + Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + .. + })) => (), + _ => return None, + } + + let params = lsp::TextDocumentPositionParams { + text_document, + position, + }; + + Some(self.call::(params)) + } + // empty string to get all symbols pub fn workspace_symbols(&self, query: String) -> Option>> { let capabilities = self.capabilities.get().unwrap(); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index d59eebdbe..08519366b 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -1232,49 +1232,115 @@ pub fn hover(cx: &mut Context) { } pub fn rename_symbol(cx: &mut Context) { - let (view, doc) = current_ref!(cx.editor); - let text = doc.text().slice(..); - let primary_selection = doc.selection(view.id).primary(); - let prefill = if primary_selection.len() > 1 { - primary_selection - } else { - use helix_core::textobject::{textobject_word, TextObject}; - textobject_word(text, primary_selection, TextObject::Inside, 1, false) + fn get_prefill_from_word_boundary(editor: &Editor) -> String { + let (view, doc) = current_ref!(editor); + let text = doc.text().slice(..); + let primary_selection = doc.selection(view.id).primary(); + if primary_selection.len() > 1 { + primary_selection + } else { + use helix_core::textobject::{textobject_word, TextObject}; + textobject_word(text, primary_selection, TextObject::Inside, 1, false) + } + .fragment(text) + .into() } - .fragment(text) - .into(); - ui::prompt_with_input( - cx, - "rename-to:".into(), - prefill, - None, - ui::completers::none, - move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { - if event != PromptEvent::Validate { - return; + + fn get_prefill_from_lsp_response( + editor: &Editor, + offset_encoding: OffsetEncoding, + response: Option, + ) -> Result { + match response { + Some(lsp::PrepareRenameResponse::Range(range)) => { + let text = doc!(editor).text(); + + Ok(lsp_range_to_range(text, range, offset_encoding) + .ok_or("lsp sent invalid selection range for rename")? + .fragment(text.slice(..)) + .into()) + } + Some(lsp::PrepareRenameResponse::RangeWithPlaceholder { placeholder, .. }) => { + Ok(placeholder) + } + Some(lsp::PrepareRenameResponse::DefaultBehavior { .. }) => { + Ok(get_prefill_from_word_boundary(editor)) } + None => Err("lsp did not respond to prepare rename request"), + } + } - let (view, doc) = current!(cx.editor); - let language_server = language_server!(cx.editor, doc); - let offset_encoding = language_server.offset_encoding(); + fn create_rename_prompt(editor: &Editor, prefill: String) -> Box { + let prompt = ui::Prompt::new( + "rename-to:".into(), + None, + ui::completers::none, + move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { + if event != PromptEvent::Validate { + return; + } - let pos = doc.position(view.id, offset_encoding); + let (view, doc) = current!(cx.editor); + let language_server = language_server!(cx.editor, doc); + let offset_encoding = language_server.offset_encoding(); + + let pos = doc.position(view.id, offset_encoding); + + let future = + match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) { + Some(future) => future, + None => { + cx.editor + .set_error("Language server does not support symbol renaming"); + return; + } + }; + match block_on(future) { + Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits), + Err(err) => cx.editor.set_error(err.to_string()), + } + }, + ) + .with_line(prefill, editor); - let future = - match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) { - Some(future) => future, - None => { - cx.editor - .set_error("Language server does not support symbol renaming"); + Box::new(prompt) + } + + let (view, doc) = current!(cx.editor); + let language_server = language_server!(cx.editor, doc); + let offset_encoding = language_server.offset_encoding(); + + let pos = doc.position(view.id, offset_encoding); + + match language_server.prepare_rename(doc.identifier(), pos) { + // Language server supports textDocument/prepareRename, use it. + Some(future) => cx.callback( + future, + move |editor, compositor, response: Option| { + let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response) + { + Ok(p) => p, + Err(e) => { + editor.set_error(e); return; } }; - match block_on(future) { - Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits), - Err(err) => cx.editor.set_error(err.to_string()), - } - }, - ); + + let prompt = create_rename_prompt(editor, prefill); + + compositor.push(prompt); + }, + ), + // Language server does not support textDocument/prepareRename, fall back + // to word boundary selection. + None => { + let prefill = get_prefill_from_word_boundary(cx.editor); + + let prompt = create_rename_prompt(cx.editor, prefill); + + cx.push_layer(prompt); + } + }; } pub fn select_references_to_symbol_under_cursor(cx: &mut Context) { From 34be71fb50738a7e9d9e5ee5090680a0d84a321c Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 8 Mar 2023 15:06:34 +0100 Subject: [PATCH 73/81] Theme - auy_evolve: Up bufferline fg brightness (#6225) Currently a bit hard to discern inactive and active buffers in a brighter environment. --- runtime/themes/ayu_evolve.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime/themes/ayu_evolve.toml b/runtime/themes/ayu_evolve.toml index 2c545beb1..b57235a6a 100644 --- a/runtime/themes/ayu_evolve.toml +++ b/runtime/themes/ayu_evolve.toml @@ -25,6 +25,8 @@ inherits = 'ayu_dark' "ui.cursor.primary.select" = { fg = "dark_gray", bg = "magenta" } "ui.cursor.primary.insert" = { fg = "dark_gray", bg = "green" } "ui.text.inactive" = "gray" +"ui.bufferline" = { fg = "light_gray", bg = "background" } +"ui.bufferline.active" = { fg = "light_gray", bg = "dark_gray" } [palette] background = '#020202' From bc23e548050570d297f263c5f0204af1d2210830 Mon Sep 17 00:00:00 2001 From: workingj <19818596+workingj@users.noreply.github.com> Date: Thu, 9 Mar 2023 03:17:45 +0100 Subject: [PATCH 74/81] feat(theme): Update pop-dark statusline (#6227) * update pop-theme for color-modes * fixed ui.statusline.select not worrking * adjustments for nicer statusline visuals * added status line color --- runtime/themes/pop-dark.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/themes/pop-dark.toml b/runtime/themes/pop-dark.toml index d2010ed62..17621d2af 100644 --- a/runtime/themes/pop-dark.toml +++ b/runtime/themes/pop-dark.toml @@ -28,6 +28,7 @@ error = { fg = 'brownD', bg = 'redE', modifiers = ['bold'] } 'ui.linenr' = { bg = 'brownU', fg = 'greyL' } 'ui.linenr.selected' = { fg = 'orangeH' } 'ui.cursorline' = { bg = 'brownH' } +'ui.statusline' = { fg = 'greyT', bg = 'brownU' } 'ui.statusline.inactive' = { fg = 'greyT', bg = 'brownN' } 'ui.statusline.normal' = { fg = 'greyT', bg = 'brownD', modifiers = ['bold'] } 'ui.statusline.select' = { bg = 'blueL', fg = 'brownD', modifiers = ['bold'] } From 4300a3ad058ea383a59a0a90f31a597eb264a7d4 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Mar 2023 19:08:47 +0100 Subject: [PATCH 75/81] create savepoint before requesting completion --- helix-term/src/commands.rs | 1 + helix-term/src/ui/editor.rs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e09a1c5bb..55ca875dc 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4181,6 +4181,7 @@ pub fn completion(cx: &mut Context) { iter.reverse(); let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count(); let start_offset = cursor.saturating_sub(offset); + doc.savepoint(); cx.callback( future, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 59f371bda..2ea1b7147 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -957,9 +957,6 @@ impl EditorView { return; } - // Immediately initialize a savepoint - doc_mut!(editor).savepoint(); - editor.last_completion = None; self.last_insert.1.push(InsertEvent::TriggerCompletion); From 2588fa3710921683c16a84ffd91103a0823a033b Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Mar 2023 19:10:47 +0100 Subject: [PATCH 76/81] save selection before completion savepoint Currently, the selection is not saved/restored when completion checkpoints are applied. This is usually fine because undoing changes usually restores maps selections back in insert mode. But this is not always the case and especially problematic in the presence of multi-cursor completions (since completions are applied relative to the selection/cursor) and snippets (which can change the selection) --- helix-term/src/commands.rs | 2 +- helix-term/src/ui/completion.rs | 2 +- helix-term/src/ui/editor.rs | 4 ++-- helix-view/src/document.rs | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 55ca875dc..bc0e8ebea 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4181,7 +4181,7 @@ pub fn completion(cx: &mut Context) { iter.reverse(); let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count(); let start_offset = cursor.saturating_sub(offset); - doc.savepoint(); + doc.savepoint(&view); cx.callback( future, diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 85931fe32..179a8cf8c 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -235,7 +235,7 @@ impl Completion { ); // initialize a savepoint - doc.savepoint(); + doc.savepoint(&view); doc.apply(&transaction, view.id); editor.last_completion = Some(CompleteAction { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2ea1b7147..62f04cc9d 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -914,8 +914,8 @@ impl EditorView { doc.apply(&tx, view.id); } InsertEvent::TriggerCompletion => { - let (_, doc) = current!(cxt.editor); - doc.savepoint(); + let (view, doc) = current!(cxt.editor); + doc.savepoint(view); } } } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 4d3586f1a..13ffe7948 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -941,7 +941,8 @@ impl Document { } pub fn savepoint(&mut self) { - self.savepoint = Some(Transaction::new(self.text())); + self.savepoint = + Some(Transaction::new(self.text()).with_selection(self.selection(view.id).clone())); } pub fn restore(&mut self, view: &mut View) { From e8898fd9a8ac8120827fb2d6f4752b3cb2431a62 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Mar 2023 21:56:55 +0100 Subject: [PATCH 77/81] store multiple snapshots on the document at once Fixing autocomplete required moving the document savepoint before the asynchronous completion request. However, this in turn causes new bugs: If the completion popup is open, the savepoint is restored when the popup closes (or another entry is selected). However, at that point a new completion request might already have been created which would have replaced the new savepoint (therefore leading to incorrectly applied complies). This commit fixes that bug by allowing in arbitrary number of savepoints to be tracked on the document. The savepoints are reference counted and therefore remain valid as long as any reference to them remains. Weak reference are stored on the document and any reference that can not be upgraded anymore (hence no strong reference remain) are automatically discarded. --- Cargo.lock | 1 + helix-term/src/commands.rs | 3 +- helix-term/src/ui/completion.rs | 8 ++-- helix-term/src/ui/editor.rs | 14 +++++-- helix-view/Cargo.toml | 1 + helix-view/src/document.rs | 69 ++++++++++++++++++++++++++------- 6 files changed, 72 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a03f9c921..a1a9eae4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1241,6 +1241,7 @@ dependencies = [ "libc", "log", "once_cell", + "parking_lot", "serde", "serde_json", "slotmap", diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index bc0e8ebea..01673c895 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4181,7 +4181,7 @@ pub fn completion(cx: &mut Context) { iter.reverse(); let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count(); let start_offset = cursor.saturating_sub(offset); - doc.savepoint(&view); + let savepoint = doc.savepoint(view); cx.callback( future, @@ -4209,6 +4209,7 @@ pub fn completion(cx: &mut Context) { let ui = compositor.find::().unwrap(); ui.set_completion( editor, + savepoint, items, offset_encoding, start_offset, diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 179a8cf8c..ef88938fe 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,12 +1,13 @@ use crate::compositor::{Component, Context, Event, EventResult}; use helix_view::{ + document::SavePoint, editor::CompleteAction, theme::{Modifier, Style}, ViewId, }; use tui::{buffer::Buffer as Surface, text::Span}; -use std::borrow::Cow; +use std::{borrow::Cow, sync::Arc}; use helix_core::{Change, Transaction}; use helix_view::{graphics::Rect, Document, Editor}; @@ -101,6 +102,7 @@ impl Completion { pub fn new( editor: &Editor, + savepoint: Arc, mut items: Vec, offset_encoding: helix_lsp::OffsetEncoding, start_offset: usize, @@ -213,11 +215,10 @@ impl Completion { let (view, doc) = current!(editor); // if more text was entered, remove it - doc.restore(view); + doc.restore(view, &savepoint); match event { PromptEvent::Abort => { - doc.restore(view); editor.last_completion = None; } PromptEvent::Update => { @@ -235,7 +236,6 @@ impl Completion { ); // initialize a savepoint - doc.savepoint(&view); doc.apply(&transaction, view.id); editor.last_completion = Some(CompleteAction { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 62f04cc9d..c81ae635a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -940,17 +940,25 @@ impl EditorView { } } + #[allow(clippy::too_many_arguments)] pub fn set_completion( &mut self, editor: &mut Editor, + savepoint: Arc, items: Vec, offset_encoding: helix_lsp::OffsetEncoding, start_offset: usize, trigger_offset: usize, size: Rect, ) { - let mut completion = - Completion::new(editor, items, offset_encoding, start_offset, trigger_offset); + let mut completion = Completion::new( + editor, + savepoint, + items, + offset_encoding, + start_offset, + trigger_offset, + ); if completion.is_empty() { // skip if we got no completion results @@ -969,8 +977,6 @@ impl EditorView { self.completion = None; // Clear any savepoints - let doc = doc_mut!(editor); - doc.savepoint = None; editor.clear_idle_timer(); // don't retrigger } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 54b679ade..e3f98a8d3 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -43,6 +43,7 @@ toml = "0.7" log = "~0.4" which = "4.4" +parking_lot = "0.12.1" [target.'cfg(windows)'.dependencies] diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 13ffe7948..db12fb92b 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -9,6 +9,7 @@ use helix_core::text_annotations::TextAnnotations; use helix_core::Range; use helix_vcs::{DiffHandle, DiffProviderRegistry}; +use ::parking_lot::Mutex; use serde::de::{self, Deserialize, Deserializer}; use serde::Serialize; use std::borrow::Cow; @@ -18,7 +19,7 @@ use std::fmt::Display; use std::future::Future; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use std::time::SystemTime; use helix_core::{ @@ -105,6 +106,13 @@ pub struct DocumentSavedEvent { pub type DocumentSavedEventResult = Result; pub type DocumentSavedEventFuture = BoxFuture<'static, DocumentSavedEventResult>; +#[derive(Debug)] +pub struct SavePoint { + /// The view this savepoint is associated with + pub view: ViewId, + revert: Mutex, +} + pub struct Document { pub(crate) id: DocumentId, text: Rope, @@ -136,7 +144,7 @@ pub struct Document { pub history: Cell, pub config: Arc>, - pub savepoint: Option, + savepoints: Vec>, // Last time we wrote to the file. This will carry the time the file was last opened if there // were no saves. @@ -389,7 +397,7 @@ impl Document { diagnostics: Vec::new(), version: 0, history: Cell::new(History::default()), - savepoint: None, + savepoints: Vec::new(), last_saved_time: SystemTime::now(), last_saved_revision: 0, modified_since_accessed: false, @@ -846,11 +854,18 @@ impl Document { } // generate revert to savepoint - if self.savepoint.is_some() { - take_with(&mut self.savepoint, |prev_revert| { - let revert = transaction.invert(&old_doc); - Some(revert.compose(prev_revert.unwrap())) - }); + if !self.savepoints.is_empty() { + let revert = transaction.invert(&old_doc); + self.savepoints + .retain_mut(|save_point| match save_point.upgrade() { + Some(savepoint) => { + let mut revert_to_savepoint = savepoint.revert.lock(); + *revert_to_savepoint = + revert.clone().compose(mem::take(&mut revert_to_savepoint)); + true + } + None => false, + }) } // update tree-sitter syntax tree @@ -940,15 +955,39 @@ impl Document { self.undo_redo_impl(view, false) } - pub fn savepoint(&mut self) { - self.savepoint = - Some(Transaction::new(self.text()).with_selection(self.selection(view.id).clone())); + /// Creates a reference counted snapshot (called savpepoint) of the document. + /// + /// The snapshot will remain valid (and updated) idenfinitly as long as ereferences to it exist. + /// Restoring the snapshot will restore the selection and the contents of the document to + /// the state it had when this function was called. + pub fn savepoint(&mut self, view: &View) -> Arc { + let revert = Transaction::new(self.text()).with_selection(self.selection(view.id).clone()); + let savepoint = Arc::new(SavePoint { + view: view.id, + revert: Mutex::new(revert), + }); + self.savepoints.push(Arc::downgrade(&savepoint)); + savepoint } - pub fn restore(&mut self, view: &mut View) { - if let Some(revert) = self.savepoint.take() { - self.apply(&revert, view.id); - } + pub fn restore(&mut self, view: &mut View, savepoint: &SavePoint) { + assert_eq!( + savepoint.view, view.id, + "Savepoint must not be used with a different view!" + ); + // search and remove savepoint using a ptr comparison + // this avoids a deadlock as we need to lock the mutex + let savepoint_idx = self + .savepoints + .iter() + .position(|savepoint_ref| savepoint_ref.as_ptr() == savepoint as *const _) + .expect("Savepoint must belong to this document"); + + let savepoint_ref = self.savepoints.remove(savepoint_idx); + let mut revert = savepoint.revert.lock(); + self.apply(&revert, view.id); + *revert = Transaction::new(self.text()).with_selection(self.selection(view.id).clone()); + self.savepoints.push(savepoint_ref) } fn earlier_later_impl(&mut self, view: &mut View, uk: UndoKind, earlier: bool) -> bool { From 8cb7cdfd7a01b9cb50b9142e9a5d133bd1e23256 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Mar 2023 23:12:50 +0100 Subject: [PATCH 78/81] discard stale completion requests Completion requests are computed asynchronously to avoid common micro freezes while editing. This means that once a completion request completes, the state of the editor might have changed. Currently, there is a check to ensure we are still in insert mode. However, we also need to ensure that the view and document hasn't changed to avoid accidentally using a savepoint with the wrong view/document. Furthermore, the editor might request a new completion while the previous completion request hasn't complemented yet. This can lead to weird flickering or an outdated completion request replacing a newer completion that has already completed (the LSP server is not required to process completion requests in order). This change also needed to ensure determinism/linear ordering so that completion popup always correspond to the last completion request. --- helix-term/src/commands.rs | 31 +++++++++++++++++++++++++++++-- helix-term/src/ui/editor.rs | 1 + helix-view/src/editor.rs | 11 ++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 01673c895..574e1edf6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5,6 +5,7 @@ pub(crate) mod typed; pub use dap::*; use helix_vcs::Hunk; pub use lsp::*; +use tokio::sync::oneshot; use tui::widgets::Row; pub use typed::*; @@ -4171,6 +4172,24 @@ pub fn completion(cx: &mut Context) { None => return, }; + // setup a chanel that allows the request to be canceled + let (tx, rx) = oneshot::channel(); + // set completion_request so that this request can be canceled + // by setting completion_request, the old channel stored there is dropped + // and the associated request is automatically dropped + cx.editor.completion_request_handle = Some(tx); + let future = async move { + tokio::select! { + biased; + _ = rx => { + Ok(serde_json::Value::Null) + } + res = future => { + res + } + } + }; + let trigger_offset = cursor; // TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply @@ -4183,11 +4202,19 @@ pub fn completion(cx: &mut Context) { let start_offset = cursor.saturating_sub(offset); let savepoint = doc.savepoint(view); + let trigger_doc = doc.id(); + let trigger_view = view.id; + cx.callback( future, move |editor, compositor, response: Option| { - if editor.mode != Mode::Insert { - // we're not in insert mode anymore + let (view, doc) = current_ref!(editor); + // check if the completion request is stale. + // + // Completions are completed asynchrounsly and therefore the user could + //switch document/view or leave insert mode. In all of thoise cases the + // completion should be discarded + if editor.mode != Mode::Insert || view.id != trigger_view || doc.id() != trigger_doc { return; } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c81ae635a..859176fb4 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -820,6 +820,7 @@ impl EditorView { (Mode::Insert, Mode::Normal) => { // if exiting insert mode, remove completion self.completion = None; + cxt.editor.completion_request_handle = None; // TODO: Use an on_mode_change hook to remove signature help cxt.jobs.callback(async { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5b819b333..c6541105a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -31,7 +31,7 @@ use std::{ use tokio::{ sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Notify, RwLock, + oneshot, Notify, RwLock, }, time::{sleep, Duration, Instant, Sleep}, }; @@ -852,6 +852,14 @@ pub struct Editor { /// avoid calculating the cursor position multiple /// times during rendering and should not be set by other functions. pub cursor_cache: Cell>>, + /// When a new completion request is sent to the server old + /// unifinished request must be dropped. Each completion + /// request is associated with a channel that cancels + /// when the channel is dropped. That channel is stored + /// here. When a new completion request is sent this + /// field is set and any old requests are automatically + /// canceled as a result + pub completion_request_handle: Option>, } pub type RedrawHandle = (Arc, Arc>); @@ -950,6 +958,7 @@ impl Editor { redraw_handle: Default::default(), needs_redraw: false, cursor_cache: Cell::new(None), + completion_request_handle: None, } } From aabc8af95dd1c093da4b67151f6a7026a85e9c0e Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Fri, 3 Mar 2023 00:34:05 +0100 Subject: [PATCH 79/81] correctly store snapshots when repeating insert-mode actions Repeating completions currently crates a savepoint when a completion popup was triggered (so after the request completed). Just like for normal completions the savepoint must be created at the request. The occurrence of the completion request was previously not saved in `last_insert`. To that end a new `InsertEvent::RequestCompletion` variant has been added. When replayed this event creates a snapshot that is "actived" by the `TriggerCompletion` event and subsequently used during any `InsertEvent::CompletiuonApply` events. --- helix-term/src/commands.rs | 19 ++++++++++++++++++- helix-term/src/ui/editor.rs | 18 +++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 574e1edf6..6817bc5c7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -53,7 +53,10 @@ use crate::{ filter_picker_entry, job::Callback, keymap::ReverseKeymap, - ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, + ui::{ + self, editor::InsertEvent, overlay::overlayed, FilePicker, Picker, Popup, Prompt, + PromptEvent, + }, }; use crate::job::{self, Jobs}; @@ -4205,6 +4208,20 @@ pub fn completion(cx: &mut Context) { let trigger_doc = doc.id(); let trigger_view = view.id; + // FIXME: The commands Context can only have a single callback + // which means it gets overwritten when executing keybindings + // with multiple commands or macros. This would mean that completion + // might be incorrectly applied when repeating the insertmode action + // + // TODO: to solve this either make cx.callback a Vec of callbacks or + // alternatively move `last_insert` to `helix_view::Editor` + cx.callback = Some(Box::new( + move |compositor: &mut Compositor, _cx: &mut compositor::Context| { + let ui = compositor.find::().unwrap(); + ui.last_insert.1.push(InsertEvent::RequestCompletion); + }, + )); + cx.callback( future, move |editor, compositor, response: Option| { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 859176fb4..4abbe01e7 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -21,14 +21,14 @@ use helix_core::{ visual_offset_from_block, Position, Range, Selection, Transaction, }; use helix_view::{ - document::{Mode, SCRATCH_BUFFER_NAME}, + document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, editor::{CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, }; -use std::{num::NonZeroUsize, path::PathBuf, rc::Rc}; +use std::{mem::take, num::NonZeroUsize, path::PathBuf, rc::Rc, sync::Arc}; use tui::buffer::Buffer as Surface; @@ -39,7 +39,7 @@ pub struct EditorView { pub keymaps: Keymaps, on_next_key: Option, pseudo_pending: Vec, - last_insert: (commands::MappableCommand, Vec), + pub(crate) last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, spinners: ProgressSpinners, } @@ -49,6 +49,7 @@ pub enum InsertEvent { Key(KeyEvent), CompletionApply(CompleteAction), TriggerCompletion, + RequestCompletion, } impl Default for EditorView { @@ -891,6 +892,8 @@ impl EditorView { for _ in 0..cxt.editor.count.map_or(1, NonZeroUsize::into) { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); + let mut last_savepoint = None; + let mut last_request_savepoint = None; // then replay the inputs for key in self.last_insert.1.clone() { match key { @@ -898,7 +901,9 @@ impl EditorView { InsertEvent::CompletionApply(compl) => { let (view, doc) = current!(cxt.editor); - doc.restore(view); + if let Some(last_savepoint) = last_savepoint.as_deref() { + doc.restore(view, last_savepoint); + } let text = doc.text().slice(..); let cursor = doc.selection(view.id).primary().cursor(text); @@ -915,8 +920,11 @@ impl EditorView { doc.apply(&tx, view.id); } InsertEvent::TriggerCompletion => { + last_savepoint = take(&mut last_request_savepoint); + } + InsertEvent::RequestCompletion => { let (view, doc) = current!(cxt.editor); - doc.savepoint(view); + last_request_savepoint = Some(doc.savepoint(view)); } } } From 2cf4ce235662fcb272c684751b844b2ebc1b757f Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Thu, 9 Mar 2023 05:08:28 +0100 Subject: [PATCH 80/81] Fix `shrink_selection` with multiple cursors. (#6093) * Fix #6092 Cause were some incorrect assumptions that missed an edge case in the `Selection.contains()` calculation. Tests were added accordingly. * Fix Selection.contains() edge-case handling. Removing the len check short-circuit was the only thing needed as pointed out by @dead10ck. --- helix-core/src/selection.rs | 11 ++++++----- helix-term/src/commands.rs | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 0db7634c9..0eb2b755e 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -625,11 +625,6 @@ impl Selection { // returns true if self ⊇ other pub fn contains(&self, other: &Selection) -> bool { - // can't contain other if it is larger - if other.len() > self.len() { - return false; - } - let (mut iter_self, mut iter_other) = (self.iter(), other.iter()); let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next()); @@ -1240,5 +1235,11 @@ mod test { vec!((3, 4), (7, 9)) )); assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6)))); + + // multiple ranges of other are all contained in some ranges of self, + assert!(contains( + vec!((1, 4), (7, 10)), + vec!((1, 2), (3, 4), (7, 9)) + )); } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6817bc5c7..803f4051d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4371,7 +4371,6 @@ fn shrink_selection(cx: &mut Context) { // try to restore previous selection if let Some(prev_selection) = view.object_selections.pop() { if current_selection.contains(&prev_selection) { - // allow shrinking the selection only if current selection contains the previous object selection doc.set_selection(view.id, prev_selection); return; } else { From ce1fb9e64c189fb7476b4c72c6774a5bf6cbfd0f Mon Sep 17 00:00:00 2001 From: paul-scott Date: Fri, 10 Mar 2023 01:50:43 +1100 Subject: [PATCH 81/81] Generalised to multiple runtime directories with priorities (#5411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Generalised to multiple runtime directories with priorities This is an implementation for #3346. Previously, one of the following runtime directories were used: 1. `$HELIX_RUNTIME` 2. sibling directory to `$CARGO_MANIFEST_DIR` 3. subdirectory of user config directory 4. subdirectory of path to helix executable The first directory provided / found to exist in this order was used as a root for all runtime file searches (grammars, themes, queries). This change lowers the priority of `$HELIX_RUNTIME` so that the user config runtime has higher priority. More significantly, all of these directories are now searched for runtime files, enabling a user to override default or system-level runtime files. If the same file name appears in multiple runtime directories, the following priority is now used: 1. sibling directory to `$CARGO_MANIFEST_DIR` 2. subdirectory of user config directory 3. `$HELIX_RUNTIME` 4. subdirectory of path to helix executable One exception to this rule is that a user can have a `themes` directory directly in the user config directory that has higher piority to `themes` directories in runtime directories. That behaviour has been preserved. As part of implementing this feature `theme::Loader` was simplified and the cycle detection logic of the theme inheritance was improved to cover more cases and to be more explicit. * Removed AsRef usage to avoid binary growth * Health displaying ;-separated runtime dirs * Changed HELIX_RUNTIME build from src instructions * Updated doc for more detail on runtime directories * Improved health symlink printing and theme cycle errors The health display of runtime symlinks now prints both ends of the link. Separate errors are given when theme file is not found and when the only theme file found would form an inheritence cycle. * Satisfied clippy on passing Path * Clarified highest priority runtime directory purpose * Further clarified multiple runtime details in book Also gave markdown headings to subsections. Fixed a error with table indentation not building table that also appears present on master. --------- Co-authored-by: Paul Scott Co-authored-by: Blaž Hrastnik --- book/src/install.md | 38 +++++++++---- helix-loader/src/grammar.rs | 23 +++++--- helix-loader/src/lib.rs | 81 ++++++++++++++++++++++----- helix-term/src/application.rs | 10 ++-- helix-term/src/commands/typed.rs | 2 +- helix-term/src/health.rs | 38 +++++++++---- helix-term/src/ui/mod.rs | 8 +-- helix-view/src/theme.rs | 95 ++++++++++++++++++-------------- 8 files changed, 197 insertions(+), 98 deletions(-) diff --git a/book/src/install.md b/book/src/install.md index f9cf9a3ba..bd3f502b6 100644 --- a/book/src/install.md +++ b/book/src/install.md @@ -137,8 +137,8 @@ cargo install --path helix-term --locked ``` This command will create the `hx` executable and construct the tree-sitter -grammars either in the `runtime` folder, or in the folder specified in `HELIX_RUNTIME` -(as described below). To build the tree-sitter grammars requires a c++ compiler to be installed, for example `gcc-c++`. +grammars in the local `runtime` folder. To build the tree-sitter grammars requires +a c++ compiler to be installed, for example `gcc-c++`. > 💡 If you are using the musl-libc instead of glibc the following environment variable must be set during the build > to ensure tree-sitter grammars can be loaded correctly: @@ -149,11 +149,13 @@ grammars either in the `runtime` folder, or in the folder specified in `HELIX_RU > 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch > grammars with `hx --grammar fetch` (requires `git`) and compile them with -> `hx --grammar build` (requires a C++ compiler). +> `hx --grammar build` (requires a C++ compiler). This will install them in +> the `runtime` directory within the user's helix config directory (more +> [details below](#multiple-runtime-directories)). ### Configuring Helix's runtime files -- **Linux and macOS** +#### Linux and macOS Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent: @@ -167,7 +169,7 @@ Or, create a symlink in `~/.config/helix` that links to the source code director ln -s $PWD/runtime ~/.config/helix/runtime ``` -- **Windows** +#### Windows Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for `Edit environment variables for your account`) or use the `setx` command in @@ -182,13 +184,27 @@ setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime" Or, create a symlink in `%appdata%\helix\` that links to the source code directory: - | Method | Command | - | ---------- | -------------------------------------------------------------------------------------- | - | PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` | - | Cmd | `cd %appdata%\helix`
`mklink /D runtime "%userprofile%\src\helix\runtime"` | +| Method | Command | +| ---------- | -------------------------------------------------------------------------------------- | +| PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` | +| Cmd | `cd %appdata%\helix`
`mklink /D runtime "%userprofile%\src\helix\runtime"` | - > 💡 On Windows, creating a symbolic link may require running PowerShell or - > Cmd as an administrator. +> 💡 On Windows, creating a symbolic link may require running PowerShell or +> Cmd as an administrator. + +#### Multiple runtime directories + +When Helix finds multiple runtime directories it will search through them for files in the +following order: + +1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for + developing and testing helix only). +2. `runtime/` subdirectory of OS-dependent helix user config directory. +3. `$HELIX_RUNTIME`. +4. `runtime/` subdirectory of path to Helix executable. + +This order also sets the priority for selecting which file will be used if multiple runtime +directories have files with the same name. ### Validating the installation diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 01c966c8c..a85cb274c 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -67,8 +67,9 @@ pub fn get_language(name: &str) -> Result { #[cfg(not(target_arch = "wasm32"))] pub fn get_language(name: &str) -> Result { use libloading::{Library, Symbol}; - let mut library_path = crate::runtime_dir().join("grammars").join(name); - library_path.set_extension(DYLIB_EXTENSION); + let mut rel_library_path = PathBuf::new().join("grammars").join(name); + rel_library_path.set_extension(DYLIB_EXTENSION); + let library_path = crate::runtime_file(&rel_library_path); let library = unsafe { Library::new(&library_path) } .with_context(|| format!("Error opening dynamic library {:?}", library_path))?; @@ -252,7 +253,9 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result { remote, revision, .. } = grammar.source { - let grammar_dir = crate::runtime_dir() + let grammar_dir = crate::runtime_dirs() + .first() + .expect("No runtime directories provided") // guaranteed by post-condition .join("grammars") .join("sources") .join(&grammar.grammar_id); @@ -350,7 +353,9 @@ fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result< let grammar_dir = if let GrammarSource::Local { path } = &grammar.source { PathBuf::from(&path) } else { - crate::runtime_dir() + crate::runtime_dirs() + .first() + .expect("No runtime directories provided") // guaranteed by post-condition .join("grammars") .join("sources") .join(&grammar.grammar_id) @@ -401,7 +406,10 @@ fn build_tree_sitter_library( None } }; - let parser_lib_path = crate::runtime_dir().join("grammars"); + let parser_lib_path = crate::runtime_dirs() + .first() + .expect("No runtime directories provided") // guaranteed by post-condition + .join("grammars"); let mut library_path = parser_lib_path.join(&grammar.grammar_id); library_path.set_extension(DYLIB_EXTENSION); @@ -511,9 +519,6 @@ fn mtime(path: &Path) -> Result { /// Gives the contents of a file from a language's `runtime/queries/` /// directory pub fn load_runtime_file(language: &str, filename: &str) -> Result { - let path = crate::RUNTIME_DIR - .join("queries") - .join(language) - .join(filename); + let path = crate::runtime_file(&PathBuf::new().join("queries").join(language).join(filename)); std::fs::read_to_string(path) } diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 8dc2928ad..04b44b5aa 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -2,11 +2,12 @@ pub mod config; pub mod grammar; use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH"); -pub static RUNTIME_DIR: once_cell::sync::Lazy = once_cell::sync::Lazy::new(runtime_dir); +static RUNTIME_DIRS: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(prioritize_runtime_dirs); static CONFIG_FILE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); @@ -25,31 +26,83 @@ pub fn initialize_config_file(specified_file: Option) { CONFIG_FILE.set(config_file).ok(); } -pub fn runtime_dir() -> PathBuf { - if let Ok(dir) = std::env::var("HELIX_RUNTIME") { - return dir.into(); - } - +/// A list of runtime directories from highest to lowest priority +/// +/// The priority is: +/// +/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set) +/// 2. subdirectory of user config directory (always included) +/// 3. `HELIX_RUNTIME` (if environment variable is set) +/// 4. subdirectory of path to helix executable (always included) +/// +/// Postcondition: returns at least two paths (they might not exist). +fn prioritize_runtime_dirs() -> Vec { + const RT_DIR: &str = "runtime"; + // Adding higher priority first + let mut rt_dirs = Vec::new(); if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent let path = std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR); log::debug!("runtime dir: {}", path.to_string_lossy()); - return path; + rt_dirs.push(path); } - const RT_DIR: &str = "runtime"; - let conf_dir = config_dir().join(RT_DIR); - if conf_dir.exists() { - return conf_dir; + let conf_rt_dir = config_dir().join(RT_DIR); + rt_dirs.push(conf_rt_dir); + + if let Ok(dir) = std::env::var("HELIX_RUNTIME") { + rt_dirs.push(dir.into()); } // fallback to location of the executable being run // canonicalize the path in case the executable is symlinked - std::env::current_exe() + let exe_rt_dir = std::env::current_exe() .ok() .and_then(|path| std::fs::canonicalize(path).ok()) .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR))) - .unwrap() + .unwrap(); + rt_dirs.push(exe_rt_dir); + rt_dirs +} + +/// Runtime directories ordered from highest to lowest priority +/// +/// All directories should be checked when looking for files. +/// +/// Postcondition: returns at least one path (it might not exist). +pub fn runtime_dirs() -> &'static [PathBuf] { + &RUNTIME_DIRS +} + +/// Find file with path relative to runtime directory +/// +/// `rel_path` should be the relative path from within the `runtime/` directory. +/// The valid runtime directories are searched in priority order and the first +/// file found to exist is returned, otherwise None. +fn find_runtime_file(rel_path: &Path) -> Option { + RUNTIME_DIRS.iter().find_map(|rt_dir| { + let path = rt_dir.join(rel_path); + if path.exists() { + Some(path) + } else { + None + } + }) +} + +/// Find file with path relative to runtime directory +/// +/// `rel_path` should be the relative path from within the `runtime/` directory. +/// The valid runtime directories are searched in priority order and the first +/// file found to exist is returned, otherwise the path to the final attempt +/// that failed. +pub fn runtime_file(rel_path: &Path) -> PathBuf { + find_runtime_file(rel_path).unwrap_or_else(|| { + RUNTIME_DIRS + .last() + .map(|dir| dir.join(rel_path)) + .unwrap_or_default() + }) } pub fn config_dir() -> PathBuf { diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index d56e7c884..c7e939959 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -31,6 +31,7 @@ use crate::{ use log::{debug, error, warn}; use std::{ io::{stdin, stdout}, + path::Path, sync::Arc, time::{Duration, Instant}, }; @@ -113,10 +114,9 @@ impl Application { use helix_view::editor::Action; - let theme_loader = std::sync::Arc::new(theme::Loader::new( - &helix_loader::config_dir(), - &helix_loader::runtime_dir(), - )); + let mut theme_parent_dirs = vec![helix_loader::config_dir()]; + theme_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned()); + let theme_loader = std::sync::Arc::new(theme::Loader::new(&theme_parent_dirs)); let true_color = config.editor.true_color || crate::true_color(); let theme = config @@ -162,7 +162,7 @@ impl Application { compositor.push(editor_view); if args.load_tutor { - let path = helix_loader::runtime_dir().join("tutor"); + let path = helix_loader::runtime_file(Path::new("tutor")); editor.open(&path, Action::VerticalSplit)?; // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(editor).set_path(None)?; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 5ea611086..e9a722258 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1565,7 +1565,7 @@ fn tutor( return Ok(()); } - let path = helix_loader::runtime_dir().join("tutor"); + let path = helix_loader::runtime_file(Path::new("tutor")); cx.editor.open(&path, Action::Replace)?; // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(cx.editor).set_path(None)?; diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index 6558fe19f..480c2c675 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -52,7 +52,7 @@ pub fn general() -> std::io::Result<()> { let config_file = helix_loader::config_file(); let lang_file = helix_loader::lang_config_file(); let log_file = helix_loader::log_file(); - let rt_dir = helix_loader::runtime_dir(); + let rt_dirs = helix_loader::runtime_dirs(); let clipboard_provider = get_clipboard_provider(); if config_file.exists() { @@ -66,17 +66,31 @@ pub fn general() -> std::io::Result<()> { writeln!(stdout, "Language file: default")?; } writeln!(stdout, "Log file: {}", log_file.display())?; - writeln!(stdout, "Runtime directory: {}", rt_dir.display())?; - - if let Ok(path) = std::fs::read_link(&rt_dir) { - let msg = format!("Runtime directory is symlinked to {}", path.display()); - writeln!(stdout, "{}", msg.yellow())?; - } - if !rt_dir.exists() { - writeln!(stdout, "{}", "Runtime directory does not exist.".red())?; - } - if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) { - writeln!(stdout, "{}", "Runtime directory is empty.".red())?; + writeln!( + stdout, + "Runtime directories: {}", + rt_dirs + .iter() + .map(|d| d.to_string_lossy()) + .collect::>() + .join(";") + )?; + for rt_dir in rt_dirs.iter() { + if let Ok(path) = std::fs::read_link(rt_dir) { + let msg = format!( + "Runtime directory {} is symlinked to: {}", + rt_dir.display(), + path.display() + ); + writeln!(stdout, "{}", msg.yellow())?; + } + if !rt_dir.exists() { + let msg = format!("Runtime directory does not exist: {}", rt_dir.display()); + writeln!(stdout, "{}", msg.yellow())?; + } else if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) { + let msg = format!("Runtime directory is empty: {}", rt_dir.display()); + writeln!(stdout, "{}", msg.yellow())?; + } } writeln!(stdout, "Clipboard provider: {}", clipboard_provider.name())?; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index d7717f8cf..3e9a14b06 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -280,10 +280,10 @@ pub mod completers { } pub fn theme(_editor: &Editor, input: &str) -> Vec { - let mut names = theme::Loader::read_names(&helix_loader::runtime_dir().join("themes")); - names.extend(theme::Loader::read_names( - &helix_loader::config_dir().join("themes"), - )); + let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes")); + for rt_dir in helix_loader::runtime_dirs() { + names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); + } names.push("default".into()); names.push("base16_default".into()); names.sort(); diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index ce061babe..5d79ff26b 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, path::{Path, PathBuf}, str, }; @@ -37,19 +37,21 @@ pub static BASE16_DEFAULT_THEME: Lazy = Lazy::new(|| Theme { #[derive(Clone, Debug)] pub struct Loader { - user_dir: PathBuf, - default_dir: PathBuf, + /// Theme directories to search from highest to lowest priority + theme_dirs: Vec, } impl Loader { - /// Creates a new loader that can load themes from two directories. - pub fn new>(user_dir: P, default_dir: P) -> Self { + /// Creates a new loader that can load themes from multiple directories. + /// + /// The provided directories should be ordered from highest to lowest priority. + /// The directories will have their "themes" subdirectory searched. + pub fn new(dirs: &[PathBuf]) -> Self { Self { - user_dir: user_dir.as_ref().join("themes"), - default_dir: default_dir.as_ref().join("themes"), + theme_dirs: dirs.iter().map(|p| p.join("themes")).collect(), } } - /// Loads a theme first looking in the `user_dir` then in `default_dir` + /// Loads a theme searching directories in priority order. pub fn load(&self, name: &str) -> Result { if name == "default" { return Ok(self.default()); @@ -58,7 +60,8 @@ impl Loader { return Ok(self.base16_default()); } - let theme = self.load_theme(name, name, false).map(Theme::from)?; + let mut visited_paths = HashSet::new(); + let theme = self.load_theme(name, &mut visited_paths).map(Theme::from)?; Ok(Theme { name: name.into(), @@ -66,16 +69,18 @@ impl Loader { }) } - // load the theme and its parent recursively and merge them - // `base_theme_name` is the theme from the config.toml, - // used to prevent some circular loading scenarios - fn load_theme( - &self, - name: &str, - base_theme_name: &str, - only_default_dir: bool, - ) -> Result { - let path = self.path(name, only_default_dir); + /// Recursively load a theme, merging with any inherited parent themes. + /// + /// The paths that have been visited in the inheritance hierarchy are tracked + /// to detect and avoid cycling. + /// + /// It is possible for one file to inherit from another file with the same name + /// so long as the second file is in a themes directory with lower priority. + /// However, it is not recommended that users do this as it will make tracing + /// errors more difficult. + fn load_theme(&self, name: &str, visited_paths: &mut HashSet) -> Result { + let path = self.path(name, visited_paths)?; + let theme_toml = self.load_toml(path)?; let inherits = theme_toml.get("inherits"); @@ -92,11 +97,7 @@ impl Loader { // load default themes's toml from const. "default" => DEFAULT_THEME_DATA.clone(), "base16_default" => BASE16_DEFAULT_THEME_DATA.clone(), - _ => self.load_theme( - parent_theme_name, - base_theme_name, - base_theme_name == parent_theme_name, - )?, + _ => self.load_theme(parent_theme_name, visited_paths)?, }; self.merge_themes(parent_theme_toml, theme_toml) @@ -148,7 +149,7 @@ impl Loader { merge_toml_values(theme, palette.into(), 1) } - // Loads the theme data as `toml::Value` first from the user_dir then in default_dir + // Loads the theme data as `toml::Value` fn load_toml(&self, path: PathBuf) -> Result { let data = std::fs::read_to_string(path)?; let value = toml::from_str(&data)?; @@ -156,25 +157,35 @@ impl Loader { Ok(value) } - // Returns the path to the theme with the name - // With `only_default_dir` as false the path will first search for the user path - // disabled it ignores the user path and returns only the default path - fn path(&self, name: &str, only_default_dir: bool) -> PathBuf { + /// Returns the path to the theme with the given name + /// + /// Ignores paths already visited and follows directory priority order. + fn path(&self, name: &str, visited_paths: &mut HashSet) -> Result { let filename = format!("{}.toml", name); - let user_path = self.user_dir.join(&filename); - if !only_default_dir && user_path.exists() { - user_path - } else { - self.default_dir.join(filename) - } - } - - /// Lists all theme names available in default and user directory - pub fn names(&self) -> Vec { - let mut names = Self::read_names(&self.user_dir); - names.extend(Self::read_names(&self.default_dir)); - names + let mut cycle_found = false; // track if there was a path, but it was in a cycle + self.theme_dirs + .iter() + .find_map(|dir| { + let path = dir.join(&filename); + if !path.exists() { + None + } else if visited_paths.contains(&path) { + // Avoiding cycle, continuing to look in lower priority directories + cycle_found = true; + None + } else { + visited_paths.insert(path.clone()); + Some(path) + } + }) + .ok_or_else(|| { + if cycle_found { + anyhow!("Theme: cycle found in inheriting: {}", name) + } else { + anyhow!("Theme: file not found for: {}", name) + } + }) } pub fn default_theme(&self, true_color: bool) -> Theme {