From 6c0786edc575c5b70bd0d5dcbdaf083f8b5525b1 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 11 Oct 2021 20:31:47 -0500 Subject: [PATCH 001/122] prefer elixir-lang/tree-sitter-elixir --- .gitmodules | 2 +- helix-syntax/languages/tree-sitter-elixir | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index a8e6481ea..01260b847 100644 --- a/.gitmodules +++ b/.gitmodules @@ -84,7 +84,7 @@ shallow = true [submodule "helix-syntax/languages/tree-sitter-elixir"] path = helix-syntax/languages/tree-sitter-elixir - url = https://github.com/IceDragon200/tree-sitter-elixir + url = https://github.com/elixir-lang/tree-sitter-elixir shallow = true [submodule "helix-syntax/languages/tree-sitter-nix"] path = helix-syntax/languages/tree-sitter-nix diff --git a/helix-syntax/languages/tree-sitter-elixir b/helix-syntax/languages/tree-sitter-elixir index 295e62a43..7ae20df18 160000 --- a/helix-syntax/languages/tree-sitter-elixir +++ b/helix-syntax/languages/tree-sitter-elixir @@ -1 +1 @@ -Subproject commit 295e62a43b92cea909cfabe57e8818d177f4857b +Subproject commit 7ae20df181b86c79d826abd5aec7a3e32e3d8438 From d1b434d2304218673b7e7564f9e59910b475349c Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 11 Oct 2021 21:10:14 -0500 Subject: [PATCH 002/122] add highlights query from elixir-lang/tree-sitter-elixir --- runtime/queries/elixir/highlights.scm | 301 +++++++++++++++----------- 1 file changed, 179 insertions(+), 122 deletions(-) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index 6bf93a210..bb88e4502 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -1,125 +1,186 @@ -["when" "and" "or" "not in" "not" "in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword - -[(true) (false) (nil)] @constant.builtin - -(keyword - [(keyword_literal) - ":"] @tag) - -(keyword - (keyword_string - [(string_start) - (string_content) - (string_end)] @tag)) - -[(atom_literal) - (atom_start) - (atom_content) - (atom_end)] @tag - -[(comment) - (unused_identifier)] @comment - -(escape_sequence) @escape - -(call function: (function_identifier) @keyword - (#match? @keyword "^(defmodule|defexception|defp|def|with|case|cond|raise|import|require|use|defmacrop|defmacro|defguardp|defguard|defdelegate|defstruct|alias|defimpl|defprotocol|defoverridable|receive|if|for|try|throw|unless|reraise|super|quote|unquote|unquote_splicing)$")) - -(call function: (function_identifier) @keyword - [(call - function: (function_identifier) @function - (arguments - [(identifier) @variable.parameter - (_ (identifier) @variable.parameter) - (_ (_ (identifier) @variable.parameter)) - (_ (_ (_ (identifier) @variable.parameter))) - (_ (_ (_ (_ (identifier) @variable.parameter)))) - (_ (_ (_ (_ (_ (identifier) @variable.parameter)))))])) - (binary_op - left: - (call - function: (function_identifier) @function - (arguments - [(identifier) @variable.parameter - (_ (identifier) @variable.parameter) - (_ (_ (identifier) @variable.parameter)) - (_ (_ (_ (identifier) @variable.parameter))) - (_ (_ (_ (_ (identifier) @variable.parameter)))) - (_ (_ (_ (_ (_ (identifier) @variable.parameter)))))])) +; Reserved keywords + +["when" "and" "or" "not" "in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword + +; Operators + +; * doc string +(unary_operator + operator: "@" @comment.doc + operand: (call + target: (identifier) @comment.doc.__attribute__ + (arguments + [ + (string) @comment.doc + (charlist) @comment.doc + (sigil + quoted_start: _ @comment.doc + quoted_end: _ @comment.doc) @comment.doc + (boolean) @comment.doc + ])) + (#match? @comment.doc.__attribute__ "^(moduledoc|typedoc|doc)$")) + +; * module attribute +(unary_operator + operator: "@" @attribute + operand: [ + (identifier) @attribute + (call + target: (identifier) @attribute) + (boolean) @attribute + (nil) @attribute + ]) + +; * capture operand +(unary_operator + operator: "&" + operand: (integer) @operator) + +(operator_identifier) @operator + +(unary_operator + operator: _ @operator) + +(binary_operator + operator: _ @operator) + +(dot + operator: _ @operator) + +(stab_clause + operator: _ @operator) + +; Literals + +[ + (boolean) + (nil) +] @constant + +[ + (integer) + (float) +] @number + +(alias) @type + +(char) @constant + +; Quoted content + +(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded + +(escape_sequence) @string.escape + +[ + (atom) + (quoted_atom) + (keyword) + (quoted_keyword) +] @string.special.symbol + +[ + (string) + (charlist) +] @string + +; Note that we explicitly target sigil quoted start/end, so they are not overridden by delimiters + +(sigil + (sigil_name) @__name__ + quoted_start: _ @string + quoted_end: _ @string + (#match? @__name__ "^[sS]$")) @string + +(sigil + (sigil_name) @__name__ + quoted_start: _ @string.regex + quoted_end: _ @string.regex + (#match? @__name__ "^[rR]$")) @string.regex + +(sigil + (sigil_name) @__name__ + quoted_start: _ @string.special + quoted_end: _ @string.special) @string.special + +; Calls + +; * definition keyword +(call + target: (identifier) @keyword + (#match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$")) + +; * kernel or special forms keyword +(call + target: (identifier) @keyword + (#match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$")) + +; * function call +(call + target: [ + ; local + (identifier) @function + ; remote + (dot + right: (identifier) @function) + ]) + +; * just identifier in function definition +(call + target: (identifier) @keyword + (arguments + [ + (identifier) @function + (binary_operator + left: (identifier) @function operator: "when") - (binary_op - left: (identifier) @variable.parameter - operator: _ @function - right: (identifier) @variable.parameter)] - (#match? @keyword "^(defp|def|defmacrop|defmacro|defguardp|defguard|defdelegate)$")) - -(call (function_identifier) @keyword - [(call - function: (function_identifier) @function) - (identifier) @function - (binary_op - left: - [(call - function: (function_identifier) @function) - (identifier) @function] - operator: "when")] - (#match? @keyword "^(defp|def|defmacrop|defmacro|defguardp|defguard|defdelegate)$")) - -(anonymous_function - (stab_expression - left: (bare_arguments - [(identifier) @variable.parameter - (_ (identifier) @variable.parameter) - (_ (_ (identifier) @variable.parameter)) - (_ (_ (_ (identifier) @variable.parameter))) - (_ (_ (_ (_ (identifier) @variable.parameter)))) - (_ (_ (_ (_ (_ (identifier) @variable.parameter)))))]))) - -(unary_op - operator: "@" - (call (identifier) @attribute - (heredoc - [(heredoc_start) - (heredoc_content) - (heredoc_end)] @doc)) - (#match? @attribute "^(doc|moduledoc)$")) - -(module) @type - -(unary_op - operator: "@" @attribute - [(call - function: (function_identifier) @attribute) - (identifier) @attribute]) - -(unary_op - operator: _ @operator) - -(binary_op - operator: _ @operator) - -(heredoc - [(heredoc_start) - (heredoc_content) - (heredoc_end)] @string) - -(string - [(string_start) - (string_content) - (string_end)] @string) - -(sigil_start) @string.special -(sigil_content) @string -(sigil_end) @string.special - -(interpolation - "#{" @punctuation.special - "}" @punctuation.special) + ]) + (#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$")) + +; * pipe into identifier (definition) +(call + target: (identifier) @keyword + (arguments + (binary_operator + operator: "|>" + right: (identifier) @variable)) + (#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$")) + +; * pipe into identifier (function call) +(binary_operator + operator: "|>" + right: (identifier) @function) + +; Identifiers + +; * special +( + (identifier) @constant.builtin + (#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$") +) + +; * unused +( + (identifier) @comment.unused + (#match? @comment.unused "^_") +) + +; * regular +(identifier) @variable + +; Comment + +(comment) @comment + +; Punctuation + +[ + "%" +] @punctuation [ "," - "->" - "." + ";" ] @punctuation.delimiter [ @@ -132,7 +193,3 @@ "<<" ">>" ] @punctuation.bracket - -(special_identifier) @function.special - -(ERROR) @warning From 5db248cc1ce6077293e4bc96513daf6448f12774 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 11 Oct 2021 21:47:49 -0500 Subject: [PATCH 003/122] describe atoms as tags --- runtime/queries/elixir/highlights.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index bb88e4502..e37f9b8d9 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -77,7 +77,7 @@ (quoted_atom) (keyword) (quoted_keyword) -] @string.special.symbol +] @tag [ (string) From 95ab40d1718b3ed4c4cfef6cf02603651e6159f6 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 11 Oct 2021 21:48:14 -0500 Subject: [PATCH 004/122] use the warning type for tree-sitter ERRORs --- runtime/queries/elixir/highlights.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index e37f9b8d9..f9ceb46e4 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -193,3 +193,5 @@ "<<" ">>" ] @punctuation.bracket + +(ERROR) @warning From b2655a7f5ca0edfffcaabb8ab84b53571afba19a Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 11 Oct 2021 21:59:07 -0500 Subject: [PATCH 005/122] add LICENSE snippet at elixir hightlights top --- runtime/queries/elixir/highlights.scm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index f9ceb46e4..a840018a2 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -1,3 +1,20 @@ +; The following code originates mostly from +; https://github.com/elixir-lang/tree-sitter-elixir, with minor edits to +; align the captures with helix. The following should be considered +; Copyright 2021 The Elixir Team +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; https://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. + ; Reserved keywords ["when" "and" "or" "not" "in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword From c502cafecc42cc6099241e7ba129727bedf150d7 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 12 Oct 2021 16:14:32 -0500 Subject: [PATCH 006/122] highlight calls to erlang modules as types connects https://github.com/elixir-lang/tree-sitter-elixir/pull/5 --- runtime/queries/elixir/highlights.scm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index a840018a2..a5b3884e0 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -81,6 +81,10 @@ (alias) @type +(call + target: (dot + left: (atom) @type)) + (char) @constant ; Quoted content From 4771cc7ee49ebf59f837a6899a6b1d7e044c011a Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 14 Oct 2021 13:45:32 -0500 Subject: [PATCH 007/122] align highlight scopes with documented scopes --- book/src/themes.md | 2 ++ runtime/queries/elixir/highlights.scm | 45 +++++++++++++-------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index a99e3a59e..2d75ac576 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -120,6 +120,8 @@ We use a similar set of scopes as - `path` - `url` +- `symbol` - Elixir atoms, Ruby symbols, Clojure keywords + - `comment` - Code comments - `line` - Single line comments (`//`) - `block` - Block comments (e.g. (`/* */`) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index a5b3884e0..82ef081db 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -23,29 +23,29 @@ ; * doc string (unary_operator - operator: "@" @comment.doc + operator: "@" @comment.block.documentation operand: (call - target: (identifier) @comment.doc.__attribute__ + target: (identifier) @comment.block.documentation.__attribute__ (arguments [ - (string) @comment.doc - (charlist) @comment.doc + (string) @comment.block.documentation + (charlist) @comment.block.documentation (sigil - quoted_start: _ @comment.doc - quoted_end: _ @comment.doc) @comment.doc - (boolean) @comment.doc + quoted_start: _ @comment.block.documentation + quoted_end: _ @comment.block.documentation) @comment.block.documentation + (boolean) @comment.block.documentation ])) - (#match? @comment.doc.__attribute__ "^(moduledoc|typedoc|doc)$")) + (#match? @comment.block.documentation.__attribute__ "^(moduledoc|typedoc|doc)$")) ; * module attribute (unary_operator - operator: "@" @attribute + operator: "@" @variable.property operand: [ - (identifier) @attribute + (identifier) @variable.property (call - target: (identifier) @attribute) - (boolean) @attribute - (nil) @attribute + target: (identifier) @variable.property) + (boolean) @variable.property + (nil) @variable.property ]) ; * capture operand @@ -69,10 +69,9 @@ ; Literals -[ - (boolean) - (nil) -] @constant +(nil) @constant.builtin + +(boolean) @constant.builtin.boolean [ (integer) @@ -85,20 +84,20 @@ target: (dot left: (atom) @type)) -(char) @constant +(char) @constant.character ; Quoted content -(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded +(interpolation "#{" @escape "}" @escape) @embedded -(escape_sequence) @string.escape +(escape_sequence) @escape [ (atom) (quoted_atom) (keyword) (quoted_keyword) -] @tag +] @symbol [ (string) @@ -182,8 +181,8 @@ ; * unused ( - (identifier) @comment.unused - (#match? @comment.unused "^_") + (identifier) @comment + (#match? @comment "^_") ) ; * regular From 8f658f0dceffa7bcf2ea18cd6fd0a7dab4e37663 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 14 Oct 2021 13:46:58 -0500 Subject: [PATCH 008/122] use latest tree-sitter-elixir with 'not in' query support connects https://github.com/elixir-lang/tree-sitter-elixir/issues/9 --- helix-syntax/languages/tree-sitter-elixir | 2 +- runtime/queries/elixir/highlights.scm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-syntax/languages/tree-sitter-elixir b/helix-syntax/languages/tree-sitter-elixir index 7ae20df18..f5d7bda54 160000 --- a/helix-syntax/languages/tree-sitter-elixir +++ b/helix-syntax/languages/tree-sitter-elixir @@ -1 +1 @@ -Subproject commit 7ae20df181b86c79d826abd5aec7a3e32e3d8438 +Subproject commit f5d7bda543da788bd507b05bd722627dde66c9ec diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index 82ef081db..33a84ace8 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -17,7 +17,7 @@ ; Reserved keywords -["when" "and" "or" "not" "in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword +["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword ; Operators From 80b54f2f69165897bfab376d031fab8e040331b6 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 14 Oct 2021 13:58:08 -0500 Subject: [PATCH 009/122] use special.string.symbol instead of symbol this aligns better with how ruby highlights symbols --- book/src/themes.md | 3 +-- runtime/queries/elixir/highlights.scm | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 2d75ac576..5a4d04038 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -119,8 +119,7 @@ We use a similar set of scopes as - `special` - `path` - `url` - -- `symbol` - Elixir atoms, Ruby symbols, Clojure keywords + - `symbol` - Erlang/Elixir atoms, Ruby symbols, Clojure keywords - `comment` - Code comments - `line` - Single line comments (`//`) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index 33a84ace8..a882fb63b 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -97,7 +97,7 @@ (quoted_atom) (keyword) (quoted_keyword) -] @symbol +] @string.special.symbol [ (string) From 4d8eb09b7c436a82e3deff09cbf65a8c68201522 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 15 Oct 2021 17:39:05 -0500 Subject: [PATCH 010/122] scope arities in captures as operators --- runtime/queries/elixir/highlights.scm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index a882fb63b..08e09f37a 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -48,10 +48,17 @@ (nil) @variable.property ]) -; * capture operand +; * capture operator (unary_operator operator: "&" - operand: (integer) @operator) + operand: [ + (integer) @operator + (binary_operator + left: [ + (call target: (dot left: (_) right: (identifier) @function)) + (identifier) @function + ] operator: "/" right: (integer) @operator) + ]) (operator_identifier) @operator From bb011f9fb22ce8bce7720668d02790b2524e5c3c Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Mon, 18 Oct 2021 09:01:53 +0800 Subject: [PATCH 011/122] Add indents for python, but it's not perfect. (#837) * add indents for python, but it's not Perfect * add last line --- runtime/queries/python/indents.toml | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 runtime/queries/python/indents.toml diff --git a/runtime/queries/python/indents.toml b/runtime/queries/python/indents.toml new file mode 100644 index 000000000..6bc684864 --- /dev/null +++ b/runtime/queries/python/indents.toml @@ -0,0 +1,39 @@ +indent = [ + "list", + "tuple", + "dictionary", + "set", + + "if_statement", + "for_statement", + "while_statement", + "with_statement", + "try_statement", + "import_from_statement", + + "parenthesized_expression", + "generator_expression", + "list_comprehension", + "set_comprehension", + "dictionary_comprehension", + + "tuple_pattern", + "list_pattern", + "argument_list", + "parameters", + "binary_operator", + + "function_definition", + "class_definition", +] + +outdent = [ + ")", + "]", + "}", + "return_statement", + "pass_statement", + "raise_statement", +] + +ignore = ["string"] From c278b43319b1a8beea872bc9b9678f3cef230fd7 Mon Sep 17 00:00:00 2001 From: Ray Gervais Date: Sun, 17 Oct 2021 21:31:57 -0400 Subject: [PATCH 012/122] adds: base16 theme for Helix editor (#833) --- runtime/themes/base16_default_dark.toml | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 runtime/themes/base16_default_dark.toml diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml new file mode 100644 index 000000000..625fc5abb --- /dev/null +++ b/runtime/themes/base16_default_dark.toml @@ -0,0 +1,50 @@ +# Author: RayGervais + +"ui.background" = { bg = "base00" } +"ui.menu" = "base01" +"ui.menu.selected" = { fg = "base04", bg = "base01" } +"ui.linenr" = {fg = "base01" } +"ui.popup" = { bg = "base01" } +"ui.window" = { bg = "base01" } +"ui.liner.selected" = "base02" +"ui.selection" = "base02" +"comment" = "base03" +"ui.statusline" = {fg = "base04", bg = "base01" } +"ui.help" = { fg = "base04", bg = "base01" } +"ui.cursor" = { fg = "base05", modifiers = ["reversed"] } +"ui.text" = { fg = "base05" } +"operator" = "base05" +"ui.text.focus" = { fg = "base05" } +"variable" = "base08" +"number" = "base09" +"constant" = "base09" +"attributes" = "base09" +"type" = "base0A" +"ui.cursor.match" = { fg = "base0A", modifiers = ["underlined"] } +"strings" = "base0B" +"property" = "base0B" +"escape" = "base0C" +"function" = "base0D" +"constructor" = "base0D" +"special" = "base0D" +"keyword" = "base0E" +"label" = "base0E" +"namespace" = "base0E" + +[palette] +base00 = "#181818" # Default Background +base01 = "#282828" # Lighter Background (Used for status bars, line number and folding marks) +base02 = "#383838" # Selection Background +base03 = "#585858" # Comments, Invisibles, Line Highlighting +base04 = "#b8b8b8" # Dark Foreground (Used for status bars) +base05 = "#d8d8d8" # Default Foreground, Caret, Delimiters, Operators +base06 = "#e8e8e8" # Light Foreground (Not often used) +base07 = "#f8f8f8" # Light Background (Not often used) +base08 = "#ab4642" # Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted +base09 = "#dc9656" # Integers, Boolean, Constants, XML Attributes, Markup Link Url +base0A = "#f7ca88" # Classes, Markup Bold, Search Text Background +base0B = "#a1b56c" # Strings, Inherited Class, Markup Code, Diff Inserted +base0C = "#86c1b9" # Support, Regular Expressions, Escape Characters, Markup Quotes +base0D = "#7cafc2" # Functions, Methods, Attribute IDs, Headings +base0E = "#ba8baf" # Keywords, Storage, Selector, Markup Italic, Diff Changed +base0F = "#a16946" # Deprecated, Opening/Closing Embedded Language Tags, e.g. From 9ac0c951615d624041836cdd7afd869391c72fff Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Mon, 18 Oct 2021 14:14:50 +0800 Subject: [PATCH 013/122] Improve completion trigger (#838) * improve idle completion trigger * add completion-trigger-len to book * rename semantics_completion to language_server_completion and optimize idle completion trigger --- book/src/configuration.md | 1 + helix-term/src/application.rs | 4 ++-- helix-term/src/commands.rs | 39 ++++++++++++++++++++++++----------- helix-view/src/editor.rs | 2 ++ 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index d47f95d91..7d6ff28f8 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -21,6 +21,7 @@ To override global configuration parameters, create a `config.toml` file located | `auto-pairs` | Enable automatic insertion of pairs to parenthese, brackets, etc. | `true` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `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` | ## LSP diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 0e7d0e558..a7281ecf6 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -235,7 +235,7 @@ impl Application { } pub fn handle_idle_timeout(&mut self) { - use crate::commands::{completion, Context}; + use crate::commands::{insert::idle_completion, Context}; use helix_view::document::Mode; if doc_mut!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion { @@ -262,7 +262,7 @@ impl Application { callback: None, on_next_key_callback: None, }; - completion(&mut cx); + idle_completion(&mut cx); self.render(); } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 95c46a4ed..2811f293a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3308,7 +3308,26 @@ pub mod insert { pub type Hook = fn(&Rope, &Selection, char) -> Option; pub type PostHook = fn(&mut Context, char); - fn completion(cx: &mut Context, ch: char) { + // It trigger completion when idle timer reaches deadline + // Only trigger completion if the word under cursor is longer than n characters + pub fn idle_completion(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + let cursor = doc.selection(view.id).primary().cursor(text); + + use helix_core::chars::char_is_word; + let mut iter = text.chars_at(cursor); + iter.reverse(); + for _ in 0..cx.editor.config.completion_trigger_len { + match iter.next() { + Some(c) if char_is_word(c) => {} + _ => return, + } + } + super::completion(cx); + } + + fn language_server_completion(cx: &mut Context, ch: char) { // if ch matches completion char, trigger completion let doc = doc_mut!(cx.editor); let language_server = match doc.language_server() { @@ -3318,19 +3337,14 @@ pub mod insert { let capabilities = language_server.capabilities(); - if let lsp::ServerCapabilities { - completion_provider: - Some(lsp::CompletionOptions { - trigger_characters: Some(triggers), - .. - }), + if let Some(lsp::CompletionOptions { + trigger_characters: Some(triggers), .. - } = capabilities + }) = &capabilities.completion_provider { // TODO: what if trigger is multiple chars long - let is_trigger = triggers.iter().any(|trigger| trigger.contains(ch)); - - if is_trigger { + if triggers.iter().any(|trigger| trigger.contains(ch)) { + cx.editor.clear_idle_timer(); super::completion(cx); } } @@ -3412,7 +3426,8 @@ pub mod insert { // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc) // this could also generically look at Transaction, but it's a bit annoying to look at // Operation instead of Change. - for hook in &[completion, signature_help] { + for hook in &[language_server_completion, signature_help] { + // for hook in &[signature_help] { hook(cx, c); } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 90e3bf471..52fca6d25 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -58,6 +58,7 @@ pub struct Config { /// Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. Defaults to 400ms. #[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")] pub idle_timeout: Duration, + pub completion_trigger_len: u8, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -87,6 +88,7 @@ impl Default for Config { auto_pairs: true, auto_completion: true, idle_timeout: Duration::from_millis(400), + completion_trigger_len: 2, } } } From 2ac9d30bf3614a08f2d0216010f5d73845c205fa Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Mon, 18 Oct 2021 15:39:54 +0800 Subject: [PATCH 014/122] improve menu selected color for nord (#873) --- runtime/themes/nord.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index ee7c88650..de5f1add5 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -2,7 +2,7 @@ # "ui.linenr.selected" = { fg = "#d8dee9" } "ui.text.focus" = { fg = "#88c0d0", modifiers= ["bold"] } -# "ui.menu.selected" = { fg = "#e5ded6", bg = "#313f4e" } +"ui.menu.selected" = { fg = "#88c0d0", bg = "#313f4e" } # "info" = "#b48ead" # "hint" = "#a3be8c" From c212325e6a7400a37181e8d2d83c4157ff93d63f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 09:35:52 +0900 Subject: [PATCH 015/122] build(deps): bump encoding_rs from 0.8.28 to 0.8.29 (#877) Bumps [encoding_rs](https://github.com/hsivonen/encoding_rs) from 0.8.28 to 0.8.29. - [Release notes](https://github.com/hsivonen/encoding_rs/releases) - [Commits](https://github.com/hsivonen/encoding_rs/compare/v0.8.28...v0.8.29) --- updated-dependencies: - dependency-name: encoding_rs 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 3b4c14669..b1351ee1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,9 +175,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encoding_rs" -version = "0.8.28" +version = "0.8.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" dependencies = [ "cfg-if", ] From cdfa0dfa36ae7b6ae5296d6991ddb7bc7a73f9fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 09:36:25 +0900 Subject: [PATCH 016/122] build(deps): bump chardetng from 0.1.14 to 0.1.15 (#879) Bumps [chardetng](https://github.com/hsivonen/chardetng) from 0.1.14 to 0.1.15. - [Release notes](https://github.com/hsivonen/chardetng/releases) - [Commits](https://github.com/hsivonen/chardetng/commits) --- updated-dependencies: - dependency-name: chardetng 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 b1351ee1c..1f7393cc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,9 +78,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chardetng" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a5a2ca47925d19fb6835f53b3e70dec0d25659211c8ee5cc784f1fd6838f9c" +checksum = "83ee29c16b81c32fbc882ecc568305793338a8353952573db837f4f4a6cd5c2e" dependencies = [ "cfg-if", "encoding_rs", From 7146ae9388bc523d56d2388b1898eab611cb13c3 Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Tue, 19 Oct 2021 11:17:05 +0800 Subject: [PATCH 017/122] Refactor nord theme (#874) * refactor again * remove useless color --- runtime/themes/nord.toml | 104 +++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index de5f1add5..29f3875dc 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -1,84 +1,102 @@ # Author : RayGervais -# "ui.linenr.selected" = { fg = "#d8dee9" } -"ui.text.focus" = { fg = "#88c0d0", modifiers= ["bold"] } -"ui.menu.selected" = { fg = "#88c0d0", bg = "#313f4e" } +"ui.linenr.selected" = { fg = "nord4" } +"ui.text.focus" = { fg = "nord8", modifiers= ["bold"] } +"ui.menu.selected" = { fg = "nord8", bg = "nord2" } -# "info" = "#b48ead" -# "hint" = "#a3be8c" +"info" = "nord8" +"hint" = "nord8" # Polar Night # nord0 - background color -"ui.background" = { bg = "#2e3440" } -"ui.statusline.inactive" = { fg = "#d8dEE9", bg = "#2e3440" } +"ui.background" = { bg = "nord0" } +"ui.statusline.inactive" = { fg = "nord4", bg = "nord0" } # nord1 - status bars, panels, modals, autocompletion -"ui.statusline" = { fg = "#88c0d0", bg = "#3b4252" } +"ui.statusline" = { fg = "nord8", bg = "nord1" } "ui.popup" = { bg = "#232d38" } "ui.window" = { bg = "#232d38" } -"ui.help" = { bg = "#232d38", fg = "#e5ded6" } +"ui.help" = { bg = "#232d38", fg = "nord4" } # nord2 - active line, highlighting -"ui.selection" = { bg = "#434c5e" } -"ui.cursor.match" = { bg = "434c5e" } +"ui.selection" = { bg = "nord2" } +"ui.cursor.match" = { bg = "nord2" } -# nord3 - comments -"comment" = "#616E88" -"ui.linenr" = { fg = "#616E88" } +# nord3 - comments, nord3 based lighter color +# relative: https://github.com/arcticicestudio/nord/issues/94 +"comment" = "gray" +"ui.linenr" = { fg = "gray" } # Snow Storm # nord4 - cursor, variables, constants, attributes, fields -"ui.cursor.primary" = { fg = "#d8dee9", modifiers = ["reversed"] } -"attribute" = "#d8dee9" -"variable" = "#d8dee9" -"constant" = "#d8dee9" -"variable.builtin" = "#d8dee9" -"constant.builtin" = "#d8dee9" -"namespace" = "#d8dee9" +"ui.cursor.primary" = { fg = "nord4", modifiers = ["reversed"] } +"attribute" = "nord4" +"variable" = "nord4" +"constant" = "nord4" +"variable.builtin" = "nord4" +"constant.builtin" = "nord4" +"namespace" = "nord4" # nord5 - suble UI text # nord6 - base text, punctuation -"ui.text" = { fg = "#eceff4" } -"punctuation" = "#eceff4" +"ui.text" = { fg = "nord6" } +"punctuation" = "nord6" # Frost # nord7 - classes, types, primiatives -"type" = "#8fbcbb" -"type.builtin" = { fg = "#8fbcbb"} -"label" = "#8fbcbb" +"type" = "nord7" +"type.builtin" = { fg = "nord7"} +"label" = "nord7" # nord8 - declaration, methods, routines -"constructor" = "#88c0d0" -"function" = "#88c0d0" -"function.macro" = { fg = "#88c0d0" } -"function.builtin" = { fg = "#88c0d0" } +"constructor" = "nord8" +"function" = "nord8" +"function.macro" = { fg = "nord8" } +"function.builtin" = { fg = "nord8" } # nord9 - operator, tags, units, punctuations -"punctuation.delimiter" = "#81a1c1" -"operator" = { fg = "#81a1c1" } -"property" = "#81a1c1" +"punctuation.delimiter" = "nord9" +"operator" = { fg = "nord9" } +"property" = "nord9" # nord10 - keywords, special -"keyword" = { fg = "#5e81ac" } -"keyword.directive" = "#5e81ac" -"variable.parameter" = "#5e81ac" +"keyword" = { fg = "nord10" } +"keyword.directive" = "nord10" +"variable.parameter" = "nord10" # Aurora # nord11 - error -"error" = "#bf616a" +"error" = "nord11" # nord12 - annotations, decorators -"special" = "#d08770" -"module" = "#d08770" +"special" = "nord12" +"module" = "nord12" # nord13 - warnings, escape characters, regex -"warning" = "#ebcb8b" -"escape" = { fg = "#ebcb8b" } +"warning" = "nord13" +"escape" = { fg = "nord13" } # nord14 - strings -"string" = "#a3be8c" +"string" = "nord14" # nord15 - integer, floating point -"number" = "#b48ead" +"number" = "nord15" + +[palette] +nord0 = "#2e3440" +nord1 = "#3b4252" +nord2 = "#434c5e" +nord4 = "#d8dee9" +nord6 = "#eceff4" +nord7 = "#8fbcbb" +nord8 = "#88c0d0" +nord9 = "#81a1c1" +nord10 = "#5e81ac" +nord11 = "#bf616a" +nord12 = "#d08770" +nord13 = "#ebcb8b" +nord14 = "#a3be8c" +nord15 = "#b48ead" +gray = "#616e88" From 1766bdb9d4c3df24a010041cd014b6fecb7641fa Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 18 Oct 2021 23:08:06 -0500 Subject: [PATCH 018/122] clean up combined-injections comment (#880) --- helix-core/src/syntax.rs | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 9c433f3db..0929e38fa 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -527,39 +527,7 @@ impl LanguageLayer { self.tree.as_ref(), ) .ok_or(Error::Cancelled)?; - // unsafe { syntax.parser.set_cancellation_flag(None) }; - // let mut cursor = syntax.cursors.pop().unwrap_or_else(QueryCursor::new); - - // Process combined injections. (ERB, EJS, etc https://github.com/tree-sitter/tree-sitter/pull/526) - // if let Some(combined_injections_query) = &config.combined_injections_query { - // let mut injections_by_pattern_index = - // vec![(None, Vec::new(), false); combined_injections_query.pattern_count()]; - // let matches = - // cursor.matches(combined_injections_query, tree.root_node(), RopeProvider(source)); - // for mat in matches { - // let entry = &mut injections_by_pattern_index[mat.pattern_index]; - // let (language_name, content_node, include_children) = - // injection_for_match(config, combined_injections_query, &mat, source); - // if language_name.is_some() { - // entry.0 = language_name; - // } - // if let Some(content_node) = content_node { - // entry.1.push(content_node); - // } - // entry.2 = include_children; - // } - // for (lang_name, content_nodes, includes_children) in injections_by_pattern_index { - // if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) { - // if let Some(next_config) = (injection_callback)(lang_name) { - // let ranges = - // Self::intersect_ranges(&ranges, &content_nodes, includes_children); - // if !ranges.is_empty() { - // queue.push((next_config, depth + 1, ranges)); - // } - // } - // } - // } - // } + self.tree = Some(tree) } Ok(()) From 67829976faca1c2fdeb3964c25cb35bc41f0b8df Mon Sep 17 00:00:00 2001 From: VuiMuich Date: Tue, 19 Oct 2021 11:37:38 +0200 Subject: [PATCH 019/122] Add `C-j` and `C-k` to keybinds for picker (#876) * Add `C-j` and `C-k` for moving down/up in pickers * Add new binds to keymap doc --- book/src/keymap.md | 18 +++++++++--------- helix-term/src/ui/picker.rs | 8 ++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 156b1d99f..1e19a4eb4 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -242,12 +242,12 @@ commands (including goto) to extend the existing selection instead of replacing Keys to use within picker. Remapping currently not supported. -| Key | Description | -| ----- | ------------- | -| `Up`, `Ctrl-p` | Previous entry | -| `Down`, `Ctrl-n` | Next entry | -| `Ctrl-space` | Filter options | -| `Enter` | Open selected | -| `Ctrl-h` | Open horizontally | -| `Ctrl-v` | Open vertically | -| `Escape`, `Ctrl-c` | Close picker | +| Key | Description | +| ----- | ------------- | +| `Up`, `Ctrl-k`, `Ctrl-p` | Previous entry | +| `Down`, `Ctrl-j`, `Ctrl-n` | Next entry | +| `Ctrl-space` | Filter options | +| `Enter` | Open selected | +| `Ctrl-h` | Open horizontally | +| `Ctrl-v` | Open vertically | +| `Escape`, `Ctrl-c` | Close picker | diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 9be2a73e2..6f584178a 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -336,6 +336,10 @@ impl Component for Picker { code: KeyCode::BackTab, .. } + | KeyEvent { + code: KeyCode::Char('k'), + modifiers: KeyModifiers::CONTROL, + } | KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::CONTROL, @@ -349,6 +353,10 @@ impl Component for Picker { | KeyEvent { code: KeyCode::Tab, .. } + | KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::CONTROL, + } | KeyEvent { code: KeyCode::Char('n'), modifiers: KeyModifiers::CONTROL, From 9688cb74a1e931edfa625cd28a828e728fcea675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 19 Oct 2021 23:57:22 +0900 Subject: [PATCH 020/122] Update dependencies to bump crossterm to 0.22.1 Fixes #825 Fixes #690 --- Cargo.lock | 44 +++++++++++++++++++++---------------------- helix-term/Cargo.toml | 2 +- helix-tui/Cargo.toml | 2 +- helix-view/Cargo.toml | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f7393cc0..aa6fb1419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,9 +122,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" dependencies = [ "bitflags", "crossterm_winapi", @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi", ] @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -549,9 +549,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" [[package]] name = "libloading" @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -738,9 +738,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" dependencies = [ "unicode-xid", ] @@ -767,9 +767,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -956,9 +956,9 @@ checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" [[package]] name = "slab" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "slotmap" @@ -983,9 +983,9 @@ checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" [[package]] name = "syn" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4eac2e6c19f5c3abc0c229bea31ff0b9b091c7b14990e8924b92902a303a0c0" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", @@ -1078,9 +1078,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb" +checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" dependencies = [ "proc-macro2", "quote", @@ -1128,9 +1128,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-general-category" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index fe4da96ef..244d3c130 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -31,7 +31,7 @@ once_cell = "1.8" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } num_cpus = "1" tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.21", features = ["event-stream"] } +crossterm = { version = "0.22", features = ["event-stream"] } signal-hook = "0.3" 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 80a772a46..2b42d299c 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.8" -crossterm = { version = "0.21", optional = true } +crossterm = { version = "0.22", optional = true } serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.4", path = "../helix-view", features = ["term"] } helix-core = { version = "0.4", path = "../helix-core" } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index c0a39700e..fce3fdd13 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -18,7 +18,7 @@ bitflags = "1.3" anyhow = "1" helix-core = { version = "0.4", path = "../helix-core" } helix-lsp = { version = "0.4", path = "../helix-lsp"} -crossterm = { version = "0.21", optional = true } +crossterm = { version = "0.22", optional = true } # Conversion traits once_cell = "1.8" From e9b23c29d8e8bca1d4140d02350bdae824a651b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 20 Oct 2021 00:00:28 +0900 Subject: [PATCH 021/122] Ignore errors when disabling mouse capture --- helix-term/src/application.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index a7281ecf6..82ad04d78 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -563,7 +563,9 @@ impl Application { let mut stdout = stdout(); // reset cursor shape write!(stdout, "\x1B[2 q")?; - execute!(stdout, DisableMouseCapture)?; + // Ignore errors on disabling, this might trigger on windows if we call + // disable without calling enable previously + let _ = execute!(stdout, DisableMouseCapture); execute!(stdout, terminal::LeaveAlternateScreen)?; terminal::disable_raw_mode()?; Ok(()) From b1ebd7a07e55f8ff5b8ef9fcbf09940a9f4a4f39 Mon Sep 17 00:00:00 2001 From: radical3dd Date: Thu, 21 Oct 2021 02:44:53 +0200 Subject: [PATCH 022/122] Replace current selection with all yanked values. (#882) --- helix-term/src/commands.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2811f293a..9f54292d8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3782,11 +3782,21 @@ fn replace_with_yanked(cx: &mut Context) { let registers = &mut cx.editor.registers; if let Some(values) = registers.read(reg_name) { - if let Some(yank) = values.first() { + if !values.is_empty() { + let repeat = std::iter::repeat( + values + .last() + .map(|value| Tendril::from_slice(value)) + .unwrap(), + ); + let mut values = values + .iter() + .map(|value| Tendril::from_slice(value)) + .chain(repeat); let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { if !range.is_empty() { - (range.from(), range.to(), Some(yank.as_str().into())) + (range.from(), range.to(), Some(values.next().unwrap())) } else { (range.from(), range.to(), None) } From f467154e18a146d39acdc73e2f5bfacc5aadf8e4 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 21 Oct 2021 20:58:26 -0400 Subject: [PATCH 023/122] Add `Alt-,` to `keymap.md`, and replace hard-to-see commas with slashes (#884) * Add `A-,` to `keymap.md`, and remove out-of-place commas * Update book/src/keymap.md Co-authored-by: Ivan Tham * Add slashes in place of previous commas in `keymap.md` Co-authored-by: Ivan Tham --- book/src/keymap.md | 65 +++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 1e19a4eb4..277cced23 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -6,38 +6,38 @@ > NOTE: Unlike vim, `f`, `F`, `t` and `T` are not confined to the current line. -| Key | Description | Command | -| ----- | ----------- | ------- | -| `h`, `Left` | Move left | `move_char_left` | -| `j`, `Down` | Move down | `move_char_right` | -| `k`, `Up` | Move up | `move_line_up` | -| `l`, `Right` | Move right | `move_line_down` | -| `w` | Move next word start | `move_next_word_start` | -| `b` | Move previous word start | `move_prev_word_start` | -| `e` | Move next word end | `move_next_word_end` | -| `W` | Move next WORD start | `move_next_long_word_start` | -| `B` | Move previous WORD start | `move_prev_long_word_start` | -| `E` | Move next WORD end | `move_next_long_word_end` | -| `t` | Find 'till next char | `find_till_char` | -| `f` | Find next char | `find_next_char` | -| `T` | Find 'till previous char | `till_prev_char` | -| `F` | Find previous char | `find_prev_char` | -| `Home` | Move to the start of the line | `goto_line_start` | -| `End` | Move to the end of the line | `goto_line_end` | -| `PageUp` | Move page up | `page_up` | -| `PageDown` | Move page down | `page_down` | -| `Ctrl-u` | Move half page up | `half_page_up` | -| `Ctrl-d` | Move half page down | `half_page_down` | -| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` | -| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` | -| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` | -| `g` | Enter [goto mode](#goto-mode) | N/A | -| `m` | Enter [match mode](#match-mode) | N/A | -| `:` | Enter command mode | `command_mode` | -| `z` | Enter [view mode](#view-mode) | N/A | -| `Z` | Enter sticky [view mode](#view-mode) | N/A | -| `Ctrl-w` | Enter [window mode](#window-mode) | N/A | -| `Space` | Enter [space mode](#space-mode) | N/A | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `h`/`Left` | Move left | `move_char_left` | +| `j`/`Down` | Move down | `move_char_right` | +| `k`/`Up` | Move up | `move_line_up` | +| `l`/`Right` | Move right | `move_line_down` | +| `w` | Move next word start | `move_next_word_start` | +| `b` | Move previous word start | `move_prev_word_start` | +| `e` | Move next word end | `move_next_word_end` | +| `W` | Move next WORD start | `move_next_long_word_start` | +| `B` | Move previous WORD start | `move_prev_long_word_start` | +| `E` | Move next WORD end | `move_next_long_word_end` | +| `t` | Find 'till next char | `find_till_char` | +| `f` | Find next char | `find_next_char` | +| `T` | Find 'till previous char | `till_prev_char` | +| `F` | Find previous char | `find_prev_char` | +| `Home` | Move to the start of the line | `goto_line_start` | +| `End` | Move to the end of the line | `goto_line_end` | +| `PageUp` | Move page up | `page_up` | +| `PageDown` | Move page down | `page_down` | +| `Ctrl-u` | Move half page up | `half_page_up` | +| `Ctrl-d` | Move half page down | `half_page_down` | +| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` | +| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` | +| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` | +| `g` | Enter [goto mode](#goto-mode) | N/A | +| `m` | Enter [match mode](#match-mode) | N/A | +| `:` | Enter command mode | `command_mode` | +| `z` | Enter [view mode](#view-mode) | N/A | +| `Z` | Enter sticky [view mode](#view-mode) | N/A | +| `Ctrl-w` | Enter [window mode](#window-mode) | N/A | +| `Space` | Enter [space mode](#space-mode) | N/A | ### Changes @@ -86,6 +86,7 @@ | `;` | Collapse selection onto a single cursor | `collapse_selection` | | `Alt-;` | Flip selection cursor and anchor | `flip_selections` | | `,` | Keep only the primary selection | `keep_primary_selection` | +| `Alt-,` | Remove the primary selection | `remove_primary_selection` | | `C` | Copy selection onto the next line | `copy_selection_on_next_line` | | `Alt-C` | Copy selection onto the previous line | `copy_selection_on_prev_line` | | `(` | Rotate main selection backward | `rotate_selections_backward` | From 2edc85e953e003f8b8121e7757e9d45f5216f649 Mon Sep 17 00:00:00 2001 From: Ray Gervais Date: Thu, 21 Oct 2021 20:58:49 -0400 Subject: [PATCH 024/122] fixes: missing info, warning diagnostic (#890) --- runtime/themes/base16_default_dark.toml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml index 625fc5abb..a5dc0852a 100644 --- a/runtime/themes/base16_default_dark.toml +++ b/runtime/themes/base16_default_dark.toml @@ -1,7 +1,7 @@ # Author: RayGervais "ui.background" = { bg = "base00" } -"ui.menu" = "base01" +"ui.menu" = "base01" "ui.menu.selected" = { fg = "base04", bg = "base01" } "ui.linenr" = {fg = "base01" } "ui.popup" = { bg = "base01" } @@ -12,24 +12,33 @@ "ui.statusline" = {fg = "base04", bg = "base01" } "ui.help" = { fg = "base04", bg = "base01" } "ui.cursor" = { fg = "base05", modifiers = ["reversed"] } -"ui.text" = { fg = "base05" } -"operator" = "base05" +"ui.text" = { fg = "base05" } +"operator" = "base05" "ui.text.focus" = { fg = "base05" } "variable" = "base08" -"number" = "base09" -"constant" = "base09" +"number" = "base09" +"constant" = "base09" "attributes" = "base09" "type" = "base0A" "ui.cursor.match" = { fg = "base0A", modifiers = ["underlined"] } "strings" = "base0B" "property" = "base0B" "escape" = "base0C" -"function" = "base0D" +"function" = "base0D" "constructor" = "base0D" -"special" = "base0D" +"special" = "base0D" "keyword" = "base0E" -"label" = "base0E" +"label" = "base0E" "namespace" = "base0E" +"ui.popup" = { bg = "base01" } +"ui.window" = { bg = "base00" } +"ui.help" = { bg = "base01", fg = "base06" } + +"info" = "base03" +"hint" = "base03" +"debug" = "base03" +"diagnostic" = "base03" +"error" = "base0E" [palette] base00 = "#181818" # Default Background From 3b032e8e1fd342261b153aeb375f9c0e8d882b34 Mon Sep 17 00:00:00 2001 From: Daniel S Poulin Date: Thu, 21 Oct 2021 21:02:05 -0400 Subject: [PATCH 025/122] First stab at ignoring compressed files from picker (#767) --- helix-term/src/ui/mod.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 810a99660..30a9ec6bc 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -91,9 +91,25 @@ pub fn regex_prompt( } pub fn file_picker(root: PathBuf) -> FilePicker { - use ignore::Walk; + use ignore::{types::TypesBuilder, WalkBuilder}; use std::time; - let files = Walk::new(&root).filter_map(|entry| { + + // We want to exclude files that the editor can't handle yet + let mut type_builder = TypesBuilder::new(); + let mut walk_builder = WalkBuilder::new(&root); + let walk_builder = match type_builder.add( + "compressed", + "*.{zip,gz,bz2,zst,lzo,sz,tgz,tbz2,lz,lz4,lzma,lzo,z,Z,xz,7z,rar,cab}", + ) { + Err(_) => &walk_builder, + _ => { + type_builder.negate("all"); + let excluded_types = type_builder.build().unwrap(); + walk_builder.types(excluded_types) + } + }; + + let files = walk_builder.build().filter_map(|entry| { let entry = entry.ok()?; // Path::is_dir() traverses symlinks, so we use it over DirEntry::is_dir if entry.path().is_dir() { From 182a59b5528075c0171756bff71275db8a7995f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 22 Oct 2021 12:07:41 +0900 Subject: [PATCH 026/122] Update to rust 1.56 + 2021 edition --- flake.lock | 68 ++++++++++++++++++++++++++++--------- flake.nix | 2 ++ helix-core/Cargo.toml | 2 +- helix-lsp/Cargo.toml | 2 +- helix-syntax/Cargo.toml | 2 +- helix-term/Cargo.toml | 2 +- helix-term/src/ui/menu.rs | 32 ++++++++--------- helix-term/src/ui/picker.rs | 23 ++++--------- helix-tui/Cargo.toml | 2 +- helix-view/Cargo.toml | 2 +- helix-view/src/theme.rs | 1 - 11 files changed, 82 insertions(+), 56 deletions(-) diff --git a/flake.lock b/flake.lock index 21e44c6e9..2029d5809 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "devshell": { "locked": { - "lastModified": 1630239564, - "narHash": "sha256-lv7atkVE1+dFw0llmzONsbSIo5ao85KpNSRoFk4K8vU=", + "lastModified": 1632436039, + "narHash": "sha256-OtITeVWcKXn1SpVEnImpTGH91FycCskGBPqmlxiykv4=", "owner": "numtide", "repo": "devshell", - "rev": "bd86d3a2bb28ce4d223315e0eca0d59fef8a0a73", + "rev": "7a7a7aa0adebe5488e5abaec688fd9ae0f8ea9c6", "type": "github" }, "original": { @@ -15,6 +15,21 @@ "type": "github" } }, + "flake-utils": { + "locked": { + "lastModified": 1623875721, + "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "flakeCompat": { "flake": false, "locked": { @@ -37,14 +52,16 @@ "nixpkgs": [ "nixpkgs" ], - "rustOverlay": "rustOverlay" + "rustOverlay": [ + "rust-overlay" + ] }, "locked": { - "lastModified": 1631254163, - "narHash": "sha256-8+nOGLH1fXwWnNMTQq/Igk434BzZF5Vld45xLDLiNDQ=", + "lastModified": 1634796585, + "narHash": "sha256-CW4yx6omk5qCXUIwXHp/sztA7u0SpyLq9NEACPnkiz8=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "432d8504a32232e8d74710024d5bf5cc31767651", + "rev": "a84a2137a396f303978f1d48341e0390b0e16a8b", "type": "github" }, "original": { @@ -55,11 +72,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1631206977, - "narHash": "sha256-o3Dct9aJ5ht5UaTUBzXrRcK1RZt2eG5/xSlWJuUCVZM=", + "lastModified": 1634782485, + "narHash": "sha256-psfh4OQSokGXG0lpq3zKFbhOo3QfoeudRcaUnwMRkQo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4f6d8095fd51954120a1d08ea5896fe42dc3923b", + "rev": "34ad3ffe08adfca17fcb4e4a47bb5f3b113687be", "type": "github" }, "original": { @@ -69,21 +86,40 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1628186154, + "narHash": "sha256-r2d0wvywFnL9z4iptztdFMhaUIAaGzrSs7kSok0PgmE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "06552b72346632b6943c8032e57e702ea12413bf", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "flakeCompat": "flakeCompat", "nixCargoIntegration": "nixCargoIntegration", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" } }, - "rustOverlay": { - "flake": false, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, "locked": { - "lastModified": 1631240108, - "narHash": "sha256-ffsTkAGyQLxu4E28nVcqwc8xFL/H1UEwrRw2ITI43Aw=", + "lastModified": 1634869268, + "narHash": "sha256-RVAcEFlFU3877Mm4q/nbXGEYTDg/wQNhzmXGMTV6wBs=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "3a29d5e726b855d9463eb5dfe04f1ec14d413289", + "rev": "c02c2d86354327317546501af001886fbb53d374", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bcc9383e7..c11452683 100644 --- a/flake.nix +++ b/flake.nix @@ -3,9 +3,11 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + rust-overlay.url = "github:oxalica/rust-overlay"; nixCargoIntegration = { url = "github:yusdacra/nix-cargo-integration"; inputs.nixpkgs.follows = "nixpkgs"; + inputs.rustOverlay.follows = "rust-overlay"; }; flakeCompat = { url = "github:edolstra/flake-compat"; diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 510964536..93ebb1332 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-core" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "Helix editor core editing primitives" categories = ["editor"] diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index b4c8c1398..455407ad2 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-lsp" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "LSP client implementation for Helix project" categories = ["editor"] diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml index 9c2b82759..122fa4603 100644 --- a/helix-syntax/Cargo.toml +++ b/helix-syntax/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-syntax" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "Tree-sitter grammars support" categories = ["editor"] diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 244d3c130..78afab01e 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -3,7 +3,7 @@ name = "helix-term" version = "0.4.1" description = "A post-modern text editor." authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" categories = ["editor", "command-line-utilities"] repository = "https://github.com/helix-editor/helix" diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 055593fda..1130089df 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -64,25 +64,23 @@ impl Menu { } pub fn score(&mut self, pattern: &str) { - // need to borrow via pattern match otherwise it complains about simultaneous borrow - let Self { - ref mut matcher, - ref mut matches, - ref options, - .. - } = *self; - // reuse the matches allocation - matches.clear(); - matches.extend(options.iter().enumerate().filter_map(|(index, option)| { - let text = option.filter_text(); - // TODO: using fuzzy_indices could give us the char idx for match highlighting - matcher - .fuzzy_match(text, pattern) - .map(|score| (index, score)) - })); + self.matches.clear(); + self.matches.extend( + self.options + .iter() + .enumerate() + .filter_map(|(index, option)| { + let text = option.filter_text(); + // TODO: using fuzzy_indices could give us the char idx for match highlighting + self.matcher + .fuzzy_match(text, pattern) + .map(|score| (index, score)) + }), + ); // matches.sort_unstable_by_key(|(_, score)| -score); - matches.sort_unstable_by_key(|(index, _score)| options[*index].sort_text()); + self.matches + .sort_unstable_by_key(|(index, _score)| self.options[*index].sort_text()); // reset cursor position self.cursor = None; diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 6f584178a..1f08cf13b 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -233,37 +233,28 @@ impl Picker { } pub fn score(&mut self) { - // need to borrow via pattern match otherwise it complains about simultaneous borrow - let Self { - ref mut matcher, - ref mut matches, - ref filters, - ref format_fn, - .. - } = *self; - let pattern = &self.prompt.line; // reuse the matches allocation - matches.clear(); - matches.extend( + self.matches.clear(); + self.matches.extend( self.options .iter() .enumerate() .filter_map(|(index, option)| { // filter options first before matching - if !filters.is_empty() { - filters.binary_search(&index).ok()?; + if !self.filters.is_empty() { + self.filters.binary_search(&index).ok()?; } // TODO: maybe using format_fn isn't the best idea here - let text = (format_fn)(option); + let text = (self.format_fn)(option); // TODO: using fuzzy_indices could give us the char idx for match highlighting - matcher + self.matcher .fuzzy_match(&text, pattern) .map(|score| (index, score)) }), ); - matches.sort_unstable_by_key(|(_, score)| -score); + self.matches.sort_unstable_by_key(|(_, score)| -score); // reset cursor position self.cursor = 0; diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 2b42d299c..f0c0d7e2d 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Blaž Hrastnik "] description = """ A library to build rich terminal user interfaces or dashboards """ -edition = "2018" +edition = "2021" license = "MPL-2.0" categories = ["editor"] repository = "https://github.com/helix-editor/helix" diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index fce3fdd13..ef09b964f 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -2,7 +2,7 @@ name = "helix-view" version = "0.4.1" authors = ["Blaž Hrastnik "] -edition = "2018" +edition = "2021" license = "MPL-2.0" description = "UI abstractions for use in backends" categories = ["editor"] diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 9c33685be..757316bde 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - convert::TryFrom, path::{Path, PathBuf}, }; From 96945be1a8d551f09865f13a7d8972174dbbc1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 22 Oct 2021 12:46:51 +0900 Subject: [PATCH 027/122] Fix doctest broken on 2021 edition --- helix-term/src/compositor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index cad1df054..dc8b91d75 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -207,7 +207,7 @@ pub trait AnyComponent { /// /// ```rust /// use helix_term::{ui::Text, compositor::Component}; - /// let boxed: Box = Box::new(Text::new("text".to_string())); + /// let boxed: Box = Box::new(Text::new("text".to_string())); /// let text: Box = boxed.as_boxed_any().downcast().unwrap(); /// ``` fn as_boxed_any(self: Box) -> Box; From 75a8e8afbd06b69f182f6fb9c0d3b0cd2241c0c8 Mon Sep 17 00:00:00 2001 From: Rowan H <37909949+RLHerbert@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:54:02 -0700 Subject: [PATCH 028/122] Typo fix (#893) --- book/src/remapping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index 81f45da3f..3369f0315 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -2,7 +2,7 @@ One-way key remapping is temporarily supported via a simple TOML configuration file. (More powerful solutions such as rebinding via commands will be -available in the feature). +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 From 6c995fa6906f9d6895d27fd4b595ba254cbb966a Mon Sep 17 00:00:00 2001 From: Rowan H <37909949+RLHerbert@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:54:23 -0700 Subject: [PATCH 029/122] Fixed incorrect move commands (#894) --- book/src/keymap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 277cced23..3dfc5dc49 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -9,9 +9,9 @@ | Key | Description | Command | | ----- | ----------- | ------- | | `h`/`Left` | Move left | `move_char_left` | -| `j`/`Down` | Move down | `move_char_right` | +| `j`/`Down` | Move down | `move_line_down` | | `k`/`Up` | Move up | `move_line_up` | -| `l`/`Right` | Move right | `move_line_down` | +| `l`/`Right` | Move right | `move_char_right` | | `w` | Move next word start | `move_next_word_start` | | `b` | Move previous word start | `move_prev_word_start` | | `e` | Move next word end | `move_next_word_end` | From 787ba4f233a38e42cfeab5c5125122d7d7b85e8f Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sat, 23 Oct 2021 01:57:21 +0200 Subject: [PATCH 030/122] CMake support (#888) --- .gitmodules | 4 + helix-syntax/languages/tree-sitter-cmake | 1 + languages.toml | 8 ++ runtime/queries/cmake/highlights.scm | 97 ++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 160000 helix-syntax/languages/tree-sitter-cmake create mode 100644 runtime/queries/cmake/highlights.scm diff --git a/.gitmodules b/.gitmodules index 01260b847..7ed34ad39 100644 --- a/.gitmodules +++ b/.gitmodules @@ -130,3 +130,7 @@ path = helix-syntax/languages/tree-sitter-tsq url = https://github.com/tree-sitter/tree-sitter-tsq shallow = true +[submodule "helix-syntax/languages/tree-sitter-cmake"] + path = helix-syntax/languages/tree-sitter-cmake + url = https://github.com/uyha/tree-sitter-cmake + shallow = true diff --git a/helix-syntax/languages/tree-sitter-cmake b/helix-syntax/languages/tree-sitter-cmake new file mode 160000 index 000000000..f6616f1e4 --- /dev/null +++ b/helix-syntax/languages/tree-sitter-cmake @@ -0,0 +1 @@ +Subproject commit f6616f1e417ee8b62daf251aa1daa5d73781c596 diff --git a/languages.toml b/languages.toml index 67f4d3d6f..a393b4a6b 100644 --- a/languages.toml +++ b/languages.toml @@ -346,3 +346,11 @@ file-types = ["scm"] roots = [] comment-token = ";" indent = { tab-width = 2, unit = " " } + +[[language]] +name = "cmake" +scope = "source.cmake" +file-types = ["cmake", "CMakeLists.txt"] +roots = [] +comment-token = "#" +indent = { tab-width = 2, unit = " " } diff --git a/runtime/queries/cmake/highlights.scm b/runtime/queries/cmake/highlights.scm new file mode 100644 index 000000000..71e9b5d9c --- /dev/null +++ b/runtime/queries/cmake/highlights.scm @@ -0,0 +1,97 @@ +[ + (quoted_argument) + (bracket_argument) + ] @string + +(variable) @variable + +[ + (bracket_comment) + (line_comment) + ] @comment + +(normal_command (identifier) @function) + +["ENV" "CACHE"] @string.special.symbol +["$" "{" "}" "<" ">"] @punctuation +["(" ")"] @punctuation.bracket + +[ + (function) + (endfunction) + (macro) + (endmacro) + ] @keyword.function + +[ + (if) + (elseif) + (else) + (endif) + ] @keyword.control.conditional + +[ + (foreach) + (endforeach) + (while) + (endwhile) + ] @keyword.control.repeat + +(function_command + (function) + . (argument) @function + (argument)* @variable.parameter + ) + +(macro_command + (macro) + . (argument) @function.macro + (argument)* @variable.parameter + ) + +(normal_command + (identifier) @function.builtin + . (argument) @variable + (#match? @function.builtin "^(?i)(set)$")) + +(normal_command + (identifier) @function.builtin + . (argument) + (argument) @constant + (#match? @constant "^(?:PARENT_SCOPE|CACHE)$") + (#match? @function.builtin "^(?i)(unset)$")) + +(normal_command + (identifier) @function.builtin + . (argument) + . (argument) + (argument) @constant + (#match? @constant "^(?:PARENT_SCOPE|CACHE|FORCE)$") + (#match? @function.builtin "^(?i)(set)$") + ) + +((argument) @constant.builtin.boolean + (#match? @constant.builtin.boolean "^(?i)(?:1|on|yes|true|y|0|off|no|false|n|ignore|notfound|.*-notfound)$") + ) + +(if_command + (if) + (argument) @operator + (#match? @operator "^(?:NOT|AND|OR|COMMAND|POLICY|TARGET|TEST|DEFINED|IN_LIST|EXISTS|IS_NEWER_THAN|IS_DIRECTORY|IS_SYMLINK|IS_ABSOLUTE|MATCHES|LESS|GREATER|EQUAL|LESS_EQUAL|GREATER_EQUAL|STRLESS|STRGREATER|STREQUAL|STRLESS_EQUAL|STRGREATER_EQUAL|VERSION_LESS|VERSION_GREATER|VERSION_EQUAL|VERSION_LESS_EQUAL|VERSION_GREATER_EQUAL)$") +) + +(normal_command + (identifier) @function.builtin + . (argument) + (argument) @constant + (#match? @constant "^(?:ALL|COMMAND|DEPENDS|BYPRODUCTS|WORKING_DIRECTORY|COMMENT|JOB_POOL|VERBATIM|USES_TERMINAL|COMMAND_EXPAND_LISTS|SOURCES)$") + (#match? @function.builtin "^(?i)(add_custom_target)$") + ) + +(normal_command + (identifier) @function.builtin + (argument) @constant + (#match? @constant "^(?:OUTPUT|COMMAND|MAIN_DEPENDENCY|DEPENDS|BYPRODUCTS|IMPLICIT_DEPENDS|WORKING_DIRECTORY|COMMENT|DEPFILE|JOB_POOL|VERBATIM|APPEND|USES_TERMINAL|COMMAND_EXPAND_LISTS)$") + (#match? @function.builtin "^(?i)(add_custom_command)$") + ) + From c5298caa752dee136ab1a21dae27a702a00d8eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 23 Oct 2021 11:33:17 +0900 Subject: [PATCH 031/122] book: Add a link to tutor.txt --- book/src/usage.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/src/usage.md b/book/src/usage.md index 9ee8634c6..2de8d01a9 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -2,6 +2,8 @@ (Currently not fully documented, see the [keymappings](./keymap.md) list for more.) +See [tutor.txt](https://github.com/helix-editor/helix/blob/master/runtime/tutor.txt) for a vimtutor-like introduction. + ## 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: From 4ee92cad19cc94f0751f91fa9391d1899353d740 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Sat, 23 Oct 2021 08:11:19 +0530 Subject: [PATCH 032/122] Add treesitter textobjects (#728) * Add treesitter textobject queries Only for Go, Python and Rust for now. * Add tree-sitter textobjects Only has functions and class objects as of now. * Fix tests * Add docs for tree-sitter textobjects * Add guide for creating new textobject queries * Add parameter textobject Only parameter.inside is implemented now, parameter.around will probably require custom predicates akin to nvim' `make-range` since we want to select a trailing comma too (a comma will be an anonymous node and matching against them doesn't work similar to named nodes) * Simplify TextObject cell init --- book/src/SUMMARY.md | 2 + book/src/guides/README.md | 4 ++ book/src/guides/textobject.md | 30 +++++++++++++++ book/src/usage.md | 13 +++++-- helix-core/src/indent.rs | 1 + helix-core/src/syntax.rs | 43 +++++++++++++++++++++- helix-core/src/textobject.rs | 51 ++++++++++++++++++++++++++ helix-term/src/commands.rs | 19 ++++++++++ runtime/queries/go/textobjects.scm | 21 +++++++++++ runtime/queries/python/textobjects.scm | 14 +++++++ runtime/queries/rust/textobjects.scm | 26 +++++++++++++ 11 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 book/src/guides/README.md create mode 100644 book/src/guides/textobject.md create mode 100644 runtime/queries/go/textobjects.scm create mode 100644 runtime/queries/python/textobjects.scm create mode 100644 runtime/queries/rust/textobjects.scm diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 3fa8e0676..56f50e212 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -8,3 +8,5 @@ - [Keymap](./keymap.md) - [Key Remapping](./remapping.md) - [Hooks](./hooks.md) +- [Guides](./guides/README.md) + - [Adding Textobject Queries](./guides/textobject.md) diff --git a/book/src/guides/README.md b/book/src/guides/README.md new file mode 100644 index 000000000..96e629783 --- /dev/null +++ b/book/src/guides/README.md @@ -0,0 +1,4 @@ +# Guides + +This section contains guides for adding new language server configurations, +tree-sitter grammers, textobject queries, etc. diff --git a/book/src/guides/textobject.md b/book/src/guides/textobject.md new file mode 100644 index 000000000..50b3b574a --- /dev/null +++ b/book/src/guides/textobject.md @@ -0,0 +1,30 @@ +# 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 +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 +them under your local runtime directory (`~/.config/helix/runtime` on Linux +for example). + +The following [captures][tree-sitter-captures] are recognized: + +| Capture Name | +| --- | +| `function.inside` | +| `function.around` | +| `class.inside` | +| `class.around` | +| `parameter.inside` | + +[Example query files][textobject-examples] can be found in the helix GitHub repository. + +[textobjects]: ../usage.md#textobjects +[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/usage.md b/book/src/usage.md index 2de8d01a9..d31e03a1b 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -51,9 +51,10 @@ Multiple characters are currently not supported, but planned. ## Textobjects -Currently supported: `word`, `surround`. +Currently supported: `word`, `surround`, `function`, `class`, `parameter`. ![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) @@ -62,5 +63,11 @@ Currently supported: `word`, `surround`. | --- | --- | | `w` | Word | | `(`, `[`, `'`, etc | Specified surround pairs | - -Textobjects based on treesitter, like `function`, `class`, etc are planned. +| `f` | Function | +| `c` | Class | +| `p` | Parameter | + +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](https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=) +currently have the query file implemented. Contributions are welcome ! diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index d9a0155fb..20f034ea7 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -464,6 +464,7 @@ where unit: String::from(" "), }), indent_query: OnceCell::new(), + textobject_query: OnceCell::new(), }], }); diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 0929e38fa..f4b4535bd 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -49,7 +49,7 @@ pub struct Configuration { #[serde(rename_all = "kebab-case")] pub struct LanguageConfiguration { #[serde(rename = "name")] - pub(crate) language_id: String, + pub language_id: String, pub scope: String, // source.rust pub file_types: Vec, // filename ends_with? pub roots: Vec, // these indicate project roots <.git, Cargo.toml> @@ -76,6 +76,8 @@ pub struct LanguageConfiguration { #[serde(skip)] pub(crate) indent_query: OnceCell>, + #[serde(skip)] + pub(crate) textobject_query: OnceCell>, } #[derive(Debug, Serialize, Deserialize)] @@ -105,6 +107,32 @@ pub struct IndentQuery { pub outdent: HashSet, } +#[derive(Debug)] +pub struct TextObjectQuery { + pub query: Query, +} + +impl TextObjectQuery { + /// Run the query on the given node and return sub nodes which match given + /// capture ("function.inside", "class.around", etc). + pub fn capture_nodes<'a>( + &'a self, + capture_name: &str, + node: Node<'a>, + slice: RopeSlice<'a>, + cursor: &'a mut QueryCursor, + ) -> Option>> { + let capture_idx = self.query.capture_index_for_name(capture_name)?; + let captures = cursor.captures(&self.query, node, RopeProvider(slice)); + + captures + .filter_map(move |(mat, idx)| { + (mat.captures[idx].index == capture_idx).then(|| mat.captures[idx].node) + }) + .into() + } +} + fn load_runtime_file(language: &str, filename: &str) -> Result { let path = crate::RUNTIME_DIR .join("queries") @@ -153,7 +181,6 @@ impl LanguageConfiguration { // highlights_query += "\n(ERROR) @error"; let injections_query = read_query(&language, "injections.scm"); - let locals_query = read_query(&language, "locals.scm"); if highlights_query.is_empty() { @@ -203,6 +230,18 @@ impl LanguageConfiguration { .as_ref() } + pub fn textobject_query(&self) -> Option<&TextObjectQuery> { + self.textobject_query + .get_or_init(|| -> Option { + let lang_name = self.language_id.to_ascii_lowercase(); + let query_text = read_query(&lang_name, "textobjects.scm"); + let lang = self.highlight_config.get()?.as_ref()?.language; + let query = Query::new(lang, &query_text).ok()?; + Some(TextObjectQuery { query }) + }) + .as_ref() + } + pub fn scope(&self) -> &str { &self.scope } diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index b965f6dfc..975ed115b 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -1,9 +1,13 @@ +use std::fmt::Display; + use ropey::RopeSlice; +use tree_sitter::{Node, QueryCursor}; use crate::chars::{categorize_char, char_is_whitespace, CharCategory}; use crate::graphemes::next_grapheme_boundary; use crate::movement::Direction; use crate::surround; +use crate::syntax::LanguageConfiguration; use crate::Range; fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize { @@ -51,6 +55,15 @@ pub enum TextObject { Inside, } +impl Display for TextObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Around => "around", + Self::Inside => "inside", + }) + } +} + // count doesn't do anything yet pub fn textobject_word( slice: RopeSlice, @@ -108,6 +121,44 @@ pub fn textobject_surround( .unwrap_or(range) } +/// Transform the given range to select text objects based on tree-sitter. +/// `object_name` is a query capture base name like "function", "class", etc. +/// `slice_tree` is the tree-sitter node corresponding to given text slice. +pub fn textobject_treesitter( + slice: RopeSlice, + range: Range, + textobject: TextObject, + object_name: &str, + slice_tree: Node, + lang_config: &LanguageConfiguration, + _count: usize, +) -> Range { + let get_range = move || -> Option { + let byte_pos = slice.char_to_byte(range.cursor(slice)); + + let capture_name = format!("{}.{}", object_name, textobject); // eg. function.inner + let mut cursor = QueryCursor::new(); + let node = lang_config + .textobject_query()? + .capture_nodes(&capture_name, slice_tree, slice, &mut cursor)? + .filter(|node| node.byte_range().contains(&byte_pos)) + .min_by_key(|node| node.byte_range().len())?; + + let len = slice.len_bytes(); + let start_byte = node.start_byte(); + let end_byte = node.end_byte(); + if start_byte >= len || end_byte >= len { + return None; + } + + let start_char = slice.byte_to_char(start_byte); + let end_char = slice.byte_to_char(end_byte); + + Some(Range::new(start_char, end_char)) + }; + get_range().unwrap_or(range) +} + #[cfg(test)] mod test { use super::TextObject::*; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9f54292d8..272a9d9a2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4465,9 +4465,28 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); + let textobject_treesitter = |obj_name: &str, range: Range| -> Range { + let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) { + Some(t) => t, + None => return range, + }; + textobject::textobject_treesitter( + text, + range, + objtype, + obj_name, + syntax.tree().root_node(), + lang_config, + count, + ) + }; + let selection = doc.selection(view.id).clone().transform(|range| { match ch { 'w' => textobject::textobject_word(text, range, objtype, count), + 'c' => textobject_treesitter("class", range), + 'f' => textobject_treesitter("function", range), + 'p' => textobject_treesitter("parameter", range), // TODO: cancel new ranges if inconsistent surround matches across lines ch if !ch.is_ascii_alphanumeric() => { textobject::textobject_surround(text, range, objtype, ch, count) diff --git a/runtime/queries/go/textobjects.scm b/runtime/queries/go/textobjects.scm new file mode 100644 index 000000000..9bcfc6903 --- /dev/null +++ b/runtime/queries/go/textobjects.scm @@ -0,0 +1,21 @@ +(function_declaration + body: (block)? @function.inside) @function.around + +(func_literal + (_)? @function.inside) @function.around + +(method_declaration + body: (block)? @function.inside) @function.around + +;; struct and interface declaration as class textobject? +(type_declaration + (type_spec (type_identifier) (struct_type (field_declaration_list (_)?) @class.inside))) @class.around + +(type_declaration + (type_spec (type_identifier) (interface_type (method_spec_list (_)?) @class.inside))) @class.around + +(parameter_list + (_) @parameter.inside) + +(argument_list + (_) @parameter.inside) diff --git a/runtime/queries/python/textobjects.scm b/runtime/queries/python/textobjects.scm new file mode 100644 index 000000000..a52538afd --- /dev/null +++ b/runtime/queries/python/textobjects.scm @@ -0,0 +1,14 @@ +(function_definition + body: (block)? @function.inside) @function.around + +(class_definition + body: (block)? @class.inside) @class.around + +(parameters + (_) @parameter.inside) + +(lambda_parameters + (_) @parameter.inside) + +(argument_list + (_) @parameter.inside) diff --git a/runtime/queries/rust/textobjects.scm b/runtime/queries/rust/textobjects.scm new file mode 100644 index 000000000..e3132687a --- /dev/null +++ b/runtime/queries/rust/textobjects.scm @@ -0,0 +1,26 @@ +(function_item + body: (_) @function.inside) @function.around + +(struct_item + body: (_) @class.inside) @class.around + +(enum_item + body: (_) @class.inside) @class.around + +(union_item + body: (_) @class.inside) @class.around + +(trait_item + body: (_) @class.inside) @class.around + +(impl_item + body: (_) @class.inside) @class.around + +(parameters + (_) @parameter.inside) + +(closure_parameters + (_) @parameter.inside) + +(arguments + (_) @parameter.inside) From 0f886af4b993c836bb2d522f6e036362593ff8b8 Mon Sep 17 00:00:00 2001 From: Oskar Nehlin Date: Sat, 23 Oct 2021 13:06:40 +0200 Subject: [PATCH 033/122] Add commands for moving between splits with a direction (#860) * Add commands for moving between splits with a direction * Update keymaps * Change picker mapping * Add test and clean up some comments --- book/src/keymap.md | 18 ++-- helix-term/src/commands.rs | 20 ++++ helix-term/src/keymap.rs | 6 +- helix-term/src/ui/picker.rs | 2 +- helix-view/src/editor.rs | 18 +++- helix-view/src/tree.rs | 191 ++++++++++++++++++++++++++++++++++-- 6 files changed, 236 insertions(+), 19 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 3dfc5dc49..644dc1c9b 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -181,12 +181,16 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`). This layer is similar to vim keybindings as kakoune does not support window. -| Key | Description | Command | -| ----- | ------------- | ------- | -| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | -| `v`, `Ctrl-v` | Vertical right split | `vsplit` | -| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` | -| `q`, `Ctrl-q` | Close current window | `wclose` | +| Key | Description | Command | +| ----- | ------------- | ------- | +| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | +| `v`, `Ctrl-v` | Vertical right split | `vsplit` | +| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | +| `h`, `Ctrl-h` | Move to left split | `jump_view_left` | +| `j`, `Ctrl-j` | Move to split below | `jump_view_down` | +| `k`, `Ctrl-k` | Move to split above | `jump_view_up` | +| `l`, `Ctrl-l` | Move to right split | `jump_view_right` | +| `q`, `Ctrl-q` | Close current window | `wclose` | #### Space mode @@ -249,6 +253,6 @@ Keys to use within picker. Remapping currently not supported. | `Down`, `Ctrl-j`, `Ctrl-n` | Next entry | | `Ctrl-space` | Filter options | | `Enter` | Open selected | -| `Ctrl-h` | Open horizontally | +| `Ctrl-s` | Open horizontally | | `Ctrl-v` | Open vertically | | `Escape`, `Ctrl-c` | Close picker | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 272a9d9a2..9d73ba6e7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -301,6 +301,10 @@ impl Command { expand_selection, "Expand selection to parent syntax node", jump_forward, "Jump forward on jumplist", jump_backward, "Jump backward on jumplist", + jump_view_right, "Jump to the split to the right", + jump_view_left, "Jump to the split to the left", + jump_view_up, "Jump to the split above", + jump_view_down, "Jump to the split below", rotate_view, "Goto next window", hsplit, "Horizontal bottom split", vsplit, "Vertical right split", @@ -4373,6 +4377,22 @@ fn rotate_view(cx: &mut Context) { cx.editor.focus_next() } +fn jump_view_right(cx: &mut Context) { + cx.editor.focus_right() +} + +fn jump_view_left(cx: &mut Context) { + cx.editor.focus_left() +} + +fn jump_view_up(cx: &mut Context) { + cx.editor.focus_up() +} + +fn jump_view_down(cx: &mut Context) { + cx.editor.focus_down() +} + // split helper, clear it later fn split(cx: &mut Context, action: Action) { let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index cd4d3a32a..f877387c6 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -520,9 +520,13 @@ impl Default for Keymaps { "C-w" => { "Window" "C-w" | "w" => rotate_view, - "C-h" | "h" => hsplit, + "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-q" | "q" => wclose, + "C-h" | "h" => jump_view_left, + "C-j" | "j" => jump_view_down, + "C-k" | "k" => jump_view_up, + "C-l" | "l" => jump_view_right, }, // move under c diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 1f08cf13b..7e257c0b9 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -373,7 +373,7 @@ impl Component for Picker { return close_fn; } KeyEvent { - code: KeyCode::Char('h'), + code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, } => { if let Some(option) = self.selection() { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 52fca6d25..813c86fd5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -2,7 +2,7 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, graphics::{CursorKind, Rect}, theme::{self, Theme}, - tree::Tree, + tree::{self, Tree}, Document, DocumentId, View, ViewId, }; @@ -355,6 +355,22 @@ impl Editor { self.tree.focus_next(); } + pub fn focus_right(&mut self) { + self.tree.focus_direction(tree::Direction::Right); + } + + pub fn focus_left(&mut self) { + self.tree.focus_direction(tree::Direction::Left); + } + + pub fn focus_up(&mut self) { + self.tree.focus_direction(tree::Direction::Up); + } + + pub fn focus_down(&mut self) { + self.tree.focus_direction(tree::Direction::Down); + } + pub fn should_close(&self) -> bool { self.tree.is_empty() } diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 576f64f08..064334b12 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -47,13 +47,21 @@ impl Node { // TODO: screen coord to container + container coordinate helpers -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Layout { Horizontal, Vertical, // could explore stacked/tabbed } +#[derive(Debug, Clone, Copy)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + #[derive(Debug)] pub struct Container { layout: Layout, @@ -150,7 +158,6 @@ impl Tree { } => container, _ => unreachable!(), }; - if container.layout == layout { // insert node after the current item if there is children already let pos = if container.children.is_empty() { @@ -393,6 +400,112 @@ impl Tree { Traverse::new(self) } + // Finds the split in the given direction if it exists + pub fn find_split_in_direction(&self, id: ViewId, direction: Direction) -> Option { + let parent = self.nodes[id].parent; + // Base case, we found the root of the tree + if parent == id { + return None; + } + // Parent must always be a container + let parent_container = match &self.nodes[parent].content { + Content::Container(container) => container, + Content::View(_) => unreachable!(), + }; + + match (direction, parent_container.layout) { + (Direction::Up, Layout::Vertical) + | (Direction::Left, Layout::Horizontal) + | (Direction::Right, Layout::Horizontal) + | (Direction::Down, Layout::Vertical) => { + // The desired direction of movement is not possible within + // the parent container so the search must continue closer to + // the root of the split tree. + self.find_split_in_direction(parent, direction) + } + (Direction::Up, Layout::Horizontal) + | (Direction::Down, Layout::Horizontal) + | (Direction::Left, Layout::Vertical) + | (Direction::Right, Layout::Vertical) => { + // It's possible to move in the desired direction within + // the parent container so an attempt is made to find the + // correct child. + match self.find_child(id, &parent_container.children, direction) { + // Child is found, search is ended + Some(id) => Some(id), + // A child is not found. This could be because of either two scenarios + // 1. Its not possible to move in the desired direction, and search should end + // 2. A layout like the following with focus at X and desired direction Right + // | _ | x | | + // | _ _ _ | | + // | _ _ _ | | + // The container containing X ends at X so no rightward movement is possible + // however there still exists another view/container to the right that hasn't + // been explored. Thus another search is done here in the parent container + // before concluding it's not possible to move in the desired direction. + None => self.find_split_in_direction(parent, direction), + } + } + } + } + + fn find_child(&self, id: ViewId, children: &[ViewId], direction: Direction) -> Option { + let mut child_id = match direction { + // index wise in the child list the Up and Left represents a -1 + // thus reversed iterator. + Direction::Up | Direction::Left => children + .iter() + .rev() + .skip_while(|i| **i != id) + .copied() + .nth(1)?, + // Down and Right => +1 index wise in the child list + Direction::Down | Direction::Right => { + children.iter().skip_while(|i| **i != id).copied().nth(1)? + } + }; + let (current_x, current_y) = match &self.nodes[self.focus].content { + Content::View(current_view) => (current_view.area.left(), current_view.area.top()), + Content::Container(_) => unreachable!(), + }; + + // If the child is a container the search finds the closest container child + // visually based on screen location. + while let Content::Container(container) = &self.nodes[child_id].content { + match (direction, container.layout) { + (_, Layout::Vertical) => { + // find closest split based on x because y is irrelevant + // in a vertical container (and already correct based on previous search) + child_id = *container.children.iter().min_by_key(|id| { + let x = match &self.nodes[**id].content { + Content::View(view) => view.inner_area().left(), + Content::Container(container) => container.area.left(), + }; + (current_x as i16 - x as i16).abs() + })?; + } + (_, Layout::Horizontal) => { + // find closest split based on y because x is irrelevant + // in a horizontal container (and already correct based on previous search) + child_id = *container.children.iter().min_by_key(|id| { + let y = match &self.nodes[**id].content { + Content::View(view) => view.inner_area().top(), + Content::Container(container) => container.area.top(), + }; + (current_y as i16 - y as i16).abs() + })?; + } + } + } + Some(child_id) + } + + pub fn focus_direction(&mut self, direction: Direction) { + if let Some(id) = self.find_split_in_direction(self.focus, direction) { + self.focus = id; + } + } + pub fn focus_next(&mut self) { // This function is very dumb, but that's because we don't store any parent links. // (we'd be able to go parent.next_sibling() recursively until we find something) @@ -420,13 +533,12 @@ impl Tree { // if found = container -> found = first child // } - let iter = self.traverse(); - - let mut iter = iter.skip_while(|&(key, _view)| key != self.focus); - iter.next(); // take the focused value - - if let Some((key, _)) = iter.next() { - self.focus = key; + let mut views = self + .traverse() + .skip_while(|&(id, _view)| id != self.focus) + .skip(1); // Skip focused value + if let Some((id, _)) = views.next() { + self.focus = id; } else { // extremely crude, take the first item again let (key, _) = self.traverse().next().unwrap(); @@ -472,3 +584,64 @@ impl<'a> Iterator for Traverse<'a> { } } } + +#[cfg(test)] +mod test { + use super::*; + use crate::DocumentId; + + #[test] + fn find_split_in_direction() { + let mut tree = Tree::new(Rect { + x: 0, + y: 0, + width: 180, + height: 80, + }); + let mut view = View::new(DocumentId::default()); + view.area = Rect::new(0, 0, 180, 80); + tree.insert(view); + + let l0 = tree.focus; + let view = View::new(DocumentId::default()); + tree.split(view, Layout::Vertical); + let r0 = tree.focus; + + tree.focus = l0; + let view = View::new(DocumentId::default()); + tree.split(view, Layout::Horizontal); + let l1 = tree.focus; + + tree.focus = l0; + let view = View::new(DocumentId::default()); + tree.split(view, Layout::Vertical); + let l2 = tree.focus; + + // Tree in test + // | L0 | L2 | | + // | L1 | R0 | + tree.focus = l2; + assert_eq!(Some(l0), tree.find_split_in_direction(l2, Direction::Left)); + assert_eq!(Some(l1), tree.find_split_in_direction(l2, Direction::Down)); + assert_eq!(Some(r0), tree.find_split_in_direction(l2, Direction::Right)); + assert_eq!(None, tree.find_split_in_direction(l2, Direction::Up)); + + tree.focus = l1; + assert_eq!(None, tree.find_split_in_direction(l1, Direction::Left)); + assert_eq!(None, tree.find_split_in_direction(l1, Direction::Down)); + assert_eq!(Some(r0), tree.find_split_in_direction(l1, Direction::Right)); + assert_eq!(Some(l0), tree.find_split_in_direction(l1, Direction::Up)); + + tree.focus = l0; + assert_eq!(None, tree.find_split_in_direction(l0, Direction::Left)); + assert_eq!(Some(l1), tree.find_split_in_direction(l0, Direction::Down)); + assert_eq!(Some(l2), tree.find_split_in_direction(l0, Direction::Right)); + assert_eq!(None, tree.find_split_in_direction(l0, Direction::Up)); + + tree.focus = r0; + assert_eq!(Some(l2), tree.find_split_in_direction(r0, Direction::Left)); + assert_eq!(None, tree.find_split_in_direction(r0, Direction::Down)); + assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right)); + assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up)); + } +} From 0cb5e0b2caba61bbcf6f57ce58506882766d5eea Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Sat, 23 Oct 2021 08:52:18 -0400 Subject: [PATCH 034/122] log syntax highlighting init errors (#895) --- Cargo.lock | 1 + helix-core/Cargo.toml | 1 + helix-core/src/syntax.rs | 4 +++- helix-term/src/main.rs | 7 ++++++- helix-view/src/clipboard.rs | 22 ++++++---------------- helix-view/src/editor.rs | 6 +++++- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa6fb1419..ad12adf90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,6 +363,7 @@ dependencies = [ "arc-swap", "etcetera", "helix-syntax", + "log", "once_cell", "quickcheck", "regex", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 93ebb1332..84d029d2e 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -27,6 +27,7 @@ once_cell = "1.8" arc-swap = "1" regex = "1" +log = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.5" diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index f4b4535bd..281a70f91 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -186,7 +186,9 @@ impl LanguageConfiguration { if highlights_query.is_empty() { None } else { - let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?; + let language = get_language(&crate::RUNTIME_DIR, &self.language_id) + .map_err(|e| log::info!("{}", e)) + .ok()?; let config = HighlightConfiguration::new( language, &highlights_query, diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 180dacd1f..2589a375b 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -16,6 +16,11 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { }; // Separate file config so we can include year, month and day in file logs + let file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(logpath)?; let file_config = fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( @@ -26,7 +31,7 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { message )) }) - .chain(fern::log_file(logpath)?); + .chain(file); base_config.chain(file_config).apply()?; diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index a11224ace..a492652d8 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -116,7 +116,7 @@ pub fn get_clipboard_provider() -> Box { } } else { #[cfg(target_os = "windows")] - return Box::new(provider::WindowsProvider::new()); + return Box::new(provider::WindowsProvider::default()); #[cfg(not(target_os = "windows"))] return Box::new(provider::NopProvider::new()); @@ -145,15 +145,15 @@ mod provider { use anyhow::{bail, Context as _, Result}; use std::borrow::Cow; + #[cfg(not(target_os = "windows"))] #[derive(Debug)] pub struct NopProvider { buf: String, primary_buf: String, } + #[cfg(not(target_os = "windows"))] impl NopProvider { - #[allow(dead_code)] - // Only dead_code on Windows. pub fn new() -> Self { Self { buf: String::new(), @@ -162,6 +162,7 @@ mod provider { } } + #[cfg(not(target_os = "windows"))] impl ClipboardProvider for NopProvider { fn name(&self) -> Cow { Cow::Borrowed("none") @@ -186,19 +187,8 @@ mod provider { } #[cfg(target_os = "windows")] - #[derive(Debug)] - pub struct WindowsProvider { - selection_buf: String, - } - - #[cfg(target_os = "windows")] - impl WindowsProvider { - pub fn new() -> Self { - Self { - selection_buf: String::new(), - } - } - } + #[derive(Default, Debug)] + pub struct WindowsProvider; #[cfg(target_os = "windows")] impl ClipboardProvider for WindowsProvider { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 813c86fd5..51fe8a422 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -294,7 +294,11 @@ impl Editor { self.language_servers .get(language) .map_err(|e| { - log::error!("Failed to get LSP, {}, for `{}`", e, language.scope()) + log::error!( + "Failed to initialize the LSP for `{}` {{ {} }}", + language.scope(), + e + ) }) .ok() }); From 971ba8929fcc879c2c24b9ab204849500ffe6fce Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Sun, 24 Oct 2021 15:55:29 +0800 Subject: [PATCH 035/122] Filter completion items from language server by starts_with word under cursor (#883) * filter items by starts_with pre nth char of cursor * add config for filter completion items by starts_with * filter items by starts_with pre nth char of cursor * add config for filter completion items by starts_with * remove completion items pre filter configuratio --- helix-term/src/commands.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9d73ba6e7..07485f9f4 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4142,6 +4142,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); + let prefix = text.slice(start_offset..cursor).to_string(); cx.callback( future, @@ -4154,7 +4155,7 @@ pub fn completion(cx: &mut Context) { return; } - let items = match response { + let mut items = match response { Some(lsp::CompletionResponse::Array(items)) => items, // TODO: do something with is_incomplete Some(lsp::CompletionResponse::List(lsp::CompletionList { @@ -4164,6 +4165,18 @@ pub fn completion(cx: &mut Context) { None => Vec::new(), }; + if !prefix.is_empty() { + items = items + .into_iter() + .filter(|item| { + item.filter_text + .as_ref() + .unwrap_or(&item.label) + .starts_with(&prefix) + }) + .collect(); + } + if items.is_empty() { // editor.set_error("No completion available".to_string()); return; From 4b4e972af00a97c7b6e164f4cfdac38954ebae58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 23 Oct 2021 15:20:45 +0900 Subject: [PATCH 036/122] nix: Update lld to 12 --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index c11452683..296a68d5a 100644 --- a/flake.nix +++ b/flake.nix @@ -63,7 +63,7 @@ ''; }; shell = common: prev: { - packages = prev.packages ++ (with common.pkgs; [ lld_10 lldb cargo-tarpaulin ]); + packages = prev.packages ++ (with common.pkgs; [ lld_12 lldb cargo-tarpaulin ]); env = prev.env ++ [ { name = "HELIX_RUNTIME"; eval = "$PWD/runtime"; } { name = "RUST_BACKTRACE"; value = "1"; } From c913bade0a3b0edaaf87e1a5e795edc951cead78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 24 Oct 2021 17:20:30 +0900 Subject: [PATCH 037/122] fix: Indentation used different default on `hx` vs `hx new_file.txt` --- helix-view/src/document.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 1f1b1f5f1..8804681bb 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -23,6 +23,8 @@ use crate::{DocumentId, Theme, ViewId}; /// 8kB of buffer space for encoding and decoding `Rope`s. const BUF_SIZE: usize = 8192; +const DEFAULT_INDENT: IndentStyle = IndentStyle::Spaces(4); + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Mode { Normal, @@ -325,7 +327,7 @@ impl Document { encoding, text, selections: HashMap::default(), - indent_style: IndentStyle::Spaces(4), + indent_style: DEFAULT_INDENT, mode: Mode::Normal, restore_cursor: false, syntax: None, @@ -495,17 +497,15 @@ impl Document { } /// Detect the indentation used in the file, or otherwise defaults to the language indentation - /// configured in `languages.toml`, with a fallback back to 2 space indentation if it isn't + /// configured in `languages.toml`, with a fallback to 4 space indentation if it isn't /// specified. Line ending is likewise auto-detected, and will fallback to the default OS /// line ending. pub fn detect_indent_and_line_ending(&mut self) { self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| { - IndentStyle::from_str( - self.language - .as_ref() - .and_then(|config| config.indent.as_ref()) - .map_or(" ", |config| config.unit.as_str()), // Fallback to 2 spaces. - ) + self.language + .as_ref() + .and_then(|config| config.indent.as_ref()) + .map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit)) }); self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING); } From cee7ad781e5f6de249d728425a6283a26bb62dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 24 Oct 2021 17:28:29 +0900 Subject: [PATCH 038/122] Mark a few functions as `const` --- helix-core/src/line_ending.rs | 6 +++--- helix-core/src/register.rs | 4 ++-- helix-term/src/ui/editor.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index 18ea5f9f5..3541305c3 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -20,7 +20,7 @@ pub enum LineEnding { impl LineEnding { #[inline] - pub fn len_chars(&self) -> usize { + pub const fn len_chars(&self) -> usize { match self { Self::Crlf => 2, _ => 1, @@ -28,7 +28,7 @@ impl LineEnding { } #[inline] - pub fn as_str(&self) -> &'static str { + pub const fn as_str(&self) -> &'static str { match self { Self::Crlf => "\u{000D}\u{000A}", Self::LF => "\u{000A}", @@ -42,7 +42,7 @@ impl LineEnding { } #[inline] - pub fn from_char(ch: char) -> Option { + pub const fn from_char(ch: char) -> Option { match ch { '\u{000A}' => Some(LineEnding::LF), '\u{000B}' => Some(LineEnding::VT), diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs index c3e6652e6..c5444eb73 100644 --- a/helix-core/src/register.rs +++ b/helix-core/src/register.rs @@ -7,7 +7,7 @@ pub struct Register { } impl Register { - pub fn new(name: char) -> Self { + pub const fn new(name: char) -> Self { Self { name, values: Vec::new(), @@ -18,7 +18,7 @@ impl Register { Self { name, values } } - pub fn name(&self) -> char { + pub const fn name(&self) -> char { self.name } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 9234bb964..692696a6d 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1106,7 +1106,7 @@ fn canonicalize_key(key: &mut KeyEvent) { } #[inline] -fn abs_diff(a: usize, b: usize) -> usize { +const fn abs_diff(a: usize, b: usize) -> usize { if a > b { a - b } else { From 2ed01f2d9c8c98a7e59fef1bb19af56ec897084b Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Sun, 24 Oct 2021 21:47:10 +0800 Subject: [PATCH 039/122] find motion and textobj motion repeat (#891) --- helix-term/src/commands.rs | 210 +++++++++++++++++++------------------ helix-term/src/keymap.rs | 1 + helix-view/src/editor.rs | 14 +++ 3 files changed, 122 insertions(+), 103 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 07485f9f4..c698c641c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -12,8 +12,13 @@ use helix_core::{ }; use helix_view::{ - clipboard::ClipboardType, document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode, - view::View, Document, DocumentId, Editor, ViewId, + clipboard::ClipboardType, + document::Mode, + editor::{Action, Motion}, + input::KeyEvent, + keyboard::KeyCode, + view::View, + Document, DocumentId, Editor, ViewId, }; use anyhow::{anyhow, bail, Context as _}; @@ -198,6 +203,7 @@ impl Command { find_prev_char, "Move to previous occurance of char", extend_till_prev_char, "Extend till previous occurance of char", extend_prev_char, "Extend to previous occurance of char", + repeat_last_motion, "repeat last motion(extend_next_char, extend_till_char, find_next_char, find_till_char...)", replace, "Replace with new char", switch_case, "Switch (toggle) case", switch_to_uppercase, "Switch to uppercase", @@ -666,8 +672,7 @@ fn extend_next_long_word_end(cx: &mut Context) { extend_word_impl(cx, movement::move_next_long_word_end) } -#[inline] -fn find_char_impl(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool) +fn will_find_char(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool) where F: Fn(RopeSlice, char, usize, usize, bool) -> Option + 'static, { @@ -705,29 +710,48 @@ where _ => return, }; - let (view, doc) = current!(cx.editor); - let text = doc.text().slice(..); + find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count); + cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| { + find_char_impl(editor, &search_fn, inclusive, true, ch, 1); + }))); + }) +} - let selection = doc.selection(view.id).clone().transform(|range| { - // TODO: use `Range::cursor()` here instead. However, that works in terms of - // graphemes, whereas this function doesn't yet. So we're doing the same logic - // here, but just in terms of chars instead. - let search_start_pos = if range.anchor < range.head { - range.head - 1 - } else { - range.head - }; +// - search_fn(text, ch, search_start_pos, count, inclusive).map_or(range, |pos| { - if extend { - range.put_cursor(text, pos, true) - } else { - Range::point(range.cursor(text)).put_cursor(text, pos, true) - } - }) - }); - doc.set_selection(view.id, selection); - }) +#[inline] +fn find_char_impl( + editor: &mut Editor, + search_fn: &F, + inclusive: bool, + extend: bool, + ch: char, + count: usize, +) where + F: Fn(RopeSlice, char, usize, usize, bool) -> Option + 'static, +{ + let (view, doc) = current!(editor); + let text = doc.text().slice(..); + + let selection = doc.selection(view.id).clone().transform(|range| { + // TODO: use `Range::cursor()` here instead. However, that works in terms of + // graphemes, whereas this function doesn't yet. So we're doing the same logic + // here, but just in terms of chars instead. + let search_start_pos = if range.anchor < range.head { + range.head - 1 + } else { + range.head + }; + + search_fn(text, ch, search_start_pos, count, inclusive).map_or(range, |pos| { + if extend { + range.put_cursor(text, pos, true) + } else { + Range::point(range.cursor(text)).put_cursor(text, pos, true) + } + }) + }); + doc.set_selection(view.id, selection); } fn find_next_char_impl( @@ -741,6 +765,10 @@ fn find_next_char_impl( if inclusive { search::find_nth_next(text, ch, pos, n) } else { + let n = match text.get_char(pos) { + Some(next_ch) if next_ch == ch => n + 1, + _ => n, + }; search::find_nth_next(text, ch, pos, n).map(|n| n.saturating_sub(1)) } } @@ -755,80 +783,52 @@ fn find_prev_char_impl( if inclusive { search::find_nth_prev(text, ch, pos, n) } else { + let n = match text.get_char(pos.saturating_sub(1)) { + Some(next_ch) if next_ch == ch => n + 1, + _ => n, + }; search::find_nth_prev(text, ch, pos, n).map(|n| (n + 1).min(text.len_chars())) } } fn find_till_char(cx: &mut Context) { - find_char_impl( - cx, - find_next_char_impl, - false, /* inclusive */ - false, /* extend */ - ) + will_find_char(cx, find_next_char_impl, false, false) } fn find_next_char(cx: &mut Context) { - find_char_impl( - cx, - find_next_char_impl, - true, /* inclusive */ - false, /* extend */ - ) + will_find_char(cx, find_next_char_impl, true, false) } fn extend_till_char(cx: &mut Context) { - find_char_impl( - cx, - find_next_char_impl, - false, /* inclusive */ - true, /* extend */ - ) + will_find_char(cx, find_next_char_impl, false, true) } fn extend_next_char(cx: &mut Context) { - find_char_impl( - cx, - find_next_char_impl, - true, /* inclusive */ - true, /* extend */ - ) + will_find_char(cx, find_next_char_impl, true, true) } fn till_prev_char(cx: &mut Context) { - find_char_impl( - cx, - find_prev_char_impl, - false, /* inclusive */ - false, /* extend */ - ) + will_find_char(cx, find_prev_char_impl, false, false) } fn find_prev_char(cx: &mut Context) { - find_char_impl( - cx, - find_prev_char_impl, - true, /* inclusive */ - false, /* extend */ - ) + will_find_char(cx, find_prev_char_impl, true, false) } fn extend_till_prev_char(cx: &mut Context) { - find_char_impl( - cx, - find_prev_char_impl, - false, /* inclusive */ - true, /* extend */ - ) + will_find_char(cx, find_prev_char_impl, false, true) } fn extend_prev_char(cx: &mut Context) { - find_char_impl( - cx, - find_prev_char_impl, - true, /* inclusive */ - true, /* extend */ - ) + will_find_char(cx, find_prev_char_impl, true, true) +} + +fn repeat_last_motion(cx: &mut Context) { + let last_motion = cx.editor.last_motion.take(); + if let Some(m) = &last_motion { + m.run(cx.editor); + cx.editor.last_motion = last_motion; + } } fn replace(cx: &mut Context) { @@ -4495,39 +4495,43 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { let count = cx.count(); cx.on_next_key(move |cx, event| { if let Some(ch) = event.char() { - let (view, doc) = current!(cx.editor); - let text = doc.text().slice(..); - - let textobject_treesitter = |obj_name: &str, range: Range| -> Range { - let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) { - Some(t) => t, - None => return range, + let textobject = move |editor: &mut Editor| { + let (view, doc) = current!(editor); + let text = doc.text().slice(..); + + let textobject_treesitter = |obj_name: &str, range: Range| -> Range { + let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) { + Some(t) => t, + None => return range, + }; + textobject::textobject_treesitter( + text, + range, + objtype, + obj_name, + syntax.tree().root_node(), + lang_config, + count, + ) }; - textobject::textobject_treesitter( - text, - range, - objtype, - obj_name, - syntax.tree().root_node(), - lang_config, - count, - ) - }; - let selection = doc.selection(view.id).clone().transform(|range| { - match ch { - 'w' => textobject::textobject_word(text, range, objtype, count), - 'c' => textobject_treesitter("class", range), - 'f' => textobject_treesitter("function", range), - 'p' => textobject_treesitter("parameter", range), - // TODO: cancel new ranges if inconsistent surround matches across lines - ch if !ch.is_ascii_alphanumeric() => { - textobject::textobject_surround(text, range, objtype, ch, count) + let selection = doc.selection(view.id).clone().transform(|range| { + match ch { + 'w' => textobject::textobject_word(text, range, objtype, count), + 'c' => textobject_treesitter("class", range), + 'f' => textobject_treesitter("function", range), + 'p' => textobject_treesitter("parameter", range), + // TODO: cancel new ranges if inconsistent surround matches across lines + ch if !ch.is_ascii_alphanumeric() => { + textobject::textobject_surround(text, range, objtype, ch, count) + } + _ => range, } - _ => range, - } - }); - doc.set_selection(view.id, selection); + }); + doc.set_selection(view.id, selection); + }; + textobject(&mut cx.editor); + cx.editor.last_motion = Some(Motion(Box::new(textobject))); } }) } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index f877387c6..495fe892f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -395,6 +395,7 @@ impl Default for Keymaps { "F" => find_prev_char, "r" => replace, "R" => replace_with_yanked, + "A-." => repeat_last_motion, "~" => switch_case, "`" => switch_to_lowercase, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 51fe8a422..09fc33341 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -93,6 +93,18 @@ impl Default for Config { } } +pub struct Motion(pub Box); +impl Motion { + pub fn run(&self, e: &mut Editor) { + (self.0)(e) + } +} +impl std::fmt::Debug for Motion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("motion") + } +} + #[derive(Debug)] pub struct Editor { pub tree: Tree, @@ -112,6 +124,7 @@ pub struct Editor { pub config: Config, pub idle_timer: Pin>, + pub last_motion: Option, } #[derive(Debug, Copy, Clone)] @@ -147,6 +160,7 @@ impl Editor { clipboard_provider: get_clipboard_provider(), status_msg: None, idle_timer: Box::pin(sleep(config.idle_timeout)), + last_motion: None, config, } } From 42eee9d5bfe6248eb3a343bd12897f4915303857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 24 Oct 2021 23:09:15 +0900 Subject: [PATCH 040/122] book: Document Alt-. and . --- book/src/keymap.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 644dc1c9b..2ff8bfe6b 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -22,6 +22,7 @@ | `f` | Find next char | `find_next_char` | | `T` | Find 'till previous char | `till_prev_char` | | `F` | Find previous char | `find_prev_char` | +| `Alt-.` | Repeat last motion (`f`, `t` or `m`) | `repeat_last_motion` | | `Home` | Move to the start of the line | `goto_line_start` | | `End` | Move to the end of the line | `goto_line_end` | | `PageUp` | Move page up | `page_up` | @@ -54,6 +55,7 @@ | `A` | Insert at the end of the line | `append_to_line` | | `o` | Open new line below selection | `open_below` | | `O` | Open new line above selection | `open_above` | +| `.` | Repeat last change | N/A | | `u` | Undo change | `undo` | | `U` | Redo change | `redo` | | `y` | Yank selection | `yank` | From a7d87c79ce751ce5d97843026de82d5ee124d176 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sun, 24 Oct 2021 20:25:47 -0400 Subject: [PATCH 041/122] Fix `:quit!` description and tense of other commands (#902) --- helix-term/src/commands.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c698c641c..d6e5bfe71 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2110,7 +2110,7 @@ mod cmd { TypableCommand { name: "quit!", aliases: &["q!"], - doc: "Close the current view.", + doc: "Close the current view forcefully (ignoring unsaved changes).", fun: force_quit, completer: None, }, @@ -2173,35 +2173,35 @@ mod cmd { TypableCommand { name: "write-quit", aliases: &["wq", "x"], - doc: "Writes changes to disk and closes the current view. Accepts an optional path (:wq some/path.txt)", + doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)", fun: write_quit, completer: Some(completers::filename), }, TypableCommand { name: "write-quit!", aliases: &["wq!", "x!"], - doc: "Writes changes to disk and closes the current view forcefully. Accepts an optional path (:wq! some/path.txt)", + doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)", fun: force_write_quit, completer: Some(completers::filename), }, TypableCommand { name: "write-all", aliases: &["wa"], - doc: "Writes changes from all views to disk.", + doc: "Write changes from all views to disk.", fun: write_all, completer: None, }, TypableCommand { name: "write-quit-all", aliases: &["wqa", "xa"], - doc: "Writes changes from all views to disk and close all views.", + doc: "Write changes from all views to disk and close all views.", fun: write_all_quit, completer: None, }, TypableCommand { name: "write-quit-all!", aliases: &["wqa!", "xa!"], - doc: "Writes changes from all views to disk and close all views forcefully (ignoring unsaved changes).", + doc: "Write changes from all views to disk and close all views forcefully (ignoring unsaved changes).", fun: force_write_all_quit, completer: None, }, From d4d16ca1b06ed18843ec3382f485c09b04e35e18 Mon Sep 17 00:00:00 2001 From: Ray Gervais Date: Sun, 24 Oct 2021 21:18:04 -0400 Subject: [PATCH 042/122] runtime: Rose Pine colorscheme (#897) --- runtime/themes/rose_pine.toml | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 runtime/themes/rose_pine.toml diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml new file mode 100644 index 000000000..a982fd6e0 --- /dev/null +++ b/runtime/themes/rose_pine.toml @@ -0,0 +1,61 @@ +# Author: RayGervais + +"ui.background" = { bg = "base" } +"ui.menu" = "surface" +"ui.menu.selected" = { fg = "iris", bg = "surface" } +"ui.linenr" = {fg = "subtle" } +"ui.popup" = { bg = "overlay" } +"ui.window" = { bg = "overlay" } +"ui.liner.selected" = "highlightOverlay" +"ui.selection" = "highlight" +"comment" = "subtle" +"ui.statusline" = {fg = "foam", bg = "surface" } +"ui.help" = { fg = "foam", bg = "surface" } +"ui.cursor" = { fg = "rose", modifiers = ["reversed"] } +"ui.text" = { fg = "text" } +"operator" = "rose" +"ui.text.focus" = { fg = "base05" } +"variable" = "text" +"number" = "iris" +"constant" = "gold" +"attributes" = "gold" +"type" = "foam" +"ui.cursor.match" = { fg = "gold", modifiers = ["underlined"] } +"string" = "gold" +"property" = "foam" +"escape" = "subtle" +"function" = "rose" +"function.builtin" = "rose" +"function.method" = "foam" +"constructor" = "gold" +"special" = "gold" +"keyword" = "pine" +"label" = "iris" +"namespace" = "pine" +"ui.popup" = { bg = "overlay" } +"ui.window" = { bg = "base" } +"ui.help" = { bg = "overlay", fg = "foam" } +"text" = "text" + +"info" = "gold" +"hint" = "gold" +"debug" = "rose" +"diagnostic" = "rose" +"error" = "love" + +[palette] +base = "#191724" +surface = "#1f1d2e" +overlay = "#26233a" +inactive = "#555169" +subtle = "#6e6a86" +text = "#e0def4" +love = "#eb6f92" +gold = "#f6c177" +rose = "#ebbcba" +pine = "#31748f" +foam = "#9ccfd8" +iris = "#c4a7e7" +highlight = "#2a2837" +highlightInactive = "#211f2d" +highlightOverlay = "#3a384a" From bfb6cff5a9b0ff5c37085086f895d3f14eaa5782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 25 Oct 2021 11:01:21 +0900 Subject: [PATCH 043/122] fix: Compose where changes.compose(empty_other) --- helix-core/src/transaction.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index d682f0582..0e49fbe3e 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -132,6 +132,9 @@ impl ChangeSet { if self.changes.is_empty() { return other; } + if other.changes.is_empty() { + return self; + } let len = self.changes.len(); From 3edca7854e66cbdb0c4baca25962a4f390fede55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 25 Oct 2021 11:03:18 +0900 Subject: [PATCH 044/122] completion: fully revert state before apply & insertText common prefix --- helix-core/src/transaction.rs | 7 ++++ helix-term/src/ui/completion.rs | 71 ++++++++++++++------------------- helix-term/src/ui/editor.rs | 13 +++++- helix-view/src/document.rs | 24 ++++++++++- 4 files changed, 71 insertions(+), 44 deletions(-) diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 0e49fbe3e..dfc18fbea 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -468,6 +468,13 @@ impl Transaction { } } + pub fn compose(mut self, other: Self) -> Self { + self.changes = self.changes.compose(other.changes); + // Other selection takes precedence + self.selection = other.selection; + self + } + pub fn with_selection(mut self, selection: Selection) -> Self { self.selection = Some(selection); self diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c75b24f1a..44879fcf2 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface; use std::borrow::Cow; use helix_core::Transaction; -use helix_view::{graphics::Rect, Document, Editor, View}; +use helix_view::{graphics::Rect, Document, Editor}; use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -83,13 +83,13 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Self { - // let items: Vec = Vec::new(); let menu = Menu::new(items, move |editor: &mut Editor, item, event| { fn item_to_transaction( doc: &Document, - view: &View, item: &CompletionItem, offset_encoding: helix_lsp::OffsetEncoding, + start_offset: usize, + trigger_offset: usize, ) -> Transaction { if let Some(edit) = &item.text_edit { let edit = match edit { @@ -105,63 +105,52 @@ impl Completion { ) } else { let text = item.insert_text.as_ref().unwrap_or(&item.label); - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); + // 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); Transaction::change( doc.text(), - vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), + vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), ) } } + let (view, doc) = current!(editor); + + // if more text was entered, remove it + doc.restore(view.id); + match event { PromptEvent::Abort => {} PromptEvent::Update => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); + + // initialize a savepoint + doc.savepoint(); - let transaction = item_to_transaction(doc, view, item, offset_encoding); doc.apply(&transaction, view.id); } PromptEvent::Validate => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } - - let transaction = item_to_transaction(doc, view, item, offset_encoding); + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); doc.apply(&transaction, view.id); if let Some(additional_edits) = &item.additional_text_edits { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 692696a6d..850fec0f0 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -13,7 +13,7 @@ use helix_core::{ syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, + LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ document::Mode, @@ -721,7 +721,7 @@ impl EditorView { pub fn set_completion( &mut self, - editor: &Editor, + editor: &mut Editor, items: Vec, offset_encoding: helix_lsp::OffsetEncoding, start_offset: usize, @@ -736,6 +736,9 @@ impl EditorView { return; } + // Immediately initialize a savepoint + doc_mut!(editor).savepoint(); + // TODO : propagate required size on resize to completion too completion.required_size((size.width, size.height)); self.completion = Some(completion); @@ -945,6 +948,9 @@ impl Component for EditorView { if callback.is_some() { // assume close_fn self.completion = None; + // Clear any savepoints + let (_, doc) = current!(cxt.editor); + doc.savepoint = None; cxt.editor.clear_idle_timer(); // don't retrigger } } @@ -959,6 +965,9 @@ impl Component for EditorView { completion.update(&mut cxt); if completion.is_empty() { self.completion = None; + // Clear any savepoints + let (_, doc) = current!(cxt.editor); + doc.savepoint = None; cxt.editor.clear_idle_timer(); // don't retrigger } } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 8804681bb..23c2dbc6b 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -97,6 +97,9 @@ pub struct Document { // it back as it separated from the edits. We could split out the parts manually but that will // be more troublesome. history: Cell, + + pub savepoint: Option, + last_saved_revision: usize, version: i32, // should be usize? @@ -328,6 +331,7 @@ impl Document { text, selections: HashMap::default(), indent_style: DEFAULT_INDENT, + line_ending: DEFAULT_LINE_ENDING, mode: Mode::Normal, restore_cursor: false, syntax: None, @@ -337,9 +341,9 @@ impl Document { diagnostics: Vec::new(), version: 0, history: Cell::new(History::default()), + savepoint: None, last_saved_revision: 0, language_server: None, - line_ending: DEFAULT_LINE_ENDING, } } @@ -635,6 +639,14 @@ impl Document { if !transaction.changes().is_empty() { self.version += 1; + // 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())) + }); + } + // update tree-sitter syntax tree if let Some(syntax) = &mut self.syntax { // TODO: no unwrap @@ -724,6 +736,16 @@ impl Document { } } + pub fn savepoint(&mut self) { + self.savepoint = Some(Transaction::new(self.text())); + } + + pub fn restore(&mut self, view_id: ViewId) { + if let Some(revert) = self.savepoint.take() { + self.apply(&revert, view_id); + } + } + /// Undo modifications to the [`Document`] according to `uk`. pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) { let txns = self.history.get_mut().earlier(uk); From acc5ac5e731ea978fdd8a0f6762f2cd5101780b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 25 Oct 2021 11:11:11 +0900 Subject: [PATCH 045/122] fix warning --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 850fec0f0..97658c645 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -13,7 +13,7 @@ use helix_core::{ syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, Transaction, + LineEnding, Position, Range, Selection, }; use helix_view::{ document::Mode, From 92c2d5d3bffb52a165fad75b6db2120de37b5ae4 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Mon, 25 Oct 2021 12:02:16 -0400 Subject: [PATCH 046/122] Document more of helix-core (#904) --- helix-core/src/auto_pairs.rs | 3 + helix-core/src/chars.rs | 2 + helix-core/src/comment.rs | 3 + helix-core/src/diagnostic.rs | 5 ++ helix-core/src/graphemes.rs | 4 +- helix-core/src/history.rs | 105 ++++++++++++++++++++++------------- 6 files changed, 81 insertions(+), 41 deletions(-) diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 9b901e9b1..cc9668529 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -1,3 +1,6 @@ +//! When typing the opening character of one of the possible pairs defined below, +//! this module provides the functionality to insert the paired closing character. + use crate::{Range, Rope, Selection, Tendril, Transaction}; use smallvec::SmallVec; diff --git a/helix-core/src/chars.rs b/helix-core/src/chars.rs index 24133dd33..c8e5efbde 100644 --- a/helix-core/src/chars.rs +++ b/helix-core/src/chars.rs @@ -1,3 +1,5 @@ +//! Utility functions to categorize a `char`. + use crate::LineEnding; #[derive(Debug, Eq, PartialEq)] diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index 3d8e1ce38..4072a5323 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -1,3 +1,6 @@ +//! This module contains the functionality toggle comments on lines over the selection +//! using the comment character defined in the user's `languages.toml` + use crate::{ find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction, }; diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index e08a71e7b..ab47e0752 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,3 +1,6 @@ +//! LSP diagnostic utility types. + +/// Describes the severity level of a [`Diagnostic`]. #[derive(Debug, Eq, PartialEq)] pub enum Severity { Error, @@ -6,12 +9,14 @@ pub enum Severity { Hint, } +/// A range of `char`s within the text. #[derive(Debug)] pub struct Range { pub start: usize, pub end: usize, } +/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) #[derive(Debug)] pub struct Diagnostic { pub range: Range, diff --git a/helix-core/src/graphemes.rs b/helix-core/src/graphemes.rs index 0465fe510..56f5bacb7 100644 --- a/helix-core/src/graphemes.rs +++ b/helix-core/src/graphemes.rs @@ -1,4 +1,6 @@ -// Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs +//! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents. +//! +//! Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice}; use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; use unicode_width::UnicodeWidthStr; diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 67ded1661..cf62708a9 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -4,48 +4,50 @@ use regex::Regex; use std::num::NonZeroUsize; use std::time::{Duration, Instant}; -// Stores the history of changes to a buffer. -// -// Currently the history is represented as a vector of revisions. The vector -// always has at least one element: the empty root revision. Each revision -// with the exception of the root has a parent revision, a [Transaction] -// that can be applied to its parent to transition from the parent to itself, -// and an inversion of that transaction to transition from the parent to its -// latest child. -// -// When using `u` to undo a change, an inverse of the stored transaction will -// be applied which will transition the buffer to the parent state. -// -// Each revision with the exception of the last in the vector also has a -// last child revision. When using `U` to redo a change, the last child transaction -// will be applied to the current state of the buffer. -// -// The current revision is the one currently displayed in the buffer. -// -// Commiting a new revision to the history will update the last child of the -// current revision, and push a new revision to the end of the vector. -// -// Revisions are commited with a timestamp. :earlier and :later can be used -// to jump to the closest revision to a moment in time relative to the timestamp -// of the current revision plus (:later) or minus (:earlier) the duration -// given to the command. If a single integer is given, the editor will instead -// jump the given number of revisions in the vector. -// -// Limitations: -// * Changes in selections currently don't commit history changes. The selection -// will only be updated to the state after a commited buffer change. -// * The vector of history revisions is currently unbounded. This might -// cause the memory consumption to grow significantly large during long -// editing sessions. -// * Because delete transactions currently don't store the text that they -// delete, we also store an inversion of the transaction. +/// Stores the history of changes to a buffer. +/// +/// Currently the history is represented as a vector of revisions. The vector +/// always has at least one element: the empty root revision. Each revision +/// with the exception of the root has a parent revision, a [Transaction] +/// that can be applied to its parent to transition from the parent to itself, +/// and an inversion of that transaction to transition from the parent to its +/// latest child. +/// +/// When using `u` to undo a change, an inverse of the stored transaction will +/// be applied which will transition the buffer to the parent state. +/// +/// Each revision with the exception of the last in the vector also has a +/// last child revision. When using `U` to redo a change, the last child transaction +/// will be applied to the current state of the buffer. +/// +/// The current revision is the one currently displayed in the buffer. +/// +/// Commiting a new revision to the history will update the last child of the +/// current revision, and push a new revision to the end of the vector. +/// +/// Revisions are commited with a timestamp. :earlier and :later can be used +/// to jump to the closest revision to a moment in time relative to the timestamp +/// of the current revision plus (:later) or minus (:earlier) the duration +/// given to the command. If a single integer is given, the editor will instead +/// jump the given number of revisions in the vector. +/// +/// Limitations: +/// * Changes in selections currently don't commit history changes. The selection +/// will only be updated to the state after a commited buffer change. +/// * The vector of history revisions is currently unbounded. This might +/// cause the memory consumption to grow significantly large during long +/// editing sessions. +/// * Because delete transactions currently don't store the text that they +/// delete, we also store an inversion of the transaction. +/// +/// Using time to navigate the history: https://github.com/helix-editor/helix/pull/194 #[derive(Debug)] pub struct History { revisions: Vec, current: usize, } -// A single point in history. See [History] for more information. +/// A single point in history. See [History] for more information. #[derive(Debug)] struct Revision { parent: usize, @@ -111,6 +113,7 @@ impl History { self.current == 0 } + /// Undo the last edit. pub fn undo(&mut self) -> Option<&Transaction> { if self.at_root() { return None; @@ -121,6 +124,7 @@ impl History { Some(¤t_revision.inversion) } + /// Redo the last edit. pub fn redo(&mut self) -> Option<&Transaction> { let current_revision = &self.revisions[self.current]; let last_child = current_revision.last_child?; @@ -147,8 +151,8 @@ impl History { } } - // List of nodes on the way from `n` to 'a`. Doesn`t include `a`. - // Includes `n` unless `a == n`. `a` must be an ancestor of `n`. + /// List of nodes on the way from `n` to 'a`. Doesn`t include `a`. + /// Includes `n` unless `a == n`. `a` must be an ancestor of `n`. fn path_up(&self, mut n: usize, a: usize) -> Vec { let mut path = Vec::new(); while n != a { @@ -158,6 +162,7 @@ impl History { path } + /// Create a [`Transaction`] that will jump to a specific revision in the history. fn jump_to(&mut self, to: usize) -> Vec { let lca = self.lowest_common_ancestor(self.current, to); let up = self.path_up(self.current, lca); @@ -171,10 +176,12 @@ impl History { up_txns.chain(down_txns).collect() } + /// Creates a [`Transaction`] that will undo `delta` revisions. fn jump_backward(&mut self, delta: usize) -> Vec { self.jump_to(self.current.saturating_sub(delta)) } + /// Creates a [`Transaction`] that will redo `delta` revisions. fn jump_forward(&mut self, delta: usize) -> Vec { self.jump_to( self.current @@ -183,7 +190,7 @@ impl History { ) } - // Helper for a binary search case below. + /// Helper for a binary search case below. fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize { let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp); let dur_i = self.revisions[i].timestamp.duration_since(instant); @@ -194,6 +201,8 @@ impl History { } } + /// Creates a [`Transaction`] that will match a revision created at around + /// `instant`. fn jump_instant(&mut self, instant: Instant) -> Vec { let search_result = self .revisions @@ -209,6 +218,8 @@ impl History { self.jump_to(revision) } + /// Creates a [`Transaction`] that will match a revision created `duration` ago + /// from the timestamp of current revision. fn jump_duration_backward(&mut self, duration: Duration) -> Vec { match self.revisions[self.current].timestamp.checked_sub(duration) { Some(instant) => self.jump_instant(instant), @@ -216,6 +227,8 @@ impl History { } } + /// Creates a [`Transaction`] that will match a revision created `duration` in + /// the future from the timestamp of the current revision. fn jump_duration_forward(&mut self, duration: Duration) -> Vec { match self.revisions[self.current].timestamp.checked_add(duration) { Some(instant) => self.jump_instant(instant), @@ -223,6 +236,7 @@ impl History { } } + /// Creates an undo [`Transaction`]. pub fn earlier(&mut self, uk: UndoKind) -> Vec { use UndoKind::*; match uk { @@ -231,6 +245,7 @@ impl History { } } + /// Creates a redo [`Transaction`]. pub fn later(&mut self, uk: UndoKind) -> Vec { use UndoKind::*; match uk { @@ -240,13 +255,14 @@ impl History { } } +/// Whether to undo by a number of edits or a duration of time. #[derive(Debug, PartialEq)] pub enum UndoKind { Steps(usize), TimePeriod(std::time::Duration), } -// A subset of sytemd.time time span syntax units. +/// A subset of sytemd.time time span syntax units. const TIME_UNITS: &[(&[&str], &str, u64)] = &[ (&["seconds", "second", "sec", "s"], "seconds", 1), (&["minutes", "minute", "min", "m"], "minutes", 60), @@ -254,11 +270,20 @@ const TIME_UNITS: &[(&[&str], &str, u64)] = &[ (&["days", "day", "d"], "days", 24 * 60 * 60), ]; +/// Checks if the duration input can be turned into a valid duration. It must be a +/// positive integer and denote the [unit of time.](`TIME_UNITS`) +/// Examples of valid durations: +/// * `5 sec` +/// * `5 min` +/// * `5 hr` +/// * `5 days` static DURATION_VALIDATION_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap()); +/// Captures both the number and unit as separate capture groups. static NUMBER_UNIT_REGEX: Lazy = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap()); +/// Parse a string (e.g. "5 sec") and try to convert it into a [`Duration`]. fn parse_human_duration(s: &str) -> Result { if !DURATION_VALIDATION_REGEX.is_match(s) { return Err("duration should be composed \ From a0cb9d82d1245253698bd7e319b07ae50adb715d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 09:41:45 +0900 Subject: [PATCH 047/122] build(deps): bump clipboard-win from 4.2.1 to 4.2.2 (#911) Bumps [clipboard-win](https://github.com/DoumanAsh/clipboard-win) from 4.2.1 to 4.2.2. - [Release notes](https://github.com/DoumanAsh/clipboard-win/releases) - [Commits](https://github.com/DoumanAsh/clipboard-win/commits) --- updated-dependencies: - dependency-name: clipboard-win 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 ad12adf90..17bc73a99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4ea1881992efc993e4dc50a324cdbd03216e41bdc8385720ff47efc9bd2ca8" +checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" dependencies = [ "error-code", "str-buf", From bca98b5bedfa6c9410384f26a2df6115874351bc Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 26 Oct 2021 08:42:08 +0800 Subject: [PATCH 048/122] Add c-j c-k to menu keymap for move_up move_down (#908) --- helix-term/src/ui/menu.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 1130089df..dd163d34e 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -214,6 +214,10 @@ impl Component for Menu { | KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Char('k'), + modifiers: KeyModifiers::CONTROL, } => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); @@ -231,6 +235,10 @@ impl Component for Menu { | KeyEvent { code: KeyCode::Char('n'), modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::CONTROL, } => { self.move_down(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); From b142fd4080d99a7e4f39bb06207ded6771d47b20 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 26 Oct 2021 08:42:23 +0800 Subject: [PATCH 049/122] move_up will select last item, when no item selected (#907) --- helix-term/src/ui/menu.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index dd163d34e..3c492d149 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -98,7 +98,8 @@ impl Menu { pub fn move_up(&mut self) { let len = self.matches.len(); - let pos = self.cursor.map_or(0, |i| (i + len.saturating_sub(1)) % len) % len; + let max_index = len.saturating_sub(1); + let pos = self.cursor.map_or(max_index, |i| (i + max_index) % len) % len; self.cursor = Some(pos); self.adjust_scroll(); } From f331ba9df4d7d22a5e9599737da581feb630f748 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 26 Oct 2021 08:42:37 +0800 Subject: [PATCH 050/122] Clear competion items when start_offset > cursor (#906) --- helix-term/src/ui/completion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 44879fcf2..a893e70b1 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -199,7 +199,7 @@ impl Completion { .selection(view.id) .primary() .cursor(doc.text().slice(..)); - if self.start_offset <= cursor { + if self.trigger_offset <= cursor { let fragment = doc.text().slice(self.start_offset..cursor); let text = Cow::from(fragment); // TODO: logic is same as ui/picker From d61e5e686be14b61b6d26c591147f8bfedd378bf Mon Sep 17 00:00:00 2001 From: radical3dd Date: Tue, 26 Oct 2021 02:43:14 +0200 Subject: [PATCH 051/122] Use current dir for file picker, after change dir. (#910) --- helix-term/src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 82ad04d78..662573c65 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -102,7 +102,7 @@ impl Application { if first.is_dir() { std::env::set_current_dir(&first)?; editor.new_file(Action::VerticalSplit); - compositor.push(Box::new(ui::file_picker(first.clone()))); + compositor.push(Box::new(ui::file_picker(".".into()))); } else { let nr_of_files = args.files.len(); editor.open(first.to_path_buf(), Action::VerticalSplit)?; From bf20e51044323f96f3fad7a543dc06bb745246ec Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 26 Oct 2021 08:25:56 -0500 Subject: [PATCH 052/122] use punctuation.special for interpolation #{ } --- runtime/queries/elixir/highlights.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index 08e09f37a..3fd5d1cbe 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -95,7 +95,7 @@ ; Quoted content -(interpolation "#{" @escape "}" @escape) @embedded +(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded (escape_sequence) @escape From 7e6ade9290882e2be4be98d9a01ac554552334c5 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 26 Oct 2021 08:26:17 -0500 Subject: [PATCH 053/122] fix: string.regex{=>p} --- runtime/queries/elixir/highlights.scm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index 3fd5d1cbe..b7b0cab6d 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -121,9 +121,9 @@ (sigil (sigil_name) @__name__ - quoted_start: _ @string.regex - quoted_end: _ @string.regex - (#match? @__name__ "^[rR]$")) @string.regex + quoted_start: _ @string.regexp + quoted_end: _ @string.regexp + (#match? @__name__ "^[rR]$")) @string.regexp (sigil (sigil_name) @__name__ From 2505802d39f18f2f2dcfe8e00633f895c67beb76 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Tue, 26 Oct 2021 23:24:24 -0400 Subject: [PATCH 054/122] Improve statusline (#916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve statusline * Change diagnostic count display to show counts of individual diagnostic types next to their corresponding gutter dots. * Add selection count to the statusline. * Do not display info or hint count in statusline * Reduce padding Co-authored-by: Blaž Hrastnik * Reduce padding Co-authored-by: Blaž Hrastnik * Use `Span::styled` * Reduce padding * Use `Style::patch` * Remove unnecessary `Cow` creation Co-authored-by: Blaž Hrastnik --- helix-term/src/ui/editor.rs | 97 ++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 97658c645..bf316ee36 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -548,6 +548,8 @@ impl EditorView { theme: &Theme, is_focused: bool, ) { + use tui::text::{Span, Spans}; + //------------------------------- // Left side of the status line. //------------------------------- @@ -566,17 +568,17 @@ impl EditorView { }) .unwrap_or(""); - let style = if is_focused { + let base_style = if is_focused { theme.get("ui.statusline") } else { theme.get("ui.statusline.inactive") }; // statusline - surface.set_style(viewport.with_height(1), style); + surface.set_style(viewport.with_height(1), base_style); if is_focused { - surface.set_string(viewport.x + 1, viewport.y, mode, style); + surface.set_string(viewport.x + 1, viewport.y, mode, base_style); } - surface.set_string(viewport.x + 5, viewport.y, progress, style); + surface.set_string(viewport.x + 5, viewport.y, progress, base_style); if let Some(path) = doc.relative_path() { let path = path.to_string_lossy(); @@ -587,7 +589,7 @@ impl EditorView { viewport.y, title, viewport.width.saturating_sub(6) as usize, - style, + base_style, ); } @@ -595,8 +597,50 @@ impl EditorView { // Right side of the status line. //------------------------------- - // Compute the individual info strings. - let diag_count = format!("{}", doc.diagnostics().len()); + let mut right_side_text = Spans::default(); + + // Compute the individual info strings and add them to `right_side_text`. + + // Diagnostics + let diags = doc.diagnostics().iter().fold((0, 0), |mut counts, diag| { + use helix_core::diagnostic::Severity; + match diag.severity { + Some(Severity::Warning) => counts.0 += 1, + Some(Severity::Error) | None => counts.1 += 1, + _ => {} + } + counts + }); + let (warnings, errors) = diags; + let warning_style = theme.get("warning"); + let error_style = theme.get("error"); + for i in 0..2 { + let (count, style) = match i { + 0 => (warnings, warning_style), + 1 => (errors, error_style), + _ => unreachable!(), + }; + if count == 0 { + continue; + } + let style = base_style.patch(style); + right_side_text.0.push(Span::styled("●", style)); + right_side_text + .0 + .push(Span::styled(format!(" {} ", count), base_style)); + } + + // Selections + let sels_count = doc.selection(view.id).len(); + right_side_text.0.push(Span::styled( + format!( + " {} sel{} ", + sels_count, + if sels_count == 1 { "" } else { "s" } + ), + base_style, + )); + // let indent_info = match doc.indent_style { // IndentStyle::Tabs => "tabs", // IndentStyle::Spaces(1) => "spaces:1", @@ -609,29 +653,28 @@ impl EditorView { // IndentStyle::Spaces(8) => "spaces:8", // _ => "indent:ERROR", // }; - let position_info = { - let pos = coords_at_pos( - doc.text().slice(..), - doc.selection(view.id) - .primary() - .cursor(doc.text().slice(..)), - ); - format!("{}:{}", pos.row + 1, pos.col + 1) // convert to 1-indexing - }; - // Render them to the status line together. - let right_side_text = format!( - "{} {} ", - &diag_count[..diag_count.len().min(4)], - // indent_info, - position_info + // Position + let pos = coords_at_pos( + doc.text().slice(..), + doc.selection(view.id) + .primary() + .cursor(doc.text().slice(..)), ); - let text_len = right_side_text.len() as u16; - surface.set_string( - viewport.x + viewport.width.saturating_sub(text_len), + right_side_text.0.push(Span::styled( + format!(" {}:{} ", pos.row + 1, pos.col + 1), // Convert to 1-indexing. + base_style, + )); + + // Render to the statusline. + surface.set_spans( + viewport.x + + viewport + .width + .saturating_sub(right_side_text.width() as u16), viewport.y, - right_side_text, - style, + &right_side_text, + right_side_text.width() as u16, ); } From e36ad8b4edcede174ffad404b70d7c12d007548e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 25 Oct 2021 12:07:49 +0900 Subject: [PATCH 055/122] minor: Further simplify take_with --- helix-view/src/document.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 23c2dbc6b..b0257f031 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -311,8 +311,7 @@ where T: Default, F: FnOnce(T) -> T, { - let t = mem::take(mut_ref); - let _ = mem::replace(mut_ref, f(t)); + *mut_ref = f(mem::take(mut_ref)); } use helix_lsp::lsp; From 3fe353c47cb9c56d8e08396be476cf53b688fe36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 27 Oct 2021 10:02:46 +0900 Subject: [PATCH 056/122] Remove some old TODOs --- TODO.md | 6 ------ helix-term/Cargo.toml | 2 -- 2 files changed, 8 deletions(-) diff --git a/TODO.md b/TODO.md index 90e7e4509..80a9be05e 100644 --- a/TODO.md +++ b/TODO.md @@ -6,10 +6,6 @@ - clojure - erlang -as you type completion! -- [ ] use signature_help_provider and completion_provider trigger characters in - a hook to trigger signature help text / autocompletion -- [ ] document.on_type provider triggers - [ ] completion isIncomplete support 1 @@ -18,8 +14,6 @@ as you type completion! - [ ] = for auto indent line/selection - [ ] :x for closing buffers -- [ ] repeat selection - - [ ] lsp: signature help 2 diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 78afab01e..cf1f44d59 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -44,8 +44,6 @@ log = "0.4" # File picker fuzzy-matcher = "0.3" ignore = "0.4" -# shellexpand = "2.1" -# dirs-next = "2.0" # markdown doc rendering pulldown-cmark = { version = "0.8", default-features = false } From 2cee0c58baf601995e6fd7ef774d8556bb1af6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 27 Oct 2021 10:04:23 +0900 Subject: [PATCH 057/122] minor: Rearrange helix-term Cargo.toml --- helix-term/Cargo.toml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index cf1f44d59..72bb8fec6 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -25,16 +25,20 @@ helix-core = { version = "0.4", path = "../helix-core" } helix-view = { version = "0.4", path = "../helix-view" } helix-lsp = { version = "0.4", path = "../helix-lsp" } +# Rendering +tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } +crossterm = { version = "0.22", features = ["event-stream"] } +signal-hook = "0.3" +[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 +signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } + anyhow = "1" once_cell = "1.8" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } num_cpus = "1" -tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.22", features = ["event-stream"] } -signal-hook = "0.3" - futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } +tokio-stream = "0.1.7" # Logging fern = "0.6" @@ -56,7 +60,4 @@ serde = { version = "1.0", features = ["derive"] } # ripgrep for global search grep-regex = "0.1.9" grep-searcher = "0.1.8" -tokio-stream = "0.1.7" -[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 -signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } From 1066b081ddb06a5370371ca139dc8e9b992c1242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 27 Oct 2021 18:23:17 +0900 Subject: [PATCH 058/122] fix: When cycling through prompt history, update event needs to trigger --- helix-term/src/ui/prompt.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 1d512ad22..853adfc2a 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -501,6 +501,7 @@ impl Component for Prompt { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Backward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } KeyEvent { @@ -514,6 +515,7 @@ impl Component for Prompt { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Forward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } KeyEvent { From 4a3285110343e43186302fc08ac8e0fc2aa31ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 00:13:21 +0900 Subject: [PATCH 059/122] Break CI cache --- .github/workflows/build.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d82c609e..d4822f706 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,19 +28,19 @@ jobs: uses: actions/cache@v2.1.6 with: path: ~/.cargo/registry - key: ${{ runner.os }}-v1-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v2.1.6 with: path: ~/.cargo/git - key: ${{ runner.os }}-v1-cargo-index-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo target dir uses: actions/cache@v2.1.6 with: path: target - key: ${{ runner.os }}-v1-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - name: Run cargo check uses: actions-rs/cargo@v1 @@ -67,19 +67,19 @@ jobs: uses: actions/cache@v2.1.6 with: path: ~/.cargo/registry - key: ${{ runner.os }}-v1-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v2.1.6 with: path: ~/.cargo/git - key: ${{ runner.os }}-v1-cargo-index-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo target dir uses: actions/cache@v2.1.6 with: path: target - key: ${{ runner.os }}-v1-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - name: Run cargo test uses: actions-rs/cargo@v1 @@ -112,19 +112,19 @@ jobs: uses: actions/cache@v2.1.6 with: path: ~/.cargo/registry - key: ${{ runner.os }}-v1-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index uses: actions/cache@v2.1.6 with: path: ~/.cargo/git - key: ${{ runner.os }}-v1-cargo-index-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo target dir uses: actions/cache@v2.1.6 with: path: target - key: ${{ runner.os }}-v1-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-v2-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - name: Run cargo fmt uses: actions-rs/cargo@v1 From 5501669f8c77a1b919d8d243795b280eeab3a11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 00:21:30 +0900 Subject: [PATCH 060/122] Revert "minor: Rearrange helix-term Cargo.toml" This reverts commit 2cee0c58baf601995e6fd7ef774d8556bb1af6b9. --- helix-term/Cargo.toml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 72bb8fec6..cf1f44d59 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -25,20 +25,16 @@ helix-core = { version = "0.4", path = "../helix-core" } helix-view = { version = "0.4", path = "../helix-view" } helix-lsp = { version = "0.4", path = "../helix-lsp" } -# Rendering -tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.22", features = ["event-stream"] } -signal-hook = "0.3" -[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 -signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } - anyhow = "1" once_cell = "1.8" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } num_cpus = "1" +tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } +crossterm = { version = "0.22", features = ["event-stream"] } +signal-hook = "0.3" + futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } -tokio-stream = "0.1.7" # Logging fern = "0.6" @@ -60,4 +56,7 @@ serde = { version = "1.0", features = ["derive"] } # ripgrep for global search grep-regex = "0.1.9" grep-searcher = "0.1.8" +tokio-stream = "0.1.7" +[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 +signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } From fbba47fbc09d26cc41db87c5477dd7d27e8f6787 Mon Sep 17 00:00:00 2001 From: Nehliin Date: Sat, 16 Oct 2021 15:22:28 +0200 Subject: [PATCH 061/122] Fix panic when using multi-level key mapping --- helix-term/src/keymap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 495fe892f..c7b8e895b 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -140,7 +140,7 @@ impl KeyTrieNode { } } body.sort_unstable_by_key(|(_, keys)| { - self.order.iter().position(|&k| k == keys[0]).unwrap() + self.order.iter().position(|&k| k == keys[0]).unwrap_or(0) }); let prefix = format!("{} ", self.name()); if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) { From f133d80e70622b9cf8b882473453aca02208024d Mon Sep 17 00:00:00 2001 From: Nehliin Date: Sat, 16 Oct 2021 15:22:36 +0200 Subject: [PATCH 062/122] Move test to test module --- helix-term/src/keymap.rs | 122 ++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index c7b8e895b..b91fa0556 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -654,63 +654,67 @@ pub fn merge_keys(mut config: Config) -> Config { config } -#[test] -fn merge_partial_keys() { - let config = Config { - keys: Keymaps(hashmap! { - Mode::Normal => Keymap::new( - keymap!({ "Normal mode" - "i" => normal_mode, - "无" => insert_mode, - "z" => jump_backward, - "g" => { "Merge into goto mode" - "$" => goto_line_end, - "g" => delete_char_forward, - }, - }) - ) - }), - ..Default::default() - }; - let mut merged_config = merge_keys(config.clone()); - assert_ne!(config, merged_config); - - let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap(); - assert_eq!( - keymap.get(key!('i')).kind, - KeymapResultKind::Matched(Command::normal_mode), - "Leaf should replace leaf" - ); - assert_eq!( - keymap.get(key!('无')).kind, - KeymapResultKind::Matched(Command::insert_mode), - "New leaf should be present in merged keymap" - ); - // Assumes that z is a node in the default keymap - assert_eq!( - keymap.get(key!('z')).kind, - KeymapResultKind::Matched(Command::jump_backward), - "Leaf should replace node" - ); - // Assumes that `g` is a node in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('$')]).unwrap(), - &KeyTrie::Leaf(Command::goto_line_end), - "Leaf should be present in merged subnode" - ); - // Assumes that `gg` is in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('g')]).unwrap(), - &KeyTrie::Leaf(Command::delete_char_forward), - "Leaf should replace old leaf in merged subnode" - ); - // Assumes that `ge` is in default keymap - assert_eq!( - keymap.root().search(&[key!('g'), key!('e')]).unwrap(), - &KeyTrie::Leaf(Command::goto_last_line), - "Old leaves in subnode should be present in merged node" - ); - - assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0); +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn merge_partial_keys() { + let config = Config { + keys: Keymaps(hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "i" => normal_mode, + "无" => insert_mode, + "z" => jump_backward, + "g" => { "Merge into goto mode" + "$" => goto_line_end, + "g" => delete_char_forward, + }, + }) + ) + }), + ..Default::default() + }; + let mut merged_config = merge_keys(config.clone()); + assert_ne!(config, merged_config); + + let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap(); + assert_eq!( + keymap.get(key!('i')).kind, + KeymapResultKind::Matched(Command::normal_mode), + "Leaf should replace leaf" + ); + assert_eq!( + keymap.get(key!('无')).kind, + KeymapResultKind::Matched(Command::insert_mode), + "New leaf should be present in merged keymap" + ); + // Assumes that z is a node in the default keymap + assert_eq!( + keymap.get(key!('z')).kind, + KeymapResultKind::Matched(Command::jump_backward), + "Leaf should replace node" + ); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('$')]).unwrap(), + &KeyTrie::Leaf(Command::goto_line_end), + "Leaf should be present in merged subnode" + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('g')]).unwrap(), + &KeyTrie::Leaf(Command::delete_char_forward), + "Leaf should replace old leaf in merged subnode" + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('e')]).unwrap(), + &KeyTrie::Leaf(Command::goto_last_line), + "Old leaves in subnode should be present in merged node" + ); + + assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0); + } } From a4c5f467398fd3ad769907ca000dd812d5374abc Mon Sep 17 00:00:00 2001 From: Nehliin Date: Sun, 17 Oct 2021 16:06:08 +0200 Subject: [PATCH 063/122] Fix order being empty and add test --- helix-term/src/keymap.rs | 55 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b91fa0556..8f59060f6 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -118,11 +118,22 @@ impl KeyTrieNode { } self.map.insert(key, trie); } + self.set_order(); + } - for &key in self.map.keys() { + /// Sets the order of the mapping recursivly since the + /// the trie can contain child nodes without order. + /// The order is missing from child nodes since it's not + /// parsed from the config.toml + fn set_order(&mut self) { + for (&key, trie) in self.map.iter_mut() { if !self.order.contains(&key) { self.order.push(key); } + // Order must be recursivly set + if let KeyTrie::Node(node) = trie { + node.set_order(); + } } } @@ -140,7 +151,7 @@ impl KeyTrieNode { } } body.sort_unstable_by_key(|(_, keys)| { - self.order.iter().position(|&k| k == keys[0]).unwrap_or(0) + self.order.iter().position(|&k| k == keys[0]).unwrap() }); let prefix = format!("{} ", self.name()); if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) { @@ -151,6 +162,11 @@ impl KeyTrieNode { } Info::new(self.name(), body) } + + /// Get a reference to the key trie node's order. + pub fn order(&self) -> &[KeyEvent] { + self.order.as_slice() + } } impl Default for KeyTrieNode { @@ -235,6 +251,7 @@ pub enum KeymapResultKind { /// Returned after looking up a key in [`Keymap`]. The `sticky` field has a /// reference to the sticky node if one is currently active. +#[derive(Debug)] pub struct KeymapResult<'a> { pub kind: KeymapResultKind, pub sticky: Option<&'a KeyTrieNode>, @@ -717,4 +734,38 @@ mod tests { assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1); assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0); } + + #[test] + fn order_should_be_set() { + let config = Config { + keys: Keymaps(hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "space" => { "" + "s" => { "" + "v" => vsplit, + "c" => hsplit, + }, + }, + }) + ) + }), + ..Default::default() + }; + let mut merged_config = merge_keys(config.clone()); + assert_ne!(config, merged_config); + let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap(); + // Make sure mapping works + assert_eq!( + keymap + .root() + .search(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + &KeyTrie::Leaf(Command::vsplit), + "Leaf should be present in merged subnode" + ); + // Make sure an order was set during merge + let node = keymap.root().search(&[crate::key!(' ')]).unwrap(); + assert!(!node.node().unwrap().order().is_empty()) + } } From da4d9340baca311580ee7a10eaaec1f63a77bb60 Mon Sep 17 00:00:00 2001 From: Nehliin Date: Sun, 17 Oct 2021 16:06:21 +0200 Subject: [PATCH 064/122] Make key macro more portable --- helix-term/src/keymap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 8f59060f6..ed7f7eddb 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -12,13 +12,13 @@ use std::{ #[macro_export] macro_rules! key { ($key:ident) => { - KeyEvent { + ::helix_view::input::KeyEvent { code: ::helix_view::keyboard::KeyCode::$key, modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } }; ($($ch:tt)*) => { - KeyEvent { + ::helix_view::input::KeyEvent { code: ::helix_view::keyboard::KeyCode::Char($($ch)*), modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } From 6e455fd3fb7f0ce3715ca0af72197749a2f19f57 Mon Sep 17 00:00:00 2001 From: Oskar Nehlin Date: Mon, 18 Oct 2021 17:02:03 +0200 Subject: [PATCH 065/122] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Blaž Hrastnik --- helix-term/src/keymap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index ed7f7eddb..d51204e15 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -121,7 +121,7 @@ impl KeyTrieNode { self.set_order(); } - /// Sets the order of the mapping recursivly since the + /// Sets the order of the mapping recursively since the /// the trie can contain child nodes without order. /// The order is missing from child nodes since it's not /// parsed from the config.toml @@ -130,7 +130,7 @@ impl KeyTrieNode { if !self.order.contains(&key) { self.order.push(key); } - // Order must be recursivly set + // Order must be recursively set if let KeyTrie::Node(node) = trie { node.set_order(); } From 3b0c5e993a18a2d59582855784189995c7960d6f Mon Sep 17 00:00:00 2001 From: Nehliin Date: Sat, 23 Oct 2021 15:24:19 +0200 Subject: [PATCH 066/122] Use deserialization fix instead --- helix-term/src/keymap.rs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d51204e15..5453020ec 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -78,19 +78,30 @@ macro_rules! keymap { }; } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone)] pub struct KeyTrieNode { /// A label for keys coming under this node, like "Goto mode" - #[serde(skip)] name: String, - #[serde(flatten)] map: HashMap, - #[serde(skip)] order: Vec, - #[serde(skip)] pub is_sticky: bool, } +impl<'de> Deserialize<'de> for KeyTrieNode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let map = HashMap::::deserialize(deserializer)?; + let order = map.keys().copied().collect::>(); // NOTE: map.keys() has arbitrary order + Ok(Self { + map, + order, + ..Default::default() + }) + } +} + impl KeyTrieNode { pub fn new(name: &str, map: HashMap, order: Vec) -> Self { Self { @@ -118,22 +129,10 @@ impl KeyTrieNode { } self.map.insert(key, trie); } - self.set_order(); - } - - /// Sets the order of the mapping recursively since the - /// the trie can contain child nodes without order. - /// The order is missing from child nodes since it's not - /// parsed from the config.toml - fn set_order(&mut self) { - for (&key, trie) in self.map.iter_mut() { + for &key in self.map.keys() { if !self.order.contains(&key) { self.order.push(key); } - // Order must be recursively set - if let KeyTrie::Node(node) = trie { - node.set_order(); - } } } From e2ed6915373adf27881325ebc4e2c6b98e207af3 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Wed, 27 Oct 2021 21:23:46 -0400 Subject: [PATCH 067/122] Implement `hx --tutor` and `:tutor` to load `tutor.txt` (#898) * Implement `hx --tutor` and `:tutor` to load `tutor.txt` * Document `hx --tutor` and `:tutor` * Change `Document::set_path` to take an `Option` * `Document::set_path` accepts an `Option<&Path>` instead of `&Path`. * Remove `Editor::open_tutor` and make tutor-open functionality use `Editor::open` and `Document::set_path`. * Use `PathBuf::join` Co-authored-by: Ivan Tham * Add comments explaining unsetting tutor path Co-authored-by: Ivan Tham --- book/src/usage.md | 2 +- helix-term/src/application.rs | 7 ++++++- helix-term/src/args.rs | 2 ++ helix-term/src/commands.rs | 24 ++++++++++++++++++++++-- helix-view/src/document.rs | 12 ++++++++---- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/book/src/usage.md b/book/src/usage.md index d31e03a1b..71730fa8b 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -2,7 +2,7 @@ (Currently not fully documented, see the [keymappings](./keymap.md) list for more.) -See [tutor.txt](https://github.com/helix-editor/helix/blob/master/runtime/tutor.txt) for a vimtutor-like introduction. +See [tutor.txt](https://github.com/helix-editor/helix/blob/master/runtime/tutor.txt) (accessible via `hx --tutor` or `:tutor`) for a vimtutor-like introduction. ## Registers diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 662573c65..55b12c5a2 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -97,7 +97,12 @@ impl Application { let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys))); compositor.push(editor_view); - if !args.files.is_empty() { + if args.load_tutor { + let path = helix_core::runtime_dir().join("tutor.txt"); + editor.open(path, Action::VerticalSplit)?; + // Unset path to prevent accidentally saving to the original tutor file. + doc_mut!(editor).set_path(None)?; + } else if !args.files.is_empty() { let first = &args.files[0]; // we know it's not empty if first.is_dir() { std::env::set_current_dir(&first)?; diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs index f0ef09eb0..40113db92 100644 --- a/helix-term/src/args.rs +++ b/helix-term/src/args.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; pub struct Args { pub display_help: bool, pub display_version: bool, + pub load_tutor: bool, pub verbosity: u64, pub files: Vec, } @@ -22,6 +23,7 @@ impl Args { "--" => break, // stop parsing at this point treat the remaining as files "--version" => args.display_version = true, "--help" => args.display_help = true, + "--tutor" => args.load_tutor = true, arg if arg.starts_with("--") => { return Err(Error::msg(format!( "unexpected double dash argument: {}", diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d6e5bfe71..b3be6d5b3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1557,7 +1557,8 @@ mod cmd { let (_, doc) = current!(cx.editor); if let Some(path) = path { - doc.set_path(path.as_ref()).context("invalid filepath")?; + doc.set_path(Some(path.as_ref())) + .context("invalid filepath")?; } if doc.path().is_none() { bail!("cannot write a buffer without a filename"); @@ -2099,6 +2100,18 @@ mod cmd { Ok(()) } + fn tutor( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let path = helix_core::runtime_dir().join("tutor.txt"); + cx.editor.open(path, Action::Replace)?; + // Unset path to prevent accidentally saving to the original tutor file. + doc_mut!(cx.editor).set_path(None)?; + Ok(()) + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -2351,7 +2364,14 @@ mod cmd { doc: "Open the file in a horizontal split.", fun: hsplit, completer: Some(completers::filename), - } + }, + TypableCommand { + name: "tutor", + aliases: &[], + doc: "Open the tutorial.", + fun: tutor, + completer: None, + }, ]; pub static COMMANDS: Lazy> = Lazy::new(|| { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index b0257f031..4d779656a 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -368,7 +368,7 @@ impl Document { let mut doc = Self::from(rope, Some(encoding)); // set the path and try detecting the language - doc.set_path(path)?; + doc.set_path(Some(path))?; if let Some(loader) = config_loader { doc.detect_language(theme, loader); } @@ -553,12 +553,16 @@ impl Document { self.encoding } - pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> { - let path = helix_core::path::get_canonicalized_path(path)?; + pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> { + let path = if let Some(p) = path { + Some(helix_core::path::get_canonicalized_path(p)?) + } else { + path.map(|p| p.into()) + }; // if parent doesn't exist we still want to open the document // and error out when document is saved - self.path = Some(path); + self.path = path; Ok(()) } From 0a38983ee3d4a38a026dfb44ae9cd99f66145d3d Mon Sep 17 00:00:00 2001 From: Gygaxis Vainhardt <44003709+AloeareV@users.noreply.github.com> Date: Wed, 27 Oct 2021 22:24:11 -0300 Subject: [PATCH 068/122] Remove three transmutes from helix-core syntax.rs (#923) --- helix-core/src/syntax.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 281a70f91..f3e3f238b 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -437,7 +437,7 @@ impl Syntax { /// Iterate over the highlighted regions for a given slice of source code. pub fn highlight_iter<'a>( - &self, + &'a self, source: RopeSlice<'a>, range: Option>, cancellation_flag: Option<&'a AtomicUsize>, @@ -452,11 +452,10 @@ impl Syntax { let highlighter = &mut ts_parser.borrow_mut(); highlighter.cursors.pop().unwrap_or_else(QueryCursor::new) }); - let tree_ref = unsafe { mem::transmute::<_, &'static Tree>(self.tree()) }; + let tree_ref = self.tree(); let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; - let query_ref = unsafe { mem::transmute::<_, &'static Query>(&self.config.query) }; - let config_ref = - unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) }; + let query_ref = &self.config.query; + let config_ref = self.config.as_ref(); // if reusing cursors & no range this resets to whole range cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX)); From 3e69a4852eb9914ced7438c0788170c43756d741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 10:50:17 +0900 Subject: [PATCH 069/122] Simplify set_path --- helix-view/src/document.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 4d779656a..27a19f3c5 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -554,11 +554,9 @@ impl Document { } pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> { - let path = if let Some(p) = path { - Some(helix_core::path::get_canonicalized_path(p)?) - } else { - path.map(|p| p.into()) - }; + let path = path + .map(|path| helix_core::path::get_canonicalized_path(path)) + .transpose()?; // if parent doesn't exist we still want to open the document // and error out when document is saved From c1e5831b2186a12ab593df1dc1dc698875484dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 10:51:19 +0900 Subject: [PATCH 070/122] set_path: Pass in the function directly --- helix-view/src/document.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 27a19f3c5..c623277d8 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -555,7 +555,7 @@ impl Document { pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> { let path = path - .map(|path| helix_core::path::get_canonicalized_path(path)) + .map(helix_core::path::get_canonicalized_path) .transpose()?; // if parent doesn't exist we still want to open the document From db56de589a2b5e5ad3c0ceba05f91372c382f538 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 28 Oct 2021 03:27:28 -0400 Subject: [PATCH 071/122] Add `--tutor` option to `hx --help` output (#924) * Add `--tutor` option to `hx --help` output * Adjust `--tutor` location in help output --- helix-term/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 2589a375b..f746895cf 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -60,6 +60,7 @@ ARGS: FLAGS: -h, --help Prints help information + --tutor Loads the tutorial -v Increases logging verbosity each use for up to 3 times (default file: {}) -V, --version Prints version information From f3c7f20dbce09f516ada77f34f215d64743a34f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 16:39:17 +0900 Subject: [PATCH 072/122] Release v0.5.0 --- CHANGELOG.md | 86 +++++++++++++++++++++++++++++++++++++++++ Cargo.lock | 12 +++--- helix-core/Cargo.toml | 4 +- helix-lsp/Cargo.toml | 2 +- helix-syntax/Cargo.toml | 2 +- helix-term/Cargo.toml | 8 ++-- helix-tui/Cargo.toml | 6 +-- helix-view/Cargo.toml | 6 +-- 8 files changed, 106 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f573076..e21f38368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,84 @@ +# 0.5.0 (2021-11-28) + +A big shout out to all the contributors! We had 46 contributors in this release. + +Helix has popped up in [Scoop, FreeBSD Ports and Gentu GURU](https://repology.org/project/helix/versions)! + +The following is a quick rundown of the larger changes, there were many more +(check the git history for more details). + +Breaking changes: + +- A couple of keymaps moved to resolve a few conflicting keybinds. + - Documentation popups were moved from `K` to `space+k` + - `K` is now `keep_selections` which filters selections to only keeps ones matching the regex + - `keep_primary_selection` moved from `space+space` to `,` + - `Alt-,` is now `remove_primary_selection` which keeps all selections except the primary one + - Opening files in a split moved from `C-h` to `C-s` +- Some configuration options moved from a `[terminal]` section to `[editor]`. [Consult the documentation for more information.](https://docs.helix-editor.com/configuration.html). + +Features: + +- LSP compatibility greatly improved for some implementations (Julia, Python, Typescript) +- Autocompletion! Completion now triggers automatically after a set idle timeout +- Completion documentation is now displayed next to the popup (#691) +- Treesitter textobjects (select a function via `mf`, class via `mc`) (#728) +- Global search across entire workspace `space+/` (#651) +- Relative line number support (#485) +- Prompts now store a history (72cf86e) +- `:vsplit` and `:hsplit` commands (#639) +- `C-w h/j/k/l` can now be used to navigate between splits (#860) +- `C-j` and `C-k` are now alternative keybindings to `C-n` and `C-p` in the UI (#876) +- Shell commands (shell-pipe, pipe-to, shell-insert-output, shell-append-output, keep-pipe) (#547) +- Searching now defaults to smart case search (case insensitive unless uppercase is used) (#761) +- The preview pane was improved to highlight and center line ranges +- The user `languages.toml` is now merged into defaults, no longer need to copy the entire file (dc57f8dc) +- Show hidden files in completions (#648) +- Grammar injections are now properly handled (dd0b15e) +- `v` in select mode now switches back to normal mode (#660) +- View mode can now be triggered as a "sticky" mode (#719) +- `f`/`t` and object selection motions can now be repeated via `Alt-.` (#891) +- Statusline now displays total selection count and diagnostics counts for both errors and warnings (#916) + +New grammars: + +- Ledger (#572) +- Protobuf (#614) +- Zig (#631) +- YAML (#667) +- Lua (#665) +- OCaml (#666) +- Svelte (#733) +- Vue (#787) +- Tree-sitter queries (#845) +- Elixir (we switched over to the official grammar) (6c0786e) +- Language server definitions for Nix and Elixir (#725) +- Python now uses `pylsp` instead of `pyls` +- Python now supports indentation + +New themes: + +- Monokai (#628) +- Everforest Dark (#760) +- Nord (#799) +- Base16 Default Dark (#833) +- Rose Pine (#897) + +Fixes: + +- Fix crash on empty rust file (#592) +- Exit select mode after toggle comment (#598) +- Pin popups with no positioning to the initial position (12ea3888) +- xsel copy should not freeze the editor (6dd7dc4) +- `*` now only sets the search register and doesn't jump to the next occurrence (3426285) +- Goto line start/end commands extend when in select mode (#739) +- Fix documentation popups sometimes not getting fully highlighted (066367c) +- Refactor apply_workspace_edit to remove assert (b02d872) +- Wrap around the top of the picker menu when scrolling (c7d6e44) +- Don't allow closing the last split if there's unsaved changes (3ff5b00) +- Indentation used different default on hx vs hx new_file.txt (c913bad) + # 0.4.1 (2021-08-14) A minor release that includes: @@ -7,6 +87,8 @@ A minor release that includes: # 0.4.0 (2021-08-13) +A big shout out to all the contributors! We had 28 contributors in this release. + Two months have passed, so this is another big release. A big thank you to all the contributors and package maintainers! @@ -44,6 +126,8 @@ selections in the future as well as resolves many bugs and edge cases. # 0.3.0 (2021-06-27) +A big shout out to all the contributors! We had 24 contributors in this release. + Another big release. Highlights: @@ -90,6 +174,8 @@ Includes a fix where wq/wqa could exit before file saving completed. # 0.2.0 +A big shout out to all the contributors! We had 18 contributors in this release. + Enough has changed to bump the version. We're skipping 0.1.x because previously the CLI would always report version as 0.1.0, and we'd like to distinguish it in bug reports.. diff --git a/Cargo.lock b/Cargo.lock index 17bc73a99..45a8f5da3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,7 +358,7 @@ dependencies = [ [[package]] name = "helix-core" -version = "0.4.1" +version = "0.5.0" dependencies = [ "arc-swap", "etcetera", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "helix-lsp" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "futures-executor", @@ -400,7 +400,7 @@ dependencies = [ [[package]] name = "helix-syntax" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "cc", @@ -411,7 +411,7 @@ dependencies = [ [[package]] name = "helix-term" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "chrono", @@ -441,7 +441,7 @@ dependencies = [ [[package]] name = "helix-tui" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bitflags", "cassowary", @@ -454,7 +454,7 @@ dependencies = [ [[package]] name = "helix-view" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "bitflags", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 84d029d2e..ea695d34a 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helix-core" -version = "0.4.1" +version = "0.5.0" authors = ["Blaž Hrastnik "] edition = "2021" license = "MPL-2.0" @@ -13,7 +13,7 @@ include = ["src/**/*", "README.md"] [features] [dependencies] -helix-syntax = { version = "0.4", path = "../helix-syntax" } +helix-syntax = { version = "0.5", path = "../helix-syntax" } ropey = "1.3" smallvec = "1.7" diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 455407ad2..5a284c46c 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helix-lsp" -version = "0.4.1" +version = "0.5.0" authors = ["Blaž Hrastnik "] edition = "2021" license = "MPL-2.0" diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml index 122fa4603..cceec4127 100644 --- a/helix-syntax/Cargo.toml +++ b/helix-syntax/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helix-syntax" -version = "0.4.1" +version = "0.5.0" authors = ["Blaž Hrastnik "] edition = "2021" license = "MPL-2.0" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index cf1f44d59..2414c4335 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helix-term" -version = "0.4.1" +version = "0.5.0" description = "A post-modern text editor." authors = ["Blaž Hrastnik "] edition = "2021" @@ -21,9 +21,9 @@ name = "hx" path = "src/main.rs" [dependencies] -helix-core = { version = "0.4", path = "../helix-core" } -helix-view = { version = "0.4", path = "../helix-view" } -helix-lsp = { version = "0.4", path = "../helix-lsp" } +helix-core = { version = "0.5", path = "../helix-core" } +helix-view = { version = "0.5", path = "../helix-view" } +helix-lsp = { version = "0.5", path = "../helix-lsp" } anyhow = "1" once_cell = "1.8" diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index f0c0d7e2d..6df65d360 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helix-tui" -version = "0.4.1" +version = "0.5.0" authors = ["Blaž Hrastnik "] description = """ A library to build rich terminal user interfaces or dashboards @@ -21,5 +21,5 @@ cassowary = "0.3" unicode-segmentation = "1.8" crossterm = { version = "0.22", optional = true } serde = { version = "1", "optional" = true, features = ["derive"]} -helix-view = { version = "0.4", path = "../helix-view", features = ["term"] } -helix-core = { version = "0.4", path = "../helix-core" } +helix-view = { version = "0.5", path = "../helix-view", features = ["term"] } +helix-core = { version = "0.5", path = "../helix-core" } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index ef09b964f..34f55eb60 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helix-view" -version = "0.4.1" +version = "0.5.0" authors = ["Blaž Hrastnik "] edition = "2021" license = "MPL-2.0" @@ -16,8 +16,8 @@ term = ["crossterm"] [dependencies] bitflags = "1.3" anyhow = "1" -helix-core = { version = "0.4", path = "../helix-core" } -helix-lsp = { version = "0.4", path = "../helix-lsp"} +helix-core = { version = "0.5", path = "../helix-core" } +helix-lsp = { version = "0.5", path = "../helix-lsp"} crossterm = { version = "0.22", optional = true } # Conversion traits From a1b7f003a6ea61b2a77056ce8865a779b3452975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 16:44:36 +0900 Subject: [PATCH 073/122] Include the missing dependency bump --- helix-lsp/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 5a284c46c..f9910cc0b 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://helix-editor.com" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -helix-core = { version = "0.4", path = "../helix-core" } +helix-core = { version = "0.5", path = "../helix-core" } anyhow = "1.0" futures-executor = "0.3" From 8af6d713cdcc6ce36c1da22a34a2917d80d061a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 16:49:13 +0900 Subject: [PATCH 074/122] Fix the release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e21f38368..12a3cc77b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -# 0.5.0 (2021-11-28) +# 0.5.0 (2021-10-28) A big shout out to all the contributors! We had 46 contributors in this release. From c02534d2619de0e4d13c4b3bc9b9f32b42ae410b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 28 Oct 2021 16:57:44 +0900 Subject: [PATCH 075/122] changelog: Add links to all pull requests --- CHANGELOG.md | 66 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a3cc77b..145455528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,63 +16,63 @@ Breaking changes: - `keep_primary_selection` moved from `space+space` to `,` - `Alt-,` is now `remove_primary_selection` which keeps all selections except the primary one - Opening files in a split moved from `C-h` to `C-s` -- Some configuration options moved from a `[terminal]` section to `[editor]`. [Consult the documentation for more information.](https://docs.helix-editor.com/configuration.html). +- Some configuration options moved from a `[terminal]` section to `[editor]`. [Consult the documentation for more information.](https://docs.helix-editor.com/configuration.html) Features: - LSP compatibility greatly improved for some implementations (Julia, Python, Typescript) - Autocompletion! Completion now triggers automatically after a set idle timeout -- Completion documentation is now displayed next to the popup (#691) -- Treesitter textobjects (select a function via `mf`, class via `mc`) (#728) -- Global search across entire workspace `space+/` (#651) -- Relative line number support (#485) +- Completion documentation is now displayed next to the popup ([#691](https://github.com/helix-editor/helix/pull/691)) +- Treesitter textobjects (select a function via `mf`, class via `mc`) ([#728](https://github.com/helix-editor/helix/pull/728)) +- Global search across entire workspace `space+/` ([#651](https://github.com/helix-editor/helix/pull/651)) +- Relative line number support ([#485](https://github.com/helix-editor/helix/pull/485)) - Prompts now store a history (72cf86e) -- `:vsplit` and `:hsplit` commands (#639) -- `C-w h/j/k/l` can now be used to navigate between splits (#860) -- `C-j` and `C-k` are now alternative keybindings to `C-n` and `C-p` in the UI (#876) -- Shell commands (shell-pipe, pipe-to, shell-insert-output, shell-append-output, keep-pipe) (#547) -- Searching now defaults to smart case search (case insensitive unless uppercase is used) (#761) +- `:vsplit` and `:hsplit` commands ([#639](https://github.com/helix-editor/helix/pull/639)) +- `C-w h/j/k/l` can now be used to navigate between splits ([#860](https://github.com/helix-editor/helix/pull/860)) +- `C-j` and `C-k` are now alternative keybindings to `C-n` and `C-p` in the UI ([#876](https://github.com/helix-editor/helix/pull/876)) +- Shell commands (shell-pipe, pipe-to, shell-insert-output, shell-append-output, keep-pipe) ([#547](https://github.com/helix-editor/helix/pull/547)) +- Searching now defaults to smart case search (case insensitive unless uppercase is used) ([#761](https://github.com/helix-editor/helix/pull/761)) - The preview pane was improved to highlight and center line ranges - The user `languages.toml` is now merged into defaults, no longer need to copy the entire file (dc57f8dc) -- Show hidden files in completions (#648) +- Show hidden files in completions ([#648](https://github.com/helix-editor/helix/pull/648)) - Grammar injections are now properly handled (dd0b15e) -- `v` in select mode now switches back to normal mode (#660) -- View mode can now be triggered as a "sticky" mode (#719) -- `f`/`t` and object selection motions can now be repeated via `Alt-.` (#891) -- Statusline now displays total selection count and diagnostics counts for both errors and warnings (#916) +- `v` in select mode now switches back to normal mode ([#660](https://github.com/helix-editor/helix/pull/660)) +- View mode can now be triggered as a "sticky" mode ([#719](https://github.com/helix-editor/helix/pull/719)) +- `f`/`t` and object selection motions can now be repeated via `Alt-.` ([#891](https://github.com/helix-editor/helix/pull/891)) +- Statusline now displays total selection count and diagnostics counts for both errors and warnings ([#916](https://github.com/helix-editor/helix/pull/916)) New grammars: -- Ledger (#572) -- Protobuf (#614) -- Zig (#631) -- YAML (#667) -- Lua (#665) -- OCaml (#666) -- Svelte (#733) -- Vue (#787) -- Tree-sitter queries (#845) +- Ledger ([#572](https://github.com/helix-editor/helix/pull/572)) +- Protobuf ([#614](https://github.com/helix-editor/helix/pull/614)) +- Zig ([#631](https://github.com/helix-editor/helix/pull/631)) +- YAML ([#667](https://github.com/helix-editor/helix/pull/667)) +- Lua ([#665](https://github.com/helix-editor/helix/pull/665)) +- OCaml ([#666](https://github.com/helix-editor/helix/pull/666)) +- Svelte ([#733](https://github.com/helix-editor/helix/pull/733)) +- Vue ([#787](https://github.com/helix-editor/helix/pull/787)) +- Tree-sitter queries ([#845](https://github.com/helix-editor/helix/pull/845)) - Elixir (we switched over to the official grammar) (6c0786e) -- Language server definitions for Nix and Elixir (#725) +- Language server definitions for Nix and Elixir ([#725](https://github.com/helix-editor/helix/pull/725)) - Python now uses `pylsp` instead of `pyls` - Python now supports indentation New themes: -- Monokai (#628) -- Everforest Dark (#760) -- Nord (#799) -- Base16 Default Dark (#833) -- Rose Pine (#897) +- Monokai ([#628](https://github.com/helix-editor/helix/pull/628)) +- Everforest Dark ([#760](https://github.com/helix-editor/helix/pull/760)) +- Nord ([#799](https://github.com/helix-editor/helix/pull/799)) +- Base16 Default Dark ([#833](https://github.com/helix-editor/helix/pull/833)) +- Rose Pine ([#897](https://github.com/helix-editor/helix/pull/897)) Fixes: -- Fix crash on empty rust file (#592) -- Exit select mode after toggle comment (#598) +- Fix crash on empty rust file ([#592](https://github.com/helix-editor/helix/pull/592)) +- Exit select mode after toggle comment ([#598](https://github.com/helix-editor/helix/pull/598)) - Pin popups with no positioning to the initial position (12ea3888) - xsel copy should not freeze the editor (6dd7dc4) - `*` now only sets the search register and doesn't jump to the next occurrence (3426285) -- Goto line start/end commands extend when in select mode (#739) +- Goto line start/end commands extend when in select mode ([#739](https://github.com/helix-editor/helix/pull/739)) - Fix documentation popups sometimes not getting fully highlighted (066367c) - Refactor apply_workspace_edit to remove assert (b02d872) - Wrap around the top of the picker menu when scrolling (c7d6e44) From 58b8100751059d34602800d8ebd5a511775b4731 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Thu, 28 Oct 2021 14:23:13 +0200 Subject: [PATCH 076/122] Mention CMake support in changelog (#926) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 145455528..52ca2d602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ New grammars: - Svelte ([#733](https://github.com/helix-editor/helix/pull/733)) - Vue ([#787](https://github.com/helix-editor/helix/pull/787)) - Tree-sitter queries ([#845](https://github.com/helix-editor/helix/pull/845)) +- CMake ([#888](https://github.com/helix-editor/helix/pull/888)) - Elixir (we switched over to the official grammar) (6c0786e) - Language server definitions for Nix and Elixir ([#725](https://github.com/helix-editor/helix/pull/725)) - Python now uses `pylsp` instead of `pyls` From 45fadf61518b36c2e020f7262f136588dc96b03e Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 28 Oct 2021 20:55:15 -0400 Subject: [PATCH 077/122] Add hyperlinks to fix `cargo doc` warn (#931) --- helix-core/src/graphemes.rs | 2 +- helix-core/src/history.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-core/src/graphemes.rs b/helix-core/src/graphemes.rs index 56f5bacb7..c63988757 100644 --- a/helix-core/src/graphemes.rs +++ b/helix-core/src/graphemes.rs @@ -1,6 +1,6 @@ //! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents. //! -//! Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs +//! Based on use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice}; use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; use unicode_width::UnicodeWidthStr; diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index cf62708a9..b53c01fe7 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -40,7 +40,7 @@ use std::time::{Duration, Instant}; /// * Because delete transactions currently don't store the text that they /// delete, we also store an inversion of the transaction. /// -/// Using time to navigate the history: https://github.com/helix-editor/helix/pull/194 +/// Using time to navigate the history: #[derive(Debug)] pub struct History { revisions: Vec, From f1d339919f4299d570bb6f7fcf5fcc58c0f281cf Mon Sep 17 00:00:00 2001 From: cossonleo Date: Wed, 27 Oct 2021 17:42:11 +0800 Subject: [PATCH 078/122] add expand_selection to last_motion --- helix-term/src/commands.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b3be6d5b3..08750aeb1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4348,13 +4348,17 @@ fn rotate_selection_contents_backward(cx: &mut Context) { // tree sitter node selection fn expand_selection(cx: &mut Context) { - let (view, doc) = current!(cx.editor); + let motion = |editor: &mut Editor| { + let (view, doc) = current!(editor); - if let Some(syntax) = doc.syntax() { - let text = doc.text().slice(..); - let selection = object::expand_selection(syntax, text, doc.selection(view.id)); - doc.set_selection(view.id, selection); - } + if let Some(syntax) = doc.syntax() { + let text = doc.text().slice(..); + let selection = object::expand_selection(syntax, text, doc.selection(view.id)); + doc.set_selection(view.id, selection); + } + }; + motion(&mut cx.editor); + cx.editor.last_motion = Some(Motion(Box::new(motion))); } fn match_brackets(cx: &mut Context) { From befecc8a9a087436a9dbc6942e328646c3391874 Mon Sep 17 00:00:00 2001 From: cossonleo Date: Thu, 28 Oct 2021 13:31:44 +0800 Subject: [PATCH 079/122] select smaller range on some case --- helix-core/src/object.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index d9558dd83..717c59947 100644 --- a/helix-core/src/object.rs +++ b/helix-core/src/object.rs @@ -13,8 +13,13 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) let parent = match tree .root_node() .descendant_for_byte_range(from, to) - .and_then(|node| node.parent()) - { + .and_then(|node| { + if node.child_count() == 0 || (node.start_byte() == from && node.end_byte() == to) { + node.parent() + } else { + Some(node) + } + }) { Some(parent) => parent, None => return range, }; From 21d535565bfcc5ff4e2eae34329b051944a8d1f5 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Fri, 29 Oct 2021 09:07:07 +0800 Subject: [PATCH 080/122] Support extend for multiple goto (#909) gg, ge, [n]gg --- helix-term/src/commands.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 08750aeb1..9eda2c23b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -622,14 +622,25 @@ fn goto_file_start(cx: &mut Context) { } else { push_jump(cx.editor); let (view, doc) = current!(cx.editor); - doc.set_selection(view.id, Selection::point(0)); + let text = doc.text().slice(..); + let selection = doc + .selection(view.id) + .clone() + .transform(|range| range.put_cursor(text, 0, doc.mode == Mode::Select)); + doc.set_selection(view.id, selection); } } fn goto_file_end(cx: &mut Context) { push_jump(cx.editor); let (view, doc) = current!(cx.editor); - doc.set_selection(view.id, Selection::point(doc.text().len_chars())); + let text = doc.text().slice(..); + let pos = doc.text().len_chars(); + let selection = doc + .selection(view.id) + .clone() + .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + doc.set_selection(view.id, selection); } fn extend_word_impl(cx: &mut Context, extend_fn: F) @@ -2905,8 +2916,13 @@ fn goto_line(cx: &mut Context) { doc.text().len_lines() - 1 }; let line_idx = std::cmp::min(count.get() - 1, max_line); + let text = doc.text().slice(..); let pos = doc.text().line_to_char(line_idx); - doc.set_selection(view.id, Selection::point(pos)); + let selection = doc + .selection(view.id) + .clone() + .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + doc.set_selection(view.id, selection); } } @@ -2920,8 +2936,13 @@ fn goto_last_line(cx: &mut Context) { } else { doc.text().len_lines() - 1 }; + let text = doc.text().slice(..); let pos = doc.text().line_to_char(line_idx); - doc.set_selection(view.id, Selection::point(pos)); + let selection = doc + .selection(view.id) + .clone() + .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + doc.set_selection(view.id, selection); } fn goto_last_accessed_file(cx: &mut Context) { From bc6a34d97edae55811c2476278a6288d7d258af3 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Fri, 29 Oct 2021 09:08:53 +0800 Subject: [PATCH 081/122] Make match work with extend and multi cursors (#920) --- helix-term/src/commands.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9eda2c23b..28657865b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4386,14 +4386,15 @@ fn match_brackets(cx: &mut Context) { let (view, doc) = current!(cx.editor); if let Some(syntax) = doc.syntax() { - let pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if let Some(pos) = match_brackets::find(syntax, doc.text(), pos) { - let selection = Selection::point(pos); - doc.set_selection(view.id, selection); - }; + let text = doc.text().slice(..); + let selection = doc.selection(view.id).clone().transform(|range| { + if let Some(pos) = match_brackets::find(syntax, doc.text(), range.anchor) { + range.put_cursor(text, pos, doc.mode == Mode::Select) + } else { + range + } + }); + doc.set_selection(view.id, selection); } } From cec0cfdaecdb9b3e7fdf3ae3bb3252ba3f2f2752 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Thu, 28 Oct 2021 21:11:30 -0400 Subject: [PATCH 082/122] Uncomment mapping LSP diagnostics through changes (#925) --- helix-view/src/document.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index c623277d8..02da4b7af 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -657,14 +657,13 @@ impl Document { } // map state.diagnostics over changes::map_pos too - // NOTE: seems to do nothing since the language server resends diagnostics on each edit - // for diagnostic in &mut self.diagnostics { - // use helix_core::Assoc; - // let changes = transaction.changes(); - // diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After); - // diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After); - // diagnostic.line = self.text.char_to_line(diagnostic.range.start); - // } + for diagnostic in &mut self.diagnostics { + use helix_core::Assoc; + let changes = transaction.changes(); + diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After); + diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After); + diagnostic.line = self.text.char_to_line(diagnostic.range.start); + } // emit lsp notification if let Some(language_server) = self.language_server() { From 49f6c2623fbda5ff4be86e5e7d773bf900d9c75c Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Fri, 29 Oct 2021 11:00:18 +0800 Subject: [PATCH 083/122] Bump lsp-types to 0.91.0 (#932) --- Cargo.lock | 4 +-- helix-lsp/Cargo.toml | 2 +- helix-lsp/src/client.rs | 7 +++-- helix-term/src/application.rs | 9 +++--- helix-term/src/ui/completion.rs | 51 +++++++++++++++++---------------- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45a8f5da3..8af5c45c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.90.1" +version = "0.91.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3734ab1d7d157fc0c45110e06b587c31cd82bea2ccfd6b563cbff0aaeeb1d3" +checksum = "be7801b458592d0998af808d97f6a85a6057af3aaf2a2a5c3c677702bbeb4ed7" dependencies = [ "bitflags", "serde", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index f9910cc0b..8cbff41d0 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -19,7 +19,7 @@ futures-executor = "0.3" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } jsonrpc-core = { version = "18.0", default-features = false } # don't pull in all of futures log = "0.4" -lsp-types = { version = "0.90", features = ["proposed"] } +lsp-types = { version = "0.91", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 4068ae1fe..b810feef3 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -461,7 +461,7 @@ impl Client { }; let changes = match sync_capabilities { - lsp::TextDocumentSyncKind::Full => { + lsp::TextDocumentSyncKind::FULL => { vec![lsp::TextDocumentContentChangeEvent { // range = None -> whole document range: None, //Some(Range) @@ -469,10 +469,11 @@ impl Client { text: new_text.to_string(), }] } - lsp::TextDocumentSyncKind::Incremental => { + lsp::TextDocumentSyncKind::INCREMENTAL => { Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding) } - lsp::TextDocumentSyncKind::None => return None, + lsp::TextDocumentSyncKind::NONE => return None, + kind => unimplemented!("{:?}", kind), }; Some(self.notify::( diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 55b12c5a2..6037148f6 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -389,10 +389,11 @@ impl Application { message: diagnostic.message, severity: diagnostic.severity.map( |severity| match severity { - DiagnosticSeverity::Error => Error, - DiagnosticSeverity::Warning => Warning, - DiagnosticSeverity::Information => Info, - DiagnosticSeverity::Hint => Hint, + DiagnosticSeverity::ERROR => Error, + DiagnosticSeverity::WARNING => Warning, + DiagnosticSeverity::INFORMATION => Info, + DiagnosticSeverity::HINT => Hint, + severity => unimplemented!("{:?}", severity), }, ), // code diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index a893e70b1..dcb2bfd83 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -30,31 +30,32 @@ impl menu::Item for CompletionItem { menu::Row::new(vec![ menu::Cell::from(self.label.as_str()), menu::Cell::from(match self.kind { - Some(lsp::CompletionItemKind::Text) => "text", - Some(lsp::CompletionItemKind::Method) => "method", - Some(lsp::CompletionItemKind::Function) => "function", - Some(lsp::CompletionItemKind::Constructor) => "constructor", - Some(lsp::CompletionItemKind::Field) => "field", - Some(lsp::CompletionItemKind::Variable) => "variable", - Some(lsp::CompletionItemKind::Class) => "class", - Some(lsp::CompletionItemKind::Interface) => "interface", - Some(lsp::CompletionItemKind::Module) => "module", - Some(lsp::CompletionItemKind::Property) => "property", - Some(lsp::CompletionItemKind::Unit) => "unit", - Some(lsp::CompletionItemKind::Value) => "value", - Some(lsp::CompletionItemKind::Enum) => "enum", - Some(lsp::CompletionItemKind::Keyword) => "keyword", - Some(lsp::CompletionItemKind::Snippet) => "snippet", - Some(lsp::CompletionItemKind::Color) => "color", - Some(lsp::CompletionItemKind::File) => "file", - Some(lsp::CompletionItemKind::Reference) => "reference", - Some(lsp::CompletionItemKind::Folder) => "folder", - Some(lsp::CompletionItemKind::EnumMember) => "enum_member", - Some(lsp::CompletionItemKind::Constant) => "constant", - Some(lsp::CompletionItemKind::Struct) => "struct", - Some(lsp::CompletionItemKind::Event) => "event", - Some(lsp::CompletionItemKind::Operator) => "operator", - Some(lsp::CompletionItemKind::TypeParameter) => "type_param", + Some(lsp::CompletionItemKind::TEXT) => "text", + Some(lsp::CompletionItemKind::METHOD) => "method", + Some(lsp::CompletionItemKind::FUNCTION) => "function", + Some(lsp::CompletionItemKind::CONSTRUCTOR) => "constructor", + Some(lsp::CompletionItemKind::FIELD) => "field", + Some(lsp::CompletionItemKind::VARIABLE) => "variable", + Some(lsp::CompletionItemKind::CLASS) => "class", + Some(lsp::CompletionItemKind::INTERFACE) => "interface", + Some(lsp::CompletionItemKind::MODULE) => "module", + Some(lsp::CompletionItemKind::PROPERTY) => "property", + Some(lsp::CompletionItemKind::UNIT) => "unit", + Some(lsp::CompletionItemKind::VALUE) => "value", + Some(lsp::CompletionItemKind::ENUM) => "enum", + Some(lsp::CompletionItemKind::KEYWORD) => "keyword", + Some(lsp::CompletionItemKind::SNIPPET) => "snippet", + Some(lsp::CompletionItemKind::COLOR) => "color", + Some(lsp::CompletionItemKind::FILE) => "file", + Some(lsp::CompletionItemKind::REFERENCE) => "reference", + Some(lsp::CompletionItemKind::FOLDER) => "folder", + Some(lsp::CompletionItemKind::ENUM_MEMBER) => "enum_member", + Some(lsp::CompletionItemKind::CONSTANT) => "constant", + Some(lsp::CompletionItemKind::STRUCT) => "struct", + Some(lsp::CompletionItemKind::EVENT) => "event", + Some(lsp::CompletionItemKind::OPERATOR) => "operator", + Some(lsp::CompletionItemKind::TYPE_PARAMETER) => "type_param", + Some(kind) => unimplemented!("{:?}", kind), None => "", }), // self.detail.as_deref().unwrap_or("") From a1c7e55e3be95be6769d3a08518c8a41c257016f Mon Sep 17 00:00:00 2001 From: Houkime <32805122+Houkime@users.noreply.github.com> Date: Fri, 29 Oct 2021 05:11:19 +0000 Subject: [PATCH 084/122] update cpp queries (#930) Co-authored-by: Houkime <> --- helix-syntax/languages/tree-sitter-cpp | 2 +- runtime/queries/cpp/highlights.scm | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/helix-syntax/languages/tree-sitter-cpp b/helix-syntax/languages/tree-sitter-cpp index c61212414..e8dcc9d2b 160000 --- a/helix-syntax/languages/tree-sitter-cpp +++ b/helix-syntax/languages/tree-sitter-cpp @@ -1 +1 @@ -Subproject commit c61212414a3e95b5f7507f98e83de1d638044adc +Subproject commit e8dcc9d2b404c542fd236ea5f7208f90be8a6e89 diff --git a/runtime/queries/cpp/highlights.scm b/runtime/queries/cpp/highlights.scm index 3315fde05..3348ef3ca 100644 --- a/runtime/queries/cpp/highlights.scm +++ b/runtime/queries/cpp/highlights.scm @@ -3,7 +3,7 @@ ; Functions (call_expression - function: (scoped_identifier + function: (qualified_identifier name: (identifier) @function)) (template_function @@ -13,15 +13,14 @@ name: (field_identifier) @function) (template_function - name: (scoped_identifier - name: (identifier) @function)) + name: (identifier) @function) (function_declarator - declarator: (scoped_identifier + declarator: (qualified_identifier name: (identifier) @function)) (function_declarator - declarator: (scoped_identifier + declarator: (qualified_identifier name: (identifier) @function)) (function_declarator From 68697cb3322b94b20a47671c50b4fdd143f67ae7 Mon Sep 17 00:00:00 2001 From: Za Wilcox Date: Thu, 28 Oct 2021 23:11:42 -0600 Subject: [PATCH 085/122] Move 'Note' from incorrect location (#921) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cd58d590..e78b92afe 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,11 @@ All shortcuts/keymaps can be found [in the documentation on the website](https:/ It's a terminal-based editor first, but I'd like to explore a custom renderer (similar to emacs) in wgpu or skulpin. -# Installation - Note: Only certain languages have indentation definitions at the moment. Check `runtime/queries//` for `indents.toml`. +# Installation + We provide packaging for various distributions, but here's a quick method to build from source. From e5de103728b7a1338056f70524362930695d6c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 29 Oct 2021 16:48:25 +0900 Subject: [PATCH 086/122] Extract a clear_completion method --- helix-term/src/ui/editor.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bf316ee36..c0d602c73 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -786,6 +786,14 @@ impl EditorView { completion.required_size((size.width, size.height)); self.completion = Some(completion); } + + pub fn clear_completion(&mut self, editor: &mut Editor) { + self.completion = None; + // Clear any savepoints + let (_, doc) = current!(editor); + doc.savepoint = None; + editor.clear_idle_timer(); // don't retrigger + } } impl EditorView { @@ -990,11 +998,7 @@ impl Component for EditorView { if callback.is_some() { // assume close_fn - self.completion = None; - // Clear any savepoints - let (_, doc) = current!(cxt.editor); - doc.savepoint = None; - cxt.editor.clear_idle_timer(); // don't retrigger + self.clear_completion(cxt.editor); } } } @@ -1007,11 +1011,7 @@ impl Component for EditorView { if let Some(completion) = &mut self.completion { completion.update(&mut cxt); if completion.is_empty() { - self.completion = None; - // Clear any savepoints - let (_, doc) = current!(cxt.editor); - doc.savepoint = None; - cxt.editor.clear_idle_timer(); // don't retrigger + self.clear_completion(cxt.editor); } } } From f140a2a00eecfe47115634cef3bad4fd51e03b71 Mon Sep 17 00:00:00 2001 From: Gygaxis Vainhardt <44003709+AloeareV@users.noreply.github.com> Date: Fri, 29 Oct 2021 22:48:00 -0300 Subject: [PATCH 087/122] Add arrow-key bindings for window switching (#933) --- book/src/keymap.md | 8 ++++---- helix-term/src/keymap.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 2ff8bfe6b..9ed35f771 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -188,10 +188,10 @@ This layer is similar to vim keybindings as kakoune does not support window. | `w`, `Ctrl-w` | Switch to next window | `rotate_view` | | `v`, `Ctrl-v` | Vertical right split | `vsplit` | | `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | -| `h`, `Ctrl-h` | Move to left split | `jump_view_left` | -| `j`, `Ctrl-j` | Move to split below | `jump_view_down` | -| `k`, `Ctrl-k` | Move to split above | `jump_view_up` | -| `l`, `Ctrl-l` | Move to right split | `jump_view_right` | +| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` | +| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` | +| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` | +| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` | | `q`, `Ctrl-q` | Close current window | `wclose` | #### Space mode diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 5453020ec..cd953c5c4 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -540,10 +540,10 @@ impl Default for Keymaps { "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-q" | "q" => wclose, - "C-h" | "h" => jump_view_left, - "C-j" | "j" => jump_view_down, - "C-k" | "k" => jump_view_up, - "C-l" | "l" => jump_view_right, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, }, // move under c From ea452bec80d8701c91e8fa3aadbd960d46ecc6fb Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Sat, 30 Oct 2021 10:47:51 +0530 Subject: [PATCH 088/122] Update onedark theme (#936) - Use named color palette - Remove blue highlight for variables (too much noise) - Add purple highlight for control statements (if, match, etc) --- runtime/themes/onedark.toml | 103 +++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index 37057f583..40ed1abe4 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -1,54 +1,71 @@ # Author : Gokul Soumya -"attribute" = { fg = "#E5C07B" } -"comment" = { fg = "#5C6370", modifiers = ['italic'] } -"constant" = { fg = "#56B6C2" } -"constant.builtin" = { fg = "#61AFEF" } -"constructor" = { fg = "#61AFEF" } -"escape" = { fg = "#D19A66" } -"function" = { fg = "#61AFEF" } -"function.builtin" = { fg = "#61AFEF" } -"function.macro" = { fg = "#C678DD" } -"keyword" = { fg = "#E06C75" } -"keyword.directive" = { fg = "#C678DD" } -"label" = { fg = "#C678DD" } -"namespace" = { fg = "#61AFEF" } -"number" = { fg = "#D19A66" } -"operator" = { fg = "#C678DD" } -"property" = { fg = "#E06C75" } -"special" = { fg = "#61AFEF" } -"string" = { fg = "#98C379" } -"type" = { fg = "#E5C07B" } -"type.builtin" = { fg = "#E5C07B" } -"variable" = { fg = "#61AFEF" } -"variable.builtin" = { fg = "#61AFEF" } -"variable.parameter" = { fg = "#E06C75" } +"attribute" = { fg = "yellow" } +"comment" = { fg = "light-gray", modifiers = ["italic"] } +"constant" = { fg = "cyan" } +"constant.builtin" = { fg = "blue" } +"constructor" = { fg = "blue" } +"escape" = { fg = "gold" } +"function" = { fg = "blue" } +"function.builtin" = { fg = "blue" } +"function.macro" = { fg = "purple" } +"keyword" = { fg = "red" } +"keyword.control" = { fg = "purple" } +"keyword.directive" = { fg = "purple" } +"label" = { fg = "purple" } +"namespace" = { fg = "blue" } +"number" = { fg = "gold" } +"operator" = { fg = "purple" } +"property" = { fg = "red" } +"special" = { fg = "blue" } +"string" = { fg = "green" } +"type" = { fg = "yellow" } +"type.builtin" = { fg = "yellow" } +# "variable" = { fg = "blue" } +"variable.builtin" = { fg = "blue" } +"variable.parameter" = { fg = "red" } diagnostic = { modifiers = ["underlined"] } -"info" = { fg = "#61afef", modifiers = ['bold'] } -"hint" = { fg = "#98c379", modifiers = ['bold'] } -"warning" = { fg = "#e5c07b", modifiers = ['bold'] } -"error" = { fg = "#e06c75", modifiers = ['bold'] } +"info" = { fg = "blue", modifiers = ["bold"] } +"hint" = { fg = "green", modifiers = ["bold"] } +"warning" = { fg = "yellow", modifiers = ["bold"] } +"error" = { fg = "red", modifiers = ["bold"] } -"ui.background" = { bg = "#282C34" } +"ui.background" = { bg = "black" } -"ui.cursor" = { fg = "#ABB2BF", modifiers = ["reversed"] } -"ui.cursor.primary" = { fg = "#ABB2BF", modifiers = ["reversed"] } -"ui.cursor.match" = { fg = "#61AFEF", modifiers = ['underlined']} +"ui.cursor" = { fg = "white", modifiers = ["reversed"] } +"ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] } +"ui.cursor.match" = { fg = "blue", modifiers = ["underlined"]} -"ui.selection" = { bg = "#5C6370" } -"ui.selection.primary" = { bg = "#3E4452" } +"ui.selection" = { bg = "light-gray" } +"ui.selection.primary" = { bg = "gray" } -"ui.linenr" = { fg = "#4B5263", modifiers = ['dim'] } -"ui.linenr.selected" = { fg = "#ABB2BF" } +"ui.linenr" = { fg = "linenr", modifiers = ["dim"] } +"ui.linenr.selected" = { fg = "white" } -"ui.statusline" = { fg = "#ABB2BF", bg = "#2C323C" } -"ui.statusline.inactive" = { fg = "#5C6370", bg = "#2C323C" } +"ui.statusline" = { fg = "white", bg = "light-black" } +"ui.statusline.inactive" = { fg = "light-gray", bg = "light-black" } -"ui.text" = { fg = "#ABB2BF" } -"ui.text.focus" = { fg = "#ABB2BF", bg = "#2C323C", modifiers = ['bold'] } +"ui.text" = { fg = "white" } +"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] } -"ui.help" = { bg = "#3E4452" } -"ui.popup" = { bg = "#3E4452" } -"ui.window" = { bg = "#3E4452" } -"ui.menu.selected" = { fg = "#282C34", bg = "#61AFEF" } +"ui.help" = { bg = "gray" } +"ui.popup" = { bg = "gray" } +"ui.window" = { bg = "gray" } +"ui.menu.selected" = { fg = "black", bg = "blue" } + +[palette] + +yellow = "#E5C07B" +blue = "#61AFEF" +red = "#E06C75" +purple = "#C678DD" +green = "#98C379" +gold = "#D19A66" +cyan = "#56B6C2" +white = "#ABB2BF" +black = "#282C34" +light-black = "#2C323C" +gray = "#3E4452" +light-gray = "#5C6370" +linenr = "#4B5263" From 592fba11005ef6f2eda09285b34b24eeea5efc25 Mon Sep 17 00:00:00 2001 From: kabirz Date: Sat, 30 Oct 2021 14:07:45 +0800 Subject: [PATCH 089/122] add cmake-language-server as cmake language server --- languages.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/languages.toml b/languages.toml index a393b4a6b..b2726d820 100644 --- a/languages.toml +++ b/languages.toml @@ -354,3 +354,4 @@ file-types = ["cmake", "CMakeLists.txt"] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } +language-server = { command = "cmake-language-server" } From 2f8ad7f890d99dfcad7c7a5e0b35ee5a39fcd3d0 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sat, 30 Oct 2021 20:42:49 -0400 Subject: [PATCH 090/122] If switching away from an empty scratch buffer, remove it (#935) * If switching away from an empty scratch buffer, remove it * Move `view.jumps.push` call into `else` clause * Refactor --- helix-view/src/editor.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 09fc33341..21a646510 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -232,15 +232,29 @@ impl Editor { match action { Action::Replace => { - let view = view!(self); - let jump = ( - view.doc, - self.documents[view.doc].selection(view.id).clone(), - ); - + let (view, doc) = current_ref!(self); + // If the current view is an empty scratch buffer and is not displayed in any other views, delete it. + // Boolean value is determined before the call to `view_mut` because the operation requires a borrow + // of `self.tree`, which is mutably borrowed when `view_mut` is called. + let remove_empty_scratch = !doc.is_modified() + // If the buffer has no path and is not modified, it is an empty scratch buffer. + && doc.path().is_none() + // Ensure the buffer is not displayed in any other splits. + && !self + .tree + .traverse() + .any(|(_, v)| v.doc == doc.id && v.id != view.id); let view = view_mut!(self); - view.jumps.push(jump); - view.last_accessed_doc = Some(view.doc); + if remove_empty_scratch { + // Copy `doc.id` into a variable before calling `self.documents.remove`, which requires a mutable + // borrow, invalidating direct access to `doc.id`. + let id = doc.id; + self.documents.remove(id); + } else { + let jump = (view.doc, doc.selection(view.id).clone()); + view.jumps.push(jump); + view.last_accessed_doc = Some(view.doc); + } view.doc = id; view.offset = Position::default(); From a13af476c1d679e41b7333cb45f7ef3e174395c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Nov 2021 09:15:05 +0900 Subject: [PATCH 091/122] build(deps): bump tokio from 1.12.0 to 1.13.0 (#955) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.12.0 to 1.13.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.12.0...tokio-1.13.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 | 4 ++-- helix-lsp/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8af5c45c2..fd3451791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1059,9 +1059,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" dependencies = [ "autocfg", "bytes", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 8cbff41d0..991ce84ee 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -23,5 +23,5 @@ lsp-types = { version = "0.91", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } +tokio = { version = "1.13", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio-stream = "0.1.7" From 0c381adcb1713c9bfc6d5de0ccd8bec8243e9a57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Nov 2021 09:15:25 +0900 Subject: [PATCH 092/122] build(deps): bump lsp-types from 0.91.0 to 0.91.1 (#954) Bumps [lsp-types](https://github.com/gluon-lang/lsp-types) from 0.91.0 to 0.91.1. - [Release notes](https://github.com/gluon-lang/lsp-types/releases) - [Changelog](https://github.com/gluon-lang/lsp-types/blob/master/CHANGELOG.md) - [Commits](https://github.com/gluon-lang/lsp-types/compare/v0.91.0...v0.91.1) --- updated-dependencies: - dependency-name: lsp-types 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 fd3451791..b8225550e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.91.0" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be7801b458592d0998af808d97f6a85a6057af3aaf2a2a5c3c677702bbeb4ed7" +checksum = "2368312c59425dd133cb9a327afee65be0a633a8ce471d248e2202a48f8f68ae" dependencies = [ "bitflags", "serde", From 44ff597841dc666f1bf52e3e05544edff927821b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Nov 2021 09:34:39 +0900 Subject: [PATCH 093/122] build(deps): bump tokio-stream from 0.1.7 to 0.1.8 (#953) Bumps [tokio-stream](https://github.com/tokio-rs/tokio) from 0.1.7 to 0.1.8. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-stream-0.1.7...tokio-stream-0.1.8) --- updated-dependencies: - dependency-name: tokio-stream 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 ++-- helix-lsp/Cargo.toml | 2 +- helix-term/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8225550e..0ce8ee8f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1090,9 +1090,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 991ce84ee..920ce0371 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -24,4 +24,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" tokio = { version = "1.13", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } -tokio-stream = "0.1.7" +tokio-stream = "0.1.8" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 2414c4335..45b4eb2cf 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -56,7 +56,7 @@ serde = { version = "1.0", features = ["derive"] } # ripgrep for global search grep-regex = "0.1.9" grep-searcher = "0.1.8" -tokio-stream = "0.1.7" +tokio-stream = "0.1.8" [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } From 1720b98760eb5958a31bc6e2b88564dbf5bf63e5 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 2 Nov 2021 12:32:57 +0800 Subject: [PATCH 094/122] only remove primary index when search next without extend (#948) --- helix-term/src/commands.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 28657865b..cc9106eb7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1161,7 +1161,10 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege let selection = if extend { selection.clone().push(Range::new(start, end)) } else { - Selection::single(start, end) + selection + .clone() + .remove(selection.primary_index()) + .push(Range::new(start, end)) }; doc.set_selection(view.id, selection); From 924b7d3b192915f0a56393bb2a78318e39f1a151 Mon Sep 17 00:00:00 2001 From: Daniel Poulin Date: Mon, 1 Nov 2021 10:37:14 -0400 Subject: [PATCH 095/122] Adjust PHP indentation defaults to 4 spaces In the PHP community, 4 spaces is widely considered the default, as it is recommended by the PSR-2 and PSR-12 standards, as well as popular derivative standards like those for Laravel and Symphony. --- languages.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages.toml b/languages.toml index b2726d820..bd510ea1c 100644 --- a/languages.toml +++ b/languages.toml @@ -200,7 +200,7 @@ injection-regex = "php" file-types = ["php"] roots = [] -indent = { tab-width = 2, unit = " " } +indent = { tab-width = 4, unit = " " } [[language]] name = "latex" From 9e247bf6eef34978d66cdcee535da5e4f5ba13e9 Mon Sep 17 00:00:00 2001 From: Daniel Poulin Date: Mon, 1 Nov 2021 10:41:55 -0400 Subject: [PATCH 096/122] Add indents definition based on the one from nvim-treesitter --- runtime/queries/php/indents.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 runtime/queries/php/indents.toml diff --git a/runtime/queries/php/indents.toml b/runtime/queries/php/indents.toml new file mode 100644 index 000000000..85c104db5 --- /dev/null +++ b/runtime/queries/php/indents.toml @@ -0,0 +1,17 @@ +indent = [ + "array_creation_expression", + "arguments", + "formal_parameters", + "compound_statement", + "declaration_list", + "binary_expression", + "return_statement", + "expression_statement", + "switch_block", + "anonymous_function_use_clause", +] + +oudent = [ + "}", + ")", +] From e505bf2b4809681840d1684a23ad21255fca290d Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Tue, 2 Nov 2021 19:57:18 -0500 Subject: [PATCH 097/122] chore(doc): use faq for finding helix log file (#963) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e78b92afe..faf5851ed 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Some suggestions to get started: - You can look at the [good first issue](https://github.com/helix-editor/helix/labels/E-easy) label on the issue tracker. - Help with packaging on various distributions needed! -- To use print debugging to the `~/.cache/helix/helix.log` file, you must: +- To use print debugging to the [Helix log file](https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file), you must: * Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`) * Pass the appropriate verbosity level option for the desired log level. (`hx -v ` for info, more `v`s for higher severity inclusive) - If your preferred language is missing, integrating a tree-sitter grammar for From eb8745db0999a50464ac183baa138c4e511430f2 Mon Sep 17 00:00:00 2001 From: Daniel Ebert Date: Mon, 1 Nov 2021 15:50:12 +0100 Subject: [PATCH 098/122] Implement key ordering for info box --- helix-term/src/keymap.rs | 20 ++++++++++++++------ helix-view/src/info.rs | 4 ++-- helix-view/src/input.rs | 2 +- helix-view/src/keyboard.rs | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index cd953c5c4..827f71d90 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -5,7 +5,7 @@ use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; use std::{ borrow::Cow, - collections::HashMap, + collections::{BTreeSet, HashMap}, ops::{Deref, DerefMut}, }; @@ -137,20 +137,28 @@ impl KeyTrieNode { } pub fn infobox(&self) -> Info { - let mut body: Vec<(&str, Vec)> = Vec::with_capacity(self.len()); + let mut body: Vec<(&str, BTreeSet)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { KeyTrie::Leaf(cmd) => cmd.doc(), KeyTrie::Node(n) => n.name(), }; match body.iter().position(|(d, _)| d == &desc) { - // FIXME: multiple keys are ordered randomly (use BTreeSet) - Some(pos) => body[pos].1.push(key), - None => body.push((desc, vec![key])), + Some(pos) => { + body[pos].1.insert(key); + } + None => { + let mut keys = BTreeSet::new(); + keys.insert(key); + body.push((desc, keys)); + } } } body.sort_unstable_by_key(|(_, keys)| { - self.order.iter().position(|&k| k == keys[0]).unwrap() + self.order + .iter() + .position(|&k| k == *keys.iter().next().unwrap()) + .unwrap() }); let prefix = format!("{} ", self.name()); if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) { diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 629a3112f..b5a002fa4 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,6 +1,6 @@ use crate::input::KeyEvent; use helix_core::unicode::width::UnicodeWidthStr; -use std::fmt::Write; +use std::{collections::BTreeSet, fmt::Write}; #[derive(Debug)] /// Info box used in editor. Rendering logic will be in other crate. @@ -16,7 +16,7 @@ pub struct Info { } impl Info { - pub fn new(title: &str, body: Vec<(&str, Vec)>) -> Info { + pub fn new(title: &str, body: Vec<(&str, BTreeSet)>) -> Info { let body = body .into_iter() .map(|(desc, events)| { diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 1e0ddfe25..580204ccc 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -8,7 +8,7 @@ use crate::keyboard::{KeyCode, KeyModifiers}; /// Represents a key event. // We use a newtype here because we want to customize Deserialize and Display. -#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub struct KeyEvent { pub code: KeyCode, pub modifiers: KeyModifiers, diff --git a/helix-view/src/keyboard.rs b/helix-view/src/keyboard.rs index 26a4d6d2c..810aa0635 100644 --- a/helix-view/src/keyboard.rs +++ b/helix-view/src/keyboard.rs @@ -54,7 +54,7 @@ impl From for KeyModifiers { } /// Represents a key. -#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyCode { /// Backspace key. From 7a0c4322eaeef7325878abe9a99adde4ad905f5e Mon Sep 17 00:00:00 2001 From: Triton171 Date: Tue, 2 Nov 2021 17:43:07 +0100 Subject: [PATCH 099/122] Simplify BTreeSet construction Co-authored-by: Ivan Tham --- helix-term/src/keymap.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 827f71d90..72d0a733e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -147,11 +147,7 @@ impl KeyTrieNode { Some(pos) => { body[pos].1.insert(key); } - None => { - let mut keys = BTreeSet::new(); - keys.insert(key); - body.push((desc, keys)); - } + None => body.push((desc, BTreeSet::from([key]))), } } body.sort_unstable_by_key(|(_, keys)| { From ee889aaa854d0036da3bae16252bc382e50b0df6 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Tue, 2 Nov 2021 23:00:52 -0400 Subject: [PATCH 100/122] Updated tree-sitter query scopes (#896) * updated theme scopes variable.property -> variable.field property -> variable.field * updated theme scopes * update book and themes updated book and themes to reflect scope changes * wip * update more queries * update dark_plus.toml --- book/src/themes.md | 10 +++-- helix-core/src/lib.rs | 1 + runtime/queries/bash/highlights.scm | 4 +- runtime/queries/c-sharp/highlights.scm | 10 ++--- runtime/queries/c/highlights.scm | 6 +-- runtime/queries/css/highlights.scm | 14 +++--- runtime/queries/elixir/highlights.scm | 19 ++++---- runtime/queries/go/highlights.scm | 6 +-- runtime/queries/haskell/highlights.scm | 6 +-- runtime/queries/java/highlights.scm | 11 ++--- runtime/queries/javascript/highlights.scm | 4 +- runtime/queries/json/highlights.scm | 19 ++++++-- runtime/queries/julia/highlights.scm | 12 ++--- runtime/queries/ledger/highlights.scm | 4 +- runtime/queries/lua/highlights.scm | 6 +-- runtime/queries/nix/highlights.scm | 12 +++-- runtime/queries/ocaml/highlights.scm | 8 ++-- runtime/queries/php/highlights.scm | 12 ++--- runtime/queries/protobuf/highlights.scm | 10 ++--- runtime/queries/python/highlights.scm | 11 ++--- runtime/queries/ruby/highlights.scm | 6 +-- runtime/queries/rust/highlights.scm | 35 ++++++++++----- runtime/queries/svelte/highlights.scm | 2 +- runtime/queries/toml/highlights.scm | 8 ++-- runtime/queries/tsq/highlights.scm | 4 +- runtime/queries/yaml/highlights.scm | 10 ++--- runtime/queries/zig/highlights.scm | 8 ++-- runtime/themes/base16_default_dark.toml | 6 +-- runtime/themes/bogster.toml | 6 +-- runtime/themes/dark_plus.toml | 53 +++++++++++++++-------- runtime/themes/everforest_dark.toml | 7 ++- runtime/themes/gruvbox.toml | 7 ++- runtime/themes/ingrid.toml | 6 +-- runtime/themes/monokai.toml | 7 +-- runtime/themes/nord.toml | 6 +-- runtime/themes/rose_pine.toml | 4 +- theme.toml | 2 +- 37 files changed, 198 insertions(+), 164 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 5a4d04038..ecbbb6e97 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -103,8 +103,6 @@ We use a similar set of scopes as [SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also [TextMate](https://macromates.com/manual/en/language_grammars) scopes. -- `escape` (TODO: rename to (constant).character.escape) - - `type` - Types - `builtin` - Primitive types provided by the language (`int`, `usize`) @@ -112,8 +110,11 @@ We use a similar set of scopes as - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc) - `boolean` - `character` + - `escape` + - `numeric` (numbers) + - `integer` + - `float` -- `number` (TODO: rename to constant.number/.numeric.{integer, float, complex}) - `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)? - `regexp` - Regular expressions - `special` @@ -129,7 +130,8 @@ We use a similar set of scopes as - `variable` - Variables - `builtin` - Reserved language variables (`self`, `this`, `super`, etc) - `parameter` - Function parameters - - `property` + - `other` + - `member` - Fields of composite data types (e.g. structs, unions) - `function` (TODO: ?) - `label` diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index d971464a8..96f88ee45 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -35,6 +35,7 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option { line.chars().position(|ch| !ch.is_whitespace()) } +/// Find `.git` root. pub fn find_root(root: Option<&str>) -> Option { let current_dir = std::env::current_dir().expect("unable to determine current directory"); diff --git a/runtime/queries/bash/highlights.scm b/runtime/queries/bash/highlights.scm index 754faedac..57898f277 100644 --- a/runtime/queries/bash/highlights.scm +++ b/runtime/queries/bash/highlights.scm @@ -7,7 +7,7 @@ (command_name) @function -(variable_name) @property +(variable_name) @variable.other.member [ "case" @@ -31,7 +31,7 @@ (function_definition name: (word) @function) -(file_descriptor) @number +(file_descriptor) @constant.numeric.integer [ (command_substitution) diff --git a/runtime/queries/c-sharp/highlights.scm b/runtime/queries/c-sharp/highlights.scm index b76f4e601..6e84ad837 100644 --- a/runtime/queries/c-sharp/highlights.scm +++ b/runtime/queries/c-sharp/highlights.scm @@ -20,16 +20,16 @@ ] @type.builtin ;; Enum -(enum_member_declaration (identifier) @variable.property) +(enum_member_declaration (identifier) @variable.other.member) ;; Literals [ (real_literal) (integer_literal) -] @number +] @constant.numeric.integer +(character_literal) @constant.character [ - (character_literal) (string_literal) (verbatim_string_literal) (interpolated_string_text) @@ -40,8 +40,8 @@ "$@\"" ] @string +(boolean_literal) @constant.builtin.boolean [ - (boolean_literal) (null_literal) (void_keyword) ] @constant.builtin @@ -98,7 +98,7 @@ ;; Keywords (modifier) @keyword (this_expression) @keyword -(escape_sequence) @keyword +(escape_sequence) @constant.character.escape [ "as" diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 2c42710f9..918f3f66b 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -60,8 +60,8 @@ (system_lib_string) @string (null) @constant -(number_literal) @number -(char_literal) @string +(number_literal) @constant.numeric.integer +(char_literal) @constant.character (call_expression function: (identifier) @function) @@ -73,7 +73,7 @@ (preproc_function_def name: (identifier) @function.special) -(field_identifier) @property +(field_identifier) @variable.other.member (statement_identifier) @label (type_identifier) @type (primitive_type) @type diff --git a/runtime/queries/css/highlights.scm b/runtime/queries/css/highlights.scm index 763661af7..4dfc0c66d 100644 --- a/runtime/queries/css/highlights.scm +++ b/runtime/queries/css/highlights.scm @@ -26,11 +26,11 @@ (pseudo_element_selector (tag_name) @attribute) (pseudo_class_selector (class_name) @attribute) -(class_name) @property -(id_name) @property -(namespace_name) @property -(property_name) @property -(feature_name) @property +(class_name) @variable.other.member +(id_name) @variable.other.member +(namespace_name) @variable.other.member +(property_name) @variable.other.member +(feature_name) @variable.other.member (attribute_name) @attribute @@ -55,8 +55,8 @@ (string_value) @string (color_value) @string.special -(integer_value) @number -(float_value) @number +(integer_value) @constant.numeric.integer +(float_value) @constant.numeric.float (unit) @type "#" @punctuation.delimiter diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index b7b0cab6d..76fd2af9d 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -39,13 +39,13 @@ ; * module attribute (unary_operator - operator: "@" @variable.property + operator: "@" @variable.other.member operand: [ - (identifier) @variable.property + (identifier) @variable.other.member (call - target: (identifier) @variable.property) - (boolean) @variable.property - (nil) @variable.property + target: (identifier) @variable.other.member) + (boolean) @variable.other.member + (nil) @variable.other.member ]) ; * capture operator @@ -79,11 +79,8 @@ (nil) @constant.builtin (boolean) @constant.builtin.boolean - -[ - (integer) - (float) -] @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (alias) @type @@ -97,7 +94,7 @@ (interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded -(escape_sequence) @escape +(escape_sequence) @constant.character.escape [ (atom) diff --git a/runtime/queries/go/highlights.scm b/runtime/queries/go/highlights.scm index 3129c4b2b..56384d4d7 100644 --- a/runtime/queries/go/highlights.scm +++ b/runtime/queries/go/highlights.scm @@ -25,7 +25,7 @@ (variadic_parameter_declaration (identifier) @variable.parameter) (type_identifier) @type -(field_identifier) @property +(field_identifier) @variable.other.member (identifier) @variable (package_identifier) @variable @@ -130,13 +130,13 @@ (rune_literal) ] @string -(escape_sequence) @escape +(escape_sequence) @constant.character.escape [ (int_literal) (float_literal) (imaginary_literal) -] @number +] @constant.numeric.integer [ (true) diff --git a/runtime/queries/haskell/highlights.scm b/runtime/queries/haskell/highlights.scm index dada80b60..721878769 100644 --- a/runtime/queries/haskell/highlights.scm +++ b/runtime/queries/haskell/highlights.scm @@ -13,9 +13,9 @@ (constraint class: (class_name (type)) @class) (class (class_head class: (class_name (type)) @class)) (instance (instance_head class: (class_name (type)) @class)) -(integer) @number -(exp_literal (float)) @number -(char) @literal +(integer) @constant.numeric.integer +(exp_literal (float)) @constant.numeric.float +(char) @constant.character (con_unit) @literal (con_list) @literal (tycon_arrow) @operator diff --git a/runtime/queries/java/highlights.scm b/runtime/queries/java/highlights.scm index e7d793df9..77902fce3 100644 --- a/runtime/queries/java/highlights.scm +++ b/runtime/queries/java/highlights.scm @@ -59,14 +59,15 @@ (hex_integer_literal) (decimal_integer_literal) (octal_integer_literal) +] @constant.numeric.integer + +[ (decimal_floating_point_literal) (hex_floating_point_literal) -] @number +] @constant.numeric.float -[ - (character_literal) - (string_literal) -] @string +(character_literal) @constant.character +(string_literal) @string [ (true) diff --git a/runtime/queries/javascript/highlights.scm b/runtime/queries/javascript/highlights.scm index e29829bfc..6163b680d 100644 --- a/runtime/queries/javascript/highlights.scm +++ b/runtime/queries/javascript/highlights.scm @@ -65,7 +65,7 @@ ; Properties ;----------- -(property_identifier) @property +(property_identifier) @variable.other.member ; Literals ;--------- @@ -88,7 +88,7 @@ ] @string (regex) @string.regexp -(number) @number +(number) @constant.numeric.integer ; Tokens ;------- diff --git a/runtime/queries/json/highlights.scm b/runtime/queries/json/highlights.scm index b08ea4393..6df6c9ebc 100644 --- a/runtime/queries/json/highlights.scm +++ b/runtime/queries/json/highlights.scm @@ -1,9 +1,20 @@ +[ + (true) + (false) +] @constant.builtin.boolean +(null) @constant.builtin +(number) @constant.numeric (pair key: (_) @keyword) (string) @string +(escape_sequence) @constant.character.escape +(ERROR) @error -(object - "{" @escape - (_) - "}" @escape) +"," @punctuation.delimiter +[ + "[" + "]" + "{" + "}" +] @punctuation.bracket diff --git a/runtime/queries/julia/highlights.scm b/runtime/queries/julia/highlights.scm index 7b7d426c1..7c4479853 100644 --- a/runtime/queries/julia/highlights.scm +++ b/runtime/queries/julia/highlights.scm @@ -15,7 +15,7 @@ (field_expression (identifier) - (identifier) @field .) + (identifier) @variable.other.member .) (function_definition name: (identifier) @function) @@ -80,14 +80,14 @@ (struct_definition name: (identifier) @type) -(number) @number +(number) @constant.numeric.integer (range_expression - (identifier) @number - (eq? @number "end")) + (identifier) @constant.numeric.integer + (eq? @constant.numeric.integer "end")) (range_expression (_ - (identifier) @number - (eq? @number "end"))) + (identifier) @constant.numeric.integer + (eq? @constant.numeric.integer "end"))) (coefficient_expression (number) (identifier) @constant.builtin) diff --git a/runtime/queries/ledger/highlights.scm b/runtime/queries/ledger/highlights.scm index 86c609c2a..bdf5f2dbb 100644 --- a/runtime/queries/ledger/highlights.scm +++ b/runtime/queries/ledger/highlights.scm @@ -7,9 +7,9 @@ (date) (interval) (quantity) -] @number +] @constant.numeric.integer -((account) @field) +((account) @variable.other.member) ((commodity) @text.literal) "include" @include diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm index 40c2be709..e73b32d6e 100644 --- a/runtime/queries/lua/highlights.scm +++ b/runtime/queries/lua/highlights.scm @@ -150,14 +150,14 @@ (table ["{" "}"] @constructor) (comment) @comment (string) @string -(number) @number +(number) @constant.numeric.integer (label_statement) @label ; A bit of a tricky one, this will only match field names -(field . (identifier) @property (_)) +(field . (identifier) @variable.other.member (_)) (shebang) @comment ;; Property -(property_identifier) @property +(property_identifier) @variable.other.member ;; Variable (identifier) @variable diff --git a/runtime/queries/nix/highlights.scm b/runtime/queries/nix/highlights.scm index 741b73b5d..66719e876 100644 --- a/runtime/queries/nix/highlights.scm +++ b/runtime/queries/nix/highlights.scm @@ -33,16 +33,14 @@ (uri) @string.special.uri -[ - (integer) - (float) -] @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (interpolation "${" @punctuation.special "}" @punctuation.special) @embedded -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (function universal: (identifier) @variable.parameter @@ -66,8 +64,8 @@ (binary operator: _ @operator) -(attr_identifier) @property -(inherit attrs: (attrs_inherited (identifier) @property) ) +(attr_identifier) @variable.other.member +(inherit attrs: (attrs_inherited (identifier) @variable.other.member) ) [ ";" diff --git a/runtime/queries/ocaml/highlights.scm b/runtime/queries/ocaml/highlights.scm index 160f2cb41..15f46cc14 100644 --- a/runtime/queries/ocaml/highlights.scm +++ b/runtime/queries/ocaml/highlights.scm @@ -51,14 +51,14 @@ ; Properties ;----------- -[(label_name) (field_name) (instance_variable_name)] @property +[(label_name) (field_name) (instance_variable_name)] @variable.other.member ; Constants ;---------- [(boolean) (unit)] @constant -[(number) (signed_number)] @number +[(number) (signed_number)] @constant.numeric.integer (character) @constant.character @@ -66,7 +66,7 @@ (quoted_string "{" @string "}" @string) @string -(escape_sequence) @string.escape +(escape_sequence) @constant.character.escape [ (conversion_specification) @@ -145,7 +145,7 @@ ; Attributes ;----------- -(attribute_id) @property +(attribute_id) @variable.other.member ; Comments ;--------- diff --git a/runtime/queries/php/highlights.scm b/runtime/queries/php/highlights.scm index 029045558..46b5d26c2 100644 --- a/runtime/queries/php/highlights.scm +++ b/runtime/queries/php/highlights.scm @@ -30,12 +30,12 @@ ; Member (property_element - (variable_name) @property) + (variable_name) @variable.other.member) (member_access_expression - name: (variable_name (name)) @property) + name: (variable_name (name)) @variable.other.member) (member_access_expression - name: (name) @property) + name: (name) @variable.other.member) ; Variables @@ -56,10 +56,10 @@ (string) @string (heredoc) @string -(boolean) @constant.builtin +(boolean) @constant.builtin.boolean (null) @constant.builtin -(integer) @number -(float) @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (comment) @comment "$" @operator diff --git a/runtime/queries/protobuf/highlights.scm b/runtime/queries/protobuf/highlights.scm index cd021be1a..c35c430e3 100644 --- a/runtime/queries/protobuf/highlights.scm +++ b/runtime/queries/protobuf/highlights.scm @@ -34,16 +34,14 @@ [ (fieldName) (optionName) -] @property +] @variable.other.member (enumVariantName) @type.enum.variant (fullIdent) @namespace -[ - (intLit) - (floatLit) -] @number -(boolLit) @constant.builtin +(intLit) @constant.numeric.integer +(floatLit) @constant.numeric.float +(boolLit) @constant.builtin.boolean (strLit) @string (constant) @constant diff --git a/runtime/queries/python/highlights.scm b/runtime/queries/python/highlights.scm index f64fecb2c..9131acc5b 100644 --- a/runtime/queries/python/highlights.scm +++ b/runtime/queries/python/highlights.scm @@ -29,7 +29,7 @@ name: (identifier) @function) (identifier) @variable -(attribute attribute: (identifier) @property) +(attribute attribute: (identifier) @variable.other.member) (type (identifier) @type) ; Literals @@ -40,14 +40,11 @@ (false) ] @constant.builtin -[ - (integer) - (float) -] @number - +(integer) @constant.numeric.integer +(float) @constant.numeric.float (comment) @comment (string) @string -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (interpolation "{" @punctuation.special diff --git a/runtime/queries/ruby/highlights.scm b/runtime/queries/ruby/highlights.scm index 8617d6f0c..898f8f794 100644 --- a/runtime/queries/ruby/highlights.scm +++ b/runtime/queries/ruby/highlights.scm @@ -55,7 +55,7 @@ [ (class_variable) (instance_variable) -] @property +] @variable.other.member ((identifier) @constant.builtin (#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$")) @@ -101,12 +101,12 @@ ] @string.special.symbol (regex) @string.regexp -(escape_sequence) @escape +(escape_sequence) @constant.character.escape [ (integer) (float) -] @number +] @constant.numeric.integer [ (nil) diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm index 956a5dacd..539d95500 100644 --- a/runtime/queries/rust/highlights.scm +++ b/runtime/queries/rust/highlights.scm @@ -15,15 +15,13 @@ ; Primitives ; --- -(escape_sequence) @escape +(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 [ - (integer_literal) - (float_literal) -] @number -[ - (char_literal) (string_literal) (raw_string_literal) ] @string @@ -40,10 +38,10 @@ (enum_variant (identifier) @type.enum.variant) (field_initializer - (field_identifier) @property) + (field_identifier) @variable.other.member) (shorthand_field_initializer - (identifier) @variable.property) -(shorthand_field_identifier) @variable.property + (identifier) @variable.other.member) +(shorthand_field_identifier) @variable.other.member (lifetime "'" @label @@ -81,9 +79,24 @@ ] @punctuation.bracket) ; --- -; Parameters +; 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)) + +(arguments + (identifier) @variable.parameter) (parameter pattern: (identifier) @variable.parameter) (closure_parameters @@ -336,4 +349,4 @@ (type_identifier) @type (identifier) @variable -(field_identifier) @property +(field_identifier) @variable.other.member diff --git a/runtime/queries/svelte/highlights.scm b/runtime/queries/svelte/highlights.scm index 4c6f5f356..4fcdfd669 100644 --- a/runtime/queries/svelte/highlights.scm +++ b/runtime/queries/svelte/highlights.scm @@ -29,7 +29,7 @@ (#match? @_attr "^(href|src)$")) (tag_name) @tag -(attribute_name) @property +(attribute_name) @variable.other.member (erroneous_end_tag_name) @error (comment) @comment diff --git a/runtime/queries/toml/highlights.scm b/runtime/queries/toml/highlights.scm index e4d6966fc..2742b2be6 100644 --- a/runtime/queries/toml/highlights.scm +++ b/runtime/queries/toml/highlights.scm @@ -1,17 +1,17 @@ ; Properties ;----------- -(bare_key) @property +(bare_key) @variable.other.member (quoted_key) @string ; Literals ;--------- -(boolean) @constant.builtin +(boolean) @constant.builtin.boolean (comment) @comment (string) @string -(integer) @number -(float) @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (offset_date_time) @string.special (local_date_time) @string.special (local_date) @string.special diff --git a/runtime/queries/tsq/highlights.scm b/runtime/queries/tsq/highlights.scm index 9ba5699a8..549895c15 100644 --- a/runtime/queries/tsq/highlights.scm +++ b/runtime/queries/tsq/highlights.scm @@ -35,12 +35,12 @@ (comment) @comment -(field_name) @property +(field_name) @variable.other.member (capture) @label (predicate_name) @function -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (node_name) @variable diff --git a/runtime/queries/yaml/highlights.scm b/runtime/queries/yaml/highlights.scm index 2955a4ce0..a7efb5e71 100644 --- a/runtime/queries/yaml/highlights.scm +++ b/runtime/queries/yaml/highlights.scm @@ -1,12 +1,12 @@ -(block_mapping_pair key: (_) @property) -(flow_mapping (_ key: (_) @property)) +(block_mapping_pair key: (_) @variable.other.member) +(flow_mapping (_ key: (_) @variable.other.member)) (boolean_scalar) @constant.builtin.boolean (null_scalar) @constant.builtin (double_quote_scalar) @string (single_quote_scalar) @string -(escape_sequence) @string.escape -(integer_scalar) @number -(float_scalar) @number +(escape_sequence) @constant.character.escape +(integer_scalar) @constant.numeric.integer +(float_scalar) @constant.numeric.float (comment) @comment (anchor_name) @type (alias_name) @type diff --git a/runtime/queries/zig/highlights.scm b/runtime/queries/zig/highlights.scm index 404a8682a..34dbeacd0 100644 --- a/runtime/queries/zig/highlights.scm +++ b/runtime/queries/zig/highlights.scm @@ -14,7 +14,7 @@ parameter: (IDENTIFIER) @variable.parameter [ field_member: (IDENTIFIER) field_access: (IDENTIFIER) -] @variable.property +] @variable.other.member ;; assume TitleCase is a type ( @@ -75,9 +75,9 @@ field_constant: (IDENTIFIER) @constant ((BUILTINIDENTIFIER) @keyword.control.import (#any-of? @keyword.control.import "@import" "@cImport")) -(INTEGER) @number +(INTEGER) @constant.numeric.integer -(FLOAT) @number +(FLOAT) @constant.numeric.float [ (LINESTRING) @@ -85,7 +85,7 @@ field_constant: (IDENTIFIER) @constant ] @string (CHAR_LITERAL) @constant.character -(EscapeSequence) @escape +(EscapeSequence) @constant.character.escape (FormatSequence) @string.special [ diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml index a5dc0852a..d65995c05 100644 --- a/runtime/themes/base16_default_dark.toml +++ b/runtime/themes/base16_default_dark.toml @@ -16,14 +16,14 @@ "operator" = "base05" "ui.text.focus" = { fg = "base05" } "variable" = "base08" -"number" = "base09" +"constant.numeric" = "base09" "constant" = "base09" "attributes" = "base09" "type" = "base0A" "ui.cursor.match" = { fg = "base0A", modifiers = ["underlined"] } "strings" = "base0B" -"property" = "base0B" -"escape" = "base0C" +"variable.other.member" = "base0B" +"constant.character.escape" = "base0C" "function" = "base0D" "constructor" = "base0D" "special" = "base0D" diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml index 37b9adbfd..86a6c34bf 100644 --- a/runtime/themes/bogster.toml +++ b/runtime/themes/bogster.toml @@ -8,7 +8,7 @@ "punctuation.delimiter" = "#dc7759" "operator" = { fg = "#dc7759", modifiers = ["bold"] } "special" = "#7fdc59" -"property" = "#c6b8ad" +"variable.other.member" = "#c6b8ad" "variable" = "#c6b8ad" "variable.parameter" = "#c6b8ad" "type" = "#dc597f" @@ -22,8 +22,8 @@ "constant" = "#59dcb7" "constant.builtin" = "#59dcb7" "string" = "#59dcb7" -"number" = "#59c0dc" -"escape" = { fg = "#7fdc59", modifiers = ["bold"] } +"constant.numeric" = "#59c0dc" +"constant.character.escape" = { fg = "#7fdc59", modifiers = ["bold"] } "label" = "#59c0dc" "module" = "#d32c5d" diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index c48a7e286..0554f827f 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -7,7 +7,7 @@ "type.builtin" = { fg = "type" } "type.enum.variant" = { fg = "constant" } "constructor" = { fg = "constant" } -"property" = { fg = "variable" } +"variable.other.member" = { fg = "variable" } "keyword" = { fg = "keyword" } "keyword.directive" = { fg = "keyword" } @@ -31,47 +31,64 @@ "function.macro" = { fg = "keyword" } "attribute" = { fg = "fn_declaration" } -"comment" = { fg = "#6A9955" } +"comment" = { fg = "dark_green" } -"string" = { fg = "#ce9178" } -"string.regexp" = { fg = "regex" } -"number" = { fg = "#b5cea8" } -"escape" = { fg = "#d7ba7d" } +"string" = { fg = "orange" } +"constant.character" = { fg = "orange" } +"string.regexp" = { fg = "gold" } +"constant.numeric" = { fg = "pale_green" } +"constant.character.escape" = { fg = "gold" } -"ui.background" = { fg = "#d4d4d4", bg = "#1e1e1e" } +"ui.background" = { fg = "light_gray", bg = "dark_gray2" } "ui.window" = { bg = "widget" } "ui.popup" = { bg = "widget" } "ui.help" = { bg = "widget" } "ui.menu.selected" = { bg = "widget" } +# TODO: Alternate bg colour for `ui.cursor.match` and `ui.selection`. "ui.cursor" = { fg = "cursor", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] } "ui.cursor.match" = { bg = "#3a3d41", modifiers = ["underlined"] } "ui.selection" = { bg = "#3a3d41" } -"ui.selection.primary" = { bg = "#264f78" } +"ui.selection.primary" = { bg = "dark_blue" } -"ui.linenr" = { fg = "#858585" } -"ui.linenr.selected" = { fg = "#c6c6c6" } +"ui.linenr" = { fg = "dark_gray" } +"ui.linenr.selected" = { fg = "light_gray2" } -"ui.statusline" = { fg = "#ffffff", bg = "#007acc" } -"ui.statusline.inactive" = { fg = "#ffffff", bg = "#007acc" } +"ui.statusline" = { fg = "white", bg = "blue" } +"ui.statusline.inactive" = { fg = "white", bg = "blue" } "ui.text" = { fg = "text", bg = "background" } -"ui.text.focus" = { fg = "#ffffff" } +"ui.text.focus" = { fg = "white" } -"warning" = { fg = "#cca700" } -"error" = { fg = "#ff1212" } -"info" = { fg = "#75beff" } -"hint" = { fg = "#eeeeeeb3" } +"warning" = { fg = "gold2" } +"error" = { fg = "red" } +"info" = { fg = "light_blue" } +"hint" = { fg = "light_gray3" } diagnostic = { modifiers = ["underlined"] } [palette] +white = "#ffffff" +orange = "#ce9178" +gold = "#d7ba7d" +gold2 = "#cca700" +pale_green = "#b5cea8" +dark_green = "#6A9955" +light_gray = "#d4d4d4" +light_gray2 = "#c6c6c6" +light_gray3 = "#eeeeee" +dark_gray = "#858585" +dark_gray2 = "#1e1e1e" +blue = "#007acc" +light_blue = "#75beff" +dark_blue = "#264f78" +red = "#ff1212" + type = "#4EC9B0" keyword = "#569CD6" -regex = "#CE9178" special = "#C586C0" variable = "#9CDCFE" fn_declaration = "#DCDCAA" diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml index 462c82651..bbd005e6a 100644 --- a/runtime/themes/everforest_dark.toml +++ b/runtime/themes/everforest_dark.toml @@ -8,16 +8,16 @@ # Email: sainnhe@gmail.com # License: MIT License -"escape" = "orange" +"constant.character.escape" = "orange" "type" = "yellow" "constant" = "purple" -"number" = "purple" +"constant.numeric" = "purple" "string" = "grey2" "comment" = "grey0" "variable" = "fg" "variable.builtin" = "blue" "variable.parameter" = "fg" -"variable.property" = "fg" +"variable.other.member" = "fg" "label" = "aqua" "punctuation" = "grey2" "punctuation.delimiter" = "grey2" @@ -32,7 +32,6 @@ "attribute" = "aqua" "constructor" = "yellow" "module" = "blue" -"property" = "fg" "special" = "orange" "ui.background" = { bg = "bg0" } diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml index 0a6eec07b..0ff039eab 100644 --- a/runtime/themes/gruvbox.toml +++ b/runtime/themes/gruvbox.toml @@ -9,8 +9,7 @@ "punctuation.delimiter" = "orange1" "operator" = "purple1" "special" = "purple0" -"property" = "blue1" -"variable.property" = "blue1" +"variable.other.member" = "blue1" "variable" = "fg1" "variable.builtin" = "orange1" "variable.parameter" = "fg2" @@ -24,8 +23,8 @@ "constant" = { fg = "purple1" } "constant.builtin" = { fg = "purple1", modifiers = ["bold"] } "string" = "green1" -"number" = "purple1" -"escape" = { fg = "fg2", modifiers = ["bold"] } +"constant.numeric" = "purple1" +"constant.character.escape" = { fg = "fg2", modifiers = ["bold"] } "label" = "aqua1" "module" = "aqua1" diff --git a/runtime/themes/ingrid.toml b/runtime/themes/ingrid.toml index 6a177ec7c..308294759 100644 --- a/runtime/themes/ingrid.toml +++ b/runtime/themes/ingrid.toml @@ -8,7 +8,7 @@ "punctuation.delimiter" = "#C97270" "operator" = { fg = "#D74E50", modifiers = ["bold"] } "special" = "#D68482" -"property" = "#89BEB7" +"variable.other.member" = "#89BEB7" "variable" = "#A6B6CE" "variable.parameter" = "#89BEB7" "type" = { fg = "#A6B6CE", modifiers = ["bold"] } @@ -22,8 +22,8 @@ "constant" = "#D4A520" "constant.builtin" = "#D4A520" "string" = "#D74E50" -"number" = "#D74E50" -"escape" = { fg = "#D74E50", modifiers = ["bold"] } +"constant.numeric" = "#D74E50" +"constant.character.escape" = { fg = "#D74E50", modifiers = ["bold"] } "label" = "#D68482" "module" = "#839A53" diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml index a8f03ff3c..38f9f1707 100644 --- a/runtime/themes/monokai.toml +++ b/runtime/themes/monokai.toml @@ -7,7 +7,7 @@ "type.builtin" = { fg = "#66D9EF" } "type.enum.variant" = { fg = "text" } "constructor" = { fg = "text" } -"property" = { fg = "variable" } +"variable.other.member" = { fg = "variable" } "keyword" = { fg = "keyword" } "keyword.directive" = { fg = "keyword" } @@ -34,9 +34,10 @@ "comment" = { fg = "#88846F" } "string" = { fg = "#e6db74" } +"constant.character" = { fg = "#e6db74" } "string.regexp" = { fg = "regex" } -"number" = { fg = "#ae81ff" } -"escape" = { fg = "#ae81ff" } +"constant.numeric" = { fg = "#ae81ff" } +"constant.character.escape" = { fg = "#ae81ff" } "ui.background" = { fg = "text", bg = "background" } diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index 29f3875dc..78736c3bd 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -59,7 +59,7 @@ # nord9 - operator, tags, units, punctuations "punctuation.delimiter" = "nord9" "operator" = { fg = "nord9" } -"property" = "nord9" +"variable.other.member" = "nord9" # nord10 - keywords, special "keyword" = { fg = "nord10" } @@ -76,13 +76,13 @@ # nord13 - warnings, escape characters, regex "warning" = "nord13" -"escape" = { fg = "nord13" } +"constant.character.escape" = { fg = "nord13" } # nord14 - strings "string" = "nord14" # nord15 - integer, floating point -"number" = "nord15" +"constant.numeric" = "nord15" [palette] nord0 = "#2e3440" diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml index a982fd6e0..537770084 100644 --- a/runtime/themes/rose_pine.toml +++ b/runtime/themes/rose_pine.toml @@ -16,14 +16,14 @@ "operator" = "rose" "ui.text.focus" = { fg = "base05" } "variable" = "text" -"number" = "iris" +"constant.numeric" = "iris" "constant" = "gold" "attributes" = "gold" "type" = "foam" "ui.cursor.match" = { fg = "gold", modifiers = ["underlined"] } "string" = "gold" "property" = "foam" -"escape" = "subtle" +"constant.character.escape" = "subtle" "function" = "rose" "function.builtin" = "rose" "function.method" = "foam" diff --git a/theme.toml b/theme.toml index 82b71a7dd..3956e25e6 100644 --- a/theme.toml +++ b/theme.toml @@ -6,7 +6,7 @@ punctuation = "lavender" "punctuation.delimiter" = "lavender" operator = "lilac" special = "honey" -property = "white" +variable.other.member = "white" variable = "lavender" # variable = "almond" # TODO: metavariables only # "variable.parameter" = { fg = "lavender", modifiers = ["underlined"] } From 3eb829e2330fed5ad1c095f8bba44f62361b4943 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Wed, 3 Nov 2021 11:02:29 +0800 Subject: [PATCH 101/122] Ensure coords in screen depends on char width (#885) The issue affected files with lots of tabs at the start as well. Fix #840 --- helix-core/src/lib.rs | 2 +- helix-core/src/movement.rs | 4 ++ helix-core/src/position.rs | 81 +++++++++++++++++++++++++++++---- helix-term/src/ui/completion.rs | 10 ++-- helix-view/src/view.rs | 8 ++-- 5 files changed, 87 insertions(+), 18 deletions(-) diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 96f88ee45..d1720df00 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -194,7 +194,7 @@ pub use tendril::StrTendril as Tendril; pub use {regex, tree_sitter}; pub use graphemes::RopeGraphemes; -pub use position::{coords_at_pos, pos_at_coords, Position}; +pub use position::{coords_at_pos, pos_at_coords, visual_coords_at_pos, Position}; pub use selection::{Range, Selection}; pub use smallvec::SmallVec; pub use syntax::Syntax; diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 5d080545d..9e85bd217 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -53,6 +53,10 @@ pub fn move_vertically( let pos = range.cursor(slice); // Compute the current position's 2d coordinates. + // TODO: switch this to use `visual_coords_at_pos` rather than + // `coords_at_pos` as this will cause a jerky movement when the visual + // position does not match, like moving from a line with tabs/CJK to + // a line without let Position { row, col } = coords_at_pos(slice, pos); let horiz = range.horiz.unwrap_or(col as u32); diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index 08a8aed54..c6018ce69 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -2,6 +2,7 @@ use crate::{ chars::char_is_line_ending, graphemes::{ensure_grapheme_boundary_prev, RopeGraphemes}, line_ending::line_end_char_index, + unicode::width::UnicodeWidthChar, RopeSlice, }; @@ -54,11 +55,8 @@ impl From for tree_sitter::Point { } /// Convert a character index to (line, column) coordinates. /// -/// TODO: this should be split into two methods: one for visual -/// row/column, and one for "objective" row/column (possibly with -/// the column specified in `char`s). The former would be used -/// for cursor movement, and the latter would be used for e.g. the -/// row:column display in the status line. +/// column in `char` count which can be used for row:column display in +/// status line. See [`visual_coords_at_pos`] for a visual one. pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { let line = text.char_to_line(pos); @@ -69,6 +67,28 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { Position::new(line, col) } +/// Convert a character index to (line, column) coordinates visually. +/// +/// Takes \t, double-width characters (CJK) into account as well as text +/// not in the document in the future. +/// See [`coords_at_pos`] for an "objective" one. +pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position { + let line = text.char_to_line(pos); + + let line_start = text.line_to_char(line); + let pos = ensure_grapheme_boundary_prev(text, pos); + let col = text + .slice(line_start..pos) + .chars() + .flat_map(|c| match c { + '\t' => Some(tab_width), + c => UnicodeWidthChar::width(c), + }) + .sum(); + + Position::new(line, col) +} + /// Convert (line, column) coordinates to a character index. /// /// If the `line` coordinate is beyond the end of the file, the EOF @@ -130,7 +150,6 @@ mod test { assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d // Test with wide characters. - // TODO: account for character width. let text = Rope::from("今日はいい\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -151,7 +170,6 @@ mod test { assert_eq!(coords_at_pos(slice, 9), (1, 0).into()); // Test with wide-character grapheme clusters. - // TODO: account for character width. let text = Rope::from("किमपि\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -161,7 +179,6 @@ mod test { assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // Test with tabs. - // Todo: account for tab stops. let text = Rope::from("\tHello\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -169,6 +186,54 @@ mod test { assert_eq!(coords_at_pos(slice, 2), (0, 2).into()); } + #[test] + fn test_visual_coords_at_pos() { + let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); // position on \n + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); // position on w + assert_eq!(visual_coords_at_pos(slice, 7, 8), (1, 1).into()); // position on o + assert_eq!(visual_coords_at_pos(slice, 10, 8), (1, 4).into()); // position on d + + // Test with wide characters. + let text = Rope::from("今日はいい\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 4).into()); + assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 6).into()); + assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 8).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 10).into()); + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); + + // Test with grapheme clusters. + let text = Rope::from("a̐éö̲\r\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 1).into()); + assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 7, 8), (0, 3).into()); + assert_eq!(visual_coords_at_pos(slice, 9, 8), (1, 0).into()); + + // Test with wide-character grapheme clusters. + // TODO: account for cluster. + let text = Rope::from("किमपि\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 3).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); + + // Test with tabs. + let text = Rope::from("\tHello\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 8).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 9).into()); + } + #[test] fn test_pos_at_coords() { let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index dcb2bfd83..dd782d29d 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -264,12 +264,10 @@ impl Component for Completion { .language() .and_then(|scope| scope.strip_prefix("source.")) .unwrap_or(""); - let cursor_pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row - - view.offset.row) as u16; + let text = doc.text().slice(..); + let cursor_pos = doc.selection(view.id).primary().cursor(text); + let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width()); + let cursor_pos = (coords.row - view.offset.row) as u16; let mut markdown_doc = match &option.documentation { Some(lsp::Documentation::String(contents)) | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 01f18c719..11f301550 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -2,10 +2,9 @@ use std::borrow::Cow; use crate::{graphics::Rect, Document, DocumentId, ViewId}; use helix_core::{ - coords_at_pos, graphemes::{grapheme_width, RopeGraphemes}, line_ending::line_end_char_index, - Position, RopeSlice, Selection, + visual_coords_at_pos, Position, RopeSlice, Selection, }; type Jump = (DocumentId, Selection); @@ -91,7 +90,10 @@ impl View { .selection(self.id) .primary() .cursor(doc.text().slice(..)); - let Position { col, row: line } = coords_at_pos(doc.text().slice(..), cursor); + + let Position { col, row: line } = + visual_coords_at_pos(doc.text().slice(..), cursor, doc.tab_width()); + let inner_area = self.inner_area(); let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1); From 253bd6b3a847433c181b7b49ec77e7a8aab209c3 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 3 Nov 2021 18:52:41 +0530 Subject: [PATCH 102/122] Add better description for copy_selection command (#969) --- book/src/keymap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 9ed35f771..a776ee706 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -89,8 +89,8 @@ | `Alt-;` | Flip selection cursor and anchor | `flip_selections` | | `,` | Keep only the primary selection | `keep_primary_selection` | | `Alt-,` | Remove the primary selection | `remove_primary_selection` | -| `C` | Copy selection onto the next line | `copy_selection_on_next_line` | -| `Alt-C` | Copy selection onto the previous line | `copy_selection_on_prev_line` | +| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` | +| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` | | `(` | Rotate main selection backward | `rotate_selections_backward` | | `)` | Rotate main selection forward | `rotate_selections_forward` | | `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` | From e39cfa40dfc320559c5efdb502e1149c4564cb62 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Wed, 3 Nov 2021 20:50:38 -0400 Subject: [PATCH 103/122] Hide keys bound to `no_op` from infobox (#971) --- helix-term/src/keymap.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 72d0a733e..93f64fa47 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -140,7 +140,12 @@ impl KeyTrieNode { let mut body: Vec<(&str, BTreeSet)> = Vec::with_capacity(self.len()); for (&key, trie) in self.iter() { let desc = match trie { - KeyTrie::Leaf(cmd) => cmd.doc(), + KeyTrie::Leaf(cmd) => { + if cmd.name() == "no_op" { + continue; + } + cmd.doc() + } KeyTrie::Node(n) => n.name(), }; match body.iter().position(|(d, _)| d == &desc) { From 5b5d1b9dfff6b522559174f7f8e99aeb82c674a9 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Wed, 3 Nov 2021 23:24:05 -0400 Subject: [PATCH 104/122] Truncate the starts of file paths instead of the ends in picker (#951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Truncate the starts of file paths in picker * Simplify the truncate implementation * Break loop at appropriate point * Fix alignment and ellipsis presence * Remove extraneous usage of `x_offset` Co-authored-by: Blaž Hrastnik --- helix-term/src/ui/picker.rs | 1 + helix-tui/src/buffer.rs | 78 ++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 7e257c0b9..7fc6af0f2 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -483,6 +483,7 @@ impl Component for Picker { text_style }, true, + true, ); } } diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 377e3e395..f480bc2f3 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -266,12 +266,14 @@ impl Buffer { where S: AsRef, { - self.set_string_truncated(x, y, string, width, style, false) + self.set_string_truncated(x, y, string, width, style, false, false) } /// Print at most the first `width` characters of a string if enough space is available - /// until the end of the line. If `markend` is true appends a `…` at the end of - /// truncated lines. + /// until the end of the line. If `ellipsis` is true appends a `…` at the end of + /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string + /// instead of the end. + #[allow(clippy::too_many_arguments)] pub fn set_string_truncated( &mut self, x: u16, @@ -280,6 +282,7 @@ impl Buffer { width: usize, style: Style, ellipsis: bool, + truncate_start: bool, ) -> (u16, u16) where S: AsRef, @@ -289,28 +292,59 @@ impl Buffer { let width = if ellipsis { width - 1 } else { width }; let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true); let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); - for s in graphemes { - let width = s.width(); - if width == 0 { - continue; + if !truncate_start { + for s in graphemes { + let width = s.width(); + if width == 0 { + continue; + } + // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we + // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32. + if width > max_offset.saturating_sub(x_offset) { + break; + } + + self.content[index].set_symbol(s); + self.content[index].set_style(style); + // Reset following cells if multi-width (they would be hidden by the grapheme), + for i in index + 1..index + width { + self.content[i].reset(); + } + index += width; + x_offset += width; } - // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we - // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32. - if width > max_offset.saturating_sub(x_offset) { - break; + if ellipsis && x_offset - (x as usize) < string.as_ref().width() { + self.content[index].set_symbol("…"); } - - self.content[index].set_symbol(s); - self.content[index].set_style(style); - // Reset following cells if multi-width (they would be hidden by the grapheme), - for i in index + 1..index + width { - self.content[i].reset(); + } else { + let mut start_index = self.index_of(x, y); + let mut index = self.index_of(max_offset as u16, y); + + let total_width = string.as_ref().width(); + let truncated = total_width > width; + if ellipsis && truncated { + self.content[start_index].set_symbol("…"); + start_index += 1; + } + if !truncated { + index -= width - total_width; + } + for s in graphemes.rev() { + let width = s.width(); + if width == 0 { + continue; + } + let start = index - width; + if start < start_index { + break; + } + self.content[start].set_symbol(s); + self.content[start].set_style(style); + for i in start + 1..index { + self.content[i].reset(); + } + index -= width; } - index += width; - x_offset += width; - } - if ellipsis && x_offset - (x as usize) < string.as_ref().width() { - self.content[index].set_symbol("…"); } (x_offset as u16, y) } From 70d21a903fef3ec0787c453f369d95e5223a2656 Mon Sep 17 00:00:00 2001 From: diegodox <37207872+diegodox@users.noreply.github.com> Date: Thu, 4 Nov 2021 12:24:52 +0900 Subject: [PATCH 105/122] Prevent preview binary or large file (#939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent preview binary or large file (#847) * fix wrong method name * fix add use trait * update lock file * rename MAX_PREVIEW_SIZE from MAX_BYTE_PREVIEW * read small bytes to determine cotent type * [WIP] add preview struct to represent calcurated preveiw * Refactor content type detection - Remove unwraps - Reuse a single read buffer to avoid 1kb reallocations between previews * Refactor preview rendering so we don't construct docs when not necessary * Replace unwarap whit Preview::NotFound * Use index access to hide unwrap Co-authored-by: Blaž Hrastnik * fix Get and unwarp equivalent to referce of Index acess * better preview implementation * Rename Preview enum and vairant Co-authored-by: Gokul Soumya * fixup! Rename Preview enum and vairant * simplify long match * Center text, add docs, fix formatting, refactor Co-authored-by: Blaž Hrastnik Co-authored-by: Gokul Soumya --- Cargo.lock | 10 +++ helix-term/Cargo.toml | 2 + helix-term/src/ui/picker.rs | 123 ++++++++++++++++++++++++++++++------ 3 files changed, 115 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ce8ee8f5..e036828a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "content_inspector" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" +dependencies = [ + "memchr", +] + [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -415,6 +424,7 @@ version = "0.5.0" dependencies = [ "anyhow", "chrono", + "content_inspector", "crossterm", "fern", "futures-util", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 45b4eb2cf..a0079febe 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -46,6 +46,8 @@ fuzzy-matcher = "0.3" ignore = "0.4" # markdown doc rendering pulldown-cmark = { version = "0.8", default-features = false } +# file type detection +content_inspector = "0.2.4" # config toml = "0.5" diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 7fc6af0f2..291f1f856 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -12,7 +12,12 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; use tui::widgets::Widget; -use std::{borrow::Cow, collections::HashMap, path::PathBuf}; +use std::{ + borrow::Cow, + collections::HashMap, + io::Read, + path::{Path, PathBuf}, +}; use crate::ui::{Prompt, PromptEvent}; use helix_core::Position; @@ -23,18 +28,58 @@ use helix_view::{ }; pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80; +/// Biggest file size to preview in bytes +pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024; -/// File path and line number (used to align and highlight a line) +/// File path and range of lines (used to align and highlight lines) type FileLocation = (PathBuf, Option<(usize, usize)>); pub struct FilePicker { picker: Picker, /// Caches paths to documents - preview_cache: HashMap, + preview_cache: HashMap, + read_buffer: Vec, /// Given an item in the picker, return the file path and line number to display. file_fn: Box Option>, } +pub enum CachedPreview { + Document(Document), + Binary, + LargeFile, + NotFound, +} + +// We don't store this enum in the cache so as to avoid lifetime constraints +// from borrowing a document already opened in the editor. +pub enum Preview<'picker, 'editor> { + Cached(&'picker CachedPreview), + EditorDocument(&'editor Document), +} + +impl Preview<'_, '_> { + fn document(&self) -> Option<&Document> { + match self { + Preview::EditorDocument(doc) => Some(doc), + Preview::Cached(CachedPreview::Document(doc)) => Some(doc), + _ => None, + } + } + + /// Alternate text to show for the preview. + fn placeholder(&self) -> &str { + match *self { + Self::EditorDocument(_) => "", + Self::Cached(preview) => match preview { + CachedPreview::Document(_) => "", + CachedPreview::Binary => "", + CachedPreview::LargeFile => "", + CachedPreview::NotFound => "", + }, + } + } +} + impl FilePicker { pub fn new( options: Vec, @@ -45,6 +90,7 @@ impl FilePicker { Self { picker: Picker::new(false, options, format_fn, callback_fn), preview_cache: HashMap::new(), + read_buffer: Vec::with_capacity(1024), file_fn: Box::new(preview_fn), } } @@ -60,14 +106,45 @@ impl FilePicker { }) } - fn calculate_preview(&mut self, editor: &Editor) { - if let Some((path, _line)) = self.current_file(editor) { - if !self.preview_cache.contains_key(&path) && editor.document_by_path(&path).is_none() { - // TODO: enable syntax highlighting; blocked by async rendering - let doc = Document::open(&path, None, Some(&editor.theme), None).unwrap(); - self.preview_cache.insert(path, doc); - } + /// Get (cached) preview for a given path. If a document corresponding + /// to the path is already open in the editor, it is used instead. + fn get_preview<'picker, 'editor>( + &'picker mut self, + path: &Path, + editor: &'editor Editor, + ) -> Preview<'picker, 'editor> { + if let Some(doc) = editor.document_by_path(path) { + return Preview::EditorDocument(doc); + } + + if self.preview_cache.contains_key(path) { + return Preview::Cached(&self.preview_cache[path]); } + + let data = std::fs::File::open(path).and_then(|file| { + let metadata = file.metadata()?; + // Read up to 1kb to detect the content type + let n = file.take(1024).read_to_end(&mut self.read_buffer)?; + let content_type = content_inspector::inspect(&self.read_buffer[..n]); + self.read_buffer.clear(); + Ok((metadata, content_type)) + }); + let preview = data + .map( + |(metadata, content_type)| match (metadata.len(), content_type) { + (_, content_inspector::ContentType::BINARY) => CachedPreview::Binary, + (size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => CachedPreview::LargeFile, + _ => { + // TODO: enable syntax highlighting; blocked by async rendering + Document::open(path, None, Some(&editor.theme), None) + .map(CachedPreview::Document) + .unwrap_or(CachedPreview::NotFound) + } + }, + ) + .unwrap_or(CachedPreview::NotFound); + self.preview_cache.insert(path.to_owned(), preview); + Preview::Cached(&self.preview_cache[path]) } } @@ -79,12 +156,12 @@ impl Component for FilePicker { // |picker | | | // | | | | // +---------+ +---------+ - self.calculate_preview(cx.editor); let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW; let area = inner_rect(area); // -- Render the frame: // clear area let background = cx.editor.theme.get("ui.background"); + let text = cx.editor.theme.get("ui.text"); surface.clear_with(area, background); let picker_width = if render_preview { @@ -113,17 +190,23 @@ impl Component for FilePicker { horizontal: 1, }; let inner = inner.inner(&margin); - block.render(preview_area, surface); - if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, range)| { - cx.editor - .document_by_path(&path) - .or_else(|| self.preview_cache.get(&path)) - .zip(Some(range)) - }) { + if let Some((path, range)) = self.current_file(cx.editor) { + let preview = self.get_preview(&path, cx.editor); + let doc = match preview.document() { + Some(doc) => doc, + None => { + let alt_text = preview.placeholder(); + let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2; + let y = inner.y + inner.height / 2; + surface.set_stringn(x, y, alt_text, inner.width as usize, text); + return; + } + }; + // align to middle - let first_line = line + let first_line = range .map(|(start, end)| { let height = end.saturating_sub(start) + 1; let middle = start + (height.saturating_sub(1) / 2); @@ -150,7 +233,7 @@ impl Component for FilePicker { ); // highlight the line - if let Some((start, end)) = line { + if let Some((start, end)) = range { let offset = start.saturating_sub(first_line) as u16; surface.set_style( Rect::new( From 39584cbccdb06b528220a13b643416f3fd5dc3c8 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Thu, 4 Nov 2021 11:26:01 +0800 Subject: [PATCH 106/122] Add c-s to pick word under doc cursor to prompt line & search completion (#831) * Add prompt shourtcut to book Add completions to search Add c-s to pick word under doc cursor to prompt line * limit 20 last items of search completion, update book * Update book/src/keymap.md Co-authored-by: Ivan Tham * limit search completions 200 Co-authored-by: Ivan Tham --- book/src/keymap.md | 22 ++++++++++++++++++++++ helix-term/src/commands.rs | 29 +++++++++++++++++++++++++++++ helix-term/src/ui/mod.rs | 3 ++- helix-term/src/ui/prompt.rs | 35 +++++++++++++++++++++++++++++++++-- 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index a776ee706..4a6f80bbe 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -258,3 +258,25 @@ Keys to use within picker. Remapping currently not supported. | `Ctrl-s` | Open horizontally | | `Ctrl-v` | Open vertically | | `Escape`, `Ctrl-c` | Close picker | + +# Prompt +Keys to use within prompt, Remapping currently not supported. +| Key | Description | +| ----- | ------------- | +| `Escape`, `Ctrl-c` | Close prompt | +| `Alt-b`, `Alt-Left` | Backward a word | +| `Ctrl-b`, `Left` | Backward a char | +| `Alt-f`, `Alt-Right` | Forward a word | +| `Ctrl-f`, `Right` | Forward a char | +| `Ctrl-e`, `End` | move prompt end | +| `Ctrl-a`, `Home` | move prompt start | +| `Ctrl-w` | delete previous word | +| `Ctrl-k` | delete to end of line | +| `backspace` | delete previous char | +| `Ctrl-s` | insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later | +| `Ctrl-p`, `Up` | select previous history | +| `Ctrl-n`, `Down` | select next history | +| `Tab` | slect next completion item | +| `BackTab` | slect previous completion item | +| `Enter` | Open selected | + diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cc9106eb7..6c073fb8c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1087,6 +1087,7 @@ fn select_regex(cx: &mut Context) { cx, "select:".into(), Some(reg), + |_input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1109,6 +1110,7 @@ fn split_selection(cx: &mut Context) { cx, "split:".into(), Some(reg), + |_input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1172,6 +1174,15 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege }; } +fn search_completions(cx: &mut Context, reg: Option) -> Vec { + let mut items = reg + .and_then(|reg| cx.editor.registers.get(reg)) + .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect()); + items.sort_unstable(); + items.dedup(); + items.into_iter().cloned().collect() +} + // TODO: use one function for search vs extend fn search(cx: &mut Context) { let reg = cx.register.unwrap_or('/'); @@ -1182,11 +1193,19 @@ fn search(cx: &mut Context) { // HAXX: sadly we can't avoid allocating a single string for the whole buffer since we can't // feed chunks into the regex yet let contents = doc.text().slice(..).to_string(); + let completions = search_completions(cx, Some(reg)); let prompt = ui::regex_prompt( cx, "search:".into(), Some(reg), + move |input: &str| { + completions + .iter() + .filter(|comp| comp.starts_with(input)) + .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) + .collect() + }, move |view, doc, regex, event| { if event != PromptEvent::Update { return; @@ -1246,10 +1265,19 @@ fn global_search(cx: &mut Context) { let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); let smart_case = cx.editor.config.smart_case; + + let completions = search_completions(cx, None); let prompt = ui::regex_prompt( cx, "global search:".into(), None, + move |input: &str| { + completions + .iter() + .filter(|comp| comp.starts_with(input)) + .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) + .collect() + }, move |_view, _doc, regex, event| { if event != PromptEvent::Validate { return; @@ -4086,6 +4114,7 @@ fn keep_selections(cx: &mut Context) { cx, "keep:".into(), Some(reg), + |_input: &str| Vec::new(), move |view, doc, regex, event| { if event != PromptEvent::Update { return; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 30a9ec6bc..24eb7acdf 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -29,6 +29,7 @@ pub fn regex_prompt( cx: &mut crate::commands::Context, prompt: std::borrow::Cow<'static, str>, history_register: Option, + completion_fn: impl FnMut(&str) -> Vec + 'static, fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static, ) -> Prompt { let (view, doc) = current!(cx.editor); @@ -38,7 +39,7 @@ pub fn regex_prompt( Prompt::new( prompt, history_register, - |_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate + completion_fn, move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| { match event { PromptEvent::Abort => { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 853adfc2a..c999ba140 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -185,6 +185,11 @@ impl Prompt { self.exit_selection(); } + pub fn insert_str(&mut self, s: &str) { + self.line.insert_str(self.cursor, s); + self.cursor += s.len(); + } + pub fn move_cursor(&mut self, movement: Movement) { let pos = self.eval_movement(movement); self.cursor = pos @@ -473,6 +478,26 @@ impl Component for Prompt { self.delete_char_backwards(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } + KeyEvent { + code: KeyCode::Char('s'), + modifiers: KeyModifiers::CONTROL, + } => { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + use helix_core::textobject; + let range = textobject::textobject_word( + text, + doc.selection(view.id).primary(), + textobject::TextObject::Inside, + 1, + ); + let line = text.slice(range.from()..range.to()).to_string(); + if !line.is_empty() { + self.insert_str(line.as_str()); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); + } + } KeyEvent { code: KeyCode::Enter, .. @@ -520,11 +545,17 @@ impl Component for Prompt { } KeyEvent { code: KeyCode::Tab, .. - } => self.change_completion_selection(CompletionDirection::Forward), + } => { + self.change_completion_selection(CompletionDirection::Forward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update) + } KeyEvent { code: KeyCode::BackTab, .. - } => self.change_completion_selection(CompletionDirection::Backward), + } => { + self.change_completion_selection(CompletionDirection::Backward); + (self.callback_fn)(cx, &self.line, PromptEvent::Update) + } KeyEvent { code: KeyCode::Char('q'), modifiers: KeyModifiers::CONTROL, From e2560f427ef5e75155071e39da342628f5d5896a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 4 Nov 2021 13:43:45 +0900 Subject: [PATCH 107/122] Replace documents SlotMap with BTreeMap --- helix-term/src/commands.rs | 6 ++--- helix-term/src/ui/editor.rs | 10 ++++---- helix-view/src/editor.rs | 51 ++++++++++++++++++++++--------------- helix-view/src/lib.rs | 4 ++- helix-view/src/macros.rs | 5 ++-- 5 files changed, 44 insertions(+), 32 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6c073fb8c..547a1d75e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1818,7 +1818,7 @@ mod cmd { let mut errors = String::new(); // save all documents - for (_, doc) in &mut cx.editor.documents { + for doc in &mut cx.editor.documents.values_mut() { if doc.path().is_none() { errors.push_str("cannot write a buffer without a filename\n"); continue; @@ -2512,7 +2512,7 @@ fn buffer_picker(cx: &mut Context) { cx.editor .documents .iter() - .map(|(id, doc)| (id, doc.path().cloned())) + .map(|(id, doc)| (*id, doc.path().cloned())) .collect(), move |(id, path): &(DocumentId, Option)| { let path = path.as_deref().map(helix_core::path::get_relative_path); @@ -2531,7 +2531,7 @@ fn buffer_picker(cx: &mut Context) { editor.switch(*id, Action::Replace); }, |editor, (id, path)| { - let doc = &editor.documents.get(*id)?; + let doc = &editor.documents.get(id)?; let &view_id = doc.selections().keys().next()?; let line = doc .selection(view_id) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c0d602c73..0ffde47b2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -813,12 +813,12 @@ impl EditorView { let editor = &mut cxt.editor; let result = editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&editor.documents[view.doc], row, column) + view.pos_at_screen_coords(&editor.documents[&view.doc], row, column) .map(|pos| (pos, view.id)) }); if let Some((pos, view_id)) = result { - let doc = &mut editor.documents[editor.tree.get(view_id).doc]; + let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap(); if modifiers == crossterm::event::KeyModifiers::ALT { let selection = doc.selection(view_id).clone(); @@ -870,7 +870,7 @@ impl EditorView { }; let result = cxt.editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&cxt.editor.documents[view.doc], row, column) + view.pos_at_screen_coords(&cxt.editor.documents[&view.doc], row, column) .map(|_| view.id) }); @@ -926,12 +926,12 @@ impl EditorView { } let result = editor.tree.views().find_map(|(view, _focus)| { - view.pos_at_screen_coords(&editor.documents[view.doc], row, column) + view.pos_at_screen_coords(&editor.documents[&view.doc], row, column) .map(|pos| (pos, view.id)) }); if let Some((pos, view_id)) = result { - let doc = &mut editor.documents[editor.tree.get(view_id).doc]; + let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap(); doc.set_selection(view_id, Selection::point(pos)); editor.tree.focus = view_id; commands::Command::paste_primary_clipboard_before.execute(cxt); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 21a646510..633e25410 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -8,6 +8,7 @@ use crate::{ use futures_util::future; use std::{ + collections::BTreeMap, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -15,8 +16,6 @@ use std::{ use tokio::time::{sleep, Duration, Instant, Sleep}; -use slotmap::SlotMap; - use anyhow::Error; pub use helix_core::diagnostic::Severity; @@ -108,7 +107,8 @@ impl std::fmt::Debug for Motion { #[derive(Debug)] pub struct Editor { pub tree: Tree, - pub documents: SlotMap, + pub next_document_id: usize, + pub documents: BTreeMap, pub count: Option, pub selected_register: Option, pub registers: Registers, @@ -149,7 +149,8 @@ impl Editor { Self { tree: Tree::new(area), - documents: SlotMap::with_key(), + next_document_id: 0, + documents: BTreeMap::new(), count: None, selected_register: None, theme: themes.default(), @@ -216,7 +217,7 @@ impl Editor { fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; view.ensure_cursor_in_view(doc, self.config.scrolloff) } } @@ -225,7 +226,7 @@ impl Editor { use crate::tree::Layout; use helix_core::Selection; - if !self.documents.contains_key(id) { + if !self.documents.contains_key(&id) { log::error!("cannot switch to document that does not exist (anymore)"); return; } @@ -249,7 +250,7 @@ impl Editor { // Copy `doc.id` into a variable before calling `self.documents.remove`, which requires a mutable // borrow, invalidating direct access to `doc.id`. let id = doc.id; - self.documents.remove(id); + self.documents.remove(&id); } else { let jump = (view.doc, doc.selection(view.id).clone()); view.jumps.push(jump); @@ -281,14 +282,14 @@ impl Editor { let view = View::new(id); let view_id = self.tree.split(view, Layout::Horizontal); // initialize selection for view - let doc = &mut self.documents[id]; + let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); } Action::VerticalSplit => { let view = View::new(id); let view_id = self.tree.split(view, Layout::Vertical); // initialize selection for view - let doc = &mut self.documents[id]; + let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); } } @@ -297,9 +298,11 @@ impl Editor { } pub fn new_file(&mut self, action: Action) -> DocumentId { - let doc = Document::default(); - let id = self.documents.insert(doc); - self.documents[id].id = id; + let id = DocumentId(self.next_document_id); + self.next_document_id += 1; + let mut doc = Document::default(); + doc.id = id; + self.documents.insert(id, doc); self.switch(id, action); id } @@ -349,8 +352,10 @@ impl Editor { doc.set_language_server(Some(language_server)); } - let id = self.documents.insert(doc); - self.documents[id].id = id; + let id = DocumentId(self.next_document_id); + self.next_document_id += 1; + doc.id = id; + self.documents.insert(id, doc); id }; @@ -361,16 +366,20 @@ impl Editor { pub fn close(&mut self, id: ViewId, close_buffer: bool) { let view = self.tree.get(self.tree.focus); // remove selection - self.documents[view.doc].selections.remove(&id); + self.documents + .get_mut(&view.doc) + .unwrap() + .selections + .remove(&id); if close_buffer { // get around borrowck issues - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; if let Some(language_server) = doc.language_server() { tokio::spawn(language_server.text_document_did_close(doc.identifier())); } - self.documents.remove(view.doc); + self.documents.remove(&view.doc); } self.tree.remove(id); @@ -409,18 +418,18 @@ impl Editor { pub fn ensure_cursor_in_view(&mut self, id: ViewId) { let view = self.tree.get_mut(id); - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; view.ensure_cursor_in_view(doc, self.config.scrolloff) } #[inline] pub fn document(&self, id: DocumentId) -> Option<&Document> { - self.documents.get(id) + self.documents.get(&id) } #[inline] pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> { - self.documents.get_mut(id) + self.documents.get_mut(&id) } #[inline] @@ -445,7 +454,7 @@ impl Editor { pub fn cursor(&self) -> (Option, CursorKind) { let view = view!(self); - let doc = &self.documents[view.doc]; + let doc = &self.documents[&view.doc]; let cursor = doc .selection(view.id) .primary() diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index c37474d65..3e779356c 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -12,8 +12,10 @@ pub mod theme; pub mod tree; pub mod view; +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct DocumentId(usize); + slotmap::new_key_type! { - pub struct DocumentId; pub struct ViewId; } diff --git a/helix-view/src/macros.rs b/helix-view/src/macros.rs index 0bebd02fc..63d76a420 100644 --- a/helix-view/src/macros.rs +++ b/helix-view/src/macros.rs @@ -13,7 +13,8 @@ macro_rules! current { ( $( $editor:ident ).+ ) => {{ let view = $crate::view_mut!( $( $editor ).+ ); - let doc = &mut $( $editor ).+ .documents[view.doc]; + let id = view.doc; + let doc = $( $editor ).+ .documents.get_mut(&id).unwrap(); (view, doc) }}; } @@ -56,7 +57,7 @@ macro_rules! doc { macro_rules! current_ref { ( $( $editor:ident ).+ ) => {{ let view = $( $editor ).+ .tree.get($( $editor ).+ .tree.focus); - let doc = &$( $editor ).+ .documents[view.doc]; + let doc = &$( $editor ).+ .documents[&view.doc]; (view, doc) }}; } From 78c68fae91579ccda6f65e55f79316b01c5b654a Mon Sep 17 00:00:00 2001 From: ath3 Date: Mon, 1 Nov 2021 20:52:47 +0100 Subject: [PATCH 108/122] Implement "Goto next buffer / Goto previous buffer" commands --- book/src/keymap.md | 2 ++ helix-term/src/commands.rs | 29 +++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 2 ++ 3 files changed, 33 insertions(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 4a6f80bbe..6bcd09bcb 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -161,6 +161,8 @@ Jumps to various locations. | `r` | Go to references | `goto_reference` | | `i` | Go to implementation | `goto_implementation` | | `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` | +| `n` | Go to next buffer | `goto_next_buffer` | +| `p` | Go to previous buffer | `goto_previous_buffer` | #### Match mode diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 547a1d75e..c1891bafb 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -262,6 +262,8 @@ impl Command { goto_prev_diag, "Goto previous diagnostic", goto_line_start, "Goto line start", goto_line_end, "Goto line end", + goto_next_buffer, "Goto next buffer", + goto_previous_buffer, "Goto previous buffer", // TODO: different description ? goto_line_end_newline, "Goto line end", goto_first_nonwhitespace, "Goto first non-blank in line", @@ -519,6 +521,33 @@ fn goto_line_start(cx: &mut Context) { ) } +fn goto_next_buffer(cx: &mut Context) { + goto_buffer(cx, Direction::Forward); +} + +fn goto_previous_buffer(cx: &mut Context) { + goto_buffer(cx, Direction::Backward); +} + +fn goto_buffer(cx: &mut Context, direction: Direction) { + let buf_cur = current!(cx.editor).1.id(); + + if let Some(pos) = cx.editor.documents.iter().position(|(id, _)| id == buf_cur) { + let goto_id = if direction == Direction::Forward { + if pos < cx.editor.documents.iter().count() - 1 { + cx.editor.documents.iter().nth(pos + 1).unwrap().0 + } else { + cx.editor.documents.iter().next().unwrap().0 + } + } else if pos > 0 { + cx.editor.documents.iter().nth(pos - 1).unwrap().0 + } else { + cx.editor.documents.iter().last().unwrap().0 + }; + cx.editor.switch(goto_id, Action::Replace); + } +} + fn extend_to_line_start(cx: &mut Context) { let (view, doc) = current!(cx.editor); goto_line_start_impl(view, doc, Movement::Extend) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 93f64fa47..b48eea144 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -453,6 +453,8 @@ impl Default for Keymaps { "m" => goto_window_middle, "b" => goto_window_bottom, "a" => goto_last_accessed_file, + "n" => goto_next_buffer, + "p" => goto_previous_buffer, }, ":" => command_mode, From 7b65a6d687bbf4d12de020a7785082277804bbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 4 Nov 2021 13:55:45 +0900 Subject: [PATCH 109/122] Rewrite goto_buffer --- helix-term/src/commands.rs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c1891bafb..f16afdfe9 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -530,22 +530,28 @@ fn goto_previous_buffer(cx: &mut Context) { } fn goto_buffer(cx: &mut Context, direction: Direction) { - let buf_cur = current!(cx.editor).1.id(); + let current = view!(cx.editor).doc; - if let Some(pos) = cx.editor.documents.iter().position(|(id, _)| id == buf_cur) { - let goto_id = if direction == Direction::Forward { - if pos < cx.editor.documents.iter().count() - 1 { - cx.editor.documents.iter().nth(pos + 1).unwrap().0 - } else { - cx.editor.documents.iter().next().unwrap().0 - } - } else if pos > 0 { - cx.editor.documents.iter().nth(pos - 1).unwrap().0 - } else { - cx.editor.documents.iter().last().unwrap().0 - }; - cx.editor.switch(goto_id, Action::Replace); + let id = match direction { + Direction::Forward => { + let iter = cx.editor.documents.keys(); + let mut iter = iter.skip_while(|id| *id != ¤t); + iter.next(); // skip current item + iter.next().or_else(|| cx.editor.documents.keys().next()) + } + Direction::Backward => { + let iter = cx.editor.documents.keys(); + let mut iter = iter.rev().skip_while(|id| *id != ¤t); + iter.next(); // skip current item + iter.next() + .or_else(|| cx.editor.documents.keys().rev().next()) + } } + .unwrap(); + + let id = *id; + + cx.editor.switch(id, Action::Replace); } fn extend_to_line_start(cx: &mut Context) { From aa4d0b464645b2834d7af483d17fdc11d61d994d Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Thu, 4 Nov 2021 09:57:04 +0100 Subject: [PATCH 110/122] Fix crash on changing from empty scratch buffer to itself (#975) --- helix-view/src/editor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 633e25410..63a4ab290 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -240,6 +240,8 @@ impl Editor { let remove_empty_scratch = !doc.is_modified() // If the buffer has no path and is not modified, it is an empty scratch buffer. && doc.path().is_none() + // If the buffer we are changing to is not this buffer + && id != doc.id // Ensure the buffer is not displayed in any other splits. && !self .tree From 51b4d35dce92fa7bf85780cb2ba0e531db378448 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 4 Nov 2021 21:20:06 -0400 Subject: [PATCH 111/122] Inform when reaching undo/redo bounds (#981) * Inform when reaching undo/redo bounds * `Already at oldest change` when undo fails * `Already at newest change` when redo fails * Add missing `the` --- helix-term/src/commands.rs | 10 ++++++++-- helix-view/src/document.rs | 10 ++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f16afdfe9..3d134ce5d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3679,13 +3679,19 @@ pub mod insert { fn undo(cx: &mut Context) { let (view, doc) = current!(cx.editor); let view_id = view.id; - doc.undo(view_id); + let success = doc.undo(view_id); + if !success { + cx.editor.set_status("Already at oldest change".to_owned()); + } } fn redo(cx: &mut Context) { let (view, doc) = current!(cx.editor); let view_id = view.id; - doc.redo(view_id); + let success = doc.redo(view_id); + if !success { + cx.editor.set_status("Already at newest change".to_owned()); + } } // Yank / Paste diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 02da4b7af..0d86143b4 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -704,8 +704,8 @@ impl Document { success } - /// Undo the last modification to the [`Document`]. - pub fn undo(&mut self, view_id: ViewId) { + /// Undo the last modification to the [`Document`]. Returns whether the undo was successful. + pub fn undo(&mut self, view_id: ViewId) -> bool { let mut history = self.history.take(); let success = if let Some(transaction) = history.undo() { self.apply_impl(transaction, view_id) @@ -718,10 +718,11 @@ impl Document { // reset changeset to fix len self.changes = ChangeSet::new(self.text()); } + success } - /// Redo the last modification to the [`Document`]. - pub fn redo(&mut self, view_id: ViewId) { + /// Redo the last modification to the [`Document`]. Returns whether the redo was sucessful. + pub fn redo(&mut self, view_id: ViewId) -> bool { let mut history = self.history.take(); let success = if let Some(transaction) = history.redo() { self.apply_impl(transaction, view_id) @@ -734,6 +735,7 @@ impl Document { // reset changeset to fix len self.changes = ChangeSet::new(self.text()); } + success } pub fn savepoint(&mut self) { From cfc82858679d264d178a0b072da26828e685de12 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 4 Nov 2021 22:25:08 -0400 Subject: [PATCH 112/122] Allow infoboxes to be disabled (#972) * Allow infoboxes to be disabled * Document `infoboxes` default value * Rename `infoboxes` to `auto_info` * Document `auto-info` * Fix incomplete rename --- book/src/configuration.md | 1 + helix-term/src/ui/editor.rs | 6 ++++-- helix-view/src/editor.rs | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 7d6ff28f8..be25441f5 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -22,6 +22,7 @@ To override global configuration parameters, create a `config.toml` file located | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `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` | ## LSP diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 0ffde47b2..a70155773 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1086,8 +1086,10 @@ impl Component for EditorView { ); } - if let Some(ref mut info) = self.autoinfo { - info.render(area, surface, cx); + if cx.editor.config.auto_info { + if let Some(ref mut info) = self.autoinfo { + info.render(area, surface, cx); + } } let key_width = 15u16; // for showing pending keys diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 63a4ab290..6aa8b04df 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -58,6 +58,8 @@ pub struct Config { #[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")] pub idle_timeout: Duration, pub completion_trigger_len: u8, + /// Whether to display infoboxes. Defaults to true. + pub auto_info: bool, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -88,6 +90,7 @@ impl Default for Config { auto_completion: true, idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, + auto_info: true, } } } From 911b9b3276cb155eab023b24f1a6f336f4054087 Mon Sep 17 00:00:00 2001 From: Gygaxis Vainhardt <44003709+AloeareV@users.noreply.github.com> Date: Sat, 6 Nov 2021 05:33:30 -0300 Subject: [PATCH 113/122] Add reverse search functionality (#958) * Add reverse search functionality * Change keybindings for extend to be in select mode, incorporate Movement and Direction enums * Fix accidental revert of #948 in rebase * Add reverse search to docs, clean up mismatched whitespace * Reverse search optimization * More optimization via github feedback --- book/src/keymap.md | 36 ++++++++-------- helix-term/src/commands.rs | 86 ++++++++++++++++++++++++++++---------- helix-term/src/keymap.rs | 11 +++-- 3 files changed, 90 insertions(+), 43 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 6bcd09bcb..5a6aee411 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -106,13 +106,13 @@ ### Search -> TODO: The search implementation isn't ideal yet -- we don't support searching in reverse. | Key | Description | Command | | ----- | ----------- | ------- | | `/` | Search for regex pattern | `search` | +| `?` | Search for previous pattern | `rsearch` | | `n` | Select next search match | `search_next` | -| `N` | Add next search match to selection | `extend_search_next` | +| `N` | Select previous search match | `search_prev` | | `*` | Use current selection as the search pattern | `search_selection` | ### Minor modes @@ -185,16 +185,16 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`). This layer is similar to vim keybindings as kakoune does not support window. -| Key | Description | Command | -| ----- | ------------- | ------- | -| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | -| `v`, `Ctrl-v` | Vertical right split | `vsplit` | -| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | -| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` | -| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` | -| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` | -| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` | -| `q`, `Ctrl-q` | Close current window | `wclose` | +| Key | Description | Command | +| ----- | ------------- | ------- | +| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | +| `v`, `Ctrl-v` | Vertical right split | `vsplit` | +| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | +| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` | +| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` | +| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` | +| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` | +| `q`, `Ctrl-q` | Close current window | `wclose` | #### Space mode @@ -222,12 +222,12 @@ This layer is a kludge of mappings, mostly pickers. Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired). -| Key | Description | Command | -| ----- | ----------- | ------- | -| `[d` | Go to previous diagnostic | `goto_prev_diag` | -| `]d` | Go to next diagnostic | `goto_next_diag` | -| `[D` | Go to first diagnostic in document | `goto_first_diag` | -| `]D` | Go to last diagnostic in document | `goto_last_diag` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `[d` | Go to previous diagnostic | `goto_prev_diag` | +| `]d` | Go to next diagnostic | `goto_next_diag` | +| `[D` | Go to first diagnostic in document | `goto_first_diag` | +| `]D` | Go to last diagnostic in document | `goto_last_diag` | | `[space` | Add newline above | `add_newline_above` | | `]space` | Add newline below | `add_newline_below` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3d134ce5d..c8f645317 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -217,8 +217,11 @@ impl Command { split_selection, "Split selection into subselections on regex matches", split_selection_on_newline, "Split selection on newlines", search, "Search for regex pattern", + rsearch, "Reverse search for regex pattern", search_next, "Select next search match", + search_prev, "Select previous search match", extend_search_next, "Add next search match to selection", + extend_search_prev, "Add previous search match to selection", search_selection, "Use current selection as search pattern", global_search, "Global Search in workspace folder", extend_line, "Select current line, if already selected, extend to next line", @@ -1170,38 +1173,62 @@ fn split_selection_on_newline(cx: &mut Context) { doc.set_selection(view.id, selection); } -fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) { +fn search_impl( + doc: &mut Document, + view: &mut View, + contents: &str, + regex: &Regex, + movement: Movement, + direction: Direction, +) { let text = doc.text().slice(..); let selection = doc.selection(view.id); - // Get the right side of the primary block cursor. - let start = text.char_to_byte(graphemes::next_grapheme_boundary( - text, - selection.primary().cursor(text), - )); + // Get the right side of the primary block cursor for forward search, or the + //grapheme before the start of the selection for reverse search. + let start = match direction { + Direction::Forward => text.char_to_byte(graphemes::next_grapheme_boundary( + text, + selection.primary().to(), + )), + Direction::Backward => text.char_to_byte(graphemes::prev_grapheme_boundary( + text, + selection.primary().from(), + )), + }; + + //A regex::Match returns byte-positions in the str. In the case where we + //do a reverse search and wraparound to the end, we don't need to search + //the text before the current cursor position for matches, but by slicing + //it out, we need to add it back to the position of the selection. + let mut offset = 0; // use find_at to find the next match after the cursor, loop around the end // Careful, `Regex` uses `bytes` as offsets, not character indices! - let mat = regex - .find_at(contents, start) - .or_else(|| regex.find(contents)); + let mat = match direction { + Direction::Forward => regex + .find_at(contents, start) + .or_else(|| regex.find(contents)), + Direction::Backward => regex.find_iter(&contents[..start]).last().or_else(|| { + offset = start; + regex.find_iter(&contents[start..]).last() + }), + }; // TODO: message on wraparound if let Some(mat) = mat { - let start = text.byte_to_char(mat.start()); - let end = text.byte_to_char(mat.end()); + let start = text.byte_to_char(mat.start() + offset); + let end = text.byte_to_char(mat.end() + offset); if end == 0 { // skip empty matches that don't make sense return; } - - let selection = if extend { - selection.clone().push(Range::new(start, end)) - } else { - selection + let selection = match movement { + Movement::Extend => selection.clone().push(Range::new(start, end)), + Movement::Move => selection .clone() .remove(selection.primary_index()) - .push(Range::new(start, end)) + .push(Range::new(start, end)), }; doc.set_selection(view.id, selection); @@ -1220,6 +1247,14 @@ fn search_completions(cx: &mut Context, reg: Option) -> Vec { // TODO: use one function for search vs extend fn search(cx: &mut Context) { + searcher(cx, Direction::Forward) +} + +fn rsearch(cx: &mut Context) { + searcher(cx, Direction::Backward) +} +// TODO: use one function for search vs extend +fn searcher(cx: &mut Context, direction: Direction) { let reg = cx.register.unwrap_or('/'); let (_, doc) = current!(cx.editor); @@ -1245,14 +1280,14 @@ fn search(cx: &mut Context) { if event != PromptEvent::Update { return; } - search_impl(doc, view, &contents, ®ex, false); + search_impl(doc, view, &contents, ®ex, Movement::Move, direction); }, ); cx.push_layer(Box::new(prompt)); } -fn search_next_impl(cx: &mut Context, extend: bool) { +fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) { let (view, doc) = current!(cx.editor); let registers = &cx.editor.registers; if let Some(query) = registers.read('/') { @@ -1267,7 +1302,7 @@ fn search_next_impl(cx: &mut Context, extend: bool) { .case_insensitive(case_insensitive) .build() { - search_impl(doc, view, &contents, ®ex, extend); + search_impl(doc, view, &contents, ®ex, movement, direction); } else { // get around warning `mutable_borrow_reservation_conflict` // which will be a hard error in the future @@ -1279,11 +1314,18 @@ fn search_next_impl(cx: &mut Context, extend: bool) { } fn search_next(cx: &mut Context) { - search_next_impl(cx, false); + search_next_or_prev_impl(cx, Movement::Move, Direction::Forward); } +fn search_prev(cx: &mut Context) { + search_next_or_prev_impl(cx, Movement::Move, Direction::Backward); +} fn extend_search_next(cx: &mut Context) { - search_next_impl(cx, true); + search_next_or_prev_impl(cx, Movement::Extend, Direction::Forward); +} + +fn extend_search_prev(cx: &mut Context) { + search_next_or_prev_impl(cx, Movement::Extend, Direction::Backward); } fn search_selection(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b48eea144..c85a9c3f5 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -504,10 +504,9 @@ impl Default for Keymaps { }, "/" => search, - // ? for search_reverse + "?" => rsearch, "n" => search_next, - "N" => extend_search_next, - // N for search_prev + "N" => search_prev, "*" => search_selection, "u" => undo, @@ -633,11 +632,17 @@ impl Default for Keymaps { "B" => extend_prev_long_word_start, "E" => extend_next_long_word_end, + "n" => extend_search_next, + "N" => extend_search_prev, + "t" => extend_till_char, "f" => extend_next_char, "T" => extend_till_prev_char, "F" => extend_prev_char, + "n" => extend_search_next, + "N" => extend_search_prev, + "home" => extend_to_line_start, "end" => extend_to_line_end, "esc" => exit_select_mode, From 6431b26a6a5fa4be5b91008f21537721d2ff4ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 17:37:45 +0900 Subject: [PATCH 114/122] Implement Selection::replace to replace a single range Fixes #985 Co-authored-by: Daniel S Poulin --- helix-core/src/selection.rs | 11 +++++++++++ helix-term/src/commands.rs | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 18af4d089..f3b5d2c83 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -362,6 +362,11 @@ impl Selection { /// Adds a new range to the selection and makes it the primary range. pub fn remove(mut self, index: usize) -> Self { + assert!( + self.ranges.len() > 1, + "can't remove the last range from a selection!" + ); + self.ranges.remove(index); if index < self.primary_index || self.primary_index == self.ranges.len() { self.primary_index -= 1; @@ -369,6 +374,12 @@ impl Selection { self } + /// Replace a range in the selection with a new range. + pub fn replace(mut self, index: usize, range: Range) -> Self { + self.ranges[index] = range; + self.normalize() + } + /// Map selections over a set of changes. Useful for adjusting the selection position after /// applying changes to a document. pub fn map(self, changes: &ChangeSet) -> Self { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c8f645317..e3ebd1280 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1227,8 +1227,7 @@ fn search_impl( Movement::Extend => selection.clone().push(Range::new(start, end)), Movement::Move => selection .clone() - .remove(selection.primary_index()) - .push(Range::new(start, end)), + .replace(selection.primary_index(), Range::new(start, end)), }; doc.set_selection(view.id, selection); From b81a5544248633d84615952ec6130f5104998c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 17:41:30 +0900 Subject: [PATCH 115/122] Retain range direction on search Co-authored-by: CossonLeo <20379044+cossonleo@users.noreply.github.com> --- helix-term/src/commands.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e3ebd1280..752b5900d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1223,11 +1223,18 @@ fn search_impl( // skip empty matches that don't make sense return; } + + // Determine range direction based on the primary range + let primary = selection.primary(); + let range = if primary.head < primary.anchor { + Range::new(end, start) + } else { + Range::new(start, end) + }; + let selection = match movement { - Movement::Extend => selection.clone().push(Range::new(start, end)), - Movement::Move => selection - .clone() - .replace(selection.primary_index(), Range::new(start, end)), + Movement::Extend => selection.clone().push(range), + Movement::Move => selection.clone().replace(selection.primary_index(), range), }; doc.set_selection(view.id, selection); From 4c1321b3b6b5b2f4e9f33963877a1019ba746e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 17:49:18 +0900 Subject: [PATCH 116/122] minor: Extend search was decclared twice in the keymap --- helix-term/src/keymap.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index c85a9c3f5..ce50f0ab3 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -640,9 +640,6 @@ impl Default for Keymaps { "T" => extend_till_prev_char, "F" => extend_prev_char, - "n" => extend_search_next, - "N" => extend_search_prev, - "home" => extend_to_line_start, "end" => extend_to_line_end, "esc" => exit_select_mode, From f659e1178a20cfaa151eaf62af3135a233272151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 17:54:04 +0900 Subject: [PATCH 117/122] minor: view!(..).doc is slightly more efficient than current!(..).1.id() --- helix-term/src/commands.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 752b5900d..aa18bef05 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2196,8 +2196,7 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let (_, doc) = current!(cx.editor); - let id = doc.id(); + let id = view!(cx.editor).doc; if let Some(path) = args.get(0) { cx.editor.open(path.into(), Action::VerticalSplit)?; @@ -2213,8 +2212,7 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let (_, doc) = current!(cx.editor); - let id = doc.id(); + let id = view!(cx.editor).doc; if let Some(path) = args.get(0) { cx.editor.open(path.into(), Action::HorizontalSplit)?; From 0f4cd73000140cd9872f291ddb1473f96cbc7364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 18:04:04 +0900 Subject: [PATCH 118/122] Simplify goto_*_diagnostic commands --- helix-term/src/commands.rs | 56 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index aa18bef05..2e177b597 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3348,26 +3348,24 @@ fn goto_first_diag(cx: &mut Context) { let editor = &mut cx.editor; let (_, doc) = current!(editor); - let diag = if let Some(diag) = doc.diagnostics().first() { - diag.range.start - } else { - return; + let pos = match doc.diagnostics().first() { + Some(diag) => diag.range.start, + None => return, }; - goto_pos(editor, diag); + goto_pos(editor, pos); } fn goto_last_diag(cx: &mut Context) { let editor = &mut cx.editor; let (_, doc) = current!(editor); - let diag = if let Some(diag) = doc.diagnostics().last() { - diag.range.start - } else { - return; + let pos = match doc.diagnostics().last() { + Some(diag) => diag.range.start, + None => return, }; - goto_pos(editor, diag); + goto_pos(editor, pos); } fn goto_next_diag(cx: &mut Context) { @@ -3378,20 +3376,19 @@ fn goto_next_diag(cx: &mut Context) { .selection(view.id) .primary() .cursor(doc.text().slice(..)); - let diag = if let Some(diag) = doc + + let diag = doc .diagnostics() .iter() - .map(|diag| diag.range.start) - .find(|&pos| pos > cursor_pos) - { - diag - } else if let Some(diag) = doc.diagnostics().first() { - diag.range.start - } else { - return; + .find(|diag| diag.range.start > cursor_pos) + .or_else(|| doc.diagnostics().first()); + + let pos = match diag { + Some(diag) => diag.range.start, + None => return, }; - goto_pos(editor, diag); + goto_pos(editor, pos); } fn goto_prev_diag(cx: &mut Context) { @@ -3402,21 +3399,20 @@ fn goto_prev_diag(cx: &mut Context) { .selection(view.id) .primary() .cursor(doc.text().slice(..)); - let diag = if let Some(diag) = doc + + let diag = doc .diagnostics() .iter() .rev() - .map(|diag| diag.range.start) - .find(|&pos| pos < cursor_pos) - { - diag - } else if let Some(diag) = doc.diagnostics().last() { - diag.range.start - } else { - return; + .find(|diag| diag.range.start < cursor_pos) + .or_else(|| doc.diagnostics().last()); + + let pos = match diag { + Some(diag) => diag.range.start, + None => return, }; - goto_pos(editor, diag); + goto_pos(editor, pos); } fn signature_help(cx: &mut Context) { From e80708eba7e9959fa720e2563231bba542570295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 18:58:40 +0900 Subject: [PATCH 119/122] Make sure document diagnostics are sorted --- helix-core/src/diagnostic.rs | 2 +- helix-view/src/document.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index ab47e0752..ad1ba16ab 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -10,7 +10,7 @@ pub enum Severity { } /// A range of `char`s within the text. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] pub struct Range { pub start: usize, pub end: usize, diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 0d86143b4..ce5df8ee8 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -918,6 +918,9 @@ impl Document { pub fn set_diagnostics(&mut self, diagnostics: Vec) { self.diagnostics = diagnostics; + // sort by range + self.diagnostics + .sort_unstable_by_key(|diagnostic| diagnostic.range); } } From 1a1685acf7fe9836b235dbc73361344d9330800c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 23:52:26 +0900 Subject: [PATCH 120/122] Simplify current!(..).1 into doc!() --- helix-term/src/commands.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2e177b597..f2a1e66d6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -743,13 +743,7 @@ where // usually mix line endings. But we should fix it eventually // anyway. { - current!(cx.editor) - .1 - .line_ending - .as_str() - .chars() - .next() - .unwrap() + doc!(cx.editor).line_ending.as_str().chars().next().unwrap() } KeyEvent { @@ -1746,7 +1740,7 @@ mod cmd { // If no argument, report current indent style. if args.is_empty() { - let style = current!(cx.editor).1.indent_style; + let style = doc!(cx.editor).indent_style; cx.editor.set_status(match style { Tabs => "tabs".into(), Spaces(1) => "1 space".into(), @@ -1785,7 +1779,7 @@ mod cmd { // If no argument, report current line ending setting. if args.is_empty() { - let line_ending = current!(cx.editor).1.line_ending; + let line_ending = doc!(cx.editor).line_ending; cx.editor.set_status(match line_ending { Crlf => "crlf".into(), LF => "line feed".into(), @@ -3794,7 +3788,7 @@ fn yank_joined_to_clipboard_impl( } fn yank_joined_to_clipboard(cx: &mut Context) { - let line_ending = current!(cx.editor).1.line_ending; + let line_ending = doc!(cx.editor).line_ending; let _ = yank_joined_to_clipboard_impl( &mut cx.editor, line_ending.as_str(), @@ -3828,7 +3822,7 @@ fn yank_main_selection_to_clipboard(cx: &mut Context) { } fn yank_joined_to_primary_clipboard(cx: &mut Context) { - let line_ending = current!(cx.editor).1.line_ending; + let line_ending = doc!(cx.editor).line_ending; let _ = yank_joined_to_clipboard_impl( &mut cx.editor, line_ending.as_str(), @@ -4517,7 +4511,7 @@ fn match_brackets(cx: &mut Context) { fn jump_forward(cx: &mut Context) { let count = cx.count(); - let (view, _doc) = current!(cx.editor); + let view = view_mut!(cx.editor); if let Some((id, selection)) = view.jumps.forward(count) { view.doc = *id; From 2c1313c0648977540c395de584b4293c1909cbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 23:52:49 +0900 Subject: [PATCH 121/122] Specify vector capacity on surround_add --- helix-term/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f2a1e66d6..80cbd6d29 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4698,7 +4698,7 @@ fn surround_add(cx: &mut Context) { let selection = doc.selection(view.id); let (open, close) = surround::get_pair(ch); - let mut changes = Vec::new(); + let mut changes = Vec::with_capacity(selection.len() * 2); for range in selection.iter() { changes.push((range.from(), range.from(), Some(Tendril::from_char(open)))); changes.push((range.to(), range.to(), Some(Tendril::from_char(close)))); From f979bdc442ab3150a369ff8bee0703e90e32e2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 6 Nov 2021 23:57:42 +0900 Subject: [PATCH 122/122] Specify capacity on toggle_line_comments --- helix-core/src/comment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index 4072a5323..b22a95a65 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -63,7 +63,7 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st let token = token.unwrap_or("//"); let comment = Tendril::from(format!("{} ", token)); - let mut lines: Vec = Vec::new(); + let mut lines: Vec = Vec::with_capacity(selection.len()); let mut min_next_line = 0; for selection in selection {