From ed23057ff8e01404ab608682445b4f293b6142ed Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sat, 6 Nov 2021 11:57:14 -0400 Subject: [PATCH 01/85] Launch with defaults upon invalid config/theme (#982) * Launch with defaults upon invalid config/theme * Startup message if there is a problematic config * Statusline error if trying to switch to an invalid theme * Use serde `deny_unknown_fields` for config --- helix-term/src/config.rs | 3 ++- helix-term/src/main.rs | 11 ++++++++++- helix-view/src/editor.rs | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 13917656..3745f871 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -3,6 +3,7 @@ use serde::Deserialize; use crate::keymap::Keymaps; #[derive(Debug, Default, Clone, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Config { pub theme: Option, #[serde(default)] @@ -14,7 +15,7 @@ pub struct Config { } #[derive(Debug, Default, Clone, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct LspConfig { pub display_messages: bool, } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index f746895c..e178b339 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -91,7 +91,16 @@ FLAGS: } let config = match std::fs::read_to_string(conf_dir.join("config.toml")) { - Ok(config) => merge_keys(toml::from_str(&config)?), + Ok(config) => toml::from_str(&config) + .map(merge_keys) + .unwrap_or_else(|err| { + eprintln!("Bad config: {}", err); + eprintln!("Press to continue with default config"); + use std::io::Read; + // This waits for an enter press. + let _ = std::io::stdin().read(&mut []); + Config::default() + }), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(), Err(err) => return Err(Error::new(err)), }; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 6aa8b04d..17cd3d7b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -34,7 +34,7 @@ where } #[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case", default)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. pub scrolloff: usize, @@ -195,6 +195,12 @@ impl Editor { } pub fn set_theme(&mut self, theme: Theme) { + // `ui.selection` is the only scope required to be able to render a theme. + if theme.find_scope_index("ui.selection").is_none() { + self.set_error("Invalid theme: `ui.selection` required".to_owned()); + return; + } + let scopes = theme.scopes(); for config in self .syn_loader 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 02/85] 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 56f50e21..8cadb663 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 00000000..85c3d0e8 --- /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 00000000..cef61501 --- /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 03/85] 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 85c3d0e8..00902876 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 04/85] 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 d1720df0..6168d02c 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 05/85] 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 00902876..c606f8fc 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 50b3b574..dd726b7c 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 06/85] 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 71730fa8..6b7cbc41 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 975ed115..24f063d4 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 80cbd6d2..5f091775 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 c999ba14..29ca18b1 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 07/85] 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 a4b2fb9c..136ce320 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 08/85] Adds mint language server (#974) --- languages.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/languages.toml b/languages.toml index bd510ea1..826fd93f 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 09/85] 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 4072a532..b22a95a6 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 10/85] 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 3956e25e..8c0d1f6c 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 11/85] 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 7ed34ad3..5c6f22cd 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 00000000..0ac2c6da --- /dev/null +++ b/helix-syntax/languages/tree-sitter-perl @@ -0,0 +1 @@ +Subproject commit 0ac2c6da562c7a2c26ed7e8691d4a590f7e8b90a diff --git a/languages.toml b/languages.toml index 826fd93f..98892171 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 00000000..b6f04291 --- /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 00000000..988e22b4 --- /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 12/85] 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 3369f031..5ca6cd1b 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 13/85] 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 b810feef..271fd9d5 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 5f091775..245fbe4e 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 ce50f0ab..d497401f 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 17cd3d7b..631dcf0c 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 14/85] 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 c606f8fc..446eb479 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 20f034ea..b6f5081a 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 f3e3f238..84952248 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 ce5df8ee..a68ab759 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 98892171..067138e4 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 15/85] 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 84952248..0164092d 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 a68ab759..351ad05a 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 16/85] 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 5c6f22cd..bf596bdc 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 00000000..88408ffc --- /dev/null +++ b/helix-syntax/languages/tree-sitter-glsl @@ -0,0 +1 @@ +Subproject commit 88408ffc5e27abcffced7010fc77396ae3636d7e diff --git a/languages.toml b/languages.toml index 067138e4..45fc2ab2 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 00000000..a5a5208c --- /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 00000000..af2a049f --- /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 00000000..a7fd499a --- /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 00000000..7d3323b1 --- /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 00000000..a5a5208c --- /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 17/85] 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 45fc2ab2..934def44 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 18/85] 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 0164092d..a5dd0c59 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 934def44..2ce9321e 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 19/85] 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 6168d02c..d1720df0 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 20/85] 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 e036828a..2187e422 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 21/85] 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 2187e422..14b19ae9 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 22/85] 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 a5dd0c59..142265a8 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 6037148f..f1884199 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 23/85] 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 00000000..19ff0a5f --- /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 24/85] 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 245fbe4e..42163b8e 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 11f30155..6a624ded 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 25/85] 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 5a6aee41..c6feb44f 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 26/85] 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 c6feb44f..6ae1e8d4 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 29ca18b1..22e4adb8 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 27/85] 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 24eb7acd..62da0dce 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 28/85] 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 6ae1e8d4..212ed496 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 29/85] 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 42163b8e..489308d8 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 30/85] 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 f1884199..b04eef0d 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 631dcf0c..7650d217 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 31/85] 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 d497401f..593ff0f0 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 32/85] 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 593ff0f0..b41c950b 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 33/85] 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 b41c950b..53e0d450 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 34/85] 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 53e0d450..4fb0f172 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 35/85] 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 4fb0f172..7bed3ddb 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 3c492d14..8278bd29 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 291f1f85..970d3946 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 1bab1eae..8f7921a1 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 22e4adb8..4bd5659f 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 36/85] 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 00000000..64a8d175 --- /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 37/85] 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 4bd5659f..9a60196f 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 38/85] 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 212ed496..901c8471 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 489308d8..089f92f1 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 7bed3ddb..d7040b88 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 39/85] 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 089f92f1..24c38dd5 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 40/85] 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 24c38dd5..23b385eb 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 41/85] 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 5ca6cd1b..532f502a 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 d7040b88..b2b865e4 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 a7015577..90f09e9c 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 42/85] 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 23b385eb..738621b0 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 351ad05a..80f6a740 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 43/85] 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 901c8471..7a9a4378 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 f3b5d2c8..f7c7dbcb 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 738621b0..4352ee66 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 b2b865e4..f79978fb 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 44/85] 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 7a9a4378..b9869214 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 f79978fb..1a9ea231 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 45/85] 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 b9869214..c544a472 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 46/85] 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 1a9ea231..e3e01995 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 8278bd29..e891c149 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 970d3946..c44b7625 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 9a60196f..deed1609 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 47/85] 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 4352ee66..48fd0ee0 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 48/85] 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 48fd0ee0..d5a48c5f 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 80f6a740..6b429151 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 49/85] 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 90f09e9c..dcf87203 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 50/85] 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 00000000..61ffa6f4 --- /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 e178b339..3ae4f7c9 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 51/85] 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 c544a472..6155e553 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 b53c01fe..bf2624e2 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 d5a48c5f..e37265a8 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 e3e01995..b14b1a6f 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 dcf87203..bcd9f8f0 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 6b429151..76b19a07 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 52/85] 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 bf2624e2..9fe7e530 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 53/85] 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 6155e553..9f1714f6 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 e37265a8..ebacb377 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 b14b1a6f..3280f0f8 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 c44b7625..6b1c5832 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 54/85] 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 9f1714f6..69f5f02c 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 9e85bd21..01a8f890 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 ebacb377..56cc02a2 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 3280f0f8..fc9fd590 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 55/85] 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 69f5f02c..88610a77 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 9fe7e530..4b1c8d3b 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 56cc02a2..115d1789 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 fc9fd590..010714dc 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 56/85] 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 00000000..499c171a --- /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 57/85] 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 115d1789..cb1f470b 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 58/85] 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 cb1f470b..ab62c1aa 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 59/85] 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 b04eef0d..2969a9e5 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 ab62c1aa..8c0a005c 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 3ae4f7c9..6fa1ce67 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 7650d217..e4015707 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 60/85] 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 22d29260..580cccd6 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 61/85] 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 8c0a005c..c7aab726 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 e4015707..4712c52a 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 064334b1..de5046ac 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 6a624ded..a77f1562 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 62/85] 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 88610a77..7a896035 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 c7aab726..74bc52fd 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 010714dc..fdf43d87 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 deed1609..e90b0772 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 63/85] 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 7a896035..335e393b 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 d1720df0..de7e95c1 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 00000000..e9f3c898 --- /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 74bc52fd..7ef8f56c 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 fdf43d87..bf3b594e 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 64/85] 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 7ef8f56c..e9bfdfdd 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 65/85] 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 335e393b..576f6b46 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 66/85] 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 4712c52a..364865d9 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 67/85] 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 14b19ae9..50b4255b 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 68/85] 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 50b4255b..4062e7c7 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 69/85] 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 4062e7c7..d067bce2 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 70/85] 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 d067bce2..a5d5a7ab 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 71/85] 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 a5d5a7ab..5b5c9b6d 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 72/85] 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 499c171a..afcafd54 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 19ff0a5f..aec5bf48 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 73/85] 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 bcd9f8f0..03cd0474 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 74/85] 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 78736c3b..a619f902 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 75/85] 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 f7c7dbcb..b4d1dffa 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 76/85] 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 2969a9e5..78b93cd9 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 e9bfdfdd..847e6f09 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 dc8b91d7..3a644750 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 77/85] 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 63d76a42..04f8df94 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 78/85] 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 4144ed3c..72d84271 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 79/85] 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 72d84271..61630d55 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 80/85] 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 847e6f09..e1120ef1 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 81/85] 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 e1120ef1..e5cf7bb2 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 82/85] 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 576f6b46..c88ed767 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 83/85] 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 6c37cfa1..a6644465 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 e5cf7bb2..431265cd 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 84/85] 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 6fa1ce67..88140130 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 85/85] 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 00000000..92f116ab --- /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"