From acced82be66c9a3920b8147bdd8fa6785388e8c8 Mon Sep 17 00:00:00 2001 From: ammkrn <46387933+ammkrn@users.noreply.github.com> Date: Sat, 6 Nov 2021 20:31:12 -0500 Subject: [PATCH 001/156] feat(book/src/languages.md) (#979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(book/src/languages.md) Add a section in the book about language-specific settings and the languages.toml file. * Update book/src/languages.md Co-authored-by: Gokul Soumya * feat(book/src/guides/adding_languages.md) Add book section on adding a new language to the compile-time/root languages.toml file. * Update book/src/guides/adding_languages.md Co-authored-by: Blaž Hrastnik * Update book/src/guides/adding_languages.md Co-authored-by: Blaž Hrastnik * refactor(revise book/src/languages.md) Change the book page on language settings to match suggestions by archseer and mention both toml files. Co-authored-by: Gokul Soumya Co-authored-by: Blaž Hrastnik --- book/src/SUMMARY.md | 2 ++ book/src/guides/adding_languages.md | 52 +++++++++++++++++++++++++++++ book/src/languages.md | 14 ++++++++ 3 files changed, 68 insertions(+) create mode 100644 book/src/guides/adding_languages.md create mode 100644 book/src/languages.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 56f50e212..8cadb663f 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -8,5 +8,7 @@ - [Keymap](./keymap.md) - [Key Remapping](./remapping.md) - [Hooks](./hooks.md) + - [Languages](./languages.md) - [Guides](./guides/README.md) + - [Adding Languages](./guides/adding_languages.md) - [Adding Textobject Queries](./guides/textobject.md) diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md new file mode 100644 index 000000000..85c3d0e85 --- /dev/null +++ b/book/src/guides/adding_languages.md @@ -0,0 +1,52 @@ +# Adding languages + +## Submodules + +To add a new langauge, you should first add a tree-sitter submodule. To do this, you can run the command +```sh +$ git submodule add -f helix-syntax/languages/tree-sitter- +``` +For example, to add tree-sitter-ocaml you would run +```sh +$ git submodule add -f https://github.com/tree-sitter/tree-sitter-ocaml helix-syntax/languages/tree-sitter-ocaml +``` +Make sure the submodule is shallow by doing +```sh +git config -f .gitmodules submodule.helix-syntax/languages/tree-sitter-.shallow true +``` + +or you can manually add `shallow = true` to `.gitmodules`. + +## languages.toml + +Next, you need to add the language to the `languages.toml` found in the root of the repository; this `languages.toml` file is included at compilation time, and is distinct from the `language.toml` file in the user's [configuration directory](../configuration.md). + +These are the available keys and descriptions for the file. + +| Key | Description | +| ---- | ----------- | +| name | The name of the language | +| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.` or `text.` in case of markup languages | +| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential language injection site. [link](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection) | +| file-types | The filetypes of the language, for example `["yml", "yaml"]` | +| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` | +| auto-format | Whether to autoformat this language when saving | +| comment-token | The token to use as a comment-token | +| indent | The indent to use. Has sub keys `tab-width` and `unit` | +| config | Language server configuration | + +## Queries + +For a language to have syntax-highlighting and indentation among other things, you have to add queries. Add a directory for your language with the path `runtime/queries//`. The tree-sitter [website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries) gives more info on how to write queries. + +## Common Issues + +- If you get errors when building after switching branches, you may have to remove or update tree-sitter submodules. You can update submodules by running +```sh +$ git submodule update --init +``` +- Make sure to not use the `--remote` flag. To remove submodules look inside the `.gitmodules` and remove directories that are not present inside of it. + +- If a parser is segfaulting or you want to remove the parser, make sure to remove the submodule *and* the compiled parser in `runtime/grammar/.so` + +- The indents query is `indents.toml`, *not* `indents.scm`. See [this](https://github.com/helix-editor/helix/issues/114) issue for more information. diff --git a/book/src/languages.md b/book/src/languages.md new file mode 100644 index 000000000..cef61501f --- /dev/null +++ b/book/src/languages.md @@ -0,0 +1,14 @@ +# Languages + +Language-specific settings and settings for particular language servers can be configured in a `languages.toml` file placed in your [configuration directory](./configuration.md). Helix actually uses two `languages.toml` files, the [first one](https://github.com/helix-editor/helix/blob/master/languages.toml) is in the main helix repository; it contains the default settings for each language and is included in the helix binary at compile time. Users who want to see the available settings and options can either reference the helix repo's `languages.toml` file, or consult the table in the [adding languages](./guides/adding_languages.md) section. + +Changes made to the `languages.toml` file in a user's [configuration directory](./configuration.md) are merged with helix's defaults on start-up, such that a user's settings will take precedence over defaults in the event of a collision. For example, the default `languages.toml` sets rust's `auto-format` to `true`. If a user wants to disable auto-format, they can change the `languages.toml` in their [configuration directory](./configuration.md) to make the rust entry read like the example below; the new key/value pair `auto-format = false` will override the default when the two sets of settings are merged on start-up: + +``` +# in /helix/languages.toml + +[[language]] +name = "rust" +auto-format = false +``` + From 29fe504398e483b8830564727ee8e2f209f06615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 7 Nov 2021 10:33:05 +0900 Subject: [PATCH 002/156] book: Mention git submodule sync too --- book/src/guides/adding_languages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md index 85c3d0e85..00902876d 100644 --- a/book/src/guides/adding_languages.md +++ b/book/src/guides/adding_languages.md @@ -43,7 +43,7 @@ For a language to have syntax-highlighting and indentation among other things, y - If you get errors when building after switching branches, you may have to remove or update tree-sitter submodules. You can update submodules by running ```sh -$ git submodule update --init +$ git submodule sync; git submodule update --init ``` - Make sure to not use the `--remote` flag. To remove submodules look inside the `.gitmodules` and remove directories that are not present inside of it. From 4304b52ff86f829115cb89b20160b4f80dbb3dbd Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sun, 7 Nov 2021 18:50:03 -0600 Subject: [PATCH 003/156] fix(core): stop merging array toml config values (#1004) --- helix-core/src/lib.rs | 55 +++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index d1720df00..6168d02c2 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -103,25 +103,7 @@ pub fn cache_dir() -> std::path::PathBuf { pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value { use toml::Value; - fn get_name(v: &Value) -> Option<&str> { - v.get("name").and_then(Value::as_str) - } - match (left, right) { - (Value::Array(mut left_items), Value::Array(right_items)) => { - left_items.reserve(right_items.len()); - for rvalue in right_items { - let lvalue = get_name(&rvalue) - .and_then(|rname| left_items.iter().position(|v| get_name(v) == Some(rname))) - .map(|lpos| left_items.remove(lpos)); - let mvalue = match lvalue { - Some(lvalue) => merge_toml_values(lvalue, rvalue), - None => rvalue, - }; - left_items.push(mvalue); - } - Value::Array(left_items) - } (Value::Table(mut left_map), Value::Table(right_map)) => { for (rname, rvalue) in right_map { match left_map.remove(&rname) { @@ -149,12 +131,13 @@ mod merge_toml_tests { fn language_tomls() { use toml::Value; - const USER: &str = " + const USER: &str = r#" [[language]] - name = \"nix\" - test = \"bbb\" - indent = { tab-width = 4, unit = \" \", test = \"aaa\" } - "; + name = "typescript" + test = "bbb" + indent = { tab-width = 4, unit = " ", test = "aaa" } + language-server = { command = "deno", args = ["lsp"] } + "#; let base: Value = toml::from_slice(include_bytes!("../../languages.toml")) .expect("Couldn't parse built-in langauges config"); @@ -162,23 +145,27 @@ mod merge_toml_tests { let merged = merge_toml_values(base, user); let languages = merged.get("language").unwrap().as_array().unwrap(); - let nix = languages + let ts = languages .iter() - .find(|v| v.get("name").unwrap().as_str().unwrap() == "nix") + .find(|v| v.get("name").unwrap().as_str().unwrap() == "typescript") .unwrap(); - let nix_indent = nix.get("indent").unwrap(); + let ts_indent = ts.get("indent").unwrap(); // We changed tab-width and unit in indent so check them if they are the new values + assert_eq!(ts_indent.get("tab-width").unwrap().as_integer().unwrap(), 4); + assert_eq!(ts_indent.get("unit").unwrap().as_str().unwrap(), " "); + // We added a new keys, so check them + assert_eq!(ts.get("test").unwrap().as_str().unwrap(), "bbb"); + assert_eq!(ts_indent.get("test").unwrap().as_str().unwrap(), "aaa"); assert_eq!( - nix_indent.get("tab-width").unwrap().as_integer().unwrap(), - 4 + ts.get("language-server") + .unwrap() + .get("args") + .unwrap() + .as_array() + .unwrap(), + &vec![Value::String("lsp".into())], ); - assert_eq!(nix_indent.get("unit").unwrap().as_str().unwrap(), " "); - // We added a new keys, so check them - assert_eq!(nix.get("test").unwrap().as_str().unwrap(), "bbb"); - assert_eq!(nix_indent.get("test").unwrap().as_str().unwrap(), "aaa"); - // We didn't change comment-token so it should be same - assert_eq!(nix.get("comment-token").unwrap().as_str().unwrap(), "#"); } } From e0e227d172697c0d3c418704fd20e780ee1a1032 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Mon, 8 Nov 2021 06:22:51 +0530 Subject: [PATCH 004/156] Touch up docs for adding new language (#1002) --- book/src/guides/adding_languages.md | 23 +++++++++++++++-------- book/src/guides/textobject.md | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md index 00902876d..c606f8fc6 100644 --- a/book/src/guides/adding_languages.md +++ b/book/src/guides/adding_languages.md @@ -2,13 +2,14 @@ ## Submodules -To add a new langauge, you should first add a tree-sitter submodule. To do this, you can run the command +To add a new langauge, you should first add a tree-sitter submodule. To do this, +you can run the command ```sh -$ git submodule add -f helix-syntax/languages/tree-sitter- +git submodule add -f helix-syntax/languages/tree-sitter- ``` For example, to add tree-sitter-ocaml you would run ```sh -$ git submodule add -f https://github.com/tree-sitter/tree-sitter-ocaml helix-syntax/languages/tree-sitter-ocaml +git submodule add -f https://github.com/tree-sitter/tree-sitter-ocaml helix-syntax/languages/tree-sitter-ocaml ``` Make sure the submodule is shallow by doing ```sh @@ -19,7 +20,10 @@ or you can manually add `shallow = true` to `.gitmodules`. ## languages.toml -Next, you need to add the language to the `languages.toml` found in the root of the repository; this `languages.toml` file is included at compilation time, and is distinct from the `language.toml` file in the user's [configuration directory](../configuration.md). +Next, you need to add the language to the [`languages.toml`][languages.toml] found in the root of +the repository; this `languages.toml` file is included at compilation time, and +is distinct from the `language.toml` file in the user's [configuration +directory](../configuration.md). These are the available keys and descriptions for the file. @@ -27,7 +31,7 @@ These are the available keys and descriptions for the file. | ---- | ----------- | | name | The name of the language | | scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.` or `text.` in case of markup languages | -| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential language injection site. [link](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection) | +| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. | | file-types | The filetypes of the language, for example `["yml", "yaml"]` | | roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` | | auto-format | Whether to autoformat this language when saving | @@ -42,11 +46,14 @@ For a language to have syntax-highlighting and indentation among other things, y ## Common Issues - If you get errors when building after switching branches, you may have to remove or update tree-sitter submodules. You can update submodules by running -```sh -$ git submodule sync; git submodule update --init -``` + ```sh + git submodule sync; git submodule update --init + ``` - Make sure to not use the `--remote` flag. To remove submodules look inside the `.gitmodules` and remove directories that are not present inside of it. - If a parser is segfaulting or you want to remove the parser, make sure to remove the submodule *and* the compiled parser in `runtime/grammar/.so` - The indents query is `indents.toml`, *not* `indents.scm`. See [this](https://github.com/helix-editor/helix/issues/114) issue for more information. + +[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection +[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml diff --git a/book/src/guides/textobject.md b/book/src/guides/textobject.md index 50b3b574a..dd726b7c9 100644 --- a/book/src/guides/textobject.md +++ b/book/src/guides/textobject.md @@ -5,7 +5,7 @@ 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). +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 From a252ecd8c85af5cc16638a4752011e2e920fa652 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sun, 7 Nov 2021 19:54:39 -0500 Subject: [PATCH 005/156] Add WORD textobject (#991) * Add WORD textobject * Document WORD textobject --- book/src/usage.md | 1 + helix-core/src/textobject.rs | 11 ++++++----- helix-term/src/commands.rs | 3 ++- helix-term/src/ui/prompt.rs | 1 + 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/book/src/usage.md b/book/src/usage.md index 71730fa8b..6b7cbc415 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -62,6 +62,7 @@ Currently supported: `word`, `surround`, `function`, `class`, `parameter`. | Key after `mi` or `ma` | Textobject selected | | --- | --- | | `w` | Word | +| `W` | WORD | | `(`, `[`, `'`, etc | Specified surround pairs | | `f` | Function | | `c` | Class | diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 975ed115b..24f063d44 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -10,7 +10,7 @@ use crate::surround; use crate::syntax::LanguageConfiguration; use crate::Range; -fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize { +fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction, long: bool) -> usize { use CharCategory::{Eol, Whitespace}; let iter = match direction { @@ -33,7 +33,7 @@ fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> match categorize_char(ch) { Eol | Whitespace => return pos, category => { - if category != prev_category && pos != 0 && pos != slice.len_chars() { + if !long && category != prev_category && pos != 0 && pos != slice.len_chars() { return pos; } else { match direction { @@ -70,13 +70,14 @@ pub fn textobject_word( range: Range, textobject: TextObject, _count: usize, + long: bool, ) -> Range { let pos = range.cursor(slice); - let word_start = find_word_boundary(slice, pos, Direction::Backward); + let word_start = find_word_boundary(slice, pos, Direction::Backward, long); let word_end = match slice.get_char(pos).map(categorize_char) { None | Some(CharCategory::Whitespace | CharCategory::Eol) => pos, - _ => find_word_boundary(slice, pos + 1, Direction::Forward), + _ => find_word_boundary(slice, pos + 1, Direction::Forward, long), }; // Special case. @@ -268,7 +269,7 @@ mod test { let slice = doc.slice(..); for &case in scenario { let (pos, objtype, expected_range) = case; - let result = textobject_word(slice, Range::point(pos), objtype, 1); + let result = textobject_word(slice, Range::point(pos), objtype, 1, false); assert_eq!( result, expected_range.into(), diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 80cbd6d29..5f091775e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4672,7 +4672,8 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { let selection = doc.selection(view.id).clone().transform(|range| { match ch { - 'w' => textobject::textobject_word(text, range, objtype, count), + 'w' => textobject::textobject_word(text, range, objtype, count, false), + 'W' => textobject::textobject_word(text, range, objtype, count, true), 'c' => textobject_treesitter("class", range), 'f' => textobject_treesitter("function", range), 'p' => textobject_treesitter("parameter", range), diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index c999ba140..29ca18b11 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -491,6 +491,7 @@ impl Component for Prompt { doc.selection(view.id).primary(), textobject::TextObject::Inside, 1, + false, ); let line = text.slice(range.from()..range.to()).to_string(); if !line.is_empty() { From 1e793c2bbfb4e6d3226f4ba97d99a70934b5cf5c Mon Sep 17 00:00:00 2001 From: Daniel S Poulin Date: Sun, 7 Nov 2021 19:57:26 -0500 Subject: [PATCH 006/156] Adds single and double quotes to matching pairs (#995) This enables `mm` to work on quote characters as well as highlighting of matching quote when on it. --- helix-core/src/match_brackets.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs index a4b2fb9c8..136ce320d 100644 --- a/helix-core/src/match_brackets.rs +++ b/helix-core/src/match_brackets.rs @@ -1,6 +1,13 @@ use crate::{Rope, Syntax}; -const PAIRS: &[(char, char)] = &[('(', ')'), ('{', '}'), ('[', ']'), ('<', '>')]; +const PAIRS: &[(char, char)] = &[ + ('(', ')'), + ('{', '}'), + ('[', ']'), + ('<', '>'), + ('\'', '\''), + ('"', '"'), +]; // limit matching pairs to only ( ) { } [ ] < > #[must_use] From 4010b327e2dbb96dc0ac2a5ae41913486c577bec Mon Sep 17 00:00:00 2001 From: jgart <47760695+jgarte@users.noreply.github.com> Date: Mon, 8 Nov 2021 01:01:55 +0000 Subject: [PATCH 007/156] Adds mint language server (#974) --- languages.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/languages.toml b/languages.toml index bd510ea1c..826fd93f6 100644 --- a/languages.toml +++ b/languages.toml @@ -43,6 +43,17 @@ comment-token = "#" language-server = { command = "elixir-ls" } indent = { tab-width = 2, unit = " " } +[[language]] +name = "mint" +scope = "source.mint" +injection-regex = "mint" +file-types = ["mint"] +roots = [] +comment-token = "//" + +language-server = { command = "mint", args = ["ls"] } +indent = { tab-width = 2, unit = " " } + [[language]] name = "json" scope = "source.json" From 82ff5b0ab6fd620a23744ffa514ce7a82900ca02 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 008/156] 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 { From cdc2107bca42f5affa6896771a26f09dc55beeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 8 Nov 2021 10:03:53 +0900 Subject: [PATCH 009/156] fix: #896 broke some of the default highlights --- theme.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme.toml b/theme.toml index 3956e25e6..8c0d1f6ca 100644 --- a/theme.toml +++ b/theme.toml @@ -23,8 +23,8 @@ comment = "sirocco" constant = "white" "constant.builtin" = "white" string = "silver" -number = "chamois" -escape = "honey" +"constant.numeric" = "chamois" +"constant.character.escape" = "honey" # used for lifetimes label = "honey" From 30744646cb0fab64a87f20c98bff033007512323 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Mon, 8 Nov 2021 02:05:12 +0100 Subject: [PATCH 010/156] Perl support (#978) --- .gitmodules | 4 + helix-syntax/languages/tree-sitter-perl | 1 + languages.toml | 8 ++ runtime/queries/perl/highlights.scm | 181 ++++++++++++++++++++++++ runtime/queries/perl/textobjects.scm | 8 ++ 5 files changed, 202 insertions(+) create mode 160000 helix-syntax/languages/tree-sitter-perl create mode 100644 runtime/queries/perl/highlights.scm create mode 100644 runtime/queries/perl/textobjects.scm diff --git a/.gitmodules b/.gitmodules index 7ed34ad39..5c6f22cd8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -134,3 +134,7 @@ path = helix-syntax/languages/tree-sitter-cmake url = https://github.com/uyha/tree-sitter-cmake shallow = true +[submodule "helix-syntax/languages/tree-sitter-perl"] + path = helix-syntax/languages/tree-sitter-perl + url = https://github.com/ganezdragon/tree-sitter-perl + shallow = true diff --git a/helix-syntax/languages/tree-sitter-perl b/helix-syntax/languages/tree-sitter-perl new file mode 160000 index 000000000..0ac2c6da5 --- /dev/null +++ b/helix-syntax/languages/tree-sitter-perl @@ -0,0 +1 @@ +Subproject commit 0ac2c6da562c7a2c26ed7e8691d4a590f7e8b90a diff --git a/languages.toml b/languages.toml index 826fd93f6..988921718 100644 --- a/languages.toml +++ b/languages.toml @@ -366,3 +366,11 @@ roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } language-server = { command = "cmake-language-server" } + +[[language]] +name = "perl" +scope = "source.perl" +file-types = ["pl", "pm"] +roots = [] +comment-token = "#" +indent = { tab-width = 2, unit = " " } diff --git a/runtime/queries/perl/highlights.scm b/runtime/queries/perl/highlights.scm new file mode 100644 index 000000000..b6f042917 --- /dev/null +++ b/runtime/queries/perl/highlights.scm @@ -0,0 +1,181 @@ +; Variables +(variable_declaration + . + (scope) @keyword) +[ +(single_var_declaration) +(scalar_variable) +(array_variable) +(hash_variable) +(hash_variable) +] @variable + + +[ +(package_name) +(special_scalar_variable) +(special_array_variable) +(special_hash_variable) +(special_literal) +(super) +] @constant + +( + [ + (package_name) + (super) + ] + . + ("::" @operator) +) + +(comments) @comment +(pod_statement) @comment.block.documentation + +[ +(use_no_statement) +(use_no_feature_statement) +(use_no_if_statement) +(use_no_version) +(use_constant_statement) +(use_parent_statement) +] @keyword + +(use_constant_statement + constant: (identifier) @constant) + +[ +"require" +] @keyword + +(method_invocation + . + (identifier) @variable) + +(method_invocation + (arrow_operator) + . + (identifier) @function) +(method_invocation + function_name: (identifier) @function) +(named_block_statement + function_name: (identifier) @function) + +(call_expression + function_name: (identifier) @function) +(function_definition + name: (identifier) @function) +[ +(function) +(map) +(grep) +(bless) +] @function + +[ +"return" +"sub" +"package" +"BEGIN" +"END" +] @keyword.function + +[ +"(" +")" +"[" +"]" +"{" +"}" +] @punctuation.bracket +(standard_input_to_variable) @punctuation.bracket + +[ +"=~" +"or" +"=" +"==" +"+" +"-" +"." +"//" +"||" +(arrow_operator) +(hash_arrow_operator) +(array_dereference) +(hash_dereference) +(to_reference) +(type_glob) +(hash_access_variable) +(ternary_expression) +(ternary_expression_in_hash) +] @operator + +[ +(regex_option) +(regex_option_for_substitution) +(regex_option_for_transliteration) +] @variable.parameter + +(type_glob + (identifier) @variable) +( + (scalar_variable) + . + ("->" @operator)) + +[ +(word_list_qw) +(command_qx_quoted) +(string_single_quoted) +(string_double_quoted) +(string_qq_quoted) +(bareword) +(transliteration_tr_or_y) +] @string + +[ +(regex_pattern_qr) +(patter_matcher_m) +(substitution_pattern_s) +] @string.regexp + +(escape_sequence) @string.special + +[ +"," +(semi_colon) +(start_delimiter) +(end_delimiter) +(ellipsis_statement) +] @punctuation.delimiter + +[ +(integer) +(floating_point) +(scientific_notation) +(hexadecimal) +] @constant.numeric + +[ +; (if_statement) +(unless_statement) +(if_simple_statement) +(unless_simple_statement) +] @keyword.control.conditional + +[ +"if" +"elsif" +"else" +] @keyword.control.conditional + +(foreach_statement) @keyword.control.repeat +(foreach_statement + . + (scope) @keyword) + +(function_attribute) @label + +(function_signature) @type + diff --git a/runtime/queries/perl/textobjects.scm b/runtime/queries/perl/textobjects.scm new file mode 100644 index 000000000..988e22b49 --- /dev/null +++ b/runtime/queries/perl/textobjects.scm @@ -0,0 +1,8 @@ +(function_definition + (identifier) (_) @function.inside) @function.around + +(anonymous_function + (_) @function.inside) @function.around + +(argument + (_) @parameter.inside) From bf4c70e02739601b7657c69a3876f9d1090a42c0 Mon Sep 17 00:00:00 2001 From: LollipopFt <62802897+LollipopFt@users.noreply.github.com> Date: Mon, 8 Nov 2021 23:14:03 +0800 Subject: [PATCH 011/156] added Down keymapping. (#1019) --- book/src/remapping.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book/src/remapping.md b/book/src/remapping.md index 3369f0315..5ca6cd1b8 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -38,6 +38,7 @@ Control, Shift and Alt modifiers are encoded respectively with the prefixes | Left | `"left"` | | Right | `"right"` | | Up | `"up"` | +| Down | `"down"` | | Home | `"home"` | | End | `"end"` | | Page | `"pageup"` | From 29e684941389f949491efe4d9807a0edd1facee3 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Mon, 8 Nov 2021 23:17:54 +0800 Subject: [PATCH 012/156] Add LSP rename_symbol (space-r) (#1011) improve apply_workspace_edit --- helix-lsp/src/client.rs | 27 ++++++ helix-term/src/commands.rs | 188 ++++++++++++++++++++++++++++++------- helix-term/src/keymap.rs | 1 + helix-view/src/editor.rs | 6 ++ 4 files changed, 187 insertions(+), 35 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index b810feef3..271fd9d59 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -257,6 +257,12 @@ impl Client { content_format: Some(vec![lsp::MarkupKind::Markdown]), ..Default::default() }), + rename: Some(lsp::RenameClientCapabilities { + dynamic_registration: Some(false), + prepare_support: Some(false), + prepare_support_default_behavior: None, + honors_change_annotations: Some(false), + }), code_action: Some(lsp::CodeActionClientCapabilities { code_action_literal_support: Some(lsp::CodeActionLiteralSupport { code_action_kind: lsp::CodeActionKindLiteralSupport { @@ -773,4 +779,25 @@ impl Client { self.call::(params) } + + pub async fn rename_symbol( + &self, + text_document: lsp::TextDocumentIdentifier, + position: lsp::Position, + new_name: String, + ) -> anyhow::Result { + let params = lsp::RenameParams { + text_document_position: lsp::TextDocumentPositionParams { + text_document, + position, + }, + new_name, + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + let response = self.request::(params).await?; + Ok(response.unwrap_or_default()) + } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 5f091775e..245fbe4ee 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -23,7 +23,7 @@ use helix_view::{ use anyhow::{anyhow, bail, Context as _}; use helix_lsp::{ - lsp, + block_on, lsp, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, OffsetEncoding, }; @@ -339,6 +339,7 @@ impl Command { shell_append_output, "Append output of shell command after each selection", shell_keep_pipe, "Filter selections with shell predicate", suspend, "Suspend", + rename_symbol, "Rename symbol", ); } @@ -2749,14 +2750,104 @@ pub fn code_action(cx: &mut Context) { ) } +pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { + use lsp::ResourceOp; + use std::fs; + match op { + ResourceOp::Create(op) => { + let path = op.uri.to_file_path().unwrap(); + let ignore_if_exists = if let Some(options) = &op.options { + !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) + } else { + false + }; + if ignore_if_exists && path.exists() { + Ok(()) + } else { + fs::write(&path, []) + } + } + ResourceOp::Delete(op) => { + let path = op.uri.to_file_path().unwrap(); + if path.is_dir() { + let recursive = if let Some(options) = &op.options { + options.recursive.unwrap_or(false) + } else { + false + }; + if recursive { + fs::remove_dir_all(&path) + } else { + fs::remove_dir(&path) + } + } else if path.is_file() { + fs::remove_file(&path) + } else { + Ok(()) + } + } + ResourceOp::Rename(op) => { + let from = op.old_uri.to_file_path().unwrap(); + let to = op.new_uri.to_file_path().unwrap(); + let ignore_if_exists = if let Some(options) = &op.options { + !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) + } else { + false + }; + if ignore_if_exists && to.exists() { + Ok(()) + } else { + fs::rename(&from, &to) + } + } + } +} + fn apply_workspace_edit( editor: &mut Editor, offset_encoding: OffsetEncoding, workspace_edit: &lsp::WorkspaceEdit, ) { + let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec| { + let path = uri + .to_file_path() + .expect("unable to convert URI to filepath"); + + let current_view_id = view!(editor).id; + let doc_id = editor.open(path, Action::Load).unwrap(); + let doc = editor + .document_mut(doc_id) + .expect("Document for document_changes not found"); + + // Need to determine a view for apply/append_changes_to_history + let selections = doc.selections(); + let view_id = if selections.contains_key(¤t_view_id) { + // use current if possible + current_view_id + } else { + // Hack: we take the first available view_id + selections + .keys() + .next() + .copied() + .expect("No view_id available") + }; + + let transaction = helix_lsp::util::generate_transaction_from_edits( + doc.text(), + text_edits, + offset_encoding, + ); + doc.apply(&transaction, view_id); + doc.append_changes_to_history(view_id); + }; + if let Some(ref changes) = workspace_edit.changes { log::debug!("workspace changes: {:?}", changes); - editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183")); + for (uri, text_edits) in changes { + let text_edits = text_edits.to_vec(); + apply_edits(uri, text_edits); + } return; // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used // TODO: find some example that uses workspace changes, and test it @@ -2774,30 +2865,6 @@ fn apply_workspace_edit( match document_changes { lsp::DocumentChanges::Edits(document_edits) => { for document_edit in document_edits { - let path = document_edit - .text_document - .uri - .to_file_path() - .expect("unable to convert URI to filepath"); - let current_view_id = view!(editor).id; - let doc = editor - .document_by_path_mut(path) - .expect("Document for document_changes not found"); - - // Need to determine a view for apply/append_changes_to_history - let selections = doc.selections(); - let view_id = if selections.contains_key(¤t_view_id) { - // use current if possible - current_view_id - } else { - // Hack: we take the first available view_id - selections - .keys() - .next() - .copied() - .expect("No view_id available") - }; - let edits = document_edit .edits .iter() @@ -2809,19 +2876,33 @@ fn apply_workspace_edit( }) .cloned() .collect(); - - let transaction = helix_lsp::util::generate_transaction_from_edits( - doc.text(), - edits, - offset_encoding, - ); - doc.apply(&transaction, view_id); - doc.append_changes_to_history(view_id); + apply_edits(&document_edit.text_document.uri, edits); } } lsp::DocumentChanges::Operations(operations) => { log::debug!("document changes - operations: {:?}", operations); - editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183")); + for operateion in operations { + match operateion { + lsp::DocumentChangeOperation::Op(op) => { + apply_document_resource_op(op).unwrap(); + } + + lsp::DocumentChangeOperation::Edit(document_edit) => { + let edits = document_edit + .edits + .iter() + .map(|edit| match edit { + lsp::OneOf::Left(text_edit) => text_edit, + lsp::OneOf::Right(annotated_text_edit) => { + &annotated_text_edit.text_edit + } + }) + .cloned() + .collect(); + apply_edits(&document_edit.text_document.uri, edits); + } + } + } } } } @@ -4982,3 +5063,40 @@ fn add_newline_impl(cx: &mut Context, open: Open) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); } + +fn rename_symbol(cx: &mut Context) { + let prompt = Prompt::new( + "Rename to: ".into(), + None, + |_input: &str| Vec::new(), + move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { + if event != PromptEvent::Validate { + return; + } + + log::debug!("renaming to: {:?}", input); + + let (view, doc) = current!(cx.editor); + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + let offset_encoding = language_server.offset_encoding(); + + let pos = pos_to_lsp_pos( + doc.text(), + doc.selection(view.id) + .primary() + .cursor(doc.text().slice(..)), + offset_encoding, + ); + + let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string()); + let edits = block_on(task).unwrap_or_default(); + log::debug!("Edits from LSP: {:?}", edits); + apply_workspace_edit(&mut cx.editor, offset_encoding, &edits); + }, + ); + cx.push_layer(Box::new(prompt)); +} diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index ce50f0ab3..d497401f9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -584,6 +584,7 @@ impl Default for Keymaps { "R" => replace_selections_with_clipboard, "/" => global_search, "k" => hover, + "r" => rename_symbol, }, "z" => { "View" "z" | "c" => align_view_center, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 17cd3d7b5..631dcf0c6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -287,6 +287,12 @@ impl Editor { return; } Action::Load => { + let view_id = view!(self).id; + if let Some(doc) = self.document_mut(id) { + if doc.selections().is_empty() { + doc.selections.insert(view_id, Selection::point(0)); + } + } return; } Action::HorizontalSplit => { From 77dbbc73f9c9b6599bc39b18625285685fe2e4b1 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Mon, 8 Nov 2021 16:19:44 +0100 Subject: [PATCH 013/156] Detect filetype from shebang line (#1001) --- book/src/guides/adding_languages.md | 3 ++- helix-core/src/indent.rs | 1 + helix-core/src/syntax.rs | 24 ++++++++++++++++++++ helix-view/src/document.rs | 4 +++- languages.toml | 35 +++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md index c606f8fc6..446eb479d 100644 --- a/book/src/guides/adding_languages.md +++ b/book/src/guides/adding_languages.md @@ -33,10 +33,11 @@ These are the available keys and descriptions for the file. | scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.` or `text.` in case of markup languages | | injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. | | file-types | The filetypes of the language, for example `["yml", "yaml"]` | +| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` | | roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` | | auto-format | Whether to autoformat this language when saving | | comment-token | The token to use as a comment-token | -| indent | The indent to use. Has sub keys `tab-width` and `unit` | +| indent | The indent to use. Has sub keys `tab-width` and `unit` | | config | Language server configuration | ## Queries diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 20f034ea7..b6f5081ac 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -450,6 +450,7 @@ where language: vec![LanguageConfiguration { scope: "source.rust".to_string(), file_types: vec!["rs".to_string()], + shebangs: vec![], language_id: "Rust".to_string(), highlight_config: OnceCell::new(), config: None, diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index f3e3f238b..84952248a 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -14,6 +14,8 @@ use std::{ cell::RefCell, collections::{HashMap, HashSet}, fmt, + fs::File, + io::Read, path::Path, sync::Arc, }; @@ -52,6 +54,7 @@ pub struct LanguageConfiguration { pub language_id: String, pub scope: String, // source.rust pub file_types: Vec, // filename ends_with? + pub shebangs: Vec, // interpreter(s) associated with language pub roots: Vec, // these indicate project roots <.git, Cargo.toml> pub comment_token: Option, @@ -254,6 +257,7 @@ pub struct Loader { // highlight_names ? language_configs: Vec>, language_config_ids_by_file_type: HashMap, // Vec + language_config_ids_by_shebang: HashMap, } impl Loader { @@ -261,6 +265,7 @@ impl Loader { let mut loader = Self { language_configs: Vec::new(), language_config_ids_by_file_type: HashMap::new(), + language_config_ids_by_shebang: HashMap::new(), }; for config in config.language { @@ -273,6 +278,11 @@ impl Loader { .language_config_ids_by_file_type .insert(file_type.clone(), language_id); } + for shebang in &config.shebangs { + loader + .language_config_ids_by_shebang + .insert(shebang.clone(), language_id); + } loader.language_configs.push(Arc::new(config)); } @@ -298,6 +308,20 @@ impl Loader { // TODO: content_regex handling conflict resolution } + pub fn language_config_for_shebang(&self, path: &Path) -> Option> { + // Read the first 128 bytes of the file. If its a shebang line, try to find the language + let file = File::open(path).ok()?; + let mut buf = String::with_capacity(128); + file.take(128).read_to_string(&mut buf).ok()?; + static SHEBANG_REGEX: Lazy = + Lazy::new(|| Regex::new(r"^#!\s*(?:\S*[/\\](?:env\s+)?)?([^\s\.\d]+)").unwrap()); + let configuration_id = SHEBANG_REGEX + .captures(&buf) + .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1])); + + configuration_id.and_then(|&id| self.language_configs.get(id).cloned()) + } + pub fn language_config_for_scope(&self, scope: &str) -> Option> { self.language_configs .iter() diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index ce5df8ee8..a68ab7595 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -494,7 +494,9 @@ impl Document { /// Detect the programming language based on the file type. pub fn detect_language(&mut self, theme: Option<&Theme>, config_loader: &syntax::Loader) { if let Some(path) = &self.path { - let language_config = config_loader.language_config_for_file_name(path); + let language_config = config_loader + .language_config_for_file_name(path) + .or_else(|| config_loader.language_config_for_shebang(path)); self.set_language(theme, language_config); } } diff --git a/languages.toml b/languages.toml index 988921718..067138e4c 100644 --- a/languages.toml +++ b/languages.toml @@ -3,6 +3,7 @@ name = "rust" scope = "source.rust" injection-regex = "rust" file-types = ["rs"] +shebangs = [] roots = [] auto-format = true comment-token = "//" @@ -17,6 +18,7 @@ name = "toml" scope = "source.toml" injection-regex = "toml" file-types = ["toml"] +shebangs = [] roots = [] comment-token = "#" @@ -27,6 +29,7 @@ name = "protobuf" scope = "source.proto" injection-regex = "protobuf" file-types = ["proto"] +shebangs = [] roots = [] comment-token = "//" @@ -37,6 +40,7 @@ name = "elixir" scope = "source.elixir" injection-regex = "elixir" file-types = ["ex", "exs"] +shebangs = [] roots = [] comment-token = "#" @@ -48,6 +52,7 @@ name = "mint" scope = "source.mint" injection-regex = "mint" file-types = ["mint"] +shebangs = [] roots = [] comment-token = "//" @@ -59,6 +64,7 @@ name = "json" scope = "source.json" injection-regex = "json" file-types = ["json"] +shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -68,6 +74,7 @@ name = "c" scope = "source.c" injection-regex = "c" file-types = ["c"] # TODO: ["h"] +shebangs = [] roots = [] comment-token = "//" @@ -79,6 +86,7 @@ name = "cpp" scope = "source.cpp" injection-regex = "cpp" file-types = ["cc", "hh", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino"] +shebangs = [] roots = [] comment-token = "//" @@ -90,6 +98,7 @@ name = "c-sharp" scope = "source.csharp" injection-regex = "c-?sharp" file-types = ["cs"] +shebangs = [] roots = [] comment-token = "//" @@ -100,6 +109,7 @@ name = "go" scope = "source.go" injection-regex = "go" file-types = ["go"] +shebangs = [] roots = ["Gopkg.toml", "go.mod"] auto-format = true comment-token = "//" @@ -113,6 +123,7 @@ name = "javascript" scope = "source.js" injection-regex = "^(js|javascript)$" file-types = ["js", "mjs"] +shebangs = [] roots = [] comment-token = "//" # TODO: highlights-jsx, highlights-params @@ -124,6 +135,7 @@ name = "typescript" scope = "source.ts" injection-regex = "^(ts|typescript)$" file-types = ["ts"] +shebangs = [] roots = [] # TODO: highlights-jsx, highlights-params @@ -135,6 +147,7 @@ name = "tsx" scope = "source.tsx" injection-regex = "^(tsx)$" # |typescript file-types = ["tsx"] +shebangs = [] roots = [] # TODO: highlights-jsx, highlights-params @@ -146,6 +159,7 @@ name = "css" scope = "source.css" injection-regex = "css" file-types = ["css"] +shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -155,6 +169,7 @@ name = "html" scope = "text.html.basic" injection-regex = "html" file-types = ["html"] +shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -164,6 +179,7 @@ name = "python" scope = "source.python" injection-regex = "python" file-types = ["py"] +shebangs = ["python"] roots = [] comment-token = "#" @@ -176,6 +192,7 @@ name = "nix" scope = "source.nix" injection-regex = "nix" file-types = ["nix"] +shebangs = [] roots = [] comment-token = "#" @@ -187,6 +204,7 @@ name = "ruby" scope = "source.ruby" injection-regex = "ruby" file-types = ["rb"] +shebangs = ["ruby"] roots = [] comment-token = "#" @@ -198,6 +216,7 @@ name = "bash" scope = "source.bash" injection-regex = "bash" file-types = ["sh", "bash"] +shebangs = ["sh", "bash", "dash"] roots = [] comment-token = "#" @@ -209,6 +228,7 @@ name = "php" scope = "source.php" injection-regex = "php" file-types = ["php"] +shebangs = ["php"] roots = [] indent = { tab-width = 4, unit = " " } @@ -218,6 +238,7 @@ name = "latex" scope = "source.tex" injection-regex = "tex" file-types = ["tex"] +shebangs = [] roots = [] comment-token = "%" @@ -228,6 +249,7 @@ name = "julia" scope = "source.julia" injection-regex = "julia" file-types = ["jl"] +shebangs = [] roots = [] comment-token = "#" language-server = { command = "julia", args = [ @@ -253,6 +275,7 @@ name = "java" scope = "source.java" injection-regex = "java" file-types = ["java"] +shebangs = [] roots = [] indent = { tab-width = 4, unit = " " } @@ -261,6 +284,7 @@ name = "ledger" scope = "source.ledger" injection-regex = "ledger" file-types = ["ldg", "ledger", "journal"] +shebangs = [] roots = [] comment-token = ";" indent = { tab-width = 4, unit = " " } @@ -270,6 +294,7 @@ name = "ocaml" scope = "source.ocaml" injection-regex = "ocaml" file-types = ["ml"] +shebangs = [] roots = [] comment-token = "(**)" indent = { tab-width = 2, unit = " " } @@ -278,6 +303,7 @@ indent = { tab-width = 2, unit = " " } name = "ocaml-interface" scope = "source.ocaml.interface" file-types = ["mli"] +shebangs = [] roots = [] comment-token = "(**)" indent = { tab-width = 2, unit = " "} @@ -286,6 +312,7 @@ indent = { tab-width = 2, unit = " "} name = "lua" scope = "source.lua" file-types = ["lua"] +shebangs = [] roots = [] comment-token = "--" indent = { tab-width = 2, unit = " " } @@ -295,6 +322,7 @@ name = "svelte" scope = "source.svelte" injection-regex = "svelte" file-types = ["svelte"] +shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } language-server = { command = "svelteserver", args = ["--stdio"] } @@ -305,6 +333,7 @@ name = "vue" scope = "source.vue" injection-regex = "vue" file-types = ["vue"] +shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -312,6 +341,7 @@ indent = { tab-width = 2, unit = " " } name = "yaml" scope = "source.yaml" file-types = ["yml", "yaml"] +shebangs = [] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } @@ -331,6 +361,7 @@ name = "zig" scope = "source.zig" injection-regex = "zig" file-types = ["zig"] +shebangs = [] roots = ["build.zig"] auto-format = true comment-token = "//" @@ -343,6 +374,7 @@ name = "prolog" scope = "source.prolog" roots = [] file-types = ["pl", "prolog"] +shebangs = ["swipl"] comment-token = "%" language-server = { command = "swipl", args = [ @@ -354,6 +386,7 @@ language-server = { command = "swipl", args = [ name = "tsq" scope = "source.tsq" file-types = ["scm"] +shebangs = [] roots = [] comment-token = ";" indent = { tab-width = 2, unit = " " } @@ -362,6 +395,7 @@ indent = { tab-width = 2, unit = " " } name = "cmake" scope = "source.cmake" file-types = ["cmake", "CMakeLists.txt"] +shebangs = [] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } @@ -371,6 +405,7 @@ language-server = { command = "cmake-language-server" } name = "perl" scope = "source.perl" file-types = ["pl", "pm"] +shebangs = ["perl"] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } From 549cdee56159bed4266990ae66591dd6299293d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 9 Nov 2021 00:30:34 +0900 Subject: [PATCH 014/156] Refactor shebang detection to reuse the loaded buffer --- helix-core/src/syntax.rs | 11 +++-------- helix-view/src/document.rs | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 84952248a..0164092d2 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -14,8 +14,6 @@ use std::{ cell::RefCell, collections::{HashMap, HashSet}, fmt, - fs::File, - io::Read, path::Path, sync::Arc, }; @@ -308,15 +306,12 @@ impl Loader { // TODO: content_regex handling conflict resolution } - pub fn language_config_for_shebang(&self, path: &Path) -> Option> { - // Read the first 128 bytes of the file. If its a shebang line, try to find the language - let file = File::open(path).ok()?; - let mut buf = String::with_capacity(128); - file.take(128).read_to_string(&mut buf).ok()?; + pub fn language_config_for_shebang(&self, source: &Rope) -> Option> { + let line = Cow::from(source.line(0)); static SHEBANG_REGEX: Lazy = Lazy::new(|| Regex::new(r"^#!\s*(?:\S*[/\\](?:env\s+)?)?([^\s\.\d]+)").unwrap()); let configuration_id = SHEBANG_REGEX - .captures(&buf) + .captures(&line) .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1])); configuration_id.and_then(|&id| self.language_configs.get(id).cloned()) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a68ab7595..351ad05a2 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -496,7 +496,7 @@ impl Document { if let Some(path) = &self.path { let language_config = config_loader .language_config_for_file_name(path) - .or_else(|| config_loader.language_config_for_shebang(path)); + .or_else(|| config_loader.language_config_for_shebang(self.text())); self.set_language(theme, language_config); } } From 3f0345ff5814cea1d733ec2c5d846db66de29af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=2E=20Szczygie=C5=82?= Date: Mon, 8 Nov 2021 16:48:00 +0100 Subject: [PATCH 015/156] glsl support (#993) * add glsl language support * glsl: use indents.toml file --- .gitmodules | 4 +++ helix-syntax/languages/tree-sitter-glsl | 1 + languages.toml | 8 ++++++ runtime/queries/glsl/folds.scm | 1 + runtime/queries/glsl/highlights.scm | 37 +++++++++++++++++++++++++ runtime/queries/glsl/indents.toml | 19 +++++++++++++ runtime/queries/glsl/injections.scm | 3 ++ runtime/queries/glsl/locals.scm | 1 + 8 files changed, 74 insertions(+) create mode 160000 helix-syntax/languages/tree-sitter-glsl create mode 100644 runtime/queries/glsl/folds.scm create mode 100644 runtime/queries/glsl/highlights.scm create mode 100644 runtime/queries/glsl/indents.toml create mode 100644 runtime/queries/glsl/injections.scm create mode 100644 runtime/queries/glsl/locals.scm diff --git a/.gitmodules b/.gitmodules index 5c6f22cd8..bf596bdc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -134,6 +134,10 @@ path = helix-syntax/languages/tree-sitter-cmake url = https://github.com/uyha/tree-sitter-cmake shallow = true +[submodule "helix-syntax/languages/tree-sitter-glsl"] + path = helix-syntax/languages/tree-sitter-glsl + url = https://github.com/theHamsta/tree-sitter-glsl.git + shallow = true [submodule "helix-syntax/languages/tree-sitter-perl"] path = helix-syntax/languages/tree-sitter-perl url = https://github.com/ganezdragon/tree-sitter-perl diff --git a/helix-syntax/languages/tree-sitter-glsl b/helix-syntax/languages/tree-sitter-glsl new file mode 160000 index 000000000..88408ffc5 --- /dev/null +++ b/helix-syntax/languages/tree-sitter-glsl @@ -0,0 +1 @@ +Subproject commit 88408ffc5e27abcffced7010fc77396ae3636d7e diff --git a/languages.toml b/languages.toml index 067138e4c..45fc2ab26 100644 --- a/languages.toml +++ b/languages.toml @@ -401,6 +401,14 @@ comment-token = "#" indent = { tab-width = 2, unit = " " } language-server = { command = "cmake-language-server" } +[[language]] +name = "glsl" +scope = "source.glsl" +file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ] +roots = [] +comment-token = "//" +indent = { tab-width = 4, unit = " " } + [[language]] name = "perl" scope = "source.perl" diff --git a/runtime/queries/glsl/folds.scm b/runtime/queries/glsl/folds.scm new file mode 100644 index 000000000..a5a5208ca --- /dev/null +++ b/runtime/queries/glsl/folds.scm @@ -0,0 +1 @@ +; inherits: c diff --git a/runtime/queries/glsl/highlights.scm b/runtime/queries/glsl/highlights.scm new file mode 100644 index 000000000..af2a049fb --- /dev/null +++ b/runtime/queries/glsl/highlights.scm @@ -0,0 +1,37 @@ +; inherits: c + +[ + "in" + "out" + "inout" + "uniform" + "shared" + "layout" + "attribute" + "varying" + "buffer" + "coherent" + "readonly" + "writeonly" + "precision" + "highp" + "mediump" + "lowp" + "centroid" + "sample" + "patch" + "smooth" + "flat" + "noperspective" + "invariant" + "precise" +] @keyword + +"subroutine" @keyword.function + +(extension_storage_class) @attribute + +( + (identifier) @variable.builtin + (#match? @variable.builtin "^gl_") +) diff --git a/runtime/queries/glsl/indents.toml b/runtime/queries/glsl/indents.toml new file mode 100644 index 000000000..a7fd499a9 --- /dev/null +++ b/runtime/queries/glsl/indents.toml @@ -0,0 +1,19 @@ +indent = [ + "init_declarator", + "compound_statement", + "preproc_arg", + "field_declaration_list", + "case_statement", + "conditional_expression", + "enumerator_list", + "struct_specifier", + "compound_literal_expression" +] + +outdent = [ + "#define", + "#ifdef", + "#endif", + "{", + "}" +] diff --git a/runtime/queries/glsl/injections.scm b/runtime/queries/glsl/injections.scm new file mode 100644 index 000000000..7d3323b16 --- /dev/null +++ b/runtime/queries/glsl/injections.scm @@ -0,0 +1,3 @@ +(preproc_arg) @glsl + +(comment) @comment diff --git a/runtime/queries/glsl/locals.scm b/runtime/queries/glsl/locals.scm new file mode 100644 index 000000000..a5a5208ca --- /dev/null +++ b/runtime/queries/glsl/locals.scm @@ -0,0 +1 @@ +; inherits: c From 41fc3263256615219d2930398817b961d9a54512 Mon Sep 17 00:00:00 2001 From: Curiosidad-Racional Date: Tue, 9 Nov 2021 02:49:43 +0100 Subject: [PATCH 016/156] Fix panicked missing field `shebangs` (#1025) Fix the error: ``` thread 'main' panicked at 'Could not parse merged (built-in + user) languages.toml: Error { inner: ErrorInner { kind: Custom, line: None, col: 0, at: None, message: "missing field `shebangs`", key: ["language"] } }', helix-term/src/application.rs:87:14 ``` --- languages.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/languages.toml b/languages.toml index 45fc2ab26..934def44e 100644 --- a/languages.toml +++ b/languages.toml @@ -405,6 +405,7 @@ language-server = { command = "cmake-language-server" } name = "glsl" scope = "source.glsl" file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ] +shebangs = [] roots = [] comment-token = "//" indent = { tab-width = 4, unit = " " } From f804ed3192600fcfd704f3613edde37bbba26050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 9 Nov 2021 10:57:08 +0900 Subject: [PATCH 017/156] Make shebangs optional, they don't make sense outside of scripts --- helix-core/src/syntax.rs | 3 ++- languages.toml | 28 +++------------------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 0164092d2..a5dd0c594 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -52,7 +52,8 @@ pub struct LanguageConfiguration { pub language_id: String, pub scope: String, // source.rust pub file_types: Vec, // filename ends_with? - pub shebangs: Vec, // interpreter(s) associated with language + #[serde(default)] + pub shebangs: Vec, // interpreter(s) associated with language pub roots: Vec, // these indicate project roots <.git, Cargo.toml> pub comment_token: Option, diff --git a/languages.toml b/languages.toml index 934def44e..2ce9321e0 100644 --- a/languages.toml +++ b/languages.toml @@ -3,7 +3,6 @@ name = "rust" scope = "source.rust" injection-regex = "rust" file-types = ["rs"] -shebangs = [] roots = [] auto-format = true comment-token = "//" @@ -18,7 +17,6 @@ name = "toml" scope = "source.toml" injection-regex = "toml" file-types = ["toml"] -shebangs = [] roots = [] comment-token = "#" @@ -29,7 +27,6 @@ name = "protobuf" scope = "source.proto" injection-regex = "protobuf" file-types = ["proto"] -shebangs = [] roots = [] comment-token = "//" @@ -40,7 +37,7 @@ name = "elixir" scope = "source.elixir" injection-regex = "elixir" file-types = ["ex", "exs"] -shebangs = [] +shebangs = ["elixir"] roots = [] comment-token = "#" @@ -64,7 +61,6 @@ name = "json" scope = "source.json" injection-regex = "json" file-types = ["json"] -shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -74,7 +70,6 @@ name = "c" scope = "source.c" injection-regex = "c" file-types = ["c"] # TODO: ["h"] -shebangs = [] roots = [] comment-token = "//" @@ -86,7 +81,6 @@ name = "cpp" scope = "source.cpp" injection-regex = "cpp" file-types = ["cc", "hh", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino"] -shebangs = [] roots = [] comment-token = "//" @@ -98,7 +92,6 @@ name = "c-sharp" scope = "source.csharp" injection-regex = "c-?sharp" file-types = ["cs"] -shebangs = [] roots = [] comment-token = "//" @@ -109,7 +102,6 @@ name = "go" scope = "source.go" injection-regex = "go" file-types = ["go"] -shebangs = [] roots = ["Gopkg.toml", "go.mod"] auto-format = true comment-token = "//" @@ -123,7 +115,7 @@ name = "javascript" scope = "source.js" injection-regex = "^(js|javascript)$" file-types = ["js", "mjs"] -shebangs = [] +shebangs = ["node"] roots = [] comment-token = "//" # TODO: highlights-jsx, highlights-params @@ -147,7 +139,6 @@ name = "tsx" scope = "source.tsx" injection-regex = "^(tsx)$" # |typescript file-types = ["tsx"] -shebangs = [] roots = [] # TODO: highlights-jsx, highlights-params @@ -159,7 +150,6 @@ name = "css" scope = "source.css" injection-regex = "css" file-types = ["css"] -shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -169,7 +159,6 @@ name = "html" scope = "text.html.basic" injection-regex = "html" file-types = ["html"] -shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -238,7 +227,6 @@ name = "latex" scope = "source.tex" injection-regex = "tex" file-types = ["tex"] -shebangs = [] roots = [] comment-token = "%" @@ -249,7 +237,6 @@ name = "julia" scope = "source.julia" injection-regex = "julia" file-types = ["jl"] -shebangs = [] roots = [] comment-token = "#" language-server = { command = "julia", args = [ @@ -275,7 +262,6 @@ name = "java" scope = "source.java" injection-regex = "java" file-types = ["java"] -shebangs = [] roots = [] indent = { tab-width = 4, unit = " " } @@ -284,7 +270,6 @@ name = "ledger" scope = "source.ledger" injection-regex = "ledger" file-types = ["ldg", "ledger", "journal"] -shebangs = [] roots = [] comment-token = ";" indent = { tab-width = 4, unit = " " } @@ -312,7 +297,7 @@ indent = { tab-width = 2, unit = " "} name = "lua" scope = "source.lua" file-types = ["lua"] -shebangs = [] +shebangs = ["lua"] roots = [] comment-token = "--" indent = { tab-width = 2, unit = " " } @@ -322,7 +307,6 @@ name = "svelte" scope = "source.svelte" injection-regex = "svelte" file-types = ["svelte"] -shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } language-server = { command = "svelteserver", args = ["--stdio"] } @@ -333,7 +317,6 @@ name = "vue" scope = "source.vue" injection-regex = "vue" file-types = ["vue"] -shebangs = [] roots = [] indent = { tab-width = 2, unit = " " } @@ -341,7 +324,6 @@ indent = { tab-width = 2, unit = " " } name = "yaml" scope = "source.yaml" file-types = ["yml", "yaml"] -shebangs = [] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } @@ -361,7 +343,6 @@ name = "zig" scope = "source.zig" injection-regex = "zig" file-types = ["zig"] -shebangs = [] roots = ["build.zig"] auto-format = true comment-token = "//" @@ -386,7 +367,6 @@ language-server = { command = "swipl", args = [ name = "tsq" scope = "source.tsq" file-types = ["scm"] -shebangs = [] roots = [] comment-token = ";" indent = { tab-width = 2, unit = " " } @@ -395,7 +375,6 @@ indent = { tab-width = 2, unit = " " } name = "cmake" scope = "source.cmake" file-types = ["cmake", "CMakeLists.txt"] -shebangs = [] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } @@ -405,7 +384,6 @@ language-server = { command = "cmake-language-server" } name = "glsl" scope = "source.glsl" file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ] -shebangs = [] roots = [] comment-token = "//" indent = { tab-width = 4, unit = " " } From e18198aeb2ef46681d404fe6adc8ffbd507f1db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 9 Nov 2021 10:58:23 +0900 Subject: [PATCH 018/156] Revert "fix(core): stop merging array toml config values (#1004)" It breaks languages.toml merging This reverts commit 4304b52ff86f829115cb89b20160b4f80dbb3dbd. --- helix-core/src/lib.rs | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 6168d02c2..d1720df00 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -103,7 +103,25 @@ pub fn cache_dir() -> std::path::PathBuf { pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value { use toml::Value; + fn get_name(v: &Value) -> Option<&str> { + v.get("name").and_then(Value::as_str) + } + match (left, right) { + (Value::Array(mut left_items), Value::Array(right_items)) => { + left_items.reserve(right_items.len()); + for rvalue in right_items { + let lvalue = get_name(&rvalue) + .and_then(|rname| left_items.iter().position(|v| get_name(v) == Some(rname))) + .map(|lpos| left_items.remove(lpos)); + let mvalue = match lvalue { + Some(lvalue) => merge_toml_values(lvalue, rvalue), + None => rvalue, + }; + left_items.push(mvalue); + } + Value::Array(left_items) + } (Value::Table(mut left_map), Value::Table(right_map)) => { for (rname, rvalue) in right_map { match left_map.remove(&rname) { @@ -131,13 +149,12 @@ mod merge_toml_tests { fn language_tomls() { use toml::Value; - const USER: &str = r#" + const USER: &str = " [[language]] - name = "typescript" - test = "bbb" - indent = { tab-width = 4, unit = " ", test = "aaa" } - language-server = { command = "deno", args = ["lsp"] } - "#; + name = \"nix\" + test = \"bbb\" + indent = { tab-width = 4, unit = \" \", test = \"aaa\" } + "; let base: Value = toml::from_slice(include_bytes!("../../languages.toml")) .expect("Couldn't parse built-in langauges config"); @@ -145,27 +162,23 @@ mod merge_toml_tests { let merged = merge_toml_values(base, user); let languages = merged.get("language").unwrap().as_array().unwrap(); - let ts = languages + let nix = languages .iter() - .find(|v| v.get("name").unwrap().as_str().unwrap() == "typescript") + .find(|v| v.get("name").unwrap().as_str().unwrap() == "nix") .unwrap(); - let ts_indent = ts.get("indent").unwrap(); + let nix_indent = nix.get("indent").unwrap(); // We changed tab-width and unit in indent so check them if they are the new values - assert_eq!(ts_indent.get("tab-width").unwrap().as_integer().unwrap(), 4); - assert_eq!(ts_indent.get("unit").unwrap().as_str().unwrap(), " "); - // We added a new keys, so check them - assert_eq!(ts.get("test").unwrap().as_str().unwrap(), "bbb"); - assert_eq!(ts_indent.get("test").unwrap().as_str().unwrap(), "aaa"); assert_eq!( - ts.get("language-server") - .unwrap() - .get("args") - .unwrap() - .as_array() - .unwrap(), - &vec![Value::String("lsp".into())], + nix_indent.get("tab-width").unwrap().as_integer().unwrap(), + 4 ); + assert_eq!(nix_indent.get("unit").unwrap().as_str().unwrap(), " "); + // We added a new keys, so check them + assert_eq!(nix.get("test").unwrap().as_str().unwrap(), "bbb"); + assert_eq!(nix_indent.get("test").unwrap().as_str().unwrap(), "aaa"); + // We didn't change comment-token so it should be same + assert_eq!(nix.get("comment-token").unwrap().as_str().unwrap(), "#"); } } From 81015266d910534502c06a127dee8103d7e6ec5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Nov 2021 11:04:38 +0900 Subject: [PATCH 019/156] build(deps): bump anyhow from 1.0.44 to 1.0.46 (#1029) Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.44 to 1.0.46. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.44...1.0.46) --- updated-dependencies: - dependency-name: anyhow 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 e036828a7..2187e4228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.44" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +checksum = "3aa828229c44c0293dd7d4d2300bdfc4d2883ffdba934c069a6b968957a81f70" [[package]] name = "arc-swap" From eb68cd37671ced61d2a64d2042fde53190d0f296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Nov 2021 11:04:44 +0900 Subject: [PATCH 020/156] build(deps): bump serde_json from 1.0.68 to 1.0.69 (#1030) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.68 to 1.0.69. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.68...v1.0.69) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2187e4228..14b19ae92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" dependencies = [ "itoa", "ryu", From a424ef4e209c0483385acedffd2331534f0ce906 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Mon, 8 Nov 2021 21:07:54 -0500 Subject: [PATCH 021/156] Use default `languages.toml` if user's is invalid (#994) --- helix-core/src/syntax.rs | 3 ++- helix-term/src/application.rs | 28 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index a5dd0c594..142265a82 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -40,13 +40,14 @@ where } #[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Configuration { pub language: Vec, } // largely based on tree-sitter/cli/src/loader.rs #[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct LanguageConfiguration { #[serde(rename = "name")] pub language_id: String, diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 6037148f6..f18841992 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -60,14 +60,19 @@ impl Application { std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir())); // load default and user config, and merge both - let def_lang_conf: toml::Value = toml::from_slice(include_bytes!("../../languages.toml")) - .expect("Could not parse built-in languages.toml, something must be very wrong"); - let user_lang_conf: Option = std::fs::read(conf_dir.join("languages.toml")) + let builtin_err_msg = + "Could not parse built-in languages.toml, something must be very wrong"; + let def_lang_conf: toml::Value = + toml::from_slice(include_bytes!("../../languages.toml")).expect(builtin_err_msg); + let def_syn_loader_conf: helix_core::syntax::Configuration = + def_lang_conf.clone().try_into().expect(builtin_err_msg); + let user_lang_conf = std::fs::read(conf_dir.join("languages.toml")) .ok() - .map(|raw| toml::from_slice(&raw).expect("Could not parse user languages.toml")); + .map(|raw| toml::from_slice(&raw)); let lang_conf = match user_lang_conf { - Some(value) => merge_toml_values(def_lang_conf, value), - None => def_lang_conf, + Some(Ok(value)) => Ok(merge_toml_values(def_lang_conf, value)), + Some(err @ Err(_)) => err, + None => Ok(def_lang_conf), }; let theme = if let Some(theme) = &config.theme { @@ -83,8 +88,15 @@ impl Application { }; let syn_loader_conf: helix_core::syntax::Configuration = lang_conf - .try_into() - .expect("Could not parse merged (built-in + user) languages.toml"); + .and_then(|conf| conf.try_into()) + .unwrap_or_else(|err| { + eprintln!("Bad language config: {}", err); + eprintln!("Press to continue with default language config"); + use std::io::Read; + // This waits for an enter press. + let _ = std::io::stdin().read(&mut []); + def_syn_loader_conf + }); let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); let mut editor = Editor::new( From f96be0fcbc69e7a2fd6cb4ccd16ae99002109893 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 9 Nov 2021 10:08:08 +0800 Subject: [PATCH 022/156] add solarized_light theme (#1010) * add solarized_light theme * solarized_light add constant.numeric --- runtime/themes/solarized_light.toml | 109 ++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 runtime/themes/solarized_light.toml diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml new file mode 100644 index 000000000..19ff0a5f9 --- /dev/null +++ b/runtime/themes/solarized_light.toml @@ -0,0 +1,109 @@ +"attribute" = { fg = "violet" } +"keyword" = { fg = "green" } +"keyword.directive" = { fg = "orange" } +"namespace" = { fg = "violet" } +"punctuation" = { fg = "orange" } +"punctuation.delimiter" = { fg = "orange" } +"operator" = { fg = "green" } +"special" = { fg = "orange" } +# "property" = { fg = "cyan" } +"variable" = { fg = "cyan" } +"variable.parameter" = { fg = "cyan" } +"variable.builtin" = { fg = "cyan", modifiers = ["bold"] } +"variable.other.member" = { fg = "cyan" } +"variable.function" = { fg = "blue" } +"type" = { fg = "yellow" } +"type.builtin" = { fg = "yellow", modifiers = ["bold"] } +"constructor" = { fg = "blue" } +"function" = { fg = "blue" } +"function.macro" = { fg = "magenta" } +"function.builtin" = { fg = "blue", modifiers = ["bold"] } +"function.special" = { fg = "magenta" } +"comment" = { fg = "base01", modifiers = ["italic"] } +"string" = { fg= "base1" } +"constant" = { fg = "base1" } +"constant.character" = { fg = "base1" } +"constant.builtin" = { fg = "base1", modifiers = ["bold"] } +"constant.numeric" = { fg= "base1" } +"constant.numeric.integer" = { fg= "base1" } +"constant.numeric.float" = { fg= "base1" } +"constant.character.escape" = { fg = "red", modifiers = ["bold"] } +"label" = { fg = "green" } +"module" = { fg = "violet" } +"tag" = { fg = "magenta" } + +# 背景 +"ui.background" = { bg = "base03" } + +# 行号栏 +"ui.linenr" = { fg = "base0", bg = "base02" } +# 当前行号栏 +"ui.linenr.selected" = { fg = "red", modifiers = ["bold"] } + +# 状态栏 +"ui.statusline" = { fg = "base02", bg = "base1" } +# 非活动状态栏 +"ui.statusline.inactive" = { fg = "base02", bg = "base00" } + +# 补全窗口, preview窗口 +"ui.popup" = { bg = "base1" } +# 影响 补全选中 cmd弹出信息选中 +"ui.menu.selected" = { fg = "base02", bg = "violet"} +"ui.menu" = { fg = "base02" } +# ?? +"ui.window" = { fg = "base3" } +# 命令行 补全的帮助信息 +"ui.help" = { modifiers = ["reversed"] } + +# 快捷键窗口 +"ui.info" = { bg = "base1" } +# 快捷键字体 +"ui.info.text" = {fg = "base02", modifiers = ["bold"]} + +# 普通ui的字体样式 +"ui.text" = { fg = "base1" } +# 影响 picker列表选中, 快捷键帮助窗口文本 +"ui.text.focus" = { fg = "blue", modifiers = ["bold"]} +# file picker中, 预览的当前选中项 +"ui.highlight" = { fg = "red", modifiers = ["bold", "italic", "underlined"] } + +# 主光标/selectio +"ui.cursor.primary" = {fg = "base03", bg = "base1"} +"ui.selection.primary" = { fg = "base03", bg = "base01" } +"ui.cursor.select" = {fg = "base02", bg = "green"} +"ui.selection" = { fg = "base02", bg = "yellow" } + +# normal模式的光标 +"ui.cursor" = {fg = "base03", bg = "green"} +"ui.cursor.insert" = {fg = "base03", bg = "base3"} +# 当前光标匹配的标点符号 +"ui.cursor.match" = {modifiers = ["reversed"]} + + +"warning" = { fg = "orange", modifiers= ["bold", "underlined"] } +"error" = { fg = "red", modifiers= ["bold", "underlined"] } +"info" = { fg = "blue", modifiers= ["bold", "underlined"] } +"hint" = { fg = "base01", modifiers= ["bold", "underlined"] } +"diagnostic" = { mdifiers = ["underlined"] } + +[palette] +red = '#dc322f' +green = '#859900' +yellow = '#b58900' +blue = '#268bd2' +magenta = '#d33682' +cyan = '#2aa198' +orange = '#cb4b16' +violet = '#6c71c4' + +# 深色 越来越深 +base0 = '#657b83' +base1 = '#586e75' +base2 = '#073642' +base3 = '#002b36' + +## 浅色 越來越浅 +base00 = '#839496' +base01 = '#93a1a1' +base02 = '#eee8d5' +base03 = '#fdf6e3' From a69caff450ff8201e16d0a0b4617114e03ed3c97 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 9 Nov 2021 10:11:45 +0800 Subject: [PATCH 023/156] search_impl will only align cursor center when it isn't in view (#959) --- helix-term/src/commands.rs | 22 +++++++++++++++++--- helix-view/src/view.rs | 41 ++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 245fbe4ee..42163b8e0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1175,6 +1175,7 @@ fn search_impl( regex: &Regex, movement: Movement, direction: Direction, + scrolloff: usize, ) { let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -1233,7 +1234,11 @@ fn search_impl( }; doc.set_selection(view.id, selection); - align_view(doc, view, Align::Center); + if view.is_cursor_in_view(doc, 0) { + view.ensure_cursor_in_view(doc, scrolloff); + } else { + align_view(doc, view, Align::Center) + } }; } @@ -1257,6 +1262,8 @@ fn rsearch(cx: &mut Context) { // TODO: use one function for search vs extend fn searcher(cx: &mut Context, direction: Direction) { let reg = cx.register.unwrap_or('/'); + let scrolloff = cx.editor.config.scrolloff; + let (_, doc) = current!(cx.editor); // TODO: could probably share with select_on_matches? @@ -1281,7 +1288,15 @@ fn searcher(cx: &mut Context, direction: Direction) { if event != PromptEvent::Update { return; } - search_impl(doc, view, &contents, ®ex, Movement::Move, direction); + search_impl( + doc, + view, + &contents, + ®ex, + Movement::Move, + direction, + scrolloff, + ); }, ); @@ -1289,6 +1304,7 @@ fn searcher(cx: &mut Context, direction: Direction) { } fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) { + let scrolloff = cx.editor.config.scrolloff; let (view, doc) = current!(cx.editor); let registers = &cx.editor.registers; if let Some(query) = registers.read('/') { @@ -1303,7 +1319,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir .case_insensitive(case_insensitive) .build() { - search_impl(doc, view, &contents, ®ex, movement, direction); + search_impl(doc, view, &contents, ®ex, movement, direction, scrolloff); } else { // get around warning `mutable_borrow_reservation_conflict` // which will be a hard error in the future diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 11f301550..6a624ded2 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -85,7 +85,12 @@ impl View { self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline } - pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) { + // + pub fn offset_coords_to_in_view( + &self, + doc: &Document, + scrolloff: usize, + ) -> Option<(usize, usize)> { let cursor = doc .selection(self.id) .primary() @@ -104,23 +109,43 @@ impl View { let last_col = self.offset.col + inner_area.width.saturating_sub(1) as usize; - if line > last_line.saturating_sub(scrolloff) { + let row = if line > last_line.saturating_sub(scrolloff) { // scroll down - self.offset.row += line - (last_line.saturating_sub(scrolloff)); + self.offset.row + line - (last_line.saturating_sub(scrolloff)) } else if line < self.offset.row + scrolloff { // scroll up - self.offset.row = line.saturating_sub(scrolloff); - } + line.saturating_sub(scrolloff) + } else { + self.offset.row + }; - if col > last_col.saturating_sub(scrolloff) { + let col = if col > last_col.saturating_sub(scrolloff) { // scroll right - self.offset.col += col - (last_col.saturating_sub(scrolloff)); + self.offset.col + col - (last_col.saturating_sub(scrolloff)) } else if col < self.offset.col + scrolloff { // scroll left - self.offset.col = col.saturating_sub(scrolloff); + col.saturating_sub(scrolloff) + } else { + self.offset.col + }; + if row == self.offset.row && col == self.offset.col { + None + } else { + Some((row, col)) } } + pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) { + if let Some((row, col)) = self.offset_coords_to_in_view(doc, scrolloff) { + self.offset.row = row; + self.offset.col = col; + } + } + + pub fn is_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) -> bool { + self.offset_coords_to_in_view(doc, scrolloff).is_none() + } + /// Calculates the last visible line on screen #[inline] pub fn last_line(&self, doc: &Document) -> usize { From 490919df4fe6474fcabb647d410126c60bc52880 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Tue, 9 Nov 2021 10:12:11 +0800 Subject: [PATCH 024/156] Add rename_symbol to book/ (#1031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rename_symbol book * Update book/src/keymap.md Co-authored-by: Blaž Hrastnik Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 5a6aee411..c6feb44f8 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -215,6 +215,7 @@ This layer is a kludge of mappings, mostly pickers. | `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | | `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | | `/` | Global search in workspace folder | `global_search` | +| `r` | Rename symbol using the language server | `rename_symbol` | > NOTE: Global search display results in a fuzzy picker, use `space + '` to bring it back up after opening a file. From 7c9f6202361e38951820d69227eade5f1677be3c Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 9 Nov 2021 13:43:50 +0800 Subject: [PATCH 025/156] add , , , Delete in prompt mode (#1034) --- book/src/keymap.md | 4 +++- helix-term/src/ui/prompt.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index c6feb44f8..6ae1e8d47 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -274,8 +274,10 @@ Keys to use within prompt, Remapping currently not supported. | `Ctrl-e`, `End` | move prompt end | | `Ctrl-a`, `Home` | move prompt start | | `Ctrl-w` | delete previous word | +| `Ctrl-u` | delete to start of line | | `Ctrl-k` | delete to end of line | -| `backspace` | delete previous char | +| `backspace`, `Ctrl-h` | delete previous char | +| `delete`, `Ctrl-d` | 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 | diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 29ca18b11..22e4adb8a 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -212,6 +212,14 @@ impl Prompt { self.completion = (self.completion_fn)(&self.line); } + pub fn delete_char_forwards(&mut self) { + let pos = self.eval_movement(Movement::ForwardChar(1)); + self.line.replace_range(self.cursor..pos, ""); + + self.exit_selection(); + self.completion = (self.completion_fn)(&self.line); + } + pub fn delete_word_backwards(&mut self) { let pos = self.eval_movement(Movement::BackwardWord(1)); self.line.replace_range(pos..self.cursor, ""); @@ -221,6 +229,15 @@ impl Prompt { self.completion = (self.completion_fn)(&self.line); } + pub fn kill_to_start_of_line(&mut self) { + let pos = self.eval_movement(Movement::StartOfLine); + self.line.replace_range(pos..self.cursor, ""); + self.cursor = pos; + + self.exit_selection(); + self.completion = (self.completion_fn)(&self.line); + } + pub fn kill_to_end_of_line(&mut self) { let pos = self.eval_movement(Movement::EndOfLine); self.line.replace_range(self.cursor..pos, ""); @@ -472,12 +489,31 @@ impl Component for Prompt { modifiers: KeyModifiers::CONTROL, } => self.kill_to_end_of_line(), KeyEvent { + code: KeyCode::Char('u'), + modifiers: KeyModifiers::CONTROL, + } => self.kill_to_start_of_line(), + KeyEvent { + code: KeyCode::Char('h'), + modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { code: KeyCode::Backspace, modifiers: KeyModifiers::NONE, } => { self.delete_char_backwards(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } + KeyEvent { + code: KeyCode::Char('d'), + modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Delete, + modifiers: KeyModifiers::NONE, + } => { + self.delete_char_forwards(); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); + } KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, From 97893cca641545dc42e6b3ceabc8ed433f66a4d6 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Wed, 10 Nov 2021 09:46:55 +0800 Subject: [PATCH 026/156] Restore screen position when abort search (#1047) --- helix-term/src/ui/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 24eb7acdf..62da0dce8 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -35,6 +35,7 @@ pub fn regex_prompt( let (view, doc) = current!(cx.editor); let view_id = view.id; let snapshot = doc.selection(view_id).clone(); + let offset_snapshot = view.offset; Prompt::new( prompt, @@ -45,6 +46,7 @@ pub fn regex_prompt( PromptEvent::Abort => { let (view, doc) = current!(cx.editor); doc.set_selection(view.id, snapshot.clone()); + view.offset = offset_snapshot; } PromptEvent::Validate => { // TODO: push_jump to store selection just before jump From 92d23430c0e900159fb40e507c0916b00b228755 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 10 Nov 2021 07:17:07 +0530 Subject: [PATCH 027/156] Cleanup keymap doc book page (#1042) - Clearly mark keybinds that require LSP - Fix incorrect rendering of Prompt section due to missing newline --- book/src/keymap.md | 53 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 6ae1e8d47..212ed4965 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -142,9 +142,8 @@ over text and not actively editing it). #### Goto mode -Jumps to various locations. - -> NOTE: Some of these features are only available with the LSP present. +Jumps to various locations. Mappings marked (**LSP**) require an +active language server for the file to work. | Key | Description | Command | | ----- | ----------- | ------- | @@ -156,10 +155,10 @@ Jumps to various locations. | `t` | Go to the top of the screen | `goto_window_top` | | `m` | Go to the middle of the screen | `goto_window_middle` | | `b` | Go to the bottom of the screen | `goto_window_bottom` | -| `d` | Go to definition | `goto_definition` | -| `y` | Go to type definition | `goto_type_definition` | -| `r` | Go to references | `goto_reference` | -| `i` | Go to implementation | `goto_implementation` | +| `d` | Go to definition (**LSP**) | `goto_definition` | +| `y` | Go to type definition (**LSP**) | `goto_type_definition` | +| `r` | Go to references (**LSP**) | `goto_reference` | +| `i` | Go to implementation (**LSP**) | `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` | @@ -198,15 +197,18 @@ This layer is similar to vim keybindings as kakoune does not support window. #### Space mode -This layer is a kludge of mappings, mostly pickers. +This layer is a kludge of mappings, mostly pickers. Mappings marked +(**LSP**) require an active language server for the file to work. + | Key | Description | Command | | ----- | ----------- | ------- | -| `k` | Show documentation for the item under the cursor | `hover` | | `f` | Open file picker | `file_picker` | | `b` | Open buffer picker | `buffer_picker` | -| `s` | Open symbol picker (current document) | `symbol_picker` | -| `a` | Apply code action | `code_action` | +| `k` | Show documentation for item under cursor (**LSP**) | `hover` | +| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | +| `r` | Rename symbol (**LSP**) | `rename_symbol` | +| `a` | Apply code action (**LSP**) | `code_action` | | `'` | Open last fuzzy picker | `last_picker` | | `w` | Enter [window mode](#window-mode) | N/A | | `p` | Paste system clipboard after selections | `paste_clipboard_after` | @@ -215,9 +217,8 @@ This layer is a kludge of mappings, mostly pickers. | `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | | `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | | `/` | Global search in workspace folder | `global_search` | -| `r` | Rename symbol using the language server | `rename_symbol` | -> NOTE: Global search display results in a fuzzy picker, use `space + '` to bring it back up after opening a file. +> TIP: Global search displays results in a fuzzy picker, use `space + '` to bring it back up after opening a file. #### Unimpaired @@ -263,7 +264,9 @@ Keys to use within picker. Remapping currently not supported. | `Escape`, `Ctrl-c` | Close picker | # Prompt + Keys to use within prompt, Remapping currently not supported. + | Key | Description | | ----- | ------------- | | `Escape`, `Ctrl-c` | Close prompt | @@ -271,17 +274,17 @@ Keys to use within prompt, Remapping currently not supported. | `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-u` | delete to start of line | -| `Ctrl-k` | delete to end of line | -| `backspace`, `Ctrl-h` | delete previous char | -| `delete`, `Ctrl-d` | 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 | +| `Ctrl-e`, `End` | Move prompt end | +| `Ctrl-a`, `Home` | Move prompt start | +| `Ctrl-w` | Delete previous word | +| `Ctrl-u` | Delete to start of line | +| `Ctrl-k` | Delete to end of line | +| `backspace`, `Ctrl-h` | Delete previous char | +| `delete`, `Ctrl-d` | 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` | Select next completion item | +| `BackTab` | Select previous completion item | | `Enter` | Open selected | From 68224232af1126daa5b043a096269fbd6cb53551 Mon Sep 17 00:00:00 2001 From: CossonLeo <20379044+cossonleo@users.noreply.github.com> Date: Wed, 10 Nov 2021 09:52:39 +0800 Subject: [PATCH 028/156] buffer picker add is_modifier flag (#1020) --- helix-term/src/commands.rs | 68 +++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 42163b8e0..489308d87 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2600,36 +2600,66 @@ fn file_picker(cx: &mut Context) { fn buffer_picker(cx: &mut Context) { let current = view!(cx.editor).doc; + struct BufferMeta { + id: DocumentId, + path: Option, + is_modified: bool, + is_current: bool, + } + + impl BufferMeta { + fn format(&self) -> Cow { + let path = self + .path + .as_deref() + .map(helix_core::path::get_relative_path); + let path = match path.as_deref().and_then(Path::to_str) { + Some(path) => path, + None => return Cow::Borrowed("[scratch buffer]"), + }; + + let mut flags = Vec::new(); + if self.is_modified { + flags.push("+"); + } + if self.is_current { + flags.push("*"); + } + + let flag = if flags.is_empty() { + "".into() + } else { + format!(" ({})", flags.join("")) + }; + Cow::Owned(format!("{}{}", path, flag)) + } + } + + let new_meta = |doc: &Document| BufferMeta { + id: doc.id(), + path: doc.path().cloned(), + is_modified: doc.is_modified(), + is_current: doc.id() == current, + }; + let picker = FilePicker::new( cx.editor .documents .iter() - .map(|(id, doc)| (*id, doc.path().cloned())) + .map(|(_, doc)| new_meta(doc)) .collect(), - move |(id, path): &(DocumentId, Option)| { - let path = path.as_deref().map(helix_core::path::get_relative_path); - match path.as_ref().and_then(|path| path.to_str()) { - Some(path) => { - if *id == current { - format!("{} (*)", &path).into() - } else { - path.to_owned().into() - } - } - None => "[scratch buffer]".into(), - } - }, - |editor: &mut Editor, (id, _path): &(DocumentId, Option), _action| { - editor.switch(*id, Action::Replace); + BufferMeta::format, + |editor: &mut Editor, meta, _action| { + editor.switch(meta.id, Action::Replace); }, - |editor, (id, path)| { - let doc = &editor.documents.get(id)?; + |editor, meta| { + let doc = &editor.documents.get(&meta.id)?; let &view_id = doc.selections().keys().next()?; let line = doc .selection(view_id) .primary() .cursor_line(doc.text().slice(..)); - Some((path.clone()?, Some((line, line)))) + Some((meta.path.clone()?, Some((line, line)))) }, ); cx.push_layer(Box::new(picker)); From cf831b1a65625f29d6e1bc12483a45c1adc8dff4 Mon Sep 17 00:00:00 2001 From: Jason Hansen Date: Tue, 9 Nov 2021 18:53:14 -0700 Subject: [PATCH 029/156] Allow piping from stdin into a buffer on startup (#996) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow piping from stdin into a buffer on startup * Refactor * Don't allow piping into new buffer on macOS * Update helix-term/src/application.rs Co-authored-by: Blaž Hrastnik * Update helix-term/src/application.rs Co-authored-by: Blaž Hrastnik * Fix Co-authored-by: Blaž Hrastnik --- helix-term/src/application.rs | 14 ++++++++++++-- helix-view/src/editor.rs | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index f18841992..b04eef0d8 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -7,7 +7,7 @@ use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui}; use log::{error, warn}; use std::{ - io::{stdout, Write}, + io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, }; @@ -17,6 +17,7 @@ use anyhow::Error; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream}, execute, terminal, + tty::IsTty, }; #[cfg(not(windows))] use { @@ -134,8 +135,17 @@ impl Application { } editor.set_status(format!("Loaded {} files.", nr_of_files)); } - } else { + } else if stdin().is_tty() { editor.new_file(Action::VerticalSplit); + } else if cfg!(target_os = "macos") { + // On Linux and Windows, we allow the output of a command to be piped into the new buffer. + // This doesn't currently work on macOS because of the following issue: + // https://github.com/crossterm-rs/crossterm/issues/500 + anyhow::bail!("Piping into helix-term is currently not supported on macOS"); + } else { + editor + .new_file_from_stdin(Action::VerticalSplit) + .unwrap_or_else(|_| editor.new_file(Action::VerticalSplit)); } editor.set_theme(theme); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 631dcf0c6..7650d217f 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -9,6 +9,7 @@ use crate::{ use futures_util::future; use std::{ collections::BTreeMap, + io::stdin, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -314,16 +315,24 @@ impl Editor { self._refresh(); } - pub fn new_file(&mut self, action: Action) -> DocumentId { + fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId { 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); + document.id = id; + self.documents.insert(id, document); self.switch(id, action); id } + pub fn new_file(&mut self, action: Action) -> DocumentId { + self.new_file_from_document(action, Document::default()) + } + + pub fn new_file_from_stdin(&mut self, action: Action) -> Result { + let (rope, encoding) = crate::document::from_reader(&mut stdin(), None)?; + Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding)))) + } + pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; From 565490913510d9f4423a398f7d04def693cd12ae Mon Sep 17 00:00:00 2001 From: Omnikar Date: Tue, 9 Nov 2021 21:04:03 -0500 Subject: [PATCH 030/156] Update `space w` window mode (#1050) --- helix-term/src/keymap.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d497401f9..593ff0f0f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -573,9 +573,13 @@ impl Default for Keymaps { "'" => last_picker, "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" | "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, }, "y" => yank_joined_to_clipboard, "Y" => yank_main_selection_to_clipboard, From 80036b8bd3f7815b3256aae20a17b31efc104fbd Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Tue, 9 Nov 2021 18:33:15 +0530 Subject: [PATCH 031/156] Change page keybinds in view mode b which was assigned to page_up conflicts with align to bottom, so this commit replaces page up, down, etc keybinds to use normal mode keybinds (C-f, C-b, etc) in view mode too. --- helix-term/src/keymap.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 593ff0f0f..b41c950b9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -597,10 +597,10 @@ impl Default for Keymaps { "m" => align_view_middle, "k" => scroll_up, "j" => scroll_down, - "b" => page_up, - "f" => page_down, - "u" => half_page_up, - "d" => half_page_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, }, "Z" => { "View" sticky=true "z" | "c" => align_view_center, @@ -609,10 +609,10 @@ impl Default for Keymaps { "m" => align_view_middle, "k" => scroll_up, "j" => scroll_down, - "b" => page_up, - "f" => page_down, - "u" => half_page_up, - "d" => half_page_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, }, "\"" => select_register, From f9e9efb3ecc4aa22c270c043f7442791e810940c Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 10 Nov 2021 11:45:32 +0530 Subject: [PATCH 032/156] Check for duplicate keys in default keymap --- helix-term/src/keymap.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b41c950b9..53e0d4507 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -64,10 +64,11 @@ macro_rules! keymap { $( $( let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - _map.insert( + let _duplicate = _map.insert( _key, keymap!(@trie $value) ); + debug_assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); _order.push(_key); )+ )* @@ -691,6 +692,22 @@ pub fn merge_keys(mut config: Config) -> Config { #[cfg(test)] mod tests { use super::*; + + #[test] + #[should_panic] + fn duplicate_keys_should_panic() { + keymap!({ "Normal mode" + "i" => normal_mode, + "i" => goto_definition, + }); + } + + #[test] + fn check_duplicate_keys_in_default_keymap() { + // will panic on duplicate keys, assumes that `Keymaps` uses keymap! macro + Keymaps::default(); + } + #[test] fn merge_partial_keys() { let config = Config { From e863e3b62d232d650468cb5d1f9da925fb460f5e Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 10 Nov 2021 21:28:35 +0530 Subject: [PATCH 033/156] Ensure that identical keymaps stay in sync (#1056) Space mode and view mode are duplicated on two different keybinds, and they tend to get out of sync by contributers forgetting to update both of them. This commit adds a test that explicitly checks that they are identical. Prevents issues like #1050. --- helix-term/src/keymap.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 53e0d4507..4fb0f172a 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -802,4 +802,20 @@ mod tests { let node = keymap.root().search(&[crate::key!(' ')]).unwrap(); assert!(!node.node().unwrap().order().is_empty()) } + + #[test] + fn aliased_modes_are_same_in_default_keymap() { + let keymaps = Keymaps::default(); + let root = keymaps.get(&Mode::Normal).unwrap().root(); + assert_eq!( + root.search(&[key!(' '), key!('w')]).unwrap(), + root.search(&["C-w".parse::().unwrap()]).unwrap(), + "Mismatch for window mode on `Space-w` and `Ctrl-w`" + ); + assert_eq!( + root.search(&[key!('z')]).unwrap(), + root.search(&[key!('Z')]).unwrap(), + "Mismatch for view mode on `z` and `Z`" + ); + } } From efc2b4c77be55afad07762cdde9b3b74c8477933 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 10 Nov 2021 21:28:46 +0530 Subject: [PATCH 034/156] Refactor keyevent handling using key, ctrl macros (#1058) Adds ctrl! and alt! macros (which existed before the big keymap refactor) and uses them in event handling of Components. Note that this converts crossterm's KeyEvent to our own KeyEvent on each invocation of handle_event in Components. --- helix-term/src/keymap.rs | 32 ++++++++ helix-term/src/ui/menu.rs | 57 +++----------- helix-term/src/ui/picker.rs | 65 +++------------- helix-term/src/ui/popup.rs | 28 +++---- helix-term/src/ui/prompt.rs | 144 ++++++------------------------------ 5 files changed, 86 insertions(+), 240 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 4fb0f172a..7bed3ddb0 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -25,6 +25,38 @@ macro_rules! key { }; } +#[macro_export] +macro_rules! ctrl { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, + } + }; +} + +#[macro_export] +macro_rules! alt { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::ALT, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::ALT, + } + }; +} + /// Macro for defining the root of a `Keymap` object. Example: /// /// ``` diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 3c492d149..8278bd29b 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -1,5 +1,8 @@ -use crate::compositor::{Component, Compositor, Context, EventResult}; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::{ + compositor::{Component, Compositor, Context, EventResult}, + ctrl, key, +}; +use crossterm::event::Event; use tui::{buffer::Buffer as Surface, widgets::Table}; pub use tui::widgets::{Cell, Row}; @@ -192,63 +195,25 @@ impl Component for Menu { compositor.pop(); }))); - match event { + match event.into() { // esc or ctrl-c aborts the completion and closes the menu - KeyEvent { - code: KeyCode::Esc, .. - } - | KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Esc) | ctrl!('c') => { (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Abort); return close_fn; } // arrow up/ctrl-p/shift-tab prev completion choice (including updating the doc) - KeyEvent { - code: KeyCode::BackTab, - .. - } - | KeyEvent { - code: KeyCode::Up, .. - } - | KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); } - // arrow down/ctrl-n/tab advances completion choice (including updating the doc) - KeyEvent { - code: KeyCode::Tab, - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Down, - .. - } - | KeyEvent { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('j'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { + // arrow down/ctrl-n/tab advances completion choice (including updating the doc) self.move_down(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); } - KeyEvent { - code: KeyCode::Enter, - .. - } => { + key!(Enter) => { if let Some(selection) = self.selection() { (self.callback_fn)(cx.editor, Some(selection), MenuEvent::Validate); } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 291f1f856..970d3946b 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -1,8 +1,9 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, + ctrl, key, ui::EditorView, }; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::Event; use tui::{ buffer::Buffer as Surface, widgets::{Block, BorderType, Borders}, @@ -402,81 +403,35 @@ impl Component for Picker { compositor.last_picker = compositor.pop(); }))); - match key_event { - KeyEvent { - code: KeyCode::Up, .. - } - | KeyEvent { - code: KeyCode::BackTab, - .. - } - | KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - } => { + match key_event.into() { + key!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); } - KeyEvent { - code: KeyCode::Down, - .. - } - | KeyEvent { - code: KeyCode::Tab, .. - } - | KeyEvent { - code: KeyCode::Char('j'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { self.move_down(); } - KeyEvent { - code: KeyCode::Esc, .. - } - | KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Esc) | ctrl!('c') => { return close_fn; } - KeyEvent { - code: KeyCode::Enter, - .. - } => { + key!(Enter) => { if let Some(option) = self.selection() { (self.callback_fn)(&mut cx.editor, option, Action::Replace); } return close_fn; } - KeyEvent { - code: KeyCode::Char('s'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('s') => { if let Some(option) = self.selection() { (self.callback_fn)(&mut cx.editor, option, Action::HorizontalSplit); } return close_fn; } - KeyEvent { - code: KeyCode::Char('v'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('v') => { if let Some(option) = self.selection() { (self.callback_fn)(&mut cx.editor, option, Action::VerticalSplit); } return close_fn; } - KeyEvent { - code: KeyCode::Char(' '), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!(' ') => { self.save_filter(); } _ => { diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 1bab1eae4..8f7921a11 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -1,5 +1,8 @@ -use crate::compositor::{Component, Compositor, Context, EventResult}; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::{ + compositor::{Component, Compositor, Context, EventResult}, + ctrl, key, +}; +use crossterm::event::Event; use tui::buffer::Buffer as Surface; use helix_core::Position; @@ -95,27 +98,14 @@ impl Component for Popup { compositor.pop(); }))); - match key { + match key.into() { // esc or ctrl-c aborts the completion and closes the menu - KeyEvent { - code: KeyCode::Esc, .. - } - | KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } => close_fn, - - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Esc) | ctrl!('c') => close_fn, + ctrl!('d') => { self.scroll(self.size.1 as usize / 2, true); EventResult::Consumed(None) } - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('u') => { self.scroll(self.size.1 as usize / 2, false); EventResult::Consumed(None) } diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 22e4adb8a..4bd5659f8 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -1,6 +1,8 @@ use crate::compositor::{Component, Compositor, Context, EventResult}; -use crate::ui; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::{alt, ctrl, key, ui}; +use crossterm::event::Event; +use helix_view::input::KeyEvent; +use helix_view::keyboard::{KeyCode, KeyModifiers}; use std::{borrow::Cow, ops::RangeFrom}; use tui::buffer::Buffer as Surface; @@ -421,103 +423,29 @@ impl Component for Prompt { compositor.pop(); }))); - match event { - KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Esc, .. - } => { + match event.into() { + ctrl!('c') | key!(Esc) => { (self.callback_fn)(cx, &self.line, PromptEvent::Abort); return close_fn; } - KeyEvent { - code: KeyCode::Left, - modifiers: KeyModifiers::ALT, - } - | KeyEvent { - code: KeyCode::Char('b'), - modifiers: KeyModifiers::ALT, - } => self.move_cursor(Movement::BackwardWord(1)), - KeyEvent { - code: KeyCode::Right, - modifiers: KeyModifiers::ALT, - } - | KeyEvent { - code: KeyCode::Char('f'), - modifiers: KeyModifiers::ALT, - } => self.move_cursor(Movement::ForwardWord(1)), - KeyEvent { - code: KeyCode::Char('f'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Right, - .. - } => self.move_cursor(Movement::ForwardChar(1)), - KeyEvent { - code: KeyCode::Char('b'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Left, - .. - } => self.move_cursor(Movement::BackwardChar(1)), - KeyEvent { - code: KeyCode::End, - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Char('e'), - modifiers: KeyModifiers::CONTROL, - } => self.move_end(), - KeyEvent { - code: KeyCode::Home, - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Char('a'), - modifiers: KeyModifiers::CONTROL, - } => self.move_start(), - KeyEvent { - code: KeyCode::Char('w'), - modifiers: KeyModifiers::CONTROL, - } => self.delete_word_backwards(), - KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::CONTROL, - } => self.kill_to_end_of_line(), - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - } => self.kill_to_start_of_line(), - KeyEvent { - code: KeyCode::Char('h'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Backspace, - modifiers: KeyModifiers::NONE, - } => { + alt!('b') | alt!(Left) => self.move_cursor(Movement::BackwardWord(1)), + alt!('f') | alt!(Right) => self.move_cursor(Movement::ForwardWord(1)), + ctrl!('b') | ctrl!(Left) => self.move_cursor(Movement::BackwardChar(1)), + ctrl!('f') | ctrl!(Right) => self.move_cursor(Movement::ForwardChar(1)), + ctrl!('e') | key!(End) => self.move_end(), + ctrl!('a') | key!(Home) => self.move_start(), + ctrl!('w') => self.delete_word_backwards(), + ctrl!('k') => self.kill_to_end_of_line(), + ctrl!('u') => self.kill_to_start_of_line(), + ctrl!('h') | key!(Backspace) => { self.delete_char_backwards(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Delete, - modifiers: KeyModifiers::NONE, - } => { + ctrl!('d') | key!(Delete) => { self.delete_char_forwards(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } - KeyEvent { - code: KeyCode::Char('s'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('s') => { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); @@ -535,10 +463,7 @@ impl Component for Prompt { (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } - KeyEvent { - code: KeyCode::Enter, - .. - } => { + key!(Enter) => { if self.selection.is_some() && self.line.ends_with('/') { self.completion = (self.completion_fn)(&self.line); self.exit_selection(); @@ -553,50 +478,29 @@ impl Component for Prompt { return close_fn; } } - KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Up, .. - } => { + ctrl!('p') | key!(Up) => { 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 { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Down, - .. - } => { + ctrl!('n') | key!(Down) => { 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 { - code: KeyCode::Tab, .. - } => { + key!(Tab) => { self.change_completion_selection(CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } - KeyEvent { - code: KeyCode::BackTab, - .. - } => { + key!(BackTab) => { self.change_completion_selection(CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } - KeyEvent { - code: KeyCode::Char('q'), - modifiers: KeyModifiers::CONTROL, - } => self.exit_selection(), + ctrl!('q') => self.exit_selection(), // any char event that's not combined with control or mapped to any other combo KeyEvent { code: KeyCode::Char(c), From e0540fbcc4f0c31cc968a127dedc9a6d594603ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=2E=20Szczygie=C5=82?= Date: Wed, 10 Nov 2021 17:01:19 +0100 Subject: [PATCH 035/156] Add json indents.toml file (#1055) * add glsl language support * glsl: use indents.toml file * add json indents.toml --- runtime/queries/json/indents.toml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 runtime/queries/json/indents.toml diff --git a/runtime/queries/json/indents.toml b/runtime/queries/json/indents.toml new file mode 100644 index 000000000..64a8d1757 --- /dev/null +++ b/runtime/queries/json/indents.toml @@ -0,0 +1,9 @@ +indent = [ + "object", + "array" +] + +outdent = [ + "]", + "}" +] From c7cb7527bedbd8a49471fcf79216d2f78fe21ae2 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Thu, 11 Nov 2021 03:08:19 +0100 Subject: [PATCH 036/156] Fix moving with arrow keys in prompt (#1070) --- helix-term/src/ui/prompt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 4bd5659f8..9a60196f5 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -430,8 +430,8 @@ impl Component for Prompt { } alt!('b') | alt!(Left) => self.move_cursor(Movement::BackwardWord(1)), alt!('f') | alt!(Right) => self.move_cursor(Movement::ForwardWord(1)), - ctrl!('b') | ctrl!(Left) => self.move_cursor(Movement::BackwardChar(1)), - ctrl!('f') | ctrl!(Right) => self.move_cursor(Movement::ForwardChar(1)), + ctrl!('b') | key!(Left) => self.move_cursor(Movement::BackwardChar(1)), + ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)), ctrl!('e') | key!(End) => self.move_end(), ctrl!('a') | key!(Home) => self.move_start(), ctrl!('w') => self.delete_word_backwards(), From 4d22454386d52d14f626b209016a71f119cc1cbf Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 11 Nov 2021 10:32:23 +0800 Subject: [PATCH 037/156] add wonly -- window only (#1057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add wonly * Update book/src/keymap.md Co-authored-by: Blaž Hrastnik * add `wonly` to space w mode too * remove the TODO Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 1 + helix-term/src/commands.rs | 15 +++++++++++++++ helix-term/src/keymap.rs | 2 ++ 3 files changed, 18 insertions(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 212ed4965..901c84711 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -194,6 +194,7 @@ This layer is similar to vim keybindings as kakoune does not support window. | `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` | +| `o`, `Ctrl-o` | Only keep the current window, closing all the others | `wonly` | #### Space mode diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 489308d87..089f92f1d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -320,6 +320,7 @@ impl Command { hsplit, "Horizontal bottom split", vsplit, "Vertical right split", wclose, "Close window", + wonly, "Current window only", select_register, "Select register", align_view_middle, "Align view middle", align_view_top, "Align view top", @@ -4723,6 +4724,20 @@ fn wclose(cx: &mut Context) { cx.editor.close(view_id, /* close_buffer */ false); } +fn wonly(cx: &mut Context) { + let views = cx + .editor + .tree + .views() + .map(|(v, focus)| (v.id, focus)) + .collect::>(); + for (view_id, focus) in views { + if !focus { + cx.editor.close(view_id, /* close_buffer */ false); + } + } +} + fn select_register(cx: &mut Context) { cx.on_next_key(move |cx, event| { if let Some(ch) = event.char() { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 7bed3ddb0..d7040b886 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -583,6 +583,7 @@ impl Default for Keymaps { "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-q" | "q" => wclose, + "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, "C-j" | "j" | "down" => jump_view_down, "C-k" | "k" | "up" => jump_view_up, @@ -609,6 +610,7 @@ impl Default for Keymaps { "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, "C-q" | "q" => wclose, + "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, "C-j" | "j" | "down" => jump_view_down, "C-k" | "k" | "up" => jump_view_up, From ebc14d9d206a0960a97fd706bd8fbfbedf2fb203 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Wed, 10 Nov 2021 21:33:31 -0500 Subject: [PATCH 038/156] Add `m` textobject for pair under cursor (#961) --- helix-term/src/commands.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 089f92f1d..24c38dd51 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4819,6 +4819,14 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { 'c' => textobject_treesitter("class", range), 'f' => textobject_treesitter("function", range), 'p' => textobject_treesitter("parameter", range), + 'm' => { + let ch = text.char(range.cursor(text)); + if !ch.is_ascii_alphanumeric() { + textobject::textobject_surround(text, range, objtype, ch, count) + } else { + 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) From bf70cfd050d961ce7b8d5a95fe663dff9eb1e66e Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 10 Nov 2021 19:22:15 -0800 Subject: [PATCH 039/156] helix-term/command: make scratch buffer name consistent (#1071) --- helix-term/src/commands.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 24c38dd51..23b385eb5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -53,6 +53,8 @@ use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use ignore::{DirEntry, WalkBuilder, WalkState}; use tokio_stream::wrappers::UnboundedReceiverStream; +pub const SCRATCH_BUFFER_NAME: &str = "[scratch]"; + pub struct Context<'a> { pub register: Option, pub count: Option, @@ -1890,7 +1892,7 @@ mod cmd { .map(|doc| { doc.relative_path() .map(|path| path.to_string_lossy().to_string()) - .unwrap_or_else(|| "[scratch]".into()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()) }) .collect(); if !modified.is_empty() { @@ -2616,7 +2618,7 @@ fn buffer_picker(cx: &mut Context) { .map(helix_core::path::get_relative_path); let path = match path.as_deref().and_then(Path::to_str) { Some(path) => path, - None => return Cow::Borrowed("[scratch buffer]"), + None => return Cow::Borrowed(SCRATCH_BUFFER_NAME), }; let mut flags = Vec::new(); From d131a9dd0efc5ff271f8b78cd65a8dc30c193af4 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Wed, 10 Nov 2021 23:44:50 -0500 Subject: [PATCH 040/156] Allow keys to be mapped to sequences of commands (#589) * Allow keys to be mapped to sequences of commands * Handle `Sequence` at the start of `Keymap::get` * Use `"[Multiple commands]"` as command sequence doc * Add command sequence example to `remapping.md` --- book/src/remapping.md | 1 + helix-term/src/keymap.rs | 27 ++++++++++++++++++++++++--- helix-term/src/ui/editor.rs | 5 +++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index 5ca6cd1b8..532f502ae 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -15,6 +15,7 @@ a = "move_char_left" # Maps the 'a' key to the move_char_left command w = "move_line_up" # Maps the 'w' key move_line_up "C-S-esc" = "extend_line" # Maps Control-Shift-Escape to extend_line g = { a = "code_action" } # Maps `ga` to show possible code actions +"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode [keys.insert] "A-x" = "normal_mode" # Maps Alt-X to enter normal mode diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d7040b886..b2b865e43 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -85,6 +85,10 @@ macro_rules! keymap { keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ }) }; + (@trie [$($cmd:ident),* $(,)?]) => { + $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*]) + }; + ( { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } ) => { @@ -180,6 +184,7 @@ impl KeyTrieNode { cmd.doc() } KeyTrie::Node(n) => n.name(), + KeyTrie::Sequence(_) => "[Multiple commands]", }; match body.iter().position(|(d, _)| d == &desc) { Some(pos) => { @@ -240,6 +245,7 @@ impl DerefMut for KeyTrieNode { #[serde(untagged)] pub enum KeyTrie { Leaf(Command), + Sequence(Vec), Node(KeyTrieNode), } @@ -247,14 +253,14 @@ impl KeyTrie { pub fn node(&self) -> Option<&KeyTrieNode> { match *self { KeyTrie::Node(ref node) => Some(node), - KeyTrie::Leaf(_) => None, + KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None, } } pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> { match *self { KeyTrie::Node(ref mut node) => Some(node), - KeyTrie::Leaf(_) => None, + KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None, } } @@ -271,7 +277,7 @@ impl KeyTrie { trie = match trie { KeyTrie::Node(map) => map.get(key), // leaf encountered while keys left to process - KeyTrie::Leaf(_) => None, + KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None, }? } Some(trie) @@ -283,6 +289,8 @@ pub enum KeymapResultKind { /// Needs more keys to execute a command. Contains valid keys for next keystroke. Pending(KeyTrieNode), Matched(Command), + /// Matched a sequence of commands to execute. + MatchedSequence(Vec), /// Key was not found in the root keymap NotFound, /// Key is invalid in combination with previous keys. Contains keys leading upto @@ -365,6 +373,12 @@ impl Keymap { Some(&KeyTrie::Leaf(cmd)) => { return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky()) } + Some(&KeyTrie::Sequence(ref cmds)) => { + return KeymapResult::new( + KeymapResultKind::MatchedSequence(cmds.clone()), + self.sticky(), + ) + } None => return KeymapResult::new(KeymapResultKind::NotFound, self.sticky()), Some(t) => t, }; @@ -382,6 +396,13 @@ impl Keymap { self.state.clear(); return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky()); } + Some(&KeyTrie::Sequence(ref cmds)) => { + self.state.clear(); + KeymapResult::new( + KeymapResultKind::MatchedSequence(cmds.clone()), + self.sticky(), + ) + } None => KeymapResult::new( KeymapResultKind::Cancelled(self.state.drain(..).collect()), self.sticky(), diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a70155773..90f09e9c9 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -695,6 +695,11 @@ impl EditorView { match &key_result.kind { KeymapResultKind::Matched(command) => command.execute(cxt), KeymapResultKind::Pending(node) => self.autoinfo = Some(node.infobox()), + KeymapResultKind::MatchedSequence(commands) => { + for command in commands { + command.execute(cxt); + } + } KeymapResultKind::NotFound | KeymapResultKind::Cancelled(_) => return Some(key_result), } None From 9d591427be900b7a43fc7e13dd86f31199e8c00e Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Thu, 11 Nov 2021 21:32:44 +0800 Subject: [PATCH 041/156] Fix earlier/later missing changeset update (#1069) Fix #1059 --- helix-term/src/commands.rs | 10 ++++++++-- helix-view/src/document.rs | 24 ++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 23b385eb5..738621b04 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1845,7 +1845,10 @@ mod cmd { .map_err(|s| anyhow!(s))?; let (view, doc) = current!(cx.editor); - doc.earlier(view.id, uk); + let success = doc.earlier(view.id, uk); + if !success { + cx.editor.set_status("Already at oldest change".to_owned()); + } Ok(()) } @@ -1860,7 +1863,10 @@ mod cmd { .parse::() .map_err(|s| anyhow!(s))?; let (view, doc) = current!(cx.editor); - doc.later(view.id, uk); + let success = doc.later(view.id, uk); + if !success { + cx.editor.set_status("Already at newest change".to_owned()); + } Ok(()) } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 351ad05a2..80f6a740f 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -751,19 +751,35 @@ impl Document { } /// Undo modifications to the [`Document`] according to `uk`. - pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) { + pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) -> bool { let txns = self.history.get_mut().earlier(uk); + let mut success = false; for txn in txns { - self.apply_impl(&txn, view_id); + if self.apply_impl(&txn, view_id) { + success = true; + } + } + if success { + // reset changeset to fix len + self.changes = ChangeSet::new(self.text()); } + success } /// Redo modifications to the [`Document`] according to `uk`. - pub fn later(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) { + pub fn later(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) -> bool { let txns = self.history.get_mut().later(uk); + let mut success = false; for txn in txns { - self.apply_impl(&txn, view_id); + if self.apply_impl(&txn, view_id) { + success = true; + } + } + if success { + // reset changeset to fix len + self.changes = ChangeSet::new(self.text()); } + success } /// Commit pending changes to history From bf95a9ed043242d95e431412e45e218d40a5695a Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 11 Nov 2021 19:34:08 -0500 Subject: [PATCH 042/156] Add `remove_selections` command (#1065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `remove_selections` command * Document `remove_selections` * Update helix-term/src/keymap.rs Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 1 + helix-core/src/selection.rs | 5 +++-- helix-term/src/commands.rs | 19 +++++++++++++++---- helix-term/src/keymap.rs | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 901c84711..7a9a43789 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -101,6 +101,7 @@ | | Expand selection to parent syntax node TODO: pick a key | `expand_selection` | | `J` | Join lines inside selection | `join_selections` | | `K` | Keep selections matching the regex | `keep_selections` | +| `Alt-K` | Remove selections matching the regex | `remove_selections` | | `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` | | `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` | diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index f3b5d2c83..f7c7dbcbc 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -528,14 +528,15 @@ impl<'a> IntoIterator for &'a Selection { // TODO: checkSelection -> check if valid for doc length && sorted -pub fn keep_matches( +pub fn keep_or_remove_matches( text: RopeSlice, selection: &Selection, regex: &crate::regex::Regex, + remove: bool, ) -> Option { let result: SmallVec<_> = selection .iter() - .filter(|range| regex.is_match(&range.fragment(text))) + .filter(|range| regex.is_match(&range.fragment(text)) ^ remove) .copied() .collect(); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 738621b04..4352ee668 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -302,6 +302,7 @@ impl Command { format_selections, "Format selection", join_selections, "Join lines inside selection", keep_selections, "Keep selections matching regex", + remove_selections, "Remove selections matching regex", keep_primary_selection, "Keep primary selection", remove_primary_selection, "Remove primary selection", completion, "Invoke completion popup", @@ -4320,12 +4321,12 @@ fn join_selections(cx: &mut Context) { doc.append_changes_to_history(view.id); } -fn keep_selections(cx: &mut Context) { - // keep selections matching regex +fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) { + // keep or remove selections matching regex let reg = cx.register.unwrap_or('/'); let prompt = ui::regex_prompt( cx, - "keep:".into(), + if !remove { "keep:" } else { "remove:" }.into(), Some(reg), |_input: &str| Vec::new(), move |view, doc, regex, event| { @@ -4334,7 +4335,9 @@ fn keep_selections(cx: &mut Context) { } let text = doc.text().slice(..); - if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) { + if let Some(selection) = + selection::keep_or_remove_matches(text, doc.selection(view.id), ®ex, remove) + { doc.set_selection(view.id, selection); } }, @@ -4343,6 +4346,14 @@ fn keep_selections(cx: &mut Context) { cx.push_layer(Box::new(prompt)); } +fn keep_selections(cx: &mut Context) { + keep_or_remove_selections_impl(cx, false) +} + +fn remove_selections(cx: &mut Context) { + keep_or_remove_selections_impl(cx, true) +} + fn keep_primary_selection(cx: &mut Context) { let (view, doc) = current!(cx.editor); // TODO: handle count diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b2b865e43..f79978fbb 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -577,7 +577,7 @@ impl Default for Keymaps { "=" => format_selections, "J" => join_selections, "K" => keep_selections, - // TODO: and another method for inverse + "A-K" => remove_selections, "," => keep_primary_selection, "A-," => remove_primary_selection, From 187197afb15ae13764fc2cdde87528324e183983 Mon Sep 17 00:00:00 2001 From: NexiNov <82087036+python128@users.noreply.github.com> Date: Fri, 12 Nov 2021 06:18:37 +0530 Subject: [PATCH 043/156] Add arrow keys to view mode (#987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add arrow keys to view mode * Drop C-up and C-down * Update docs for #987 * Format correctly * Drop other keymaps * Correct keymap.md * Add arrow keys to view mode Drop C-up and C-down Update docs for #987 Format correctly Drop other keymaps Correct keymap.md Rebase Co-authored-by: Rust & Python Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 24 ++++++++++++------------ helix-term/src/keymap.rs | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 7a9a43789..b98692144 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -128,18 +128,18 @@ key to return to normal mode after usage (useful when you're simply looking over text and not actively editing it). -| Key | Description | Command | -| ----- | ----------- | ------- | -| `z` , `c` | Vertically center the line | `align_view_center` | -| `t` | Align the line to the top of the screen | `align_view_top` | -| `b` | Align the line to the bottom of the screen | `align_view_bottom` | -| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` | -| `j` | Scroll the view downwards | `scroll_down` | -| `k` | Scroll the view upwards | `scroll_up` | -| `f` | Move page down | `page_down` | -| `b` | Move page up | `page_up` | -| `d` | Move half page down | `half_page_down` | -| `u` | Move half page up | `half_page_up` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `z` , `c` | Vertically center the line | `align_view_center` | +| `t` | Align the line to the top of the screen | `align_view_top` | +| `b` | Align the line to the bottom of the screen | `align_view_bottom` | +| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` | +| `j` , `down` | Scroll the view downwards | `scroll_down` | +| `k` , `up` | Scroll the view upwards | `scroll_up` | +| `f` | Move page down | `page_down` | +| `b` | Move page up | `page_up` | +| `d` | Move half page down | `half_page_down` | +| `u` | Move half page up | `half_page_up` | #### Goto mode diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index f79978fbb..1a9ea2311 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -651,8 +651,8 @@ impl Default for Keymaps { "t" => align_view_top, "b" => align_view_bottom, "m" => align_view_middle, - "k" => scroll_up, - "j" => scroll_down, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, "C-b" | "pageup" => page_up, "C-f" | "pagedown" => page_down, "C-u" => half_page_up, @@ -663,8 +663,8 @@ impl Default for Keymaps { "t" => align_view_top, "b" => align_view_bottom, "m" => align_view_middle, - "k" => scroll_up, - "j" => scroll_down, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, "C-b" | "pageup" => page_up, "C-f" | "pagedown" => page_down, "C-u" => half_page_up, From fa0cb010e16c6a7d95b8849f073f472b0bd710be Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Fri, 12 Nov 2021 07:05:32 +0530 Subject: [PATCH 044/156] docs: Mark more keybinds that require LSP and treesitter (#1081) --- book/src/keymap.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index b98692144..c544a472e 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -1,5 +1,8 @@ # Keymap +- Mappings marked (**LSP**) require an active language server for the file. +- Mappings marked (**TS**) require a tree-sitter grammar for the filetype. + ## Normal mode ### Movement @@ -64,7 +67,7 @@ | `"` `` | Select a register to yank to or paste from | `select_register` | | `>` | Indent selection | `indent` | | `<` | Unindent selection | `unindent` | -| `=` | Format selection | `format_selections` | +| `=` | Format selection (**LSP**) | `format_selections` | | `d` | Delete selection | `delete_selection` | | `c` | Change selection (delete and enter insert mode) | `change_selection` | @@ -98,7 +101,7 @@ | `%` | Select entire file | `select_all` | | `x` | Select current line, if already selected, extend to next line | `extend_line` | | `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` | -| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` | +| | Expand selection to parent syntax node TODO: pick a key (**TS**) | `expand_selection` | | `J` | Join lines inside selection | `join_selections` | | `K` | Keep selections matching the regex | `keep_selections` | | `Alt-K` | Remove selections matching the regex | `remove_selections` | @@ -143,8 +146,7 @@ over text and not actively editing it). #### Goto mode -Jumps to various locations. Mappings marked (**LSP**) require an -active language server for the file to work. +Jumps to various locations. | Key | Description | Command | | ----- | ----------- | ------- | @@ -172,7 +174,7 @@ and [textobject](./usage.md#textobject) usage. | Key | Description | Command | | ----- | ----------- | ------- | -| `m` | Goto matching bracket | `match_brackets` | +| `m` | Goto matching bracket (**TS**) | `match_brackets` | | `s` `` | Surround current selection with `` | `surround_add` | | `r` `` | Replace surround character `` with `` | `surround_replace` | | `d` `` | Delete surround character `` | `surround_delete` | @@ -199,18 +201,17 @@ This layer is similar to vim keybindings as kakoune does not support window. #### Space mode -This layer is a kludge of mappings, mostly pickers. Mappings marked -(**LSP**) require an active language server for the file to work. +This layer is a kludge of mappings, mostly pickers. | Key | Description | Command | | ----- | ----------- | ------- | | `f` | Open file picker | `file_picker` | | `b` | Open buffer picker | `buffer_picker` | -| `k` | Show documentation for item under cursor (**LSP**) | `hover` | -| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | -| `r` | Rename symbol (**LSP**) | `rename_symbol` | -| `a` | Apply code action (**LSP**) | `code_action` | +| `k` | Show documentation for item under cursor (**LSP**) | `hover` | +| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | +| `r` | Rename symbol (**LSP**) | `rename_symbol` | +| `a` | Apply code action (**LSP**) | `code_action` | | `'` | Open last fuzzy picker | `last_picker` | | `w` | Enter [window mode](#window-mode) | N/A | | `p` | Paste system clipboard after selections | `paste_clipboard_after` | @@ -226,14 +227,14 @@ This layer is a kludge of mappings, mostly pickers. Mappings marked 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` | -| `[space` | Add newline above | `add_newline_above` | -| `]space` | Add newline below | `add_newline_below` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` | +| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` | +| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` | +| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` | +| `[space` | Add newline above | `add_newline_above` | +| `]space` | Add newline below | `add_newline_below` | ## Insert Mode From d3def16584f7f35a64ab2bad578436bd13cc18b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 12 Nov 2021 16:21:03 +0900 Subject: [PATCH 045/156] fix: shift-tab mappings broken after efc2b4c7 --- helix-term/src/keymap.rs | 16 ++++++++++++++++ helix-term/src/ui/menu.rs | 4 ++-- helix-term/src/ui/picker.rs | 4 ++-- helix-term/src/ui/prompt.rs | 4 ++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 1a9ea2311..e3e019957 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -25,6 +25,22 @@ macro_rules! key { }; } +#[macro_export] +macro_rules! shift { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, + } + }; +} + #[macro_export] macro_rules! ctrl { ($key:ident) => { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 8278bd29b..e891c1492 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -1,6 +1,6 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, - ctrl, key, + ctrl, key, shift, }; use crossterm::event::Event; use tui::{buffer::Buffer as Surface, widgets::Table}; @@ -202,7 +202,7 @@ impl Component for Menu { return close_fn; } // arrow up/ctrl-p/shift-tab prev completion choice (including updating the doc) - key!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { + shift!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 970d3946b..c44b76250 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -1,6 +1,6 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, - ctrl, key, + ctrl, key, shift, ui::EditorView, }; use crossterm::event::Event; @@ -404,7 +404,7 @@ impl Component for Picker { }))); match key_event.into() { - key!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { + shift!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); } key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 9a60196f5..deed16094 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -1,5 +1,5 @@ use crate::compositor::{Component, Compositor, Context, EventResult}; -use crate::{alt, ctrl, key, ui}; +use crate::{alt, ctrl, key, shift, ui}; use crossterm::event::Event; use helix_view::input::KeyEvent; use helix_view::keyboard::{KeyCode, KeyModifiers}; @@ -496,7 +496,7 @@ impl Component for Prompt { self.change_completion_selection(CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } - key!(BackTab) => { + shift!(BackTab) => { self.change_completion_selection(CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } From 6d4409c00ffd35fbb3f92d627a21845b931c609b Mon Sep 17 00:00:00 2001 From: Omnikar Date: Fri, 12 Nov 2021 11:34:49 -0500 Subject: [PATCH 046/156] Make prompts consistent (#1080) --- helix-term/src/commands.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4352ee668..48fd0ee01 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1367,7 +1367,7 @@ fn global_search(cx: &mut Context) { let completions = search_completions(cx, None); let prompt = ui::regex_prompt( cx, - "global search:".into(), + "global-search:".into(), None, move |input: &str| { completions @@ -5154,7 +5154,7 @@ fn add_newline_impl(cx: &mut Context, open: Open) { fn rename_symbol(cx: &mut Context) { let prompt = Prompt::new( - "Rename to: ".into(), + "rename-to:".into(), None, |_input: &str| Vec::new(), move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { From b824e091a948c076a428fb981cd5be2929378533 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 12 Nov 2021 20:15:41 -0800 Subject: [PATCH 047/156] helix-term/commands: move SCRATCH_BUFFER_NAME to helix-view/document (#1091) This way, the name is accessible everywhere `Document` and related types are. --- helix-term/src/commands.rs | 4 +--- helix-view/src/document.rs | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 48fd0ee01..d5a48c5f7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -13,7 +13,7 @@ use helix_core::{ use helix_view::{ clipboard::ClipboardType, - document::Mode, + document::{Mode, SCRATCH_BUFFER_NAME}, editor::{Action, Motion}, input::KeyEvent, keyboard::KeyCode, @@ -53,8 +53,6 @@ use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use ignore::{DirEntry, WalkBuilder, WalkState}; use tokio_stream::wrappers::UnboundedReceiverStream; -pub const SCRATCH_BUFFER_NAME: &str = "[scratch]"; - pub struct Context<'a> { pub register: Option, pub count: Option, diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 80f6a740f..6b4291512 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -25,6 +25,8 @@ const BUF_SIZE: usize = 8192; const DEFAULT_INDENT: IndentStyle = IndentStyle::Spaces(4); +pub const SCRATCH_BUFFER_NAME: &str = "[scratch]"; + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Mode { Normal, From b74912ea78079cde3e1ee3b2dc1a2a6d68568a36 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sat, 13 Nov 2021 12:44:51 -0800 Subject: [PATCH 048/156] helix-term/editor: display scratch buffer name in status bar --- helix-term/src/ui/editor.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 90f09e9c9..dcf872034 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -16,7 +16,7 @@ use helix_core::{ LineEnding, Position, Range, Selection, }; use helix_view::{ - document::Mode, + document::{Mode, SCRATCH_BUFFER_NAME}, editor::LineNumber, graphics::{CursorKind, Modifier, Rect, Style}, info::Info, @@ -580,18 +580,20 @@ impl EditorView { } surface.set_string(viewport.x + 5, viewport.y, progress, base_style); - if let Some(path) = doc.relative_path() { - let path = path.to_string_lossy(); + let rel_path = doc.relative_path(); + let path = rel_path + .as_ref() + .map(|p| p.to_string_lossy()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); - let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }); - surface.set_stringn( - viewport.x + 8, - viewport.y, - title, - viewport.width.saturating_sub(6) as usize, - base_style, - ); - } + let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }); + surface.set_stringn( + viewport.x + 8, + viewport.y, + title, + viewport.width.saturating_sub(6) as usize, + base_style, + ); //------------------------------- // Right side of the status line. From 0949a0de7fec9e11f8011693f84b1939b0a6a548 Mon Sep 17 00:00:00 2001 From: Gygaxis Vainhardt <44003709+AloeareV@users.noreply.github.com> Date: Sun, 14 Nov 2021 11:09:02 -0400 Subject: [PATCH 049/156] Add commit hash to version info, if present (#957) * Add commit hash to version info, if present * Rename GIT_HASH to indicate that it includes version, fix linter error * Add whitespace after use statement Co-authored-by: Ivan Tham Co-authored-by: Ivan Tham --- helix-term/build.rs | 12 ++++++++++++ helix-term/src/main.rs | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 helix-term/build.rs diff --git a/helix-term/build.rs b/helix-term/build.rs new file mode 100644 index 000000000..61ffa6f4f --- /dev/null +++ b/helix-term/build.rs @@ -0,0 +1,12 @@ +use std::process::Command; + +fn main() { + let git_hash = Command::new("git") + .args(&["describe", "--dirty"]) + .output() + .map(|x| String::from_utf8(x.stdout).ok()) + .ok() + .flatten() + .unwrap_or_else(|| String::from(env!("CARGO_PKG_VERSION"))); + println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", git_hash); +} diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index e178b339d..3ae4f7c9b 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -66,7 +66,7 @@ FLAGS: -V, --version Prints version information ", env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION"), + env!("VERSION_AND_GIT_HASH"), env!("CARGO_PKG_AUTHORS"), env!("CARGO_PKG_DESCRIPTION"), logpath.display(), @@ -81,7 +81,7 @@ FLAGS: } if args.display_version { - println!("helix {}", env!("CARGO_PKG_VERSION")); + println!("helix {}", env!("VERSION_AND_GIT_HASH")); std::process::exit(0); } From 35c974c9c49f9127da3798c9a8e49795b3c4aadc Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sun, 14 Nov 2021 16:11:53 +0100 Subject: [PATCH 050/156] Implement "Goto last modification" command (#1067) --- book/src/keymap.md | 1 + helix-core/src/history.rs | 30 +++++++++++++++++++++++++++++- helix-term/src/commands.rs | 14 ++++++++++++++ helix-term/src/keymap.rs | 1 + helix-term/src/ui/editor.rs | 2 +- helix-view/src/document.rs | 2 +- 6 files changed, 47 insertions(+), 3 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index c544a472e..6155e5534 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -165,6 +165,7 @@ Jumps to various locations. | `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` | +| `.` | Go to last modification in current file | `goto_last_modification` | #### Match mode diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index b53c01fe7..bf2624e2e 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -1,4 +1,4 @@ -use crate::{ChangeSet, Rope, State, Transaction}; +use crate::{Assoc, ChangeSet, Rope, State, Transaction}; use once_cell::sync::Lazy; use regex::Regex; use std::num::NonZeroUsize; @@ -133,6 +133,34 @@ impl History { Some(&self.revisions[last_child.get()].transaction) } + // Get the position of last change + pub fn last_edit_pos(&self) -> Option { + if self.current == 0 { + return None; + } + let current_revision = &self.revisions[self.current]; + let primary_selection = current_revision + .inversion + .selection() + .expect("inversion always contains a selection") + .primary(); + let (_from, to, _fragment) = current_revision + .transaction + .changes_iter() + // find a change that matches the primary selection + .find(|(from, to, _fragment)| { + crate::Range::new(*from, *to).overlaps(&primary_selection) + }) + // or use the first change + .or_else(|| current_revision.transaction.changes_iter().next()) + .unwrap(); + let pos = current_revision + .transaction + .changes() + .map_pos(to, Assoc::After); + Some(pos) + } + fn lowest_common_ancestor(&self, mut a: usize, mut b: usize) -> usize { use std::collections::HashSet; let mut a_path_set = HashSet::new(); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d5a48c5f7..e37265a82 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -257,6 +257,7 @@ impl Command { goto_window_middle, "Goto window middle", goto_window_bottom, "Goto window bottom", goto_last_accessed_file, "Goto last accessed file", + goto_last_modification, "Goto last modification", goto_line, "Goto line", goto_last_line, "Goto last line", goto_first_diag, "Goto first diagnostic", @@ -3195,6 +3196,19 @@ fn goto_last_accessed_file(cx: &mut Context) { } } +fn goto_last_modification(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let pos = doc.history.get_mut().last_edit_pos(); + let text = doc.text().slice(..); + if let Some(pos) = 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 select_mode(cx: &mut Context) { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index e3e019957..b14b1a6fe 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -525,6 +525,7 @@ impl Default for Keymaps { "a" => goto_last_accessed_file, "n" => goto_next_buffer, "p" => goto_previous_buffer, + "." => goto_last_modification, }, ":" => command_mode, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dcf872034..bcd9f8f03 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -743,7 +743,7 @@ impl EditorView { std::num::NonZeroUsize::new(cxt.editor.count.map_or(i, |c| c.get() * 10 + i)); } // special handling for repeat operator - key!('.') => { + key!('.') if self.keymaps.pending().is_empty() => { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); // then replay the inputs diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 6b4291512..76b19a07d 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -98,7 +98,7 @@ pub struct Document { // It can be used as a cell where we will take it out to get some parts of the history and put // it back as it separated from the edits. We could split out the parts manually but that will // be more troublesome. - history: Cell, + pub history: Cell, pub savepoint: Option, From 1817b7f581f579232a216f92e0cac569b1a80c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 15 Nov 2021 00:12:14 +0900 Subject: [PATCH 051/156] minor: Import Range too --- helix-core/src/history.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index bf2624e2e..9fe7e5302 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -1,4 +1,4 @@ -use crate::{Assoc, ChangeSet, Rope, State, Transaction}; +use crate::{Assoc, ChangeSet, Range, Rope, State, Transaction}; use once_cell::sync::Lazy; use regex::Regex; use std::num::NonZeroUsize; @@ -148,9 +148,7 @@ impl History { .transaction .changes_iter() // find a change that matches the primary selection - .find(|(from, to, _fragment)| { - crate::Range::new(*from, *to).overlaps(&primary_selection) - }) + .find(|(from, to, _fragment)| Range::new(*from, *to).overlaps(&primary_selection)) // or use the first change .or_else(|| current_revision.transaction.changes_iter().next()) .unwrap(); From edc976b6bb36c6017bf59691abbde5c086267bfd Mon Sep 17 00:00:00 2001 From: Ebbe Steenhoudt Date: Sun, 14 Nov 2021 16:12:56 +0100 Subject: [PATCH 052/156] Added workspace_symbol_picker (#1041) * Added workspace_symbol_picker * Moved truncation of the symbol pickers to the end. * Fixed typo --- book/src/keymap.md | 1 + helix-term/src/commands.rs | 66 ++++++++++++++++++++++++++++++++++++- helix-term/src/keymap.rs | 1 + helix-term/src/ui/picker.rs | 8 ++++- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 6155e5534..9f1714f68 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -211,6 +211,7 @@ This layer is a kludge of mappings, mostly pickers. | `b` | Open buffer picker | `buffer_picker` | | `k` | Show documentation for item under cursor (**LSP**) | `hover` | | `s` | Open document symbol picker (**LSP**) | `symbol_picker` | +| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | | `r` | Rename symbol (**LSP**) | `rename_symbol` | | `a` | Apply code action (**LSP**) | `code_action` | | `'` | Open last fuzzy picker | `last_picker` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e37265a82..ebacb377d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -237,6 +237,7 @@ impl Command { code_action, "Perform code action", buffer_picker, "Open buffer picker", symbol_picker, "Open symbol picker", + workspace_symbol_picker, "Open workspace symbol picker", last_picker, "Open last picker", prepend_to_line, "Insert at start of line", append_to_line, "Insert at end of line", @@ -2723,7 +2724,7 @@ fn symbol_picker(cx: &mut Context) { } }; - let picker = FilePicker::new( + let mut picker = FilePicker::new( symbols, |symbol| (&symbol.name).into(), move |editor: &mut Editor, symbol, _action| { @@ -2748,6 +2749,69 @@ fn symbol_picker(cx: &mut Context) { Some((path, line)) }, ); + picker.truncate_start = false; + compositor.push(Box::new(picker)) + } + }, + ) +} + +fn workspace_symbol_picker(cx: &mut Context) { + let (_, doc) = current!(cx.editor); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + let offset_encoding = language_server.offset_encoding(); + + let future = language_server.workspace_symbols("".to_string()); + + let current_path = doc_mut!(cx.editor).path().cloned(); + cx.callback( + future, + move |_editor: &mut Editor, + compositor: &mut Compositor, + response: Option>| { + if let Some(symbols) = response { + let mut picker = FilePicker::new( + symbols, + move |symbol| { + let path = symbol.location.uri.to_file_path().unwrap(); + if current_path.as_ref().map(|p| p == &path).unwrap_or(false) { + (&symbol.name).into() + } else { + let relative_path = helix_core::path::get_relative_path(path.as_path()) + .to_str() + .unwrap() + .to_owned(); + format!("{} ({})", &symbol.name, relative_path).into() + } + }, + move |editor: &mut Editor, symbol, action| { + let path = symbol.location.uri.to_file_path().unwrap(); + editor.open(path, action).expect("editor.open failed"); + let (view, doc) = current!(editor); + + if let Some(range) = + lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding) + { + // we flip the range so that the cursor sits on the start of the symbol + // (for example start of the function). + doc.set_selection(view.id, Selection::single(range.head, range.anchor)); + align_view(doc, view, Align::Center); + } + }, + move |_editor, symbol| { + let path = symbol.location.uri.to_file_path().unwrap(); + let line = Some(( + symbol.location.range.start.line as usize, + symbol.location.range.end.line as usize, + )); + Some((path, line)) + }, + ); + picker.truncate_start = false; compositor.push(Box::new(picker)) } }, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b14b1a6fe..3280f0f84 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -641,6 +641,7 @@ impl Default for Keymaps { "f" => file_picker, "b" => buffer_picker, "s" => symbol_picker, + "S" => workspace_symbol_picker, "a" => code_action, "'" => last_picker, "w" => { "Window" diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index c44b76250..6b1c58321 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -37,6 +37,7 @@ type FileLocation = (PathBuf, Option<(usize, usize)>); pub struct FilePicker { picker: Picker, + pub truncate_start: bool, /// Caches paths to documents preview_cache: HashMap, read_buffer: Vec, @@ -90,6 +91,7 @@ impl FilePicker { ) -> Self { Self { picker: Picker::new(false, options, format_fn, callback_fn), + truncate_start: true, preview_cache: HashMap::new(), read_buffer: Vec::with_capacity(1024), file_fn: Box::new(preview_fn), @@ -172,6 +174,7 @@ impl Component for FilePicker { }; let picker_area = area.with_width(picker_width); + self.picker.truncate_start = self.truncate_start; self.picker.render(picker_area, surface, cx); if !render_preview { @@ -277,6 +280,8 @@ pub struct Picker { prompt: Prompt, /// Whether to render in the middle of the area render_centered: bool, + /// Wheather to truncate the start (default true) + pub truncate_start: bool, format_fn: Box Cow>, callback_fn: Box, @@ -306,6 +311,7 @@ impl Picker { cursor: 0, prompt, render_centered, + truncate_start: true, format_fn: Box::new(format_fn), callback_fn: Box::new(callback_fn), }; @@ -521,7 +527,7 @@ impl Component for Picker { text_style }, true, - true, + self.truncate_start, ); } } From 6fa76d9fe77d43ebc18cc78a6a1c1957d72cf59b Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sun, 14 Nov 2021 16:16:20 +0100 Subject: [PATCH 053/156] Add trim_selections command (#1092) --- book/src/keymap.md | 1 + helix-core/src/movement.rs | 2 +- helix-term/src/commands.rs | 37 +++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 2 +- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 9f1714f68..69f5f02cd 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -88,6 +88,7 @@ | `s` | Select all regex matches inside selections | `select_regex` | | `S` | Split selection into subselections on regex matches | `split_selection` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` | +| `_` | Trim whitespace from the selection | `trim_selections` | | `;` | Collapse selection onto a single cursor | `collapse_selection` | | `Alt-;` | Flip selection cursor and anchor | `flip_selections` | | `,` | Keep only the primary selection | `keep_primary_selection` | diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 9e85bd217..01a8f890e 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -168,7 +168,7 @@ pub fn backwards_skip_while(slice: RopeSlice, pos: usize, fun: F) -> Option bool, { - let mut chars_starting_from_next = slice.chars_at(pos + 1); + let mut chars_starting_from_next = slice.chars_at(pos); let mut backwards = iter::from_fn(|| chars_starting_from_next.prev()).enumerate(); backwards.find_map(|(i, c)| { if !fun(c) { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ebacb377d..56cc02a28 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -272,6 +272,7 @@ impl Command { // TODO: different description ? goto_line_end_newline, "Goto line end", goto_first_nonwhitespace, "Goto first non-blank in line", + trim_selections, "Trim whitespace from selections", extend_to_line_start, "Extend to line start", extend_to_line_end, "Extend to line end", extend_to_line_end_newline, "Extend to line end", @@ -584,6 +585,42 @@ fn goto_first_nonwhitespace(cx: &mut Context) { doc.set_selection(view.id, selection); } +fn trim_selections(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + let ranges: SmallVec<[Range; 1]> = doc + .selection(view.id) + .iter() + .filter_map(|range| { + if range.is_empty() || range.fragment(text).chars().all(|ch| ch.is_whitespace()) { + return None; + } + let mut start = range.from(); + let mut end = range.to(); + start = movement::skip_while(text, start, |x| x.is_whitespace()).unwrap_or(start); + end = movement::backwards_skip_while(text, end, |x| x.is_whitespace()).unwrap_or(end); + if range.anchor < range.head { + Some(Range::new(start, end)) + } else { + Some(Range::new(end, start)) + } + }) + .collect(); + + if !ranges.is_empty() { + let primary = doc.selection(view.id).primary(); + let idx = ranges + .iter() + .position(|range| range.overlaps(&primary)) + .unwrap_or(ranges.len() - 1); + doc.set_selection(view.id, Selection::new(ranges, idx)); + } else { + collapse_selection(cx); + keep_primary_selection(cx); + }; +} + fn goto_window(cx: &mut Context, align: Align) { let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 3280f0f84..fc9fd5907 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -603,7 +603,7 @@ impl Default for Keymaps { // "Q" => replay_macro, // & align selections - // _ trim selections + "_" => trim_selections, "(" => rotate_selections_backward, ")" => rotate_selections_forward, From b7c3877e947d95ee0fd9b1653dab6f65bb340439 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 14 Nov 2021 23:16:47 +0800 Subject: [PATCH 054/156] Add movement shortcut for history (#1088) alt-u and alt-U --- book/src/keymap.md | 2 ++ helix-core/src/history.rs | 2 +- helix-term/src/commands.rs | 60 +++++++++++++++++++++++++++----------- helix-term/src/keymap.rs | 2 ++ 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 69f5f02cd..88610a779 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -61,6 +61,8 @@ | `.` | Repeat last change | N/A | | `u` | Undo change | `undo` | | `U` | Redo change | `redo` | +| `Alt-u` | Move backward in history | `earlier` | +| `Alt-U` | Move forward in history | `later` | | `y` | Yank selection | `yank` | | `p` | Paste after selection | `paste_after` | | `P` | Paste before selection | `paste_before` | diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 9fe7e5302..4b1c8d3b3 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -282,7 +282,7 @@ impl History { } /// Whether to undo by a number of edits or a duration of time. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum UndoKind { Steps(usize), TimePeriod(std::time::Duration), diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 56cc02a28..115d17890 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,5 +1,7 @@ use helix_core::{ - comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent, + comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, + history::UndoKind, + indent, indent::IndentStyle, line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, @@ -284,6 +286,8 @@ impl Command { delete_word_backward, "Delete previous word", undo, "Undo change", redo, "Redo change", + earlier, "Move backward in history", + later, "Move forward in history", yank, "Yank selection", yank_joined_to_clipboard, "Join and yank selections to clipboard", yank_main_selection_to_clipboard, "Yank main selection to clipboard", @@ -1877,10 +1881,7 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let uk = args - .join(" ") - .parse::() - .map_err(|s| anyhow!(s))?; + let uk = args.join(" ").parse::().map_err(|s| anyhow!(s))?; let (view, doc) = current!(cx.editor); let success = doc.earlier(view.id, uk); @@ -1896,10 +1897,7 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let uk = args - .join(" ") - .parse::() - .map_err(|s| anyhow!(s))?; + let uk = args.join(" ").parse::().map_err(|s| anyhow!(s))?; let (view, doc) = current!(cx.editor); let success = doc.later(view.id, uk); if !success { @@ -3963,20 +3961,48 @@ pub mod insert { // storing it? fn undo(cx: &mut Context) { + let count = cx.count(); let (view, doc) = current!(cx.editor); - let view_id = view.id; - let success = doc.undo(view_id); - if !success { - cx.editor.set_status("Already at oldest change".to_owned()); + for _ in 0..count { + if !doc.undo(view.id) { + cx.editor.set_status("Already at oldest change".to_owned()); + break; + } } } fn redo(cx: &mut Context) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + for _ in 0..count { + if !doc.redo(view.id) { + cx.editor.set_status("Already at newest change".to_owned()); + break; + } + } +} + +fn earlier(cx: &mut Context) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + for _ in 0..count { + // rather than doing in batch we do this so get error halfway + if !doc.earlier(view.id, UndoKind::Steps(1)) { + cx.editor.set_status("Already at oldest change".to_owned()); + break; + } + } +} + +fn later(cx: &mut Context) { + let count = cx.count(); let (view, doc) = current!(cx.editor); - let view_id = view.id; - let success = doc.redo(view_id); - if !success { - cx.editor.set_status("Already at newest change".to_owned()); + for _ in 0..count { + // rather than doing in batch we do this so get error halfway + if !doc.later(view.id, UndoKind::Steps(1)) { + cx.editor.set_status("Already at newest change".to_owned()); + break; + } } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index fc9fd5907..010714dcb 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -582,6 +582,8 @@ impl Default for Keymaps { "u" => undo, "U" => redo, + "A-u" => earlier, + "A-U" => later, "y" => yank, // yank_all From 8f7ada12ac7ed16276dbf2176c7d91ddcb721f97 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Sun, 14 Nov 2021 19:29:39 -0500 Subject: [PATCH 055/156] Solarized dark theme (#999) * init * wip * wip --- runtime/themes/solarized_dark.toml | 109 +++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 runtime/themes/solarized_dark.toml diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml new file mode 100644 index 000000000..499c171ab --- /dev/null +++ b/runtime/themes/solarized_dark.toml @@ -0,0 +1,109 @@ +"attribute" = { fg = "violet" } +"keyword" = { fg = "green" } +"keyword.directive" = { fg = "orange" } +"namespace" = { fg = "violet" } +"punctuation" = { fg = "orange" } +"punctuation.delimiter" = { fg = "orange" } +"operator" = { fg = "green" } +"special" = { fg = "orange" } +# "property" = { fg = "cyan" } +"variable" = { fg = "cyan" } +"variable.parameter" = { fg = "cyan" } +"variable.builtin" = { fg = "cyan", modifiers = ["bold"] } +"variable.other.member" = { fg = "cyan" } +"variable.function" = { fg = "blue" } +"type" = { fg = "yellow" } +"type.builtin" = { fg = "yellow", modifiers = ["bold"] } +"constructor" = { fg = "blue" } +"function" = { fg = "blue" } +"function.macro" = { fg = "magenta" } +"function.builtin" = { fg = "blue", modifiers = ["bold"] } +"function.special" = { fg = "magenta" } +"comment" = { fg = "base01", modifiers = ["italic"] } +"string" = { fg= "base1" } +"constant" = { fg = "base1" } +"constant.character" = { fg = "base1" } +"constant.builtin" = { fg = "base1", modifiers = ["bold"] } +"constant.numeric" = { fg= "base1" } +"constant.numeric.integer" = { fg= "base1" } +"constant.numeric.float" = { fg= "base1" } +"constant.character.escape" = { fg = "red", modifiers = ["bold"] } +"label" = { fg = "green" } +"module" = { fg = "violet" } +"tag" = { fg = "magenta" } + +# 背景 +"ui.background" = { bg = "base03" } + +# 行号栏 +"ui.linenr" = { fg = "base0", bg = "base02" } +# 当前行号栏 +"ui.linenr.selected" = { fg = "red", modifiers = ["bold"] } + +# 状态栏 +"ui.statusline" = { fg = "base02", bg = "base1" } +# 非活动状态栏 +"ui.statusline.inactive" = { fg = "base02", bg = "base00" } + +# 补全窗口, preview窗口 +"ui.popup" = { bg = "base1" } +# 影响 补全选中 cmd弹出信息选中 +"ui.menu.selected" = { fg = "base02", bg = "violet"} +"ui.menu" = { fg = "base02" } +# ?? +"ui.window" = { fg = "base3" } +# 命令行 补全的帮助信息 +"ui.help" = { modifiers = ["reversed"] } + +# 快捷键窗口 +"ui.info" = { bg = "base1" } +# 快捷键字体 +"ui.info.text" = {fg = "base02", modifiers = ["bold"]} + +# 普通ui的字体样式 +"ui.text" = { fg = "base1" } +# 影响 picker列表选中, 快捷键帮助窗口文本 +"ui.text.focus" = { fg = "blue", modifiers = ["bold"]} +# file picker中, 预览的当前选中项 +"ui.highlight" = { fg = "red", modifiers = ["bold", "italic", "underlined"] } + +# 主光标/selectio +"ui.cursor.primary" = {fg = "base03", bg = "base1"} +"ui.selection.primary" = { fg = "base03", bg = "base01" } +"ui.cursor.select" = {fg = "base02", bg = "green"} +"ui.selection" = { fg = "base02", bg = "yellow" } + +# normal模式的光标 +"ui.cursor" = {fg = "base03", bg = "green"} +"ui.cursor.insert" = {fg = "base03", bg = "base3"} +# 当前光标匹配的标点符号 +"ui.cursor.match" = {modifiers = ["reversed"]} + + +"warning" = { fg = "orange", modifiers= ["bold", "underlined"] } +"error" = { fg = "red", modifiers= ["bold", "underlined"] } +"info" = { fg = "blue", modifiers= ["bold", "underlined"] } +"hint" = { fg = "base01", modifiers= ["bold", "underlined"] } +"diagnostic" = { mdifiers = ["underlined"] } + +[palette] +red = '#dc322f' +green = '#859900' +yellow = '#b58900' +blue = '#268bd2' +magenta = '#d33682' +cyan = '#2aa198' +orange = '#cb4b16' +violet = '#6c71c4' + +# 深色 越来越深 +base0 = '#839496' +base1 = '#93a1a1' +base2 = '#eee8d5' +base3 = '#fdf6e3' + +## 浅色 越來越浅 +base00 = '#657b63' +base01 = '#586e75' +base02 = '#073642' +base03 = '#002b36' From e128a8702eda3d449cfe355b853724d18b03a977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 15 Nov 2021 10:29:07 +0900 Subject: [PATCH 056/156] Implement MarkedString rendering Solves typescript and python documentation rendering --- helix-term/src/commands.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 115d17890..cb1f470b3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4660,18 +4660,28 @@ fn hover(cx: &mut Context) { move |editor: &mut Editor, compositor: &mut Compositor, response: Option| { if let Some(hover) = response { // hover.contents / .range <- used for visualizing - let contents = match hover.contents { - lsp::HoverContents::Scalar(contents) => { - // markedstring(string/languagestring to be highlighted) - // TODO - log::error!("hover contents {:?}", contents); - return; - } - lsp::HoverContents::Array(contents) => { - log::error!("hover contents {:?}", contents); - return; + + fn marked_string_to_markdown(contents: lsp::MarkedString) -> String { + match contents { + lsp::MarkedString::String(contents) => contents, + lsp::MarkedString::LanguageString(string) => { + log::error!("MarkedString {}: {}", string.language, string.value); + if string.language == "markdown" { + string.value + } else { + format!("```{}\n{}\n```", string.language, string.value) + } + } } - // TODO: render markdown + } + + let contents = match hover.contents { + lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents), + lsp::HoverContents::Array(contents) => contents + .into_iter() + .map(marked_string_to_markdown) + .collect::>() + .join("\n\n"), lsp::HoverContents::Markup(contents) => contents.value, }; From f5e070e808d2edaeae04497c01f5a0f813407956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 15 Nov 2021 10:33:14 +0900 Subject: [PATCH 057/156] minor: Remove leftover log line --- helix-term/src/commands.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cb1f470b3..ab62c1aa5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4665,7 +4665,6 @@ fn hover(cx: &mut Context) { match contents { lsp::MarkedString::String(contents) => contents, lsp::MarkedString::LanguageString(string) => { - log::error!("MarkedString {}: {}", string.language, string.value); if string.language == "markdown" { string.value } else { From 87e61a0894d6af1838c5d288fae83279004026fb Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sun, 14 Nov 2021 20:06:12 -0800 Subject: [PATCH 058/156] helix-term/commands: implement cquit (#1096) This allows you to exit helix with an exit code, e.g. `:cq 2`. --- helix-term/src/application.rs | 4 ++-- helix-term/src/commands.rs | 26 ++++++++++++++++++++++++++ helix-term/src/main.rs | 12 +++++++++--- helix-view/src/editor.rs | 3 +++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b04eef0d8..2969a9e58 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -599,7 +599,7 @@ impl Application { Ok(()) } - pub async fn run(&mut self) -> Result<(), Error> { + pub async fn run(&mut self) -> Result { self.claim_term().await?; // Exit the alternate screen and disable raw mode before panicking @@ -622,6 +622,6 @@ impl Application { self.restore_term()?; - Ok(()) + Ok(self.editor.exit_code) } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ab62c1aa5..8c0a005cc 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2042,6 +2042,25 @@ mod cmd { quit_all_impl(&mut cx.editor, args, event, true) } + fn cquit( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let exit_code = args + .first() + .and_then(|code| code.parse::().ok()) + .unwrap_or(1); + cx.editor.exit_code = exit_code; + + let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); + for view_id in views { + cx.editor.close(view_id, false); + } + + Ok(()) + } + fn theme( cx: &mut compositor::Context, args: &[&str], @@ -2411,6 +2430,13 @@ mod cmd { fun: force_quit_all, completer: None, }, + TypableCommand { + name: "cquit", + aliases: &["cq"], + doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).", + fun: cquit, + completer: None, + }, TypableCommand { name: "theme", aliases: &[], diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 3ae4f7c9b..6fa1ce671 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -38,8 +38,13 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { Ok(()) } +fn main() -> Result<()> { + let exit_code = main_impl()?; + std::process::exit(exit_code); +} + #[tokio::main] -async fn main() -> Result<()> { +async fn main_impl() -> Result { let cache_dir = helix_core::cache_dir(); if !cache_dir.exists() { std::fs::create_dir_all(&cache_dir).ok(); @@ -109,7 +114,8 @@ FLAGS: // TODO: use the thread local executor to spawn the application task separately from the work pool let mut app = Application::new(args, config).context("unable to create new application")?; - app.run().await.unwrap(); - Ok(()) + let exit_code = app.run().await?; + + Ok(exit_code) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7650d217f..e40157076 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -129,6 +129,8 @@ pub struct Editor { pub idle_timer: Pin>, pub last_motion: Option, + + pub exit_code: i32, } #[derive(Debug, Copy, Clone)] @@ -167,6 +169,7 @@ impl Editor { idle_timer: Box::pin(sleep(config.idle_timeout)), last_motion: None, config, + exit_code: 0, } } From cccc1949ebabcbc1b336f370847626c9d6774fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 15 Nov 2021 14:36:38 +0900 Subject: [PATCH 059/156] Enable thin LTO It compiles about half a second slower for me, so it seems fine to use by default. --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 22d292600..580cccd64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ members = [ [profile.dev] split-debuginfo = "unpacked" + +[profile.release] +lto = "thin" From c638b6b60e69697b7e7957ed1af1ac071c41974b Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 15 Nov 2021 07:30:45 -0800 Subject: [PATCH 060/156] helix-term/commands: implement buffer-close (bc, bclose) (#1035) * helix-view/view: impl method to remove document from jumps * helix-view/editor: impl close_document * helix-view/editor: remove close_buffer argument from `close` According to archseer, this was never implemented or used properly. Now that we have a proper "buffer close" function, we can get rid of this. * helix-term/commands: implement buffer-close (bc, bclose) This behaves the same as Kakoune's `delete-buffer` / `db` command: * With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`: * `buffer-close` once closes `ef` and switches to `cd` * `buffer-close` again closes `cd` and switches to `ab` * `buffer-close` again closes `ab` and switches to a scratch buffer * With 3 files opened from the command line with `hx -- ab cd ef`: * `buffer-close` once closes `ab` and switches to `cd` * `buffer-close` again closes `cd` and switches to `ef` * `buffer-close` again closes `ef` and switches to a scratch buffer * With 1 file opened (`ab`): * `buffer-close` once closes `ab` and switches to a scratch buffer * `buffer-close` again closes the scratch buffer and switches to a new scratch buffer * helix-term/commands: implement buffer-close! (bclose!, bc!) Namely, if you have a document open in multiple splits, all the splits will be closed at the same time, leaving only splits without that document focused (or a scratch buffer if they were all focused on that buffer). * helix-view/tree: reset focus if Tree is empty --- helix-term/src/commands.rs | 50 +++++++++++++--- helix-view/src/editor.rs | 113 ++++++++++++++++++++++++++++--------- helix-view/src/tree.rs | 3 + helix-view/src/view.rs | 4 ++ 4 files changed, 135 insertions(+), 35 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8c0a005cc..c7aab726d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1700,8 +1700,7 @@ mod cmd { buffers_remaining_impl(cx.editor)? } - cx.editor - .close(view!(cx.editor).id, /* close_buffer */ false); + cx.editor.close(view!(cx.editor).id); Ok(()) } @@ -1711,8 +1710,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor - .close(view!(cx.editor).id, /* close_buffer */ false); + cx.editor.close(view!(cx.editor).id); Ok(()) } @@ -1730,6 +1728,28 @@ mod cmd { Ok(()) } + fn buffer_close( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let view = view!(cx.editor); + let doc_id = view.doc; + cx.editor.close_document(doc_id, false)?; + Ok(()) + } + + fn force_buffer_close( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let view = view!(cx.editor); + let doc_id = view.doc; + cx.editor.close_document(doc_id, true)?; + Ok(()) + } + fn write_impl>( cx: &mut compositor::Context, path: Option

, @@ -1976,7 +1996,7 @@ mod cmd { // close all views let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); for view_id in views { - cx.editor.close(view_id, false); + cx.editor.close(view_id); } } @@ -2020,7 +2040,7 @@ mod cmd { // close all views let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect(); for view_id in views { - editor.close(view_id, false); + editor.close(view_id); } Ok(()) @@ -2332,6 +2352,20 @@ mod cmd { fun: open, completer: Some(completers::filename), }, + TypableCommand { + name: "buffer-close", + aliases: &["bc", "bclose"], + doc: "Close the current buffer.", + fun: buffer_close, + completer: None, // FIXME: buffer completer + }, + TypableCommand { + name: "buffer-close!", + aliases: &["bc!", "bclose!"], + doc: "Close the current buffer forcefully (ignoring unsaved changes).", + fun: force_buffer_close, + completer: None, // FIXME: buffer completer + }, TypableCommand { name: "write", aliases: &["w"], @@ -4914,7 +4948,7 @@ fn wclose(cx: &mut Context) { } let view_id = view!(cx.editor).id; // close current split - cx.editor.close(view_id, /* close_buffer */ false); + cx.editor.close(view_id); } fn wonly(cx: &mut Context) { @@ -4926,7 +4960,7 @@ fn wonly(cx: &mut Context) { .collect::>(); for (view_id, focus) in views { if !focus { - cx.editor.close(view_id, /* close_buffer */ false); + cx.editor.close(view_id); } } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e40157076..4712c52a9 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -22,7 +22,7 @@ use anyhow::Error; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; use helix_core::syntax; -use helix_core::Position; +use helix_core::{Position, Selection}; use serde::Deserialize; @@ -235,9 +235,28 @@ impl Editor { } } + fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) { + let view = self.tree.get_mut(current_view); + view.doc = doc_id; + view.offset = Position::default(); + + let doc = self.documents.get_mut(&doc_id).unwrap(); + + // initialize selection for view + doc.selections + .entry(view.id) + .or_insert_with(|| Selection::point(0)); + // TODO: reuse align_view + let pos = doc + .selection(view.id) + .primary() + .cursor(doc.text().slice(..)); + let line = doc.text().char_to_line(pos); + view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2); + } + pub fn switch(&mut self, id: DocumentId, action: Action) { use crate::tree::Layout; - use helix_core::Selection; if !self.documents.contains_key(&id) { log::error!("cannot switch to document that does not exist (anymore)"); @@ -271,22 +290,9 @@ impl Editor { view.jumps.push(jump); view.last_accessed_doc = Some(view.doc); } - view.doc = id; - view.offset = Position::default(); - - let (view, doc) = current!(self); - // initialize selection for view - doc.selections - .entry(view.id) - .or_insert_with(|| Selection::point(0)); - // TODO: reuse align_view - let pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - let line = doc.text().char_to_line(pos); - view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2); + let view_id = view.id; + self.replace_document_in_view(view_id, id); return; } @@ -318,11 +324,16 @@ impl Editor { self._refresh(); } - fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId { + fn new_document(&mut self, mut document: Document) -> DocumentId { let id = DocumentId(self.next_document_id); self.next_document_id += 1; document.id = id; self.documents.insert(id, document); + id + } + + fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId { + let id = self.new_document(document); self.switch(id, action); id } @@ -392,7 +403,7 @@ impl Editor { Ok(id) } - pub fn close(&mut self, id: ViewId, close_buffer: bool) { + pub fn close(&mut self, id: ViewId) { let view = self.tree.get(self.tree.focus); // remove selection self.documents @@ -401,18 +412,66 @@ impl Editor { .selections .remove(&id); - if close_buffer { - // get around borrowck issues - let doc = &self.documents[&view.doc]; + self.tree.remove(id); + self._refresh(); + } - if let Some(language_server) = doc.language_server() { - tokio::spawn(language_server.text_document_did_close(doc.identifier())); - } - self.documents.remove(&view.doc); + pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> { + let doc = match self.documents.get(&doc_id) { + Some(doc) => doc, + None => anyhow::bail!("document does not exist"), + }; + + if !force && doc.is_modified() { + anyhow::bail!( + "buffer {:?} is modified", + doc.relative_path() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| "[scratch]".into()) + ); + } + + if let Some(language_server) = doc.language_server() { + tokio::spawn(language_server.text_document_did_close(doc.identifier())); + } + + let views_to_close = self + .tree + .views() + .filter_map(|(view, _focus)| { + if view.doc == doc_id { + Some(view.id) + } else { + None + } + }) + .collect::>(); + + for view_id in views_to_close { + self.close(view_id); + } + + self.documents.remove(&doc_id); + + // If the document we removed was visible in all views, we will have no more views. We don't + // want to close the editor just for a simple buffer close, so we need to create a new view + // containing either an existing document, or a brand new document. + if self.tree.views().peekable().peek().is_none() { + let doc_id = self + .documents + .iter() + .map(|(&doc_id, _)| doc_id) + .next() + .unwrap_or_else(|| self.new_document(Document::default())); + let view = View::new(doc_id); + let view_id = self.tree.insert(view); + let doc = self.documents.get_mut(&doc_id).unwrap(); + doc.selections.insert(view_id, Selection::point(0)); } - self.tree.remove(id); self._refresh(); + + Ok(()) } pub fn resize(&mut self, area: Rect) { diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 064334b12..de5046ace 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -314,6 +314,9 @@ impl Tree { pub fn recalculate(&mut self) { if self.is_empty() { + // There are no more views, so the tree should focus itself again. + self.focus = self.root; + return; } diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 6a624ded2..a77f15629 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -54,6 +54,10 @@ impl JumpList { None } } + + pub fn remove(&mut self, doc_id: &DocumentId) { + self.jumps.retain(|(other_id, _)| other_id != doc_id); + } } #[derive(Debug)] From 46d9ae2b62f5b8494c527e0f8475509ce5fad095 Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 15 Nov 2021 23:31:20 +0800 Subject: [PATCH 061/156] Readline style insert mode (#1039) * readline style insert mode * update keymap.md * don't save change history in insert mode * Revert "don't save change history in insert mode" This reverts commit cb47f946d7fb62ceda68e7d1692a3914d0be7762. * don't affect register and history in insert mode * add insert_register * don't call exit_select_mode in insert mode * avoid set_selection * avoid duplicated current! --- book/src/keymap.md | 30 +++++++++++++---- helix-term/src/commands.rs | 67 +++++++++++++++++++++++++++++++++++-- helix-term/src/keymap.rs | 17 ++++++++++ helix-term/src/ui/prompt.rs | 9 +++++ 4 files changed, 114 insertions(+), 9 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 88610a779..7a8960355 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -243,11 +243,26 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire ## Insert Mode -| Key | Description | Command | -| ----- | ----------- | ------- | -| `Escape` | Switch to normal mode | `normal_mode` | -| `Ctrl-x` | Autocomplete | `completion` | -| `Ctrl-w` | Delete previous word | `delete_word_backward` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `Escape` | Switch to normal mode | `normal_mode` | +| `Ctrl-x` | Autocomplete | `completion` | +| `Ctrl-r` | Insert a register content | `insert_register` | +| `Ctrl-w` | Delete previous word | `delete_word_backward` | +| `Alt-d` | Delete next word | `delete_word_forward` | +| `Alt-b`, `Alt-Left` | Backward a word | `move_prev_word_end` | +| `Ctrl-b`, `Left` | Backward a char | `move_char_left` | +| `Alt-f`, `Alt-Right` | Forward a word | `move_next_word_start` | +| `Ctrl-f`, `Right` | Forward a char | `move_char_right` | +| `Ctrl-e`, `End` | move to line end | `goto_line_end_newline` | +| `Ctrl-a`, `Home` | move to line start | `goto_line_start` | +| `Ctrl-w` | delete previous word | `delete_word_backwar` | +| `Ctrl-u` | delete to start of line | `kill_to_line_start` | +| `Ctrl-k` | delete to end of line | `kill_to_line_end` | +| `backspace`, `Ctrl-h` | delete previous char | `delete_char_backward` | +| `delete`, `Ctrl-d` | delete previous char | `delete_char_forward` | +| `Ctrl-p`, `Up` | move to previous line | `move_line_up` | +| `Ctrl-n`, `Down` | move to next line | `move_line_down` | ## Select / extend mode @@ -285,6 +300,7 @@ Keys to use within prompt, Remapping currently not supported. | `Ctrl-e`, `End` | Move prompt end | | `Ctrl-a`, `Home` | Move prompt start | | `Ctrl-w` | Delete previous word | +| `Alt-d` | Delete next word | | `Ctrl-u` | Delete to start of line | | `Ctrl-k` | Delete to end of line | | `backspace`, `Ctrl-h` | Delete previous char | @@ -292,7 +308,7 @@ Keys to use within prompt, Remapping currently not supported. | `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` | Select next completion item | -| `BackTab` | Select previous completion item | +| `Tab` | Select next completion item | +| `BackTab` | Select previous completion item | | `Enter` | Open selected | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c7aab726d..74bc52fdf 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -187,6 +187,7 @@ impl Command { copy_selection_on_prev_line, "Copy selection on previous line", move_next_word_start, "Move to beginning of next word", move_prev_word_start, "Move to beginning of previous word", + move_prev_word_end, "Move to end of previous word", move_next_word_end, "Move to end of next word", move_next_long_word_start, "Move to beginning of next long word", move_prev_long_word_start, "Move to beginning of previous long word", @@ -284,6 +285,9 @@ impl Command { delete_char_backward, "Delete previous char", delete_char_forward, "Delete next char", delete_word_backward, "Delete previous word", + delete_word_forward, "Delete next word", + kill_to_line_start, "Delete content till the start of the line", + kill_to_line_end, "Delete content till the end of the line", undo, "Undo change", redo, "Redo change", earlier, "Move backward in history", @@ -330,6 +334,7 @@ impl Command { wclose, "Close window", wonly, "Current window only", select_register, "Select register", + insert_register, "Insert register", align_view_middle, "Align view middle", align_view_top, "Align view top", align_view_center, "Align view center", @@ -572,6 +577,29 @@ fn extend_to_line_start(cx: &mut Context) { goto_line_start_impl(view, doc, Movement::Extend) } +fn kill_to_line_start(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + let selection = doc.selection(view.id).clone().transform(|range| { + let line = range.cursor_line(text); + range.put_cursor(text, text.line_to_char(line), true) + }); + delete_selection_insert_mode(doc, view, &selection); +} + +fn kill_to_line_end(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + let selection = doc.selection(view.id).clone().transform(|range| { + let line = range.cursor_line(text); + let pos = line_end_char_index(&text, line); + range.put_cursor(text, pos, true) + }); + delete_selection_insert_mode(doc, view, &selection); +} + fn goto_first_nonwhitespace(cx: &mut Context) { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); @@ -684,6 +712,10 @@ fn move_prev_word_start(cx: &mut Context) { move_word_impl(cx, movement::move_prev_word_start) } +fn move_prev_word_end(cx: &mut Context) { + move_word_impl(cx, movement::move_prev_word_end) +} + fn move_next_word_end(cx: &mut Context) { move_word_impl(cx, movement::move_next_word_end) } @@ -1586,6 +1618,17 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId doc.apply(&transaction, view_id); } +#[inline] +fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Selection) { + let view_id = view.id; + + // then delete + let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + (range.from(), range.to(), None) + }); + doc.apply(&transaction, view_id); +} + fn delete_selection(cx: &mut Context) { let reg_name = cx.register.unwrap_or('"'); let (view, doc) = current!(cx.editor); @@ -4010,8 +4053,19 @@ pub mod insert { .selection(view.id) .clone() .transform(|range| movement::move_prev_word_start(text, range, count)); - doc.set_selection(view.id, selection); - delete_selection(cx) + delete_selection_insert_mode(doc, view, &selection); + } + + pub fn delete_word_forward(cx: &mut Context) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + let selection = doc + .selection(view.id) + .clone() + .transform(|range| movement::move_next_word_start(text, range, count)); + delete_selection_insert_mode(doc, view, &selection); } } @@ -4973,6 +5027,15 @@ fn select_register(cx: &mut Context) { }) } +fn insert_register(cx: &mut Context) { + cx.on_next_key(move |cx, event| { + if let Some(ch) = event.char() { + cx.editor.selected_register = Some(ch); + paste_before(cx); + } + }) +} + fn align_view_top(cx: &mut Context) { let (view, doc) = current!(cx.editor); align_view(doc, view, Align::Top); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 010714dcb..fdf43d87e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -731,21 +731,38 @@ impl Default for Keymaps { "esc" => normal_mode, "backspace" => delete_char_backward, + "C-h" => delete_char_backward, "del" => delete_char_forward, + "C-d" => delete_char_forward, "ret" => insert_newline, "tab" => insert_tab, "C-w" => delete_word_backward, + "A-d" => delete_word_forward, "left" => move_char_left, + "C-b" => move_char_left, "down" => move_line_down, + "C-n" => move_line_down, "up" => move_line_up, + "C-p" => move_line_up, "right" => move_char_right, + "C-f" => move_char_right, + "A-b" => move_prev_word_end, + "A-left" => move_prev_word_end, + "A-f" => move_next_word_start, + "A-right" => move_next_word_start, "pageup" => page_up, "pagedown" => page_down, "home" => goto_line_start, + "C-a" => goto_line_start, "end" => goto_line_end_newline, + "C-e" => goto_line_end_newline, + + "C-k" => kill_to_line_end, + "C-u" => kill_to_line_start, "C-x" => completion, + "C-r" => insert_register, }); Keymaps(hashmap!( Mode::Normal => Keymap::new(normal), diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index deed16094..e90b07727 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -231,6 +231,14 @@ impl Prompt { self.completion = (self.completion_fn)(&self.line); } + pub fn delete_word_forwards(&mut self) { + let pos = self.eval_movement(Movement::ForwardWord(1)); + self.line.replace_range(self.cursor..pos, ""); + + self.exit_selection(); + self.completion = (self.completion_fn)(&self.line); + } + pub fn kill_to_start_of_line(&mut self) { let pos = self.eval_movement(Movement::StartOfLine); self.line.replace_range(pos..self.cursor, ""); @@ -435,6 +443,7 @@ impl Component for Prompt { ctrl!('e') | key!(End) => self.move_end(), ctrl!('a') | key!(Home) => self.move_start(), ctrl!('w') => self.delete_word_backwards(), + alt!('d') => self.delete_word_forwards(), ctrl!('k') => self.kill_to_end_of_line(), ctrl!('u') => self.kill_to_start_of_line(), ctrl!('h') | key!(Backspace) => { From 6cb35d28a878470ef742b813e1e8d412d09e6b52 Mon Sep 17 00:00:00 2001 From: Jason Hansen Date: Mon, 15 Nov 2021 08:32:58 -0700 Subject: [PATCH 062/156] Add command to inc/dec number under cursor (#1027) * Add command to inc/dec number under cursor With the cursor over a number in normal mode, Ctrl + A will increment the number and Ctrl + X will decrement the number. It works with binary, octal, decimal, and hexidecimal numbers. Here are some examples. 0b01110100 0o1734 -24234 0x1F245 If the number isn't over a number it will try to find a number after the cursor on the same line. * Move several functions to helix-core * Change to work based on word under selection * It no longer finds the next number if the cursor isn't already over a number. * It only matches numbers that are part of words with other characters like "foo123bar". * It now works with multiple selections. * Add some unit tests * Fix for clippy * Simplify some things * Keep previous selection after incrementing * Use short word instead of long word This change requires us to manually handle minus sign. * Don't pad decimal numbers if no leading zeros * Handle numbers with `_` separators * Refactor and add tests * Move most of the code into core * Add tests for the incremented output * Use correct range * Formatting * Rename increment functions * Make docs more specific * This is easier to read * This is clearer * Type can be inferred --- book/src/keymap.md | 2 + helix-core/src/lib.rs | 1 + helix-core/src/numbers.rs | 499 +++++++++++++++++++++++++++++++++++++ helix-term/src/commands.rs | 38 +++ helix-term/src/keymap.rs | 3 + 5 files changed, 543 insertions(+) create mode 100644 helix-core/src/numbers.rs diff --git a/book/src/keymap.md b/book/src/keymap.md index 7a8960355..335e393b3 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -72,6 +72,8 @@ | `=` | Format selection (**LSP**) | `format_selections` | | `d` | Delete selection | `delete_selection` | | `c` | Change selection (delete and enter insert mode) | `change_selection` | +| `Ctrl-a` | Increment object (number) under cursor | `increment` | +| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | #### Shell diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index d1720df00..de7e95c16 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -10,6 +10,7 @@ pub mod line_ending; pub mod macros; pub mod match_brackets; pub mod movement; +pub mod numbers; pub mod object; pub mod path; mod position; diff --git a/helix-core/src/numbers.rs b/helix-core/src/numbers.rs new file mode 100644 index 000000000..e9f3c898d --- /dev/null +++ b/helix-core/src/numbers.rs @@ -0,0 +1,499 @@ +use std::borrow::Cow; + +use ropey::RopeSlice; + +use crate::{ + textobject::{textobject_word, TextObject}, + Range, Tendril, +}; + +#[derive(Debug, PartialEq, Eq)] +pub struct NumberIncrementor<'a> { + pub range: Range, + pub value: i64, + pub radix: u32, + + text: RopeSlice<'a>, +} + +impl<'a> NumberIncrementor<'a> { + /// Return information about number under rang if there is one. + pub fn from_range(text: RopeSlice, range: Range) -> Option { + // If the cursor is on the minus sign of a number we want to get the word textobject to the + // right of it. + let range = if range.to() < text.len_chars() + && range.to() - range.from() <= 1 + && text.char(range.from()) == '-' + { + Range::new(range.from() + 1, range.to() + 1) + } else { + range + }; + + let range = textobject_word(text, range, TextObject::Inside, 1, false); + + // If there is a minus sign to the left of the word object, we want to include it in the range. + let range = if range.from() > 0 && text.char(range.from() - 1) == '-' { + range.extend(range.from() - 1, range.from()) + } else { + range + }; + + let word: String = text + .slice(range.from()..range.to()) + .chars() + .filter(|&c| c != '_') + .collect(); + let (radix, prefixed) = if word.starts_with("0x") { + (16, true) + } else if word.starts_with("0o") { + (8, true) + } else if word.starts_with("0b") { + (2, true) + } else { + (10, false) + }; + + let number = if prefixed { &word[2..] } else { &word }; + + let value = i128::from_str_radix(number, radix).ok()?; + if (value.is_positive() && value.leading_zeros() < 64) + || (value.is_negative() && value.leading_ones() < 64) + { + return None; + } + + let value = value as i64; + Some(NumberIncrementor { + range, + value, + radix, + text, + }) + } + + /// Add `amount` to the number and return the formatted text. + pub fn incremented_text(&self, amount: i64) -> Tendril { + let old_text: Cow = self.text.slice(self.range.from()..self.range.to()).into(); + let old_length = old_text.len(); + let new_value = self.value.wrapping_add(amount); + + // Get separator indexes from right to left. + let separator_rtl_indexes: Vec = old_text + .chars() + .rev() + .enumerate() + .filter_map(|(i, c)| if c == '_' { Some(i) } else { None }) + .collect(); + + let format_length = if self.radix == 10 { + match (self.value.is_negative(), new_value.is_negative()) { + (true, false) => old_length - 1, + (false, true) => old_length + 1, + _ => old_text.len(), + } + } else { + old_text.len() - 2 + } - separator_rtl_indexes.len(); + + let mut new_text = match self.radix { + 2 => format!("0b{:01$b}", new_value, format_length), + 8 => format!("0o{:01$o}", new_value, format_length), + 10 if old_text.starts_with('0') || old_text.starts_with("-0") => { + format!("{:01$}", new_value, format_length) + } + 10 => format!("{}", new_value), + 16 => { + let (lower_count, upper_count): (usize, usize) = + old_text.chars().skip(2).fold((0, 0), |(lower, upper), c| { + ( + lower + c.is_ascii_lowercase().then(|| 1).unwrap_or(0), + upper + c.is_ascii_uppercase().then(|| 1).unwrap_or(0), + ) + }); + if upper_count > lower_count { + format!("0x{:01$X}", new_value, format_length) + } else { + format!("0x{:01$x}", new_value, format_length) + } + } + _ => unimplemented!("radix not supported: {}", self.radix), + }; + + // Add separators from original number. + for &rtl_index in &separator_rtl_indexes { + if rtl_index < new_text.len() { + let new_index = new_text.len() - rtl_index; + new_text.insert(new_index, '_'); + } + } + + // Add in additional separators if necessary. + if new_text.len() > old_length && !separator_rtl_indexes.is_empty() { + let spacing = match separator_rtl_indexes.as_slice() { + [.., b, a] => a - b - 1, + _ => separator_rtl_indexes[0], + }; + + let prefix_length = if self.radix == 10 { 0 } else { 2 }; + if let Some(mut index) = new_text.find('_') { + while index - prefix_length > spacing { + index -= spacing; + new_text.insert(index, '_'); + } + } + } + + new_text.into() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Rope; + + #[test] + fn test_decimal_at_point() { + let rope = Rope::from_str("Test text 12345 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 15), + value: 12345, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_uppercase_hexadecimal_at_point() { + let rope = Rope::from_str("Test text 0x123ABCDEF more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 21), + value: 0x123ABCDEF, + radix: 16, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_lowercase_hexadecimal_at_point() { + let rope = Rope::from_str("Test text 0xfa3b4e more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 18), + value: 0xfa3b4e, + radix: 16, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_octal_at_point() { + let rope = Rope::from_str("Test text 0o1074312 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 19), + value: 0o1074312, + radix: 8, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_binary_at_point() { + let rope = Rope::from_str("Test text 0b10111010010101 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 26), + value: 0b10111010010101, + radix: 2, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_negative_decimal_at_point() { + let rope = Rope::from_str("Test text -54321 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 16), + value: -54321, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_decimal_with_leading_zeroes_at_point() { + let rope = Rope::from_str("Test text 000045326 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 19), + value: 45326, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_negative_decimal_cursor_on_minus_sign() { + let rope = Rope::from_str("Test text -54321 more text."); + let range = Range::point(10); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 16), + value: -54321, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_under_range_start_of_rope() { + let rope = Rope::from_str("100"); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(0, 3), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_under_range_end_of_rope() { + let rope = Rope::from_str("100"); + let range = Range::point(2); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(0, 3), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_surrounded_by_punctuation() { + let rope = Rope::from_str(",100;"); + let range = Range::point(1); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(1, 4), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_not_a_number_point() { + let rope = Rope::from_str("Test text 45326 more text."); + let range = Range::point(6); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_too_large_at_point() { + let rope = Rope::from_str("Test text 0xFFFFFFFFFFFFFFFFF more text."); + let range = Range::point(12); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_cursor_one_right_of_number() { + let rope = Rope::from_str("100 "); + let range = Range::point(3); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_cursor_one_left_of_number() { + let rope = Rope::from_str(" 100"); + let range = Range::point(0); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_increment_basic_decimal_numbers() { + let tests = [ + ("100", 1, "101"), + ("100", -1, "99"), + ("99", 1, "100"), + ("100", 1000, "1100"), + ("100", -1000, "-900"), + ("-1", 1, "0"), + ("-1", 2, "1"), + ("1", -1, "0"), + ("1", -2, "-1"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .incremented_text(amount), + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_hexadedimal_numbers() { + let tests = [ + ("0x0100", 1, "0x0101"), + ("0x0100", -1, "0x00ff"), + ("0x0001", -1, "0x0000"), + ("0x0000", -1, "0xffffffffffffffff"), + ("0xffffffffffffffff", 1, "0x0000000000000000"), + ("0xffffffffffffffff", 2, "0x0000000000000001"), + ("0xffffffffffffffff", -1, "0xfffffffffffffffe"), + ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"), + ("0xabcdef1234567890", 1, "0xabcdef1234567891"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .incremented_text(amount), + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_octal_numbers() { + let tests = [ + ("0o0107", 1, "0o0110"), + ("0o0110", -1, "0o0107"), + ("0o0001", -1, "0o0000"), + ("0o7777", 1, "0o10000"), + ("0o1000", -1, "0o0777"), + ("0o0107", 10, "0o0121"), + ("0o0000", -1, "0o1777777777777777777777"), + ("0o1777777777777777777777", 1, "0o0000000000000000000000"), + ("0o1777777777777777777777", 2, "0o0000000000000000000001"), + ("0o1777777777777777777777", -1, "0o1777777777777777777776"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .incremented_text(amount), + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_binary_numbers() { + let tests = [ + ("0b00000100", 1, "0b00000101"), + ("0b00000100", -1, "0b00000011"), + ("0b00000100", 2, "0b00000110"), + ("0b00000100", -2, "0b00000010"), + ("0b00000001", -1, "0b00000000"), + ("0b00111111", 10, "0b01001001"), + ("0b11111111", 1, "0b100000000"), + ("0b10000000", -1, "0b01111111"), + ( + "0b0000", + -1, + "0b1111111111111111111111111111111111111111111111111111111111111111", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + 1, + "0b0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + 2, + "0b0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + -1, + "0b1111111111111111111111111111111111111111111111111111111111111110", + ), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .incremented_text(amount), + expected.into() + ); + } + } + + #[test] + fn test_increment_with_separators() { + let tests = [ + ("999_999", 1, "1_000_000"), + ("1_000_000", -1, "999_999"), + ("-999_999", -1, "-1_000_000"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000", -1, "0xffff_ffff_ffff_ffff"), + ("0x0000_0000_0000", -1, "0xffff_ffff_ffff_ffff"), + ("0b01111111_11111111", 1, "0b10000000_00000000"), + ("0b11111111_11111111", 1, "0b1_00000000_00000000"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .incremented_text(amount), + expected.into() + ); + } + } +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 74bc52fdf..7ef8f56c8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6,6 +6,7 @@ use helix_core::{ line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, movement::{self, Direction}, + numbers::NumberIncrementor, object, pos_at_coords, regex::{self, Regex, RegexBuilder}, register::Register, @@ -354,6 +355,8 @@ impl Command { shell_keep_pipe, "Filter selections with shell predicate", suspend, "Suspend", rename_symbol, "Rename symbol", + increment, "Increment", + decrement, "Decrement", ); } @@ -5459,3 +5462,38 @@ fn rename_symbol(cx: &mut Context) { ); cx.push_layer(Box::new(prompt)); } + +/// Increment object under cursor by count. +fn increment(cx: &mut Context) { + increment_impl(cx, cx.count() as i64); +} + +/// Decrement object under cursor by count. +fn decrement(cx: &mut Context) { + increment_impl(cx, -(cx.count() as i64)); +} + +/// Decrement object under cursor by `amount`. +fn increment_impl(cx: &mut Context, amount: i64) { + let (view, doc) = current!(cx.editor); + let selection = doc.selection(view.id); + let text = doc.text(); + + let changes = selection.ranges().iter().filter_map(|range| { + let incrementor = NumberIncrementor::from_range(text.slice(..), *range)?; + let new_text = incrementor.incremented_text(amount); + Some(( + incrementor.range.from(), + incrementor.range.to(), + Some(new_text), + )) + }); + + if changes.clone().count() > 0 { + let transaction = Transaction::change(doc.text(), changes); + let transaction = transaction.with_selection(selection.clone()); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); + } +} diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index fdf43d87e..bf3b594e8 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -698,6 +698,9 @@ impl Default for Keymaps { "A-!" => shell_append_output, "$" => shell_keep_pipe, "C-z" => suspend, + + "C-a" => increment, + "C-x" => decrement, }); let mut select = normal.clone(); select.merge_nodes(keymap!({ "Select mode" From dd98727bad2c727c0cf862dc95525a2fec16b10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 16 Nov 2021 00:37:30 +0900 Subject: [PATCH 063/156] fix: editor.close now takes only a single parameter --- 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 7ef8f56c8..e9bfdfdd1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2121,7 +2121,7 @@ mod cmd { let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); for view_id in views { - cx.editor.close(view_id, false); + cx.editor.close(view_id); } Ok(()) From c95cb2be28b247d43cd2e10e5da098131818889a Mon Sep 17 00:00:00 2001 From: NexiNov Date: Mon, 15 Nov 2021 22:16:27 +0530 Subject: [PATCH 064/156] Remove extra instance of delete_word_backword in book (#1103) --- book/src/keymap.md | 1 - 1 file changed, 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 335e393b3..576f6b46e 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -258,7 +258,6 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire | `Ctrl-f`, `Right` | Forward a char | `move_char_right` | | `Ctrl-e`, `End` | move to line end | `goto_line_end_newline` | | `Ctrl-a`, `Home` | move to line start | `goto_line_start` | -| `Ctrl-w` | delete previous word | `delete_word_backwar` | | `Ctrl-u` | delete to start of line | `kill_to_line_start` | | `Ctrl-k` | delete to end of line | `kill_to_line_end` | | `backspace`, `Ctrl-h` | delete previous char | `delete_char_backward` | From 225e7904ec4864b42d0a79f99ebad06ed681c929 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 15 Nov 2021 08:46:39 -0800 Subject: [PATCH 065/156] helix-view/editor: use SCRATCH_BUFFER_NAME const (#1104) --- helix-view/src/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 4712c52a9..364865d9c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,6 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, + document::SCRATCH_BUFFER_NAME, graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::{self, Tree}, @@ -427,7 +428,7 @@ impl Editor { "buffer {:?} is modified", doc.relative_path() .map(|path| path.to_string_lossy().to_string()) - .unwrap_or_else(|| "[scratch]".into()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()) ); } From 7e4418468a1a304de9e312014af73ed15e1e3244 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:21:02 +0900 Subject: [PATCH 066/156] build(deps): bump tokio from 1.13.0 to 1.13.1 (#1109) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.13.0 to 1.13.1. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.13.0...tokio-1.13.1) --- updated-dependencies: - dependency-name: tokio 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 14b19ae92..50b4255bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,9 +1069,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" +checksum = "52963f91310c08d91cb7bff5786dfc8b79642ab839e188187e92105dbfb9d2c8" dependencies = [ "autocfg", "bytes", From 411f522e5d6c271343aa93dee5395f8f7958a496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:21:14 +0900 Subject: [PATCH 067/156] build(deps): bump arc-swap from 1.4.0 to 1.5.0 (#1110) Bumps [arc-swap](https://github.com/vorner/arc-swap) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/vorner/arc-swap/releases) - [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md) - [Commits](https://github.com/vorner/arc-swap/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: arc-swap 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 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50b4255bb..4062e7c71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "3aa828229c44c0293dd7d4d2300bdfc4d2883ffdba934c069a6b968957a81f70" [[package]] name = "arc-swap" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6df5aef5c5830360ce5218cecb8f018af3438af5686ae945094affc86fdec63" +checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" [[package]] name = "autocfg" From bc31d998de1f1dd53350a90170360f1a2d083fd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:21:20 +0900 Subject: [PATCH 068/156] build(deps): bump cc from 1.0.71 to 1.0.72 (#1111) Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.71 to 1.0.72. - [Release notes](https://github.com/alexcrichton/cc-rs/releases) - [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.71...1.0.72) --- updated-dependencies: - dependency-name: cc 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 4062e7c71..d067bce24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,9 +66,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cfg-if" From 39479949fcad1ee719f8abdfe8c83ec26ac23b9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:21:32 +0900 Subject: [PATCH 069/156] build(deps): bump serde_json from 1.0.69 to 1.0.70 (#1112) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.69 to 1.0.70. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.69...v1.0.70) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d067bce24..a5d5a7ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" +checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" dependencies = [ "itoa", "ryu", From f1d3d970040ca85ed5a0bfb3a45e2b4d5b2f9513 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:22:01 +0900 Subject: [PATCH 070/156] build(deps): bump libloading from 0.7.1 to 0.7.2 (#1113) Bumps [libloading](https://github.com/nagisa/rust_libloading) from 0.7.1 to 0.7.2. - [Release notes](https://github.com/nagisa/rust_libloading/releases) - [Commits](https://github.com/nagisa/rust_libloading/commits) --- updated-dependencies: - dependency-name: libloading 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 a5d5a7ab3..5b5c9b6d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,9 +566,9 @@ checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" [[package]] name = "libloading" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" dependencies = [ "cfg-if", "winapi", From 335ed7fa6953ae95a9f8ff376b53228bcef80cfe Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Mon, 15 Nov 2021 22:34:25 -0500 Subject: [PATCH 071/156] Improve Solarzed Dark theme (#1105) Adjusts the Solarized Dark theme to be modeled more closely after vim's incarnation. Also adjust the Solarized Light theme to match. --- runtime/themes/solarized_dark.toml | 54 +++++++++++------------------ runtime/themes/solarized_light.toml | 19 +++------- 2 files changed, 25 insertions(+), 48 deletions(-) diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml index 499c171ab..afcafd541 100644 --- a/runtime/themes/solarized_dark.toml +++ b/runtime/themes/solarized_dark.toml @@ -2,15 +2,9 @@ "keyword" = { fg = "green" } "keyword.directive" = { fg = "orange" } "namespace" = { fg = "violet" } -"punctuation" = { fg = "orange" } -"punctuation.delimiter" = { fg = "orange" } "operator" = { fg = "green" } "special" = { fg = "orange" } -# "property" = { fg = "cyan" } -"variable" = { fg = "cyan" } -"variable.parameter" = { fg = "cyan" } "variable.builtin" = { fg = "cyan", modifiers = ["bold"] } -"variable.other.member" = { fg = "cyan" } "variable.function" = { fg = "blue" } "type" = { fg = "yellow" } "type.builtin" = { fg = "yellow", modifiers = ["bold"] } @@ -19,14 +13,10 @@ "function.macro" = { fg = "magenta" } "function.builtin" = { fg = "blue", modifiers = ["bold"] } "function.special" = { fg = "magenta" } -"comment" = { fg = "base01", modifiers = ["italic"] } -"string" = { fg= "base1" } -"constant" = { fg = "base1" } -"constant.character" = { fg = "base1" } -"constant.builtin" = { fg = "base1", modifiers = ["bold"] } -"constant.numeric" = { fg= "base1" } -"constant.numeric.integer" = { fg= "base1" } -"constant.numeric.float" = { fg= "base1" } +"comment" = { fg = "base01" } +"string" = { fg = "cyan" } +"constant" = { fg = "cyan" } +"constant.builtin" = { fg = "cyan", modifiers = ["bold"] } "constant.character.escape" = { fg = "red", modifiers = ["bold"] } "label" = { fg = "green" } "module" = { fg = "violet" } @@ -79,7 +69,6 @@ # 当前光标匹配的标点符号 "ui.cursor.match" = {modifiers = ["reversed"]} - "warning" = { fg = "orange", modifiers= ["bold", "underlined"] } "error" = { fg = "red", modifiers= ["bold", "underlined"] } "info" = { fg = "blue", modifiers= ["bold", "underlined"] } @@ -87,23 +76,22 @@ "diagnostic" = { mdifiers = ["underlined"] } [palette] -red = '#dc322f' -green = '#859900' -yellow = '#b58900' -blue = '#268bd2' -magenta = '#d33682' -cyan = '#2aa198' -orange = '#cb4b16' -violet = '#6c71c4' - # 深色 越来越深 -base0 = '#839496' -base1 = '#93a1a1' -base2 = '#eee8d5' -base3 = '#fdf6e3' +base03 = "#002b36" +base02 = "#073642" +base01 = "#586e75" +base00 = "#657b83" +base0 = "#839496" +base1 = "#93a1a1" +base2 = "#eee8d5" +base3 = "#fdf6e3" -## 浅色 越來越浅 -base00 = '#657b63' -base01 = '#586e75' -base02 = '#073642' -base03 = '#002b36' +# 浅色 越來越浅 +yellow = "#b58900" +orange = "#cb4b16" +red = "#dc322f" +magenta = "#d33682" +violet = "#6c71c4" +blue = "#268bd2" +cyan = "#2aa198" +green = "#859900" diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml index 19ff0a5f9..aec5bf48c 100644 --- a/runtime/themes/solarized_light.toml +++ b/runtime/themes/solarized_light.toml @@ -2,15 +2,9 @@ "keyword" = { fg = "green" } "keyword.directive" = { fg = "orange" } "namespace" = { fg = "violet" } -"punctuation" = { fg = "orange" } -"punctuation.delimiter" = { fg = "orange" } "operator" = { fg = "green" } "special" = { fg = "orange" } -# "property" = { fg = "cyan" } -"variable" = { fg = "cyan" } -"variable.parameter" = { fg = "cyan" } "variable.builtin" = { fg = "cyan", modifiers = ["bold"] } -"variable.other.member" = { fg = "cyan" } "variable.function" = { fg = "blue" } "type" = { fg = "yellow" } "type.builtin" = { fg = "yellow", modifiers = ["bold"] } @@ -19,14 +13,10 @@ "function.macro" = { fg = "magenta" } "function.builtin" = { fg = "blue", modifiers = ["bold"] } "function.special" = { fg = "magenta" } -"comment" = { fg = "base01", modifiers = ["italic"] } -"string" = { fg= "base1" } -"constant" = { fg = "base1" } -"constant.character" = { fg = "base1" } -"constant.builtin" = { fg = "base1", modifiers = ["bold"] } -"constant.numeric" = { fg= "base1" } -"constant.numeric.integer" = { fg= "base1" } -"constant.numeric.float" = { fg= "base1" } +"comment" = { fg = "base01" } +"string" = { fg = "cyan" } +"constant" = { fg = "cyan" } +"constant.builtin" = { fg = "cyan", modifiers = ["bold"] } "constant.character.escape" = { fg = "red", modifiers = ["bold"] } "label" = { fg = "green" } "module" = { fg = "violet" } @@ -79,7 +69,6 @@ # 当前光标匹配的标点符号 "ui.cursor.match" = {modifiers = ["reversed"]} - "warning" = { fg = "orange", modifiers= ["bold", "underlined"] } "error" = { fg = "red", modifiers= ["bold", "underlined"] } "info" = { fg = "blue", modifiers= ["bold", "underlined"] } From 8db6fffe90514e38beaf60138c1a8fb8a83ef04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 16 Nov 2021 15:02:48 +0900 Subject: [PATCH 072/156] ui: Increase diagnostics sideline width to 100 max and wrap if needed --- helix-term/src/ui/editor.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bcd9f8f03..03cd04748 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -497,7 +497,7 @@ impl EditorView { use tui::{ layout::Alignment, text::Text, - widgets::{Paragraph, Widget}, + widgets::{Paragraph, Widget, Wrap}, }; let cursor = doc @@ -529,8 +529,10 @@ impl EditorView { lines.extend(text.lines); } - let paragraph = Paragraph::new(lines).alignment(Alignment::Right); - let width = 80.min(viewport.width); + let paragraph = Paragraph::new(lines) + .alignment(Alignment::Right) + .wrap(Wrap { trim: true }); + let width = 100.min(viewport.width); let height = 15.min(viewport.height); paragraph.render( Rect::new(viewport.right() - width, viewport.y + 1, width, height), From 1132b7088a853f1edac451a34ad1cbbba03e1f26 Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Tue, 16 Nov 2021 16:46:39 +0800 Subject: [PATCH 073/156] improve nord status bar line --- runtime/themes/nord.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index 78736c3bd..a619f902e 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -10,11 +10,11 @@ # Polar Night # nord0 - background color "ui.background" = { bg = "nord0" } -"ui.statusline.inactive" = { fg = "nord4", bg = "nord0" } +"ui.statusline.inactive" = { fg = "nord8", bg = "nord1" } # nord1 - status bars, panels, modals, autocompletion -"ui.statusline" = { fg = "nord8", bg = "nord1" } +"ui.statusline" = { fg = "nord4", bg = "#4c566a" } "ui.popup" = { bg = "#232d38" } "ui.window" = { bg = "#232d38" } "ui.help" = { bg = "#232d38", fg = "nord4" } @@ -25,7 +25,7 @@ # nord3 - comments, nord3 based lighter color # relative: https://github.com/arcticicestudio/nord/issues/94 -"comment" = "gray" +"comment" = { fg = "gray", modifiers = ["italic"] } "ui.linenr" = { fg = "gray" } # Snow Storm From 90fd09f2cc4e5dfa7380036b0748adf95ffdf371 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Thu, 18 Nov 2021 01:49:56 +0100 Subject: [PATCH 074/156] Fix selection remove doc comment (#1122) --- helix-core/src/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index f7c7dbcbc..b4d1dffa5 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -360,7 +360,7 @@ impl Selection { self.normalize() } - /// Adds a new range to the selection and makes it the primary range. + /// Removes a range from the selection. pub fn remove(mut self, index: usize) -> Self { assert!( self.ranges.len() > 1, From fa4c59df4623ac33c307c5637dcf74c83c71d763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 18 Nov 2021 11:08:47 +0900 Subject: [PATCH 075/156] Simplify compositor.find --- helix-term/src/application.rs | 18 +++--------------- helix-term/src/commands.rs | 22 +++++++++------------- helix-term/src/compositor.rs | 5 +++-- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 2969a9e58..78b93cd96 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -270,12 +270,8 @@ impl Application { } let editor_view = self .compositor - .find(std::any::type_name::()) + .find::() .expect("expected at least one EditorView"); - let editor_view = editor_view - .as_any_mut() - .downcast_mut::() - .unwrap(); if editor_view.completion.is_some() { return; @@ -440,12 +436,8 @@ impl Application { { let editor_view = self .compositor - .find(std::any::type_name::()) + .find::() .expect("expected at least one EditorView"); - let editor_view = editor_view - .as_any_mut() - .downcast_mut::() - .unwrap(); let lsp::ProgressParams { token, value } = params; let lsp::ProgressParamsValue::WorkDone(work) = value; @@ -559,12 +551,8 @@ impl Application { let editor_view = self .compositor - .find(std::any::type_name::()) + .find::() .expect("expected at least one EditorView"); - let editor_view = editor_view - .as_any_mut() - .downcast_mut::() - .unwrap(); let spinner = editor_view.spinners_mut().get_or_create(server_id); if spinner.is_stopped() { spinner.start(); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e9bfdfdd1..847e6f095 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4735,19 +4735,15 @@ pub fn completion(cx: &mut Context) { return; } let size = compositor.size(); - let ui = compositor - .find(std::any::type_name::()) - .unwrap(); - if let Some(ui) = ui.as_any_mut().downcast_mut::() { - ui.set_completion( - editor, - items, - offset_encoding, - start_offset, - trigger_offset, - size, - ); - }; + let ui = compositor.find::().unwrap(); + ui.set_completion( + editor, + items, + offset_encoding, + start_offset, + trigger_offset, + size, + ); }, ); } diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index dc8b91d75..3a644750e 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -177,11 +177,12 @@ impl Compositor { .any(|component| component.type_name() == type_name) } - pub fn find(&mut self, type_name: &str) -> Option<&mut dyn Component> { + pub fn find(&mut self) -> Option<&mut T> { + let type_name = std::any::type_name::(); self.layers .iter_mut() .find(|component| component.type_name() == type_name) - .map(|component| component.as_mut()) + .and_then(|component| component.as_any_mut().downcast_mut()) } } From 27ceeb83bb055c90670cb9a4d8fdab7d5c742b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 18 Nov 2021 14:13:42 +0900 Subject: [PATCH 076/156] Simplify view/doc macros --- helix-view/src/macros.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/helix-view/src/macros.rs b/helix-view/src/macros.rs index 63d76a420..04f8df944 100644 --- a/helix-view/src/macros.rs +++ b/helix-view/src/macros.rs @@ -11,10 +11,19 @@ /// Returns `(&mut View, &mut Document)` #[macro_export] macro_rules! current { - ( $( $editor:ident ).+ ) => {{ - let view = $crate::view_mut!( $( $editor ).+ ); + ($editor:expr) => {{ + let view = $crate::view_mut!($editor); let id = view.doc; - let doc = $( $editor ).+ .documents.get_mut(&id).unwrap(); + let doc = $editor.documents.get_mut(&id).unwrap(); + (view, doc) + }}; +} + +#[macro_export] +macro_rules! current_ref { + ($editor:expr) => {{ + let view = $editor.tree.get($editor.tree.focus); + let doc = &$editor.documents[&view.doc]; (view, doc) }}; } @@ -23,8 +32,8 @@ macro_rules! current { /// Returns `&mut Document` #[macro_export] macro_rules! doc_mut { - ( $( $editor:ident ).+ ) => {{ - $crate::current!( $( $editor ).+ ).1 + ($editor:expr) => {{ + $crate::current!($editor).1 }}; } @@ -32,8 +41,8 @@ macro_rules! doc_mut { /// Returns `&mut View` #[macro_export] macro_rules! view_mut { - ( $( $editor:ident ).+ ) => {{ - $( $editor ).+ .tree.get_mut($( $editor ).+ .tree.focus) + ($editor:expr) => {{ + $editor.tree.get_mut($editor.tree.focus) }}; } @@ -41,23 +50,14 @@ macro_rules! view_mut { /// Returns `&View` #[macro_export] macro_rules! view { - ( $( $editor:ident ).+ ) => {{ - $( $editor ).+ .tree.get($( $editor ).+ .tree.focus) + ($editor:expr) => {{ + $editor.tree.get($editor.tree.focus) }}; } #[macro_export] macro_rules! doc { - ( $( $editor:ident ).+ ) => {{ - $crate::current_ref!( $( $editor ).+ ).1 - }}; -} - -#[macro_export] -macro_rules! current_ref { - ( $( $editor:ident ).+ ) => {{ - let view = $( $editor ).+ .tree.get($( $editor ).+ .tree.focus); - let doc = &$( $editor ).+ .documents[&view.doc]; - (view, doc) + ($editor:expr) => {{ + $crate::current_ref!($editor).1 }}; } From 9dcccb45bbee30011a44fcabaae94a5f6589ff7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 18 Nov 2021 18:40:27 +0900 Subject: [PATCH 077/156] ui: Stop hardcoding markdown doc colors --- helix-term/src/ui/markdown.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 4144ed3c4..72d84271b 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -61,9 +61,15 @@ fn parse<'a>( }) } - let text_style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender - let code_style = Style::default().fg(Color::Rgb(255, 255, 255)); // white - let heading_style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac + let text_style = theme.map(|theme| theme.get("ui.text")).unwrap_or_default(); + + // TODO: use better scopes for these, `markup.raw.block`, `markup.heading` + let code_style = theme + .map(|theme| theme.get("ui.text.focus")) + .unwrap_or_default(); // white + let heading_style = theme + .map(|theme| theme.get("ui.linenr.selected")) + .unwrap_or_default(); // lilac for event in parser { match event { From e9dc658de42aac74cbb3f02a3b2af2c48bb613fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 18 Nov 2021 18:41:44 +0900 Subject: [PATCH 078/156] Remove unused imports --- helix-term/src/ui/markdown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 72d84271b..61630d555 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -13,7 +13,7 @@ use helix_core::{ Rope, }; use helix_view::{ - graphics::{Color, Margin, Rect, Style}, + graphics::{Margin, Rect}, Theme, }; From bd56dde6e28b22b661ad991d0f23b66e089a9700 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Thu, 18 Nov 2021 17:46:27 +0800 Subject: [PATCH 079/156] Ensure cursor in view after pipe (#1123) Fix #1024 --- helix-term/src/commands.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 847e6f095..e1120ef1e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5377,6 +5377,10 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); } + + // after replace cursor may be out of bounds, do this to + // make sure cursor is in view and update scroll as well + view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); }, ); From 5959356a2404a8c317d07934ee388d6637c2888a Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Thu, 18 Nov 2021 23:19:40 +0800 Subject: [PATCH 080/156] Implement indent-aware delete (#1120) * delete character backward can make undent behavior * improve to handle mixed indentation --- helix-term/src/commands.rs | 70 +++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e1120ef1e..e5cf7bb22 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -10,10 +10,11 @@ use helix_core::{ object, pos_at_coords, regex::{self, Regex, RegexBuilder}, register::Register, - search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes, - RopeSlice, Selection, SmallVec, Tendril, Transaction, + search, selection, surround, textobject, + unicode::width::UnicodeWidthChar, + LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, + Transaction, }; - use helix_view::{ clipboard::ClipboardType, document::{Mode, SCRATCH_BUFFER_NAME}, @@ -4014,19 +4015,70 @@ pub mod insert { doc.apply(&transaction, view.id); } - // TODO: handle indent-aware delete pub fn delete_char_backward(cx: &mut Context) { let count = cx.count(); let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); + let indent_unit = doc.indent_unit(); + let tab_size = doc.tab_width(); + let transaction = Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { let pos = range.cursor(text); - ( - graphemes::nth_prev_grapheme_boundary(text, pos, count), - pos, - None, - ) + let line_start_pos = text.line_to_char(range.cursor_line(text)); + // considier to delete by indent level if all characters before `pos` are indent units. + let fragment = Cow::from(text.slice(line_start_pos..pos)); + if !fragment.is_empty() && fragment.chars().all(|ch| ch.is_whitespace()) { + if text.get_char(pos.saturating_sub(1)) == Some('\t') { + // fast path, delete one char + ( + graphemes::nth_prev_grapheme_boundary(text, pos, 1), + pos, + None, + ) + } else { + let unit_len = indent_unit.chars().count(); + // NOTE: indent_unit always contains 'only spaces' or 'only tab' according to `IndentStyle` definition. + let unit_size = if indent_unit.starts_with('\t') { + tab_size * unit_len + } else { + unit_len + }; + let width: usize = fragment + .chars() + .map(|ch| { + if ch == '\t' { + tab_size + } else { + // it can be none if it still meet control characters other than '\t' + // here just set the width to 1 (or some value better?). + ch.width().unwrap_or(1) + } + }) + .sum(); + let mut drop = width % unit_size; // round down to nearest unit + if drop == 0 { + drop = unit_size + }; // if it's already at a unit, consume a whole unit + let mut chars = fragment.chars().rev(); + let mut start = pos; + for _ in 0..drop { + // delete up to `drop` spaces + match chars.next() { + Some(' ') => start -= 1, + _ => break, + } + } + (start, pos, None) // delete! + } + } else { + // delete char + ( + graphemes::nth_prev_grapheme_boundary(text, pos, count), + pos, + None, + ) + } }); doc.apply(&transaction, view.id); } From f2b4ff23badc48e8a606eae07ef62fa56ebbf6f6 Mon Sep 17 00:00:00 2001 From: Martin Junghanns Date: Fri, 19 Nov 2021 03:58:22 +0100 Subject: [PATCH 081/156] Document scrolling for hover command in keymap.md (#1117) * Document scrolling for hover command in keymap.md * Move popup keys to a dedicated section --- book/src/keymap.md | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 576f6b46e..c88ed767e 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -210,25 +210,34 @@ This layer is similar to vim keybindings as kakoune does not support window. This layer is a kludge of mappings, mostly pickers. -| Key | Description | Command | -| ----- | ----------- | ------- | -| `f` | Open file picker | `file_picker` | -| `b` | Open buffer picker | `buffer_picker` | -| `k` | Show documentation for item under cursor (**LSP**) | `hover` | -| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | -| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | -| `r` | Rename symbol (**LSP**) | `rename_symbol` | -| `a` | Apply code action (**LSP**) | `code_action` | -| `'` | Open last fuzzy picker | `last_picker` | -| `w` | Enter [window mode](#window-mode) | N/A | -| `p` | Paste system clipboard after selections | `paste_clipboard_after` | -| `P` | Paste system clipboard before selections | `paste_clipboard_before` | -| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` | -| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | -| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | -| `/` | Global search in workspace folder | `global_search` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `f` | Open file picker | `file_picker` | +| `b` | Open buffer picker | `buffer_picker` | +| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` | +| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | +| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | +| `r` | Rename symbol (**LSP**) | `rename_symbol` | +| `a` | Apply code action (**LSP**) | `code_action` | +| `'` | Open last fuzzy picker | `last_picker` | +| `w` | Enter [window mode](#window-mode) | N/A | +| `p` | Paste system clipboard after selections | `paste_clipboard_after` | +| `P` | Paste system clipboard before selections | `paste_clipboard_before` | +| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` | +| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | +| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | +| `/` | Global search in workspace folder | `global_search` | > TIP: Global search displays results in a fuzzy picker, use `space + '` to bring it back up after opening a file. + +##### Popup + +Displays documentation for item under cursor. + +| Key | Description | +| ---- | ----------- | +| `Ctrl-u` | Scroll up | +| `Ctrl-d` | Scroll down | #### Unimpaired From 2b7c0866538676de4e5738d82e450163ff733104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 19 Nov 2021 12:06:19 +0900 Subject: [PATCH 082/156] fix: Expand tilde first, then deal with relative paths Otherwise the ~ gets treated as a relative path. Fixes #1107 --- helix-core/src/path.rs | 4 ++-- helix-term/src/commands.rs | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/helix-core/src/path.rs b/helix-core/src/path.rs index 6c37cfa1c..a66444651 100644 --- a/helix-core/src/path.rs +++ b/helix-core/src/path.rs @@ -40,7 +40,6 @@ pub fn expand_tilde(path: &Path) -> PathBuf { /// needs to improve on. /// Copied from cargo: pub fn get_normalized_path(path: &Path) -> PathBuf { - let path = expand_tilde(path); let mut components = path.components().peekable(); let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { components.next(); @@ -72,10 +71,11 @@ pub fn get_normalized_path(path: &Path) -> PathBuf { /// This function is used instead of `std::fs::canonicalize` because we don't want to verify /// here if the path exists, just normalize it's components. pub fn get_canonicalized_path(path: &Path) -> std::io::Result { + let path = expand_tilde(path); let path = if path.is_relative() { std::env::current_dir().map(|current_dir| current_dir.join(path))? } else { - path.to_path_buf() + path }; Ok(get_normalized_path(path.as_path())) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e5cf7bb22..431265cd5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1767,11 +1767,8 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - use helix_core::path::expand_tilde; let path = args.get(0).context("wrong argument count")?; - let _ = cx - .editor - .open(expand_tilde(Path::new(path)), Action::Replace)?; + let _ = cx.editor.open(path.into(), Action::Replace)?; Ok(()) } From ed76cdf238b93846fa4edd8f926f6c05fc26b9fd Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Thu, 18 Nov 2021 23:26:39 -0500 Subject: [PATCH 083/156] revert log truncation (#895) (#1130) --- helix-term/src/main.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 6fa1ce671..881401304 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -16,11 +16,6 @@ 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!( @@ -31,7 +26,7 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { message )) }) - .chain(file); + .chain(fern::log_file(logpath)?); base_config.chain(file_config).apply()?; From b95c9470de9f9199f109fdbfb6ec9a951fbe8866 Mon Sep 17 00:00:00 2001 From: Koen Van der Auwera Date: Sat, 20 Nov 2021 02:52:06 +0100 Subject: [PATCH 084/156] Add spacebones light theme (#1131) * Add spacebones light theme * Fix error background --- runtime/themes/spacebones_light.toml | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 runtime/themes/spacebones_light.toml diff --git a/runtime/themes/spacebones_light.toml b/runtime/themes/spacebones_light.toml new file mode 100644 index 000000000..92f116ab9 --- /dev/null +++ b/runtime/themes/spacebones_light.toml @@ -0,0 +1,65 @@ +# Author : Koen Van der Auwera +# Based on SpaceBones Light https://github.com/chipotle/spacebones +# https://github.com/chipotle/spacebones/blob/main/SpaceBones%20Light.bbColorScheme + +"attribute" = "#b1951d" +"keyword" = { fg = "#3a81c3" } +"keyword.directive" = "#3a81c3" +"namespace" = "#b1951d" +"punctuation" = "#6c3163" +"punctuation.delimiter" = "#6c3163" +"operator" = "#ba2f59" +"special" = "#ba2f59" +"property" = "#7590db" +"variable.property" = "#7590db" +"variable" = "#715ab1" +"variable.builtin" = "#715ab1" +"variable.parameter" = "#7590db" +"type" = "#6c3163" +"type.builtin" = "#6c3163" +"constructor" = { fg = "#4e3163", modifiers = ["bold"] } +"function" = { fg = "#715ab1", modifiers = ["bold"] } +"function.macro" = "#b1951d" +"function.builtin" = "#b1951d" +"comment" = { fg = "#a49da5", modifiers = ["italic"] } +"constant" = { fg = "#6c3163" } +"constant.builtin" = { fg = "#6c3163", modifiers = ["bold"] } +"string" = "#2d9574" +"number" = "#6c3163" +"escape" = { fg = "fg2", modifiers = ["bold"] } +"label" = "#b1951d" +"module" = "#b1951d" + +"warning" = { fg = "#da8b55" } +"error" = { fg = "#e0211d" } +"info" = { fg = "#b1951d" } +"hint" = { fg = "#d1dcdf" } + +"ui.background" = { bg = "bg0" } +"ui.linenr" = { fg = "bg3" } +"ui.linenr.selected" = { fg = "#b1951d" } +"ui.statusline" = { fg = "fg1", bg = "bg2" } +"ui.statusline.inactive" = { fg = "fg4", bg = "bg1" } +"ui.popup" = { bg = "bg1" } +"ui.window" = { bg = "bg1" } +"ui.help" = { bg = "bg1", fg = "fg1" } +"ui.text" = { fg = "fg1" } +"ui.text.focus" = { fg = "fg1" } +"ui.selection" = { bg = "bg3", modifiers = ["reversed"] } +"ui.cursor.primary" = { modifiers = ["reversed"] } +"ui.cursor.match" = { modifiers = ["reversed"] } +"ui.menu" = { fg = "fg1", bg = "bg2" } +"ui.menu.selected" = { fg = "#655370", bg = "#d1dcdf", modifiers = ["bold"] } + +"diagnostic" = { modifiers = ["underlined"] } + +[palette] +bg0 = "#fbf8ef" +bg1 = "#efeae9" +bg2 = "#d1dcdf" +bg3 = "#b4c6cb" + +fg1 = "#655370" +fg2 = "#5f3bc4" +fg3 = "#bdae93" +fg4 = "#a89984" From a3a3b0b517d0e690f3efc66b17ac7b9f769dba9d Mon Sep 17 00:00:00 2001 From: Martin Junghanns Date: Sat, 20 Nov 2021 06:17:25 -0800 Subject: [PATCH 085/156] Jump to end char of surrounding pair from any cursor pos (#1121) * Jump to end char of surrounding pair from any cursor pos * Separate bracket matching into exact and fuzzy search * Add constants for bracket chars * Abort early if char under cursor is not a bracket * Simplify bracket char validation * Refactor node search and unify find methods * Remove bracket constants --- helix-core/src/match_brackets.rs | 95 ++++++++++++++++++++++---------- helix-term/src/commands.rs | 4 +- helix-term/src/ui/editor.rs | 2 +- 3 files changed, 70 insertions(+), 31 deletions(-) diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs index 136ce320d..cd554005a 100644 --- a/helix-core/src/match_brackets.rs +++ b/helix-core/src/match_brackets.rs @@ -1,3 +1,5 @@ +use tree_sitter::Node; + use crate::{Rope, Syntax}; const PAIRS: &[(char, char)] = &[ @@ -6,50 +8,85 @@ const PAIRS: &[(char, char)] = &[ ('[', ']'), ('<', '>'), ('\'', '\''), - ('"', '"'), + ('\"', '\"'), ]; + // limit matching pairs to only ( ) { } [ ] < > +// Returns the position of the matching bracket under cursor. +// +// If the cursor is one the opening bracket, the position of +// the closing bracket is returned. If the cursor in the closing +// bracket, the position of the opening bracket is returned. +// +// If the cursor is not on a bracket, `None` is returned. +#[must_use] +pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option { + if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) { + return None; + } + find_pair(syntax, doc, pos, false) +} + +// Returns the position of the bracket that is closing the current scope. +// +// If the cursor is on an opening or closing bracket, the function +// behaves equivalent to [`find_matching_bracket`]. +// +// If the cursor position is within a scope, the function searches +// for the surrounding scope that is surrounded by brackets and +// returns the position of the closing bracket for that scope. +// +// If no surrounding scope is found, the function returns `None`. #[must_use] -pub fn find(syntax: &Syntax, doc: &Rope, pos: usize) -> Option { +pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option { + find_pair(syntax, doc, pos, true) +} + +fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option { let tree = syntax.tree(); + let pos = doc.char_to_byte(pos); - let byte_pos = doc.char_to_byte(pos); + let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?; - // most naive implementation: find the innermost syntax node, if we're at the edge of a node, - // return the other edge. + loop { + let (start_byte, end_byte) = surrounding_bytes(doc, &node)?; + let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte)); - let node = match tree - .root_node() - .named_descendant_for_byte_range(byte_pos, byte_pos) - { - Some(node) => node, - None => return None, - }; + if is_valid_pair(doc, start_char, end_char) { + if end_byte == pos { + return Some(start_char); + } + // We return the end char if the cursor is either on the start char + // or at some arbitrary position between start and end char. + return Some(end_char); + } - if node.is_error() { - return None; + if traverse_parents { + node = node.parent()?; + } else { + return None; + } } +} +fn is_valid_bracket(c: char) -> bool { + PAIRS.iter().any(|(l, r)| *l == c || *r == c) +} + +fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool { + PAIRS.contains(&(doc.char(start_char), doc.char(end_char))) +} + +fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> { let len = doc.len_bytes(); + let start_byte = node.start_byte(); - let end_byte = node.end_byte().saturating_sub(1); // it's end exclusive + let end_byte = node.end_byte().saturating_sub(1); + if start_byte >= len || end_byte >= len { return None; } - let start_char = doc.byte_to_char(start_byte); - let end_char = doc.byte_to_char(end_byte); - - if PAIRS.contains(&(doc.char(start_char), doc.char(end_char))) { - if start_byte == byte_pos { - return Some(end_char); - } - - if end_byte == byte_pos { - return Some(start_char); - } - } - - None + Some((start_byte, end_byte)) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 431265cd5..e70773eba 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4954,7 +4954,9 @@ fn match_brackets(cx: &mut Context) { if let Some(syntax) = doc.syntax() { 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) { + if let Some(pos) = + match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.anchor) + { range.put_cursor(text, pos, doc.mode == Mode::Select) } else { range diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 03cd04748..27d33d225 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -377,7 +377,7 @@ impl EditorView { use helix_core::match_brackets; let pos = doc.selection(view.id).primary().cursor(text); - let pos = match_brackets::find(syntax, doc.text(), pos) + let pos = match_brackets::find_matching_bracket(syntax, doc.text(), pos) .and_then(|pos| view.screen_coords_at_pos(doc, text, pos)); if let Some(pos) = pos { From 05c6cb1d0b576547c14b204e0df543650c93892f Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 20 Nov 2021 09:17:38 -0500 Subject: [PATCH 086/156] Solarized theme: fix popup colors, adjust menu (#1124) * fix popup colors, adjust menu * fix hardcoded horizontal rule color --- helix-term/src/ui/markdown.rs | 6 ++++-- runtime/themes/solarized_dark.toml | 12 ++++++------ runtime/themes/solarized_light.toml | 12 ++++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 61630d555..649703b51 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -55,7 +55,7 @@ fn parse<'a>( fn to_span(text: pulldown_cmark::CowStr) -> Span { use std::ops::Deref; Span::raw::>(match text { - CowStr::Borrowed(s) => s.to_string().into(), // could retain borrow + CowStr::Borrowed(s) => s.into(), CowStr::Boxed(s) => s.to_string().into(), CowStr::Inlined(s) => s.deref().to_owned().into(), }) @@ -179,7 +179,9 @@ fn parse<'a>( spans.push(Span::raw(" ")); } Event::Rule => { - lines.push(Spans::from("---")); + let mut span = Span::raw("---"); + span.style = code_style; + lines.push(Spans::from(span)); lines.push(Spans::default()); } // TaskListMarker(bool) true if checked diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml index afcafd541..984c86ee8 100644 --- a/runtime/themes/solarized_dark.toml +++ b/runtime/themes/solarized_dark.toml @@ -28,18 +28,18 @@ # 行号栏 "ui.linenr" = { fg = "base0", bg = "base02" } # 当前行号栏 -"ui.linenr.selected" = { fg = "red", modifiers = ["bold"] } +"ui.linenr.selected" = { fg = "blue", modifiers = ["bold"] } # 状态栏 -"ui.statusline" = { fg = "base02", bg = "base1" } +"ui.statusline" = { fg = "base03", bg = "base0" } # 非活动状态栏 -"ui.statusline.inactive" = { fg = "base02", bg = "base00" } +"ui.statusline.inactive" = { fg = "base1", bg = "base01" } # 补全窗口, preview窗口 -"ui.popup" = { bg = "base1" } +"ui.popup" = { bg = "base02" } # 影响 补全选中 cmd弹出信息选中 -"ui.menu.selected" = { fg = "base02", bg = "violet"} -"ui.menu" = { fg = "base02" } +"ui.menu.selected" = { fg = "base02", bg = "base2"} +"ui.menu" = { fg = "base1" } # ?? "ui.window" = { fg = "base3" } # 命令行 补全的帮助信息 diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml index aec5bf48c..0ab1b9626 100644 --- a/runtime/themes/solarized_light.toml +++ b/runtime/themes/solarized_light.toml @@ -28,18 +28,18 @@ # 行号栏 "ui.linenr" = { fg = "base0", bg = "base02" } # 当前行号栏 -"ui.linenr.selected" = { fg = "red", modifiers = ["bold"] } +"ui.linenr.selected" = { fg = "blue", modifiers = ["bold"] } # 状态栏 -"ui.statusline" = { fg = "base02", bg = "base1" } +"ui.statusline" = { fg = "base03", bg = "base0" } # 非活动状态栏 -"ui.statusline.inactive" = { fg = "base02", bg = "base00" } +"ui.statusline.inactive" = { fg = "base1", bg = "base01" } # 补全窗口, preview窗口 -"ui.popup" = { bg = "base1" } +"ui.popup" = { bg = "base02" } # 影响 补全选中 cmd弹出信息选中 -"ui.menu.selected" = { fg = "base02", bg = "violet"} -"ui.menu" = { fg = "base02" } +"ui.menu.selected" = { fg = "base02", bg = "base2"} +"ui.menu" = { fg = "base1" } # ?? "ui.window" = { fg = "base3" } # 命令行 补全的帮助信息 From 6a4d9693ba12feed5b6d6b1b34a4ff56cb9f9fd7 Mon Sep 17 00:00:00 2001 From: Dan Nases Sha <70554613+dannasessha@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:23:36 +0000 Subject: [PATCH 087/156] File picker config (#988) * squashed WIP commits * hide_gitignore working with config * pass reference to new config parameter of file_picker() * update config option name to match name on walk builder * add comments to config and documentation of option to book * add git_ignore option to WalkBuilder within prompt in commands.rs * WIP: add FilePickerConfig struct * WIP: cleanup * WIP: add more options including max_depth * WIP: changed defaults to match ignore crate defaults * WIP: change WalkBuilder in global_search() to use config options * WIP: removed follow_links, changed max_depth to follow config setting * WIP: update book with file-picker inline table notation * update documentation for file-picker config in book * adjusted to [editor.file-picker] in book configuration.md * adjust comments in editor.rs to be doc comments, cleanup * adjust comments * adjust book --- book/src/configuration.md | 12 ++++++ helix-term/src/application.rs | 2 +- helix-term/src/commands.rs | 79 +++++++++++++++++++++-------------- helix-term/src/ui/mod.rs | 11 ++++- helix-view/src/editor.rs | 42 +++++++++++++++++++ 5 files changed, 112 insertions(+), 34 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index be25441f5..2ed48d51f 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -24,6 +24,18 @@ To override global configuration parameters, create a `config.toml` file located | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `auto-info` | Whether to display infoboxes | `true` | +`[editor.filepicker]` section of the config. Sets options for file picker and global search. All but the last key listed in the default file-picker configuration below are IgnoreOptions: whether hidden files and files listed within ignore files are ignored by (not visible in) the helix file picker and global search. There is also one other key, `max-depth` available, which is not defined by default. + +| Key | Description | Default | +|--|--|---------| +|`hidden` | Enables ignoring hidden files. | true +|`parents` | Enables reading ignore files from parent directories. | true +|`ignore` | Enables reading `.ignore` files. | true +|`git-ignore` | Enables reading `.gitignore` files. | true +|`git-global` | Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. | true +|`git-exclude` | Enables reading `.git/info/exclude` files. | true +|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`. + ## LSP To display all language server messages in the status line add the following to your `config.toml`: diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 78b93cd96..a795a56e8 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -120,7 +120,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(".".into()))); + compositor.push(Box::new(ui::file_picker(".".into(), &config.editor))); } else { let nr_of_files = args.files.len(); editor.open(first.to_path_buf(), Action::VerticalSplit)?; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e70773eba..fde505fdc 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1440,6 +1440,7 @@ 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 file_picker_config = cx.editor.config.file_picker.clone(); let completions = search_completions(cx, None); let prompt = ui::regex_prompt( @@ -1468,41 +1469,55 @@ fn global_search(cx: &mut Context) { let search_root = std::env::current_dir() .expect("Global search error: Failed to get current dir"); - WalkBuilder::new(search_root).build_parallel().run(|| { - let mut searcher_cl = searcher.clone(); - let matcher_cl = matcher.clone(); - let all_matches_sx_cl = all_matches_sx.clone(); - Box::new(move |dent: Result| -> WalkState { - let dent = match dent { - Ok(dent) => dent, - Err(_) => return WalkState::Continue, - }; - - match dent.file_type() { - Some(fi) => { - if !fi.is_file() { - return WalkState::Continue; + WalkBuilder::new(search_root) + .hidden(file_picker_config.hidden) + .parents(file_picker_config.parents) + .ignore(file_picker_config.ignore) + .git_ignore(file_picker_config.git_ignore) + .git_global(file_picker_config.git_global) + .git_exclude(file_picker_config.git_exclude) + .max_depth(file_picker_config.max_depth) + .build_parallel() + .run(|| { + let mut searcher_cl = searcher.clone(); + let matcher_cl = matcher.clone(); + let all_matches_sx_cl = all_matches_sx.clone(); + Box::new(move |dent: Result| -> WalkState { + let dent = match dent { + Ok(dent) => dent, + Err(_) => return WalkState::Continue, + }; + + match dent.file_type() { + Some(fi) => { + if !fi.is_file() { + return WalkState::Continue; + } } + None => return WalkState::Continue, } - None => return WalkState::Continue, - } - let result_sink = sinks::UTF8(|line_num, _| { - match all_matches_sx_cl - .send((line_num as usize - 1, dent.path().to_path_buf())) - { - Ok(_) => Ok(true), - Err(_) => Ok(false), + let result_sink = sinks::UTF8(|line_num, _| { + match all_matches_sx_cl + .send((line_num as usize - 1, dent.path().to_path_buf())) + { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } + }); + let result = + searcher_cl.search_path(&matcher_cl, dent.path(), result_sink); + + if let Err(err) = result { + log::error!( + "Global search error: {}, {}", + dent.path().display(), + err + ); } - }); - let result = searcher_cl.search_path(&matcher_cl, dent.path(), result_sink); - - if let Err(err) = result { - log::error!("Global search error: {}, {}", dent.path().display(), err); - } - WalkState::Continue - }) - }); + WalkState::Continue + }) + }); } else { // Otherwise do nothing // log::warn!("Global Search Invalid Pattern") @@ -2742,7 +2757,7 @@ fn command_mode(cx: &mut Context) { fn file_picker(cx: &mut Context) { let root = find_root(None).unwrap_or_else(|| PathBuf::from("./")); - let picker = ui::file_picker(root); + let picker = ui::file_picker(root, &cx.editor.config); cx.push_layer(Box::new(picker)); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 62da0dce8..cdf423110 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -93,13 +93,22 @@ pub fn regex_prompt( ) } -pub fn file_picker(root: PathBuf) -> FilePicker { +pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker { use ignore::{types::TypesBuilder, WalkBuilder}; use std::time; // 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); + walk_builder + .hidden(config.file_picker.hidden) + .parents(config.file_picker.parents) + .ignore(config.file_picker.ignore) + .git_ignore(config.file_picker.git_ignore) + .git_global(config.file_picker.git_global) + .git_exclude(config.file_picker.git_exclude) + .max_depth(config.file_picker.max_depth); + 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}", diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 364865d9c..1ce33760e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -35,6 +35,46 @@ where Ok(Duration::from_millis(millis)) } +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct FilePickerConfig { + /// IgnoreOptions + /// Enables ignoring hidden files. + /// Whether to hide hidden files in file picker and global search results. Defaults to true. + pub hidden: bool, + /// Enables reading ignore files from parent directories. Defaults to true. + pub parents: bool, + /// Enables reading `.ignore` files. + /// Whether to hide files listed in .ignore in file picker and global search results. Defaults to true. + pub ignore: bool, + /// Enables reading `.gitignore` files. + /// Whether to hide files listed in .gitignore in file picker and global search results. Defaults to true. + pub git_ignore: bool, + /// Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. + /// Whether to hide files listed in global .gitignore in file picker and global search results. Defaults to true. + pub git_global: bool, + /// Enables reading `.git/info/exclude` files. + /// Whether to hide files listed in .git/info/exclude in file picker and global search results. Defaults to true. + pub git_exclude: bool, + /// WalkBuilder options + /// Maximum Depth to recurse directories in file picker and global search. Defaults to `None`. + pub max_depth: Option, +} + +impl Default for FilePickerConfig { + fn default() -> Self { + Self { + hidden: true, + parents: true, + ignore: true, + git_ignore: true, + git_global: true, + git_exclude: true, + max_depth: None, + } + } +} + #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { @@ -62,6 +102,7 @@ pub struct Config { pub completion_trigger_len: u8, /// Whether to display infoboxes. Defaults to true. pub auto_info: bool, + pub file_picker: FilePickerConfig, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -93,6 +134,7 @@ impl Default for Config { idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, auto_info: true, + file_picker: FilePickerConfig::default(), } } } From 17249307656d716a56cb994b11add82468dd4753 Mon Sep 17 00:00:00 2001 From: NNB Date: Mon, 22 Nov 2021 05:16:26 +0700 Subject: [PATCH 088/156] Fix "good first issue" link (#1140) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index faf5851ed..3f4087b9f 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Contributors are very welcome! **No contribution is too small and all contributi 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. +- You can look at the [good first issue](https://github.com/helix-editor/helix/issues?q=is%3Aopen+label%3AE-easy+label%3AE-good-first-issue) label on the issue tracker. - Help with packaging on various distributions needed! - 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!")`) From 4238a843f7a3be573967d0392905fb272f1f4e86 Mon Sep 17 00:00:00 2001 From: NexiNov Date: Mon, 22 Nov 2021 20:14:42 +0530 Subject: [PATCH 089/156] Add link to Keymap page in book. (#1137) --- book/src/remapping.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index 532f502ae..fffd189b7 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -53,4 +53,5 @@ Control, Shift and Alt modifiers are encoded respectively with the prefixes Keys can be disabled by binding them to the `no_op` command. -Commands can be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) +Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands. +> Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `commands!` macro. From 8fec8c079177b4255551f394c4248e790a9b37bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:55:20 +0900 Subject: [PATCH 090/156] build(deps): bump tree-sitter from 0.20.0 to 0.20.1 (#1145) Bumps [tree-sitter](https://github.com/tree-sitter/tree-sitter) from 0.20.0 to 0.20.1. - [Release notes](https://github.com/tree-sitter/tree-sitter/releases) - [Commits](https://github.com/tree-sitter/tree-sitter/compare/v0.20.0...v0.20.1) --- updated-dependencies: - dependency-name: tree-sitter 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 5b5c9b6d0..080ca2ca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,9 +1120,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ec02a07a782abef91279b72fe8fd2bee4c168a22112cedec5d3b0d49b9e4f9" +checksum = "9394e9dbfe967b5f3d6ab79e302e78b5fb7b530c368d634ff3b8d67ede138bf1" dependencies = [ "cc", "regex", From a2f301ee4f923027a304cc6ad2254e073aedc02d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:56:35 +0900 Subject: [PATCH 091/156] build(deps): bump anyhow from 1.0.46 to 1.0.48 (#1144) Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.46 to 1.0.48. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.46...1.0.48) --- updated-dependencies: - dependency-name: anyhow 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 080ca2ca0..70bd72310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.46" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa828229c44c0293dd7d4d2300bdfc4d2883ffdba934c069a6b968957a81f70" +checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e" [[package]] name = "arc-swap" From 2cc19bd8e4321135f01a5a0fb986670403117c00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:56:58 +0900 Subject: [PATCH 092/156] build(deps): bump serde_json from 1.0.70 to 1.0.71 (#1147) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.70 to 1.0.71. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.70...v1.0.71) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70bd72310..ccdc0fdc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" +checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" dependencies = [ "itoa", "ryu", From f24e5a3c414363c6847fd7e806b434f8a6772ef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:07:59 +0900 Subject: [PATCH 093/156] build(deps): bump tokio from 1.13.1 to 1.14.0 (#1146) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.13.1 to 1.14.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.13.1...tokio-1.14.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 | 8 ++++---- helix-lsp/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccdc0fdc0..8d2b4562d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,9 +1069,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52963f91310c08d91cb7bff5786dfc8b79642ab839e188187e92105dbfb9d2c8" +checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" dependencies = [ "autocfg", "bytes", @@ -1089,9 +1089,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" +checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" dependencies = [ "proc-macro2", "quote", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 920ce0371..5e4619ef8 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.13", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } +tokio = { version = "1.14", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio-stream = "0.1.8" From 21143e8d22c13ead8c1835063acb518aa5a42822 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 23 Nov 2021 22:08:05 +0800 Subject: [PATCH 094/156] Align selections via & (#1101) * align lines * remove log statement * use selections to align * fix a clippy issue * only accept 1,2,3 as user count * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * return if user count is not correct * add doc Co-authored-by: Ivan Tham --- book/src/keymap.md | 1 + helix-term/src/commands.rs | 71 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index c88ed767e..fbe77267d 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -92,6 +92,7 @@ | `s` | Select all regex matches inside selections | `select_regex` | | `S` | Split selection into subselections on regex matches | `split_selection` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` | +| `&` | Align selection in columns | `align_selections` | | `_` | Trim whitespace from the selection | `trim_selections` | | `;` | Collapse selection onto a single cursor | `collapse_selection` | | `Alt-;` | Flip selection cursor and anchor | `flip_selections` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fde505fdc..a7179c303 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -314,6 +314,7 @@ impl Command { join_selections, "Join lines inside selection", keep_selections, "Keep selections matching regex", remove_selections, "Remove selections matching regex", + align_selections, "Align selections in column", keep_primary_selection, "Keep primary selection", remove_primary_selection, "Remove primary selection", completion, "Invoke completion popup", @@ -657,6 +658,76 @@ fn trim_selections(cx: &mut Context) { }; } +// align text in selection +fn align_selections(cx: &mut Context) { + let align_style = cx.count(); + if align_style > 3 { + cx.editor.set_error( + "align only accept 1,2,3 as count to set left/center/right align".to_string(), + ); + return; + } + + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + let selection = doc.selection(view.id); + let mut column_widths = vec![]; + let mut last_line = text.len_lines(); + let mut column = 0; + // first of all, we need compute all column's width, let use max width of the selections in a column + for sel in selection { + let (l1, l2) = sel.line_range(text); + if l1 != l2 { + cx.editor + .set_error("align cannot work with multi line selections".to_string()); + return; + } + // if the selection is not in the same line with last selection, we set the column to 0 + column = if l1 != last_line { 0 } else { column + 1 }; + last_line = l1; + + if column < column_widths.len() { + if sel.to() - sel.from() > column_widths[column] { + column_widths[column] = sel.to() - sel.from(); + } + } else { + // a new column, current selection width is the temp width of the column + column_widths.push(sel.to() - sel.from()); + } + } + last_line = text.len_lines(); + // once we get the with of each column, we transform each selection with to it's column width based on the align style + let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + let l = range.cursor_line(text); + column = if l != last_line { 0 } else { column + 1 }; + last_line = l; + + ( + range.from(), + range.to(), + Some( + align_fragment_to_width(&range.fragment(text), column_widths[column], align_style) + .into(), + ), + ) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) -> String { + let trimed = fragment.trim_matches(|c| c == ' '); + let mut s = " ".repeat(width - trimed.chars().count()); + match align_style { + 1 => s.insert_str(0, trimed), // left align + 2 => s.insert_str(s.len() / 2, trimed), // center align + 3 => s.push_str(trimed), // right align + n => unimplemented!("{}", n), + } + s +} + fn goto_window(cx: &mut Context, align: Align) { let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index bf3b594e8..7f6d0c6b9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -604,7 +604,7 @@ impl Default for Keymaps { // "q" => record_macro, // "Q" => replay_macro, - // & align selections + "&" => align_selections, "_" => trim_selections, "(" => rotate_selections_backward, From 57c14d4a9315aecc1be0c8f268e7dd3d541c1480 Mon Sep 17 00:00:00 2001 From: Martin Junghanns Date: Tue, 23 Nov 2021 22:26:55 -0800 Subject: [PATCH 095/156] Add `:` and `:goto ` commands (#1128) * Add typable `goto` command * Support `:` on prompt * Rename function according to convention * Directly call into goto_line_number function --- helix-term/src/commands.rs | 42 +++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a7179c303..2c556281a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2460,6 +2460,22 @@ mod cmd { Ok(()) } + pub(super) fn goto_line_number( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let line = args[0].parse::()?; + + goto_line_impl(&mut cx.editor, NonZeroUsize::new(line)); + + let (view, doc) = current!(cx.editor); + + view.ensure_cursor_in_view(doc, line); + + Ok(()) + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -2741,6 +2757,13 @@ mod cmd { fun: tutor, completer: None, }, + TypableCommand { + name: "goto", + aliases: &["g"], + doc: "Go to line number.", + fun: goto_line_number, + completer: None, + } ]; pub static COMMANDS: Lazy> = Lazy::new(|| { @@ -2803,6 +2826,15 @@ fn command_mode(cx: &mut Context) { return; } + // If command is numeric, interpret as line number and go there. + if parts.len() == 1 && parts[0].parse::().ok().is_some() { + if let Err(e) = cmd::goto_line_number(cx, &parts[0..], event) { + cx.editor.set_error(format!("{}", e)); + } + return; + } + + // Handle typable commands if let Some(cmd) = cmd::COMMANDS.get(parts[0]) { if let Err(e) = (cmd.fun)(cx, &parts[1..], event) { cx.editor.set_error(format!("{}", e)); @@ -3436,10 +3468,14 @@ fn push_jump(editor: &mut Editor) { } fn goto_line(cx: &mut Context) { - if let Some(count) = cx.count { - push_jump(cx.editor); + goto_line_impl(&mut cx.editor, cx.count) +} - let (view, doc) = current!(cx.editor); +fn goto_line_impl(editor: &mut Editor, count: Option) { + if let Some(count) = count { + push_jump(editor); + + let (view, doc) = current!(editor); let max_line = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 { // If the last line is blank, don't jump to it. doc.text().len_lines().saturating_sub(2) From 72f606ee19a20eca7901ac1647e8efa58ea4f8b8 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Wed, 24 Nov 2021 08:46:40 +0100 Subject: [PATCH 096/156] Implement no-yank delete/change (#1099) --- book/src/keymap.md | 72 ++++++++++++++++++++------------------ helix-term/src/commands.rs | 64 +++++++++++++++++++++------------ helix-term/src/keymap.rs | 4 +-- 3 files changed, 81 insertions(+), 59 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index fbe77267d..4d5428005 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -45,44 +45,46 @@ ### Changes -| Key | Description | Command | -| ----- | ----------- | ------- | -| `r` | Replace with a character | `replace` | -| `R` | Replace with yanked text | `replace_with_yanked` | -| `~` | Switch case of the selected text | `switch_case` | -| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | -| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | -| `i` | Insert before selection | `insert_mode` | -| `a` | Insert after selection (append) | `append_mode` | -| `I` | Insert at the start of the line | `prepend_to_line` | -| `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` | -| `Alt-u` | Move backward in history | `earlier` | -| `Alt-U` | Move forward in history | `later` | -| `y` | Yank selection | `yank` | -| `p` | Paste after selection | `paste_after` | -| `P` | Paste before selection | `paste_before` | -| `"` `` | Select a register to yank to or paste from | `select_register` | -| `>` | Indent selection | `indent` | -| `<` | Unindent selection | `unindent` | -| `=` | Format selection (**LSP**) | `format_selections` | -| `d` | Delete selection | `delete_selection` | -| `c` | Change selection (delete and enter insert mode) | `change_selection` | -| `Ctrl-a` | Increment object (number) under cursor | `increment` | -| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `r` | Replace with a character | `replace` | +| `R` | Replace with yanked text | `replace_with_yanked` | +| `~` | Switch case of the selected text | `switch_case` | +| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | +| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | +| `i` | Insert before selection | `insert_mode` | +| `a` | Insert after selection (append) | `append_mode` | +| `I` | Insert at the start of the line | `prepend_to_line` | +| `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` | +| `Alt-u` | Move backward in history | `earlier` | +| `Alt-U` | Move forward in history | `later` | +| `y` | Yank selection | `yank` | +| `p` | Paste after selection | `paste_after` | +| `P` | Paste before selection | `paste_before` | +| `"` `` | Select a register to yank to or paste from | `select_register` | +| `>` | Indent selection | `indent` | +| `<` | Unindent selection | `unindent` | +| `=` | Format selection (**LSP**) | `format_selections` | +| `d` | Delete selection | `delete_selection` | +| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` | +| `c` | Change selection (delete and enter insert mode) | `change_selection` | +| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | +| `Ctrl-a` | Increment object (number) under cursor | `increment` | +| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | #### Shell -| Key | Description | Command | -| ------ | ----------- | ------- | -| | | Pipe each selection through shell command, replacing with output | `shell_pipe` | -| A-| | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | -| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | -| `A-!` | Run shell command, appending output after each selection | `shell_append_output` | +| Key | Description | Command | +| ------ | ----------- | ------- | +| | | Pipe each selection through shell command, replacing with output | `shell_pipe` | +| Alt-| | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | +| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | +| `Alt-!` | Run shell command, appending output after each selection | `shell_append_output` | ### Selection manipulation diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2c556281a..b5b6ac6de 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -9,7 +9,6 @@ use helix_core::{ numbers::NumberIncrementor, object, pos_at_coords, regex::{self, Regex, RegexBuilder}, - register::Register, search, selection, surround, textobject, unicode::width::UnicodeWidthChar, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, @@ -232,7 +231,9 @@ impl Command { extend_line, "Select current line, if already selected, extend to next line", extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)", delete_selection, "Delete selection", + delete_selection_noyank, "Delete selection, without yanking", change_selection, "Change selection (delete and enter insert mode)", + change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)", collapse_selection, "Collapse selection onto a single cursor", flip_selections, "Flip selection cursor and anchor", insert_mode, "Insert before selection", @@ -1693,19 +1694,42 @@ fn extend_to_line_bounds(cx: &mut Context) { ); } -fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) { +enum Operation { + Delete, + Change, +} + +fn delete_selection_impl(cx: &mut Context, op: Operation) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); - let selection = doc.selection(view_id); + let selection = doc.selection(view.id); - // first yank the selection - let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); - reg.write(values); + if cx.register != Some('_') { + // first yank the selection + let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); + let reg_name = cx.register.unwrap_or('"'); + let registers = &mut cx.editor.registers; + let reg = registers.get_mut(reg_name); + reg.write(values); + }; // then delete let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { (range.from(), range.to(), None) }); - doc.apply(&transaction, view_id); + doc.apply(&transaction, view.id); + + match op { + Operation::Delete => { + doc.append_changes_to_history(view.id); + // exit select mode, if currently in select mode + exit_select_mode(cx); + } + Operation::Change => { + enter_insert_mode(doc); + } + } } #[inline] @@ -1720,25 +1744,21 @@ fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Sel } fn delete_selection(cx: &mut Context) { - let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - let reg = registers.get_mut(reg_name); - delete_selection_impl(reg, doc, view.id); - - doc.append_changes_to_history(view.id); + delete_selection_impl(cx, Operation::Delete); +} - // exit select mode, if currently in select mode - exit_select_mode(cx); +fn delete_selection_noyank(cx: &mut Context) { + cx.register = Some('_'); + delete_selection_impl(cx, Operation::Delete); } fn change_selection(cx: &mut Context) { - let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - let reg = registers.get_mut(reg_name); - delete_selection_impl(reg, doc, view.id); - enter_insert_mode(doc); + delete_selection_impl(cx, Operation::Change); +} + +fn change_selection_noyank(cx: &mut Context) { + cx.register = Some('_'); + delete_selection_impl(cx, Operation::Change); } fn collapse_selection(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 7f6d0c6b9..0062e636c 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -537,9 +537,9 @@ impl Default for Keymaps { "O" => open_above, "d" => delete_selection, - // TODO: also delete without yanking + "A-d" => delete_selection_noyank, "c" => change_selection, - // TODO: also change delete without yanking + "A-c" => change_selection_noyank, "C" => copy_selection_on_next_line, "A-C" => copy_selection_on_prev_line, From 1eecd9a2ac97a3a1190753107f8e0da72a5e5e21 Mon Sep 17 00:00:00 2001 From: shenlebantongying Date: Wed, 24 Nov 2021 08:47:12 -0500 Subject: [PATCH 097/156] Add language: racket (#1143) --- languages.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/languages.toml b/languages.toml index 2ce9321e0..5326f9176 100644 --- a/languages.toml +++ b/languages.toml @@ -396,3 +396,12 @@ shebangs = ["perl"] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } + +[[language]] +name = "racket" +scope = "source.rkt" +roots = [] +file-types = ["rkt"] +shebangs = ["racket"] +comment-token = ";" +language-server = { command = "racket", args = ["-l", "racket-langserver"] } From 95f392b18db27117b26c4be732a4298353bdc27e Mon Sep 17 00:00:00 2001 From: Thanabodee Charoenpiriyakij Date: Wed, 24 Nov 2021 22:28:25 +0700 Subject: [PATCH 098/156] Fix bug report template use wrong hx version (#1158) --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9b7c22e7a..958407bb8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,7 +17,7 @@ Please search on the issue tracker before creating one. --> ### Environment - Platform: -- Helix version: +- Helix version:

~/.cache/helix/helix.log From e8f800a141af11b4f2f4c838cd4fd1f8aa7fa971 Mon Sep 17 00:00:00 2001 From: Thanabodee Charoenpiriyakij Date: Thu, 25 Nov 2021 09:02:51 +0700 Subject: [PATCH 099/156] Do not crash when run goto command without line number (#1160) * Do not crash when run goto command without line number Report an error when running goto command without entering a line number. Fixes #1159 * Use is_empty() instead check len zero --- helix-term/src/commands.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b5b6ac6de..ff8d7a4ff 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2485,6 +2485,10 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { + if args.is_empty() { + bail!("Line number required"); + } + let line = args[0].parse::()?; goto_line_impl(&mut cx.editor, NonZeroUsize::new(line)); From 67bf4250caf14d3a632abdbfa367c04b318d782c Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Thu, 25 Nov 2021 10:07:23 +0800 Subject: [PATCH 100/156] Optimize space for DocumentId with NonZeroUsize (#1097) Now Option uses one byte rather than two --- helix-view/src/editor.rs | 29 +++++++++++++++-------------- helix-view/src/lib.rs | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1ce33760e..725dc1b82 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -11,6 +11,7 @@ use futures_util::future; use std::{ collections::BTreeMap, io::stdin, + num::NonZeroUsize, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -154,7 +155,7 @@ impl std::fmt::Debug for Motion { #[derive(Debug)] pub struct Editor { pub tree: Tree, - pub next_document_id: usize, + pub next_document_id: DocumentId, pub documents: BTreeMap, pub count: Option, pub selected_register: Option, @@ -198,7 +199,8 @@ impl Editor { Self { tree: Tree::new(area), - next_document_id: 0, + // Safety: 1 is non-zero + next_document_id: DocumentId::default(), documents: BTreeMap::new(), count: None, selected_register: None, @@ -367,16 +369,19 @@ impl Editor { self._refresh(); } - fn new_document(&mut self, mut document: Document) -> DocumentId { - let id = DocumentId(self.next_document_id); - self.next_document_id += 1; - document.id = id; - self.documents.insert(id, document); + /// Generate an id for a new document and register it. + fn new_document(&mut self, mut doc: Document) -> DocumentId { + let id = self.next_document_id; + // Safety: adding 1 from 1 is fine, probably impossible to reach usize max + self.next_document_id = + DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) }); + doc.id = id; + self.documents.insert(id, doc); id } - fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId { - let id = self.new_document(document); + fn new_file_from_document(&mut self, action: Action, doc: Document) -> DocumentId { + let id = self.new_document(doc); self.switch(id, action); id } @@ -435,11 +440,7 @@ impl Editor { doc.set_language_server(Some(language_server)); } - let id = DocumentId(self.next_document_id); - self.next_document_id += 1; - doc.id = id; - self.documents.insert(id, doc); - id + self.new_document(doc) }; self.switch(id, action); diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 3e779356c..4d19ee2e0 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -12,8 +12,18 @@ pub mod theme; pub mod tree; pub mod view; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub struct DocumentId(usize); +use std::num::NonZeroUsize; + +// uses NonZeroUsize so Option use a byte rather than two +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct DocumentId(NonZeroUsize); + +impl Default for DocumentId { + fn default() -> DocumentId { + // Safety: 1 is non-zero + DocumentId(unsafe { NonZeroUsize::new_unchecked(1) }) + } +} slotmap::new_key_type! { pub struct ViewId; From 6e62c3de47ff14df394c1c17fec103c134aa045c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 26 Nov 2021 18:26:22 +0900 Subject: [PATCH 101/156] Simplify some code in editor.rs --- helix-term/src/application.rs | 2 +- helix-view/src/editor.rs | 54 +++++++++++++++-------------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index a795a56e8..90330751a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -265,7 +265,7 @@ impl Application { 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 { + if doc!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion { return; } let editor_view = self diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 725dc1b82..b93d8126a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -19,7 +19,7 @@ use std::{ use tokio::time::{sleep, Duration, Instant, Sleep}; -use anyhow::Error; +use anyhow::{bail, Context, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; @@ -188,8 +188,8 @@ pub enum Action { impl Editor { pub fn new( mut area: Rect, - themes: Arc, - config_loader: Arc, + theme_loader: Arc, + syn_loader: Arc, config: Config, ) -> Self { let language_servers = helix_lsp::Registry::new(); @@ -199,15 +199,14 @@ impl Editor { Self { tree: Tree::new(area), - // Safety: 1 is non-zero next_document_id: DocumentId::default(), documents: BTreeMap::new(), count: None, selected_register: None, - theme: themes.default(), + theme: theme_loader.default(), language_servers, - syn_loader: config_loader, - theme_loader: themes, + syn_loader, + theme_loader, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), status_msg: None, @@ -264,7 +263,6 @@ impl Editor { } pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> { - use anyhow::Context; let theme = self .theme_loader .load(theme.as_ref()) @@ -343,23 +341,22 @@ impl Editor { } Action::Load => { let view_id = view!(self).id; - if let Some(doc) = self.document_mut(id) { - if doc.selections().is_empty() { - doc.selections.insert(view_id, Selection::point(0)); - } + let doc = self.documents.get_mut(&id).unwrap(); + if doc.selections().is_empty() { + doc.selections.insert(view_id, Selection::point(0)); } return; } - Action::HorizontalSplit => { - let view = View::new(id); - let view_id = self.tree.split(view, Layout::Horizontal); - // initialize selection for view - let doc = self.documents.get_mut(&id).unwrap(); - doc.selections.insert(view_id, Selection::point(0)); - } - Action::VerticalSplit => { + Action::HorizontalSplit | Action::VerticalSplit => { let view = View::new(id); - let view_id = self.tree.split(view, Layout::Vertical); + let view_id = self.tree.split( + view, + match action { + Action::HorizontalSplit => Layout::Horizontal, + Action::VerticalSplit => Layout::Vertical, + _ => unreachable!(), + }, + ); // initialize selection for view let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); @@ -397,11 +394,7 @@ impl Editor { pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; - - let id = self - .documents() - .find(|doc| doc.path() == Some(&path)) - .map(|doc| doc.id); + let id = self.document_by_path(&path).map(|doc| doc.id); let id = if let Some(id) = id { id @@ -463,11 +456,11 @@ impl Editor { pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> { let doc = match self.documents.get(&doc_id) { Some(doc) => doc, - None => anyhow::bail!("document does not exist"), + None => bail!("document does not exist"), }; if !force && doc.is_modified() { - anyhow::bail!( + bail!( "buffer {:?} is modified", doc.relative_path() .map(|path| path.to_string_lossy().to_string()) @@ -500,7 +493,7 @@ impl Editor { // If the document we removed was visible in all views, we will have no more views. We don't // want to close the editor just for a simple buffer close, so we need to create a new view // containing either an existing document, or a brand new document. - if self.tree.views().peekable().peek().is_none() { + if self.tree.views().next().is_none() { let doc_id = self .documents .iter() @@ -585,8 +578,7 @@ impl Editor { } pub fn cursor(&self) -> (Option, CursorKind) { - let view = view!(self); - let doc = &self.documents[&view.doc]; + let (view, doc) = current_ref!(self); let cursor = doc .selection(view.id) .primary() From 4ec20eaeffa6144f01ae8d5a95b12ea1593f3080 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Fri, 26 Nov 2021 21:19:40 -0500 Subject: [PATCH 102/156] Add language support for WGSL (#1166) --- .gitmodules | 4 + helix-syntax/languages/tree-sitter-wgsl | 1 + languages.toml | 8 ++ runtime/queries/wgsl/highlights.scm | 102 ++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 160000 helix-syntax/languages/tree-sitter-wgsl create mode 100644 runtime/queries/wgsl/highlights.scm diff --git a/.gitmodules b/.gitmodules index bf596bdc1..039a3ee3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -142,3 +142,7 @@ path = helix-syntax/languages/tree-sitter-perl url = https://github.com/ganezdragon/tree-sitter-perl shallow = true +[submodule "helix-syntax/languages/tree-sitter-wgsl"] + path = helix-syntax/languages/tree-sitter-wgsl + url = https://github.com/szebniok/tree-sitter-wgsl + shallow = true diff --git a/helix-syntax/languages/tree-sitter-wgsl b/helix-syntax/languages/tree-sitter-wgsl new file mode 160000 index 000000000..f00ff5225 --- /dev/null +++ b/helix-syntax/languages/tree-sitter-wgsl @@ -0,0 +1 @@ +Subproject commit f00ff52251edbd58f4d39c9c3204383253032c11 diff --git a/languages.toml b/languages.toml index 5326f9176..5dcbd8727 100644 --- a/languages.toml +++ b/languages.toml @@ -405,3 +405,11 @@ file-types = ["rkt"] shebangs = ["racket"] comment-token = ";" language-server = { command = "racket", args = ["-l", "racket-langserver"] } + +[[language]] +name = "wgsl" +scope = "source.wgsl" +file-types = ["wgsl"] +roots = [] +comment-token = "//" +indent = { tab-width = 4, unit = " " } diff --git a/runtime/queries/wgsl/highlights.scm b/runtime/queries/wgsl/highlights.scm new file mode 100644 index 000000000..7fbc87d82 --- /dev/null +++ b/runtime/queries/wgsl/highlights.scm @@ -0,0 +1,102 @@ +(const_literal) @constant.numeric + +(type_declaration) @type + +(function_declaration + (identifier) @function) + +(struct_declaration + (identifier) @type) + +(type_constructor_or_function_call_expression + (type_declaration) @function) + +(parameter + (variable_identifier_declaration (identifier) @variable.parameter)) + +[ + "struct" + "bitcast" + ; "block" + "discard" + "enable" + "fallthrough" + "fn" + "let" + "private" + "read" + "read_write" + "return" + "storage" + "type" + "uniform" + "var" + "workgroup" + "write" + (texel_format) +] @keyword ; TODO reserved keywords + +[ + (true) + (false) +] @constant.builtin.boolean + +[ "," "." ":" ";" ] @punctuation.delimiter + +;; brackets +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +[ + "loop" + "for" + "break" + "continue" + "continuing" +] @keyword.control.repeat + +[ + "if" + "else" + "elseif" + "switch" + "case" + "default" +] @keyword.control.conditional + +[ + "&" + "&&" + "/" + "!" + "=" + "==" + "!=" + ">" + ">=" + ">>" + "<" + "<=" + "<<" + "%" + "-" + "+" + "|" + "||" + "*" + "~" + "^" +] @operator + +(attribute + (identifier) @variable.other.member) + +(comment) @comment + +(ERROR) @error From 3b2b7341a5d3d616496e8f13e6522d08c7864fb4 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sun, 28 Nov 2021 02:18:25 +0100 Subject: [PATCH 103/156] Fix next char delete key documentation for prompt (#1180) --- book/src/keymap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 4d5428005..5e861cfb9 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -317,7 +317,7 @@ Keys to use within prompt, Remapping currently not supported. | `Ctrl-u` | Delete to start of line | | `Ctrl-k` | Delete to end of line | | `backspace`, `Ctrl-h` | Delete previous char | -| `delete`, `Ctrl-d` | Delete previous char | +| `delete`, `Ctrl-d` | Delete next char | | `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later | | `Ctrl-p`, `Up` | Select previous history | | `Ctrl-n`, `Down` | Select next history | From 103b5125e4bc51965d98a38cdbeed2151ed816fa Mon Sep 17 00:00:00 2001 From: RustyStriker <61246040+RustyStriker@users.noreply.github.com> Date: Sun, 28 Nov 2021 03:19:54 +0200 Subject: [PATCH 104/156] Detect filetype on :write (#1141) fixes #1136 * removed a log::info * removed temp.rs * cargo clippy no longer complains * new get_lang_server function * get_lang_server is now launch_language_server * launch_lang_server will now close the previous one * better code readability * remove resfresh_ls(and a wrong comment) --- helix-term/src/commands.rs | 7 +++- helix-view/src/editor.rs | 79 +++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ff8d7a4ff..8e57ef30f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1907,7 +1907,7 @@ mod cmd { let jobs = &mut cx.jobs; let (_, doc) = current!(cx.editor); - if let Some(path) = path { + if let Some(ref path) = path { doc.set_path(Some(path.as_ref())) .context("invalid filepath")?; } @@ -1927,6 +1927,11 @@ mod cmd { }); let future = doc.format_and_save(fmt); cx.jobs.add(Job::new(future).wait_before_exiting()); + + if path.is_some() { + let id = doc.id(); + let _ = cx.editor.refresh_language_server(id); + } Ok(()) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b93d8126a..77cea7835 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -271,6 +271,53 @@ impl Editor { Ok(()) } + /// Refreshes the language server for a given document + pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { + let doc = self.documents.get_mut(&doc_id)?; + doc.detect_language(Some(&self.theme), &self.syn_loader); + Self::launch_language_server(&mut self.language_servers, doc) + } + + /// Launch a language server for a given document + fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> Option<()> { + // try to find a language server based on the language name + let language_server = doc.language.as_ref().and_then(|language| { + ls.get(language) + .map_err(|e| { + log::error!( + "Failed to initialize the LSP for `{}` {{ {} }}", + language.scope(), + e + ) + }) + .ok() + }); + if let Some(language_server) = language_server { + // only spawn a new lang server if the servers aren't the same + if Some(language_server.id()) != doc.language_server().map(|server| server.id()) { + if let Some(language_server) = doc.language_server() { + tokio::spawn(language_server.text_document_did_close(doc.identifier())); + } + let language_id = doc + .language() + .and_then(|s| s.split('.').last()) // source.rust + .map(ToOwned::to_owned) + .unwrap_or_default(); + + // TODO: this now races with on_init code if the init happens too quickly + tokio::spawn(language_server.text_document_did_open( + doc.url().unwrap(), + doc.version(), + doc.text(), + language_id, + )); + + doc.set_language_server(Some(language_server)); + } + } + Some(()) + } + fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { let doc = &self.documents[&view.doc]; @@ -401,37 +448,7 @@ impl Editor { } else { let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?; - // try to find a language server based on the language name - let language_server = doc.language.as_ref().and_then(|language| { - self.language_servers - .get(language) - .map_err(|e| { - log::error!( - "Failed to initialize the LSP for `{}` {{ {} }}", - language.scope(), - e - ) - }) - .ok() - }); - - if let Some(language_server) = language_server { - let language_id = doc - .language() - .and_then(|s| s.split('.').last()) // source.rust - .map(ToOwned::to_owned) - .unwrap_or_default(); - - // TODO: this now races with on_init code if the init happens too quickly - tokio::spawn(language_server.text_document_did_open( - doc.url().unwrap(), - doc.version(), - doc.text(), - language_id, - )); - - doc.set_language_server(Some(language_server)); - } + let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); self.new_document(doc) }; From 1d773bcefb40f69fe31dc048bfbdd83601fe0e62 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sun, 28 Nov 2021 02:21:40 +0100 Subject: [PATCH 105/156] Implement black hole register (#1165) --- book/src/usage.md | 2 ++ helix-core/src/register.rs | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/book/src/usage.md b/book/src/usage.md index 6b7cbc415..cf7d9d488 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -23,8 +23,10 @@ If there is a selected register before invoking a change or delete command, the | `/` | Last search | | `:` | Last executed command | | `"` | Last yanked text | +| `_` | Black hole | > There is no special register for copying to system clipboard, instead special commands and keybindings are provided. See the [keymap](keymap.md#space-mode) for the specifics. +> The black hole register works as a no-op register, meaning no data will be written to / read from it. ## Surround diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs index c5444eb73..b9eb497df 100644 --- a/helix-core/src/register.rs +++ b/helix-core/src/register.rs @@ -15,7 +15,11 @@ impl Register { } pub fn new_with_values(name: char, values: Vec) -> Self { - Self { name, values } + if name == '_' { + Self::new(name) + } else { + Self { name, values } + } } pub const fn name(&self) -> char { @@ -27,11 +31,15 @@ impl Register { } pub fn write(&mut self, values: Vec) { - self.values = values; + if self.name != '_' { + self.values = values; + } } pub fn push(&mut self, value: String) { - self.values.push(value); + if self.name != '_' { + self.values.push(value); + } } } From dc53e65b9e9be71c49eaa86e0f4dabb69f586e2e Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Mon, 29 Nov 2021 07:03:53 +0530 Subject: [PATCH 106/156] Fix surround cursor position calculation (#1183) Fixes #1077. This was caused by the assumption that a block cursor is represented as zero width internally and simply rendered to be a single width selection, where as in reality a block cursor is an actual single width selection in form and function. Behavioural changes: 1. Surround selection no longer works when cursor is _on_ a surround character that has matching pairs (like `'` or `"`). This was the intended behaviour from the start but worked till now because of the cursor position calculation mismatch. --- helix-core/src/selection.rs | 6 +- helix-core/src/surround.rs | 148 ++++++++++++++++++++--------------- helix-core/src/textobject.rs | 10 ++- 3 files changed, 92 insertions(+), 72 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index b4d1dffa5..116a1c7c0 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -308,10 +308,10 @@ impl Range { } impl From<(usize, usize)> for Range { - fn from(tuple: (usize, usize)) -> Self { + fn from((anchor, head): (usize, usize)) -> Self { Self { - anchor: tuple.0, - head: tuple.1, + anchor, + head, horiz: None, } } diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs index 32161b700..b53b0a78c 100644 --- a/helix-core/src/surround.rs +++ b/helix-core/src/surround.rs @@ -1,4 +1,4 @@ -use crate::{search, Selection}; +use crate::{search, Range, Selection}; use ropey::RopeSlice; pub const PAIRS: &[(char, char)] = &[ @@ -35,33 +35,27 @@ pub fn get_pair(ch: char) -> (char, char) { pub fn find_nth_pairs_pos( text: RopeSlice, ch: char, - pos: usize, + range: Range, n: usize, ) -> Option<(usize, usize)> { - let (open, close) = get_pair(ch); - - if text.len_chars() < 2 || pos >= text.len_chars() { + if text.len_chars() < 2 || range.to() >= text.len_chars() { return None; } + let (open, close) = get_pair(ch); + let pos = range.cursor(text); + if open == close { if Some(open) == text.get_char(pos) { - // Special case: cursor is directly on a matching char. - match pos { - 0 => Some((pos, search::find_nth_next(text, close, pos + 1, n)?)), - _ if (pos + 1) == text.len_chars() => { - Some((search::find_nth_prev(text, open, pos, n)?, pos)) - } - // We return no match because there's no way to know which - // side of the char we should be searching on. - _ => None, - } - } else { - Some(( - search::find_nth_prev(text, open, pos, n)?, - search::find_nth_next(text, close, pos, n)?, - )) + // Cursor is directly on match char. We return no match + // because there's no way to know which side of the char + // we should be searching on. + return None; } + Some(( + search::find_nth_prev(text, open, pos, n)?, + search::find_nth_next(text, close, pos, n)?, + )) } else { Some(( find_nth_open_pair(text, open, close, pos, n)?, @@ -160,8 +154,8 @@ pub fn get_surround_pos( ) -> Option> { let mut change_pos = Vec::new(); - for range in selection { - let (open_pos, close_pos) = find_nth_pairs_pos(text, ch, range.head, skip)?; + for &range in selection { + let (open_pos, close_pos) = find_nth_pairs_pos(text, ch, range, skip)?; if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) { return None; } @@ -178,67 +172,91 @@ mod test { use ropey::Rope; use smallvec::SmallVec; - #[test] - fn test_find_nth_pairs_pos() { - let doc = Rope::from("some (text) here"); + fn check_find_nth_pair_pos( + text: &str, + cases: Vec<(usize, char, usize, Option<(usize, usize)>)>, + ) { + let doc = Rope::from(text); let slice = doc.slice(..); - // cursor on [t]ext - assert_eq!(find_nth_pairs_pos(slice, '(', 6, 1), Some((5, 10))); - assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 10))); - // cursor on so[m]e - assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None); - // cursor on bracket itself - assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 10))); - assert_eq!(find_nth_pairs_pos(slice, '(', 10, 1), Some((5, 10))); + for (cursor_pos, ch, n, expected_range) in cases { + let range = find_nth_pairs_pos(slice, ch, (cursor_pos, cursor_pos + 1).into(), n); + assert_eq!( + range, expected_range, + "Expected {:?}, got {:?}", + expected_range, range + ); + } } #[test] - fn test_find_nth_pairs_pos_skip() { - let doc = Rope::from("(so (many (good) text) here)"); - let slice = doc.slice(..); + fn test_find_nth_pairs_pos() { + check_find_nth_pair_pos( + "some (text) here", + vec![ + // cursor on [t]ext + (6, '(', 1, Some((5, 10))), + (6, ')', 1, Some((5, 10))), + // cursor on so[m]e + (2, '(', 1, None), + // cursor on bracket itself + (5, '(', 1, Some((5, 10))), + (10, '(', 1, Some((5, 10))), + ], + ); + } - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 15))); - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 21))); - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27))); + #[test] + fn test_find_nth_pairs_pos_skip() { + check_find_nth_pair_pos( + "(so (many (good) text) here)", + vec![ + // cursor on go[o]d + (13, '(', 1, Some((10, 15))), + (13, '(', 2, Some((4, 21))), + (13, '(', 3, Some((0, 27))), + ], + ); } #[test] fn test_find_nth_pairs_pos_same() { - let doc = Rope::from("'so 'many 'good' text' here'"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15))); - assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21))); - assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27))); - // cursor on the quotes - assert_eq!(find_nth_pairs_pos(slice, '\'', 10, 1), None); - // this is the best we can do since opening and closing pairs are same - assert_eq!(find_nth_pairs_pos(slice, '\'', 0, 1), Some((0, 4))); - assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 27))); + check_find_nth_pair_pos( + "'so 'many 'good' text' here'", + vec![ + // cursor on go[o]d + (13, '\'', 1, Some((10, 15))), + (13, '\'', 2, Some((4, 21))), + (13, '\'', 3, Some((0, 27))), + // cursor on the quotes + (10, '\'', 1, None), + ], + ) } #[test] fn test_find_nth_pairs_pos_step() { - let doc = Rope::from("((so)((many) good (text))(here))"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24))); - assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31))); + check_find_nth_pair_pos( + "((so)((many) good (text))(here))", + vec![ + // cursor on go[o]d + (15, '(', 1, Some((5, 24))), + (15, '(', 2, Some((0, 31))), + ], + ) } #[test] fn test_find_nth_pairs_pos_mixed() { - let doc = Rope::from("(so [many {good} text] here)"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 15))); - assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 21))); - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 27))); + check_find_nth_pair_pos( + "(so [many {good} text] here)", + vec![ + // cursor on go[o]d + (13, '{', 1, Some((10, 15))), + (13, '[', 1, Some((4, 21))), + (13, '(', 1, Some((0, 27))), + ], + ) } #[test] diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 24f063d44..21ceec04f 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -114,7 +114,7 @@ pub fn textobject_surround( ch: char, count: usize, ) -> Range { - surround::find_nth_pairs_pos(slice, ch, range.head, count) + surround::find_nth_pairs_pos(slice, ch, range, count) .map(|(anchor, head)| match textobject { TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head), TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)), @@ -170,7 +170,7 @@ mod test { #[test] fn test_textobject_word() { - // (text, [(cursor position, textobject, final range), ...]) + // (text, [(char position, textobject, final range), ...]) let tests = &[ ( "cursor at beginning of doc", @@ -269,7 +269,9 @@ mod test { let slice = doc.slice(..); for &case in scenario { let (pos, objtype, expected_range) = case; - let result = textobject_word(slice, Range::point(pos), objtype, 1, false); + // cursor is a single width selection + let range = Range::new(pos, pos + 1); + let result = textobject_word(slice, range, objtype, 1, false); assert_eq!( result, expected_range.into(), @@ -283,7 +285,7 @@ mod test { #[test] fn test_textobject_surround() { - // (text, [(cursor position, textobject, final range, count), ...]) + // (text, [(cursor position, textobject, final range, surround char, count), ...]) let tests = &[ ( "simple (single) surround pairs", From 6f1a7b1220c2ae565a2542fb089eb6d87f0a9421 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:38:17 -0500 Subject: [PATCH 107/156] Add llvm grammar (#1167) --- .gitmodules | 4 ++++ book/src/themes.md | 5 +++-- languages.toml | 8 ++++++++ runtime/queries/llvm/highlights.scm | 14 ++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 runtime/queries/llvm/highlights.scm diff --git a/.gitmodules b/.gitmodules index 039a3ee3d..6295b9e95 100644 --- a/.gitmodules +++ b/.gitmodules @@ -146,3 +146,7 @@ path = helix-syntax/languages/tree-sitter-wgsl url = https://github.com/szebniok/tree-sitter-wgsl shallow = true +[submodule "helix-syntax/tree-sitter-llvm"] + path = helix-syntax/languages/tree-sitter-llvm + url = https://github.com/benwilliamgraham/tree-sitter-llvm + shallow = true diff --git a/book/src/themes.md b/book/src/themes.md index ecbbb6e97..fd3f5b1eb 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -145,11 +145,12 @@ We use a similar set of scopes as - `conditional` - `if`, `else` - `repeat` - `for`, `while`, `loop` - `import` - `import`, `export` - - (TODO: return?) + - `return` + - `operator` - `or`, `in` - `directive` - Preprocessor directives (`#if` in C) - `function` - `fn`, `func` -- `operator` - `||`, `+=`, `>`, `or` +- `operator` - `||`, `+=`, `>` - `function` - `builtin` diff --git a/languages.toml b/languages.toml index 5dcbd8727..4208e4b68 100644 --- a/languages.toml +++ b/languages.toml @@ -413,3 +413,11 @@ file-types = ["wgsl"] roots = [] comment-token = "//" indent = { tab-width = 4, unit = " " } + +[[language]] +name = "llvm" +scope = "source.llvm" +roots = [] +file-types = ["ll"] +comment-token = ";" +indent = { tab-width = 2, unit = " " } diff --git a/runtime/queries/llvm/highlights.scm b/runtime/queries/llvm/highlights.scm new file mode 100644 index 000000000..73afe85ed --- /dev/null +++ b/runtime/queries/llvm/highlights.scm @@ -0,0 +1,14 @@ +(type) @type +(statement) @keyword.operator +(number) @constant.numeric.integer +(comment) @comment +(string) @string +(label) @label +(keyword) @keyword +"ret" @keyword.control.return +(boolean) @constant.builtin.boolean +(float) @constant.numeric.float +(constant) @constant +(identifier) @variable +(symbol) @punctuation.delimiter +(bracket) @punctuation.bracket From 4f9390a435a44578bcedf1da63ea1fad92185afc Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 29 Nov 2021 09:53:29 +0800 Subject: [PATCH 108/156] gf as goto_file (#1102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * goto_file * support goto_file under current cursor * add C-w f/F * sync space w with window mode * Update helix-term/src/commands.rs Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 3 +++ helix-term/src/commands.rs | 46 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 5 +++++ 3 files changed, 54 insertions(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 5e861cfb9..865a700b4 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -160,6 +160,7 @@ Jumps to various locations. | ----- | ----------- | ------- | | `g` | Go to the start of the file | `goto_file_start` | | `e` | Go to the end of the file | `goto_last_line` | +| `f` | Go to files in the selection | `goto_file` | | `h` | Go to the start of the line | `goto_line_start` | | `l` | Go to the end of the line | `goto_line_end` | | `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` | @@ -202,6 +203,8 @@ This layer is similar to vim keybindings as kakoune does not support window. | `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` | +| `f` | Go to files in the selection in horizontal splits | `goto_file` | +| `F` | Go to files in the selection in vertical splits | `goto_file` | | `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` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8e57ef30f..aafeb4766 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -259,6 +259,9 @@ impl Command { goto_implementation, "Goto implementation", goto_file_start, "Goto file start/line", goto_file_end, "Goto file end", + goto_file, "Goto files in the selection", + goto_file_hsplit, "Goto files in the selection in horizontal splits", + goto_file_vsplit, "Goto files in the selection in vertical splits", goto_reference, "Goto references", goto_window_top, "Goto window top", goto_window_middle, "Goto window middle", @@ -835,6 +838,49 @@ fn goto_file_end(cx: &mut Context) { doc.set_selection(view.id, selection); } +fn goto_file(cx: &mut Context) { + goto_file_impl(cx, Action::Replace); +} + +fn goto_file_hsplit(cx: &mut Context) { + goto_file_impl(cx, Action::HorizontalSplit); +} + +fn goto_file_vsplit(cx: &mut Context) { + goto_file_impl(cx, Action::VerticalSplit); +} + +fn goto_file_impl(cx: &mut Context, action: Action) { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text(); + let selections = doc.selection(view.id); + let mut paths: Vec<_> = selections + .iter() + .map(|r| text.slice(r.from()..r.to()).to_string()) + .collect(); + let primary = selections.primary(); + if selections.len() == 1 && primary.to() - primary.from() == 1 { + let current_word = movement::move_next_long_word_start( + text.slice(..), + movement::move_prev_long_word_start(text.slice(..), primary, 1), + 1, + ); + paths.clear(); + paths.push( + text.slice(current_word.from()..current_word.to()) + .to_string(), + ); + } + for sel in paths { + let p = sel.trim(); + if !p.is_empty() { + if let Err(e) = cx.editor.open(PathBuf::from(p), action) { + cx.editor.set_error(format!("Open file failed: {:?}", e)); + } + } + } +} + fn extend_word_impl(cx: &mut Context, extend_fn: F) where F: Fn(RopeSlice, Range, usize) -> Range, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 0062e636c..06639dcd9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -512,6 +512,7 @@ impl Default for Keymaps { "g" => { "Goto" "g" => goto_file_start, "e" => goto_last_line, + "f" => goto_file, "h" => goto_line_start, "l" => goto_line_end, "s" => goto_first_nonwhitespace, @@ -622,6 +623,8 @@ impl Default for Keymaps { "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, @@ -650,6 +653,8 @@ impl Default for Keymaps { "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, From 42fde95223a62bded340a1737e8be50ef94af4af Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 29 Nov 2021 09:58:21 +0800 Subject: [PATCH 109/156] Accept count for goto_window (#1033) * accept count for goto_window also fix view is not fullfilled issue * fix fulfilled mispell * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * fix merge issue * revert line computation logic Co-authored-by: Ivan Tham --- helix-term/src/commands.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index aafeb4766..b4cc9ae92 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -733,10 +733,12 @@ fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) -> } fn goto_window(cx: &mut Context, align: Align) { + let count = cx.count() - 1; let (view, doc) = current!(cx.editor); let height = view.inner_area().height as usize; + // respect user given count if any // - 1 so we have at least one gap in the middle. // a height of 6 with padding of 3 on each side will keep shifting the view back and forth // as we type @@ -745,11 +747,12 @@ fn goto_window(cx: &mut Context, align: Align) { let last_line = view.last_line(doc); let line = match align { - Align::Top => (view.offset.row + scrolloff), - Align::Center => (view.offset.row + (height / 2)), - Align::Bottom => last_line.saturating_sub(scrolloff), + Align::Top => (view.offset.row + scrolloff + count), + Align::Center => (view.offset.row + ((last_line - view.offset.row) / 2)), + Align::Bottom => last_line.saturating_sub(scrolloff + count), } - .min(last_line.saturating_sub(scrolloff)); + .min(last_line.saturating_sub(scrolloff)) + .max(view.offset.row + scrolloff); let pos = doc.text().line_to_char(line); From 30171416cb5b801086da69566a82462fca16ea14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 24 Sep 2021 10:29:41 +0900 Subject: [PATCH 110/156] Gutter functions --- helix-core/src/diagnostic.rs | 4 +- helix-term/src/ui/editor.rs | 150 +++++++++++++++++++++-------------- helix-view/src/editor.rs | 2 +- 3 files changed, 94 insertions(+), 62 deletions(-) diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index ad1ba16ab..4fcf51c9c 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,7 +1,7 @@ //! LSP diagnostic utility types. /// Describes the severity level of a [`Diagnostic`]. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Severity { Error, Warning, @@ -17,7 +17,7 @@ pub struct Range { } /// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Diagnostic { pub range: Range, pub line: usize, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 27d33d225..8c4ea9ccb 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -17,7 +17,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, - editor::LineNumber, + editor::{Config, LineNumber}, graphics::{CursorKind, Modifier, Rect, Style}, info::Info, input::KeyEvent, @@ -412,22 +412,6 @@ impl EditorView { let text = doc.text().slice(..); let last_line = view.last_line(doc); - let linenr = theme.get("ui.linenr"); - let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); - - let warning = theme.get("warning"); - let error = theme.get("error"); - let info = theme.get("info"); - let hint = theme.get("hint"); - - // Whether to draw the line number for the last line of the - // document or not. We only draw it if it's not an empty line. - let draw_last = text.line_to_byte(last_line) < text.len_bytes(); - - let current_line = doc - .text() - .char_to_line(doc.selection(view.id).primary().cursor(text)); - // it's used inside an iterator so the collect isn't needless: // https://github.com/rust-lang/rust-clippy/issues/6164 #[allow(clippy::needless_collect)] @@ -437,51 +421,99 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { - use helix_core::diagnostic::Severity; - if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) { - surface.set_stringn( - viewport.x, - viewport.y + i as u16, - "●", - 1, - match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }, - ); - } + fn diagnostic( + doc: &Document, + _view: &View, + theme: &Theme, + _config: &Config, + _is_focused: bool, + _width: usize, + ) -> GutterFn { + let warning = theme.get("warning"); + let error = theme.get("error"); + let info = theme.get("info"); + let hint = theme.get("hint"); + let diagnostics = doc.diagnostics().to_vec(); // TODO + + Box::new(move |line: usize, _selected: bool| { + use helix_core::diagnostic::Severity; + if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { + return Some(( + "●".to_string(), + match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }, + )); + } + None + }) + } - let selected = cursors.contains(&line); + fn line_number( + doc: &Document, + view: &View, + theme: &Theme, + config: &Config, + is_focused: bool, + width: usize, + ) -> GutterFn { + let text = doc.text().slice(..); + let last_line = view.last_line(doc); + // Whether to draw the line number for the last line of the + // document or not. We only draw it if it's not an empty line. + let draw_last = text.line_to_byte(last_line) < text.len_bytes(); - let text = if line == last_line && !draw_last { - " ~".into() - } else { - let line = match config.line_number { - LineNumber::Absolute => line + 1, - LineNumber::Relative => { - if current_line == line { - line + 1 - } else { - abs_diff(current_line, line) - } - } - }; - format!("{:>5}", line) - }; - surface.set_stringn( - viewport.x + 1, - viewport.y + i as u16, - text, - 5, - if selected && is_focused { - linenr_select + let linenr = theme.get("ui.linenr"); + let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); + + let current_line = doc + .text() + .char_to_line(doc.selection(view.id).primary().cursor(text)); + + let config = config.line_number; + + Box::new(move |line: usize, selected: bool| { + if line == last_line && !draw_last { + Some((format!("{:>1$}", '~', width), linenr)) } else { - linenr - }, - ); + let line = match config { + LineNumber::Absolute => line + 1, + LineNumber::Relative => { + if current_line == line { + line + 1 + } else { + abs_diff(current_line, line) + } + } + }; + let style = if selected && is_focused { + linenr_select + } else { + linenr + }; + Some((format!("{:>1$}", line, width), style)) + } + }) + } + + type GutterFn = Box Option<(String, Style)>>; + type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn; + let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; + + let mut offset = 0; + for (constructor, width) in gutters { + let gutter = constructor(doc, view, theme, config, is_focused, *width); + for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { + let selected = cursors.contains(&line); + + if let Some((text, style)) = gutter(line, selected) { + surface.set_stringn(viewport.x + offset, viewport.y + i as u16, text, 5, style); + } + } + offset += *width as u16; } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 77cea7835..d5913a51d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -106,7 +106,7 @@ pub struct Config { pub file_picker: FilePickerConfig, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { /// Show absolute line number From c71c9f69e21a80c5c9c744bddbde7d5041da99a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 7 Oct 2021 10:26:28 +0900 Subject: [PATCH 111/156] TODO --- helix-term/src/ui/markdown.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 649703b51..ca8303dd9 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -228,6 +228,7 @@ impl Component for Markdown { return None; } let contents = parse(&self.contents, None, &self.config_loader); + // TODO: account for tab width let max_text_width = (viewport.0 - padding).min(120); let mut text_width = 0; let mut height = padding; From ba45db84d4b49913836a949472366a30a620e67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 22 Nov 2021 16:45:52 +0900 Subject: [PATCH 112/156] Tie the GutterFn lifetime to the doc so we can avoid cloning data --- helix-term/src/ui/editor.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 8c4ea9ccb..2cc212eab 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -421,19 +421,19 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - fn diagnostic( - doc: &Document, + fn diagnostic<'doc>( + doc: &'doc Document, _view: &View, theme: &Theme, _config: &Config, _is_focused: bool, _width: usize, - ) -> GutterFn { + ) -> GutterFn<'doc> { let warning = theme.get("warning"); let error = theme.get("error"); let info = theme.get("info"); let hint = theme.get("hint"); - let diagnostics = doc.diagnostics().to_vec(); // TODO + let diagnostics = doc.diagnostics(); Box::new(move |line: usize, _selected: bool| { use helix_core::diagnostic::Severity; @@ -452,14 +452,14 @@ impl EditorView { }) } - fn line_number( - doc: &Document, + fn line_number<'doc>( + doc: &'doc Document, view: &View, theme: &Theme, config: &Config, is_focused: bool, width: usize, - ) -> GutterFn { + ) -> GutterFn<'doc> { let text = doc.text().slice(..); let last_line = view.last_line(doc); // Whether to draw the line number for the last line of the @@ -499,8 +499,9 @@ impl EditorView { }) } - type GutterFn = Box Option<(String, Style)>>; - type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn; + type GutterFn<'doc> = Box Option<(String, Style)> + 'doc>; + type Gutter = + for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>; let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; let mut offset = 0; From 27c1a84f053d1282ed09d64ec737a46f55685d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 22 Nov 2021 17:02:46 +0900 Subject: [PATCH 113/156] Reuse a text buffer for each gutter line --- helix-term/src/ui/editor.rs | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2cc212eab..82fb8fbf6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -421,6 +421,8 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); + use std::fmt::Write; + fn diagnostic<'doc>( doc: &'doc Document, _view: &View, @@ -435,18 +437,16 @@ impl EditorView { let hint = theme.get("hint"); let diagnostics = doc.diagnostics(); - Box::new(move |line: usize, _selected: bool| { + Box::new(move |line: usize, _selected: bool, out: &mut String| { use helix_core::diagnostic::Severity; if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { - return Some(( - "●".to_string(), - match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }, - )); + write!(out, "●").unwrap(); + return Some(match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }); } None }) @@ -475,9 +475,10 @@ impl EditorView { let config = config.line_number; - Box::new(move |line: usize, selected: bool| { + Box::new(move |line: usize, selected: bool, out: &mut String| { if line == last_line && !draw_last { - Some((format!("{:>1$}", '~', width), linenr)) + write!(out, "{:>1$}", '~', width).unwrap(); + Some(linenr) } else { let line = match config { LineNumber::Absolute => line + 1, @@ -494,25 +495,38 @@ impl EditorView { } else { linenr }; - Some((format!("{:>1$}", line, width), style)) + write!(out, "{:>1$}", line, width).unwrap(); + Some(style) } }) } - type GutterFn<'doc> = Box Option<(String, Style)> + 'doc>; + type GutterFn<'doc> = Box Option