From a3a3b0b517d0e690f3efc66b17ac7b9f769dba9d Mon Sep 17 00:00:00 2001 From: Martin Junghanns Date: Sat, 20 Nov 2021 06:17:25 -0800 Subject: [PATCH 01/36] Jump to end char of surrounding pair from any cursor pos (#1121) * Jump to end char of surrounding pair from any cursor pos * Separate bracket matching into exact and fuzzy search * Add constants for bracket chars * Abort early if char under cursor is not a bracket * Simplify bracket char validation * Refactor node search and unify find methods * Remove bracket constants --- helix-core/src/match_brackets.rs | 103 +++++++++++++++++++++---------- helix-term/src/commands.rs | 4 +- helix-term/src/ui/editor.rs | 2 +- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs index 136ce320d..cd554005a 100644 --- a/helix-core/src/match_brackets.rs +++ b/helix-core/src/match_brackets.rs @@ -1,3 +1,5 @@ +use tree_sitter::Node; + use crate::{Rope, Syntax}; const PAIRS: &[(char, char)] = &[ @@ -6,50 +8,85 @@ const PAIRS: &[(char, char)] = &[ ('[', ']'), ('<', '>'), ('\'', '\''), - ('"', '"'), + ('\"', '\"'), ]; + // limit matching pairs to only ( ) { } [ ] < > +// Returns the position of the matching bracket under cursor. +// +// If the cursor is one the opening bracket, the position of +// the closing bracket is returned. If the cursor in the closing +// bracket, the position of the opening bracket is returned. +// +// If the cursor is not on a bracket, `None` is returned. #[must_use] -pub fn find(syntax: &Syntax, doc: &Rope, pos: usize) -> Option { - let tree = syntax.tree(); - - let byte_pos = doc.char_to_byte(pos); - - // most naive implementation: find the innermost syntax node, if we're at the edge of a node, - // return the other edge. - - let node = match tree - .root_node() - .named_descendant_for_byte_range(byte_pos, byte_pos) - { - Some(node) => node, - None => return None, - }; - - if node.is_error() { +pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option { + if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) { return None; } + find_pair(syntax, doc, pos, false) +} +// Returns the position of the bracket that is closing the current scope. +// +// If the cursor is on an opening or closing bracket, the function +// behaves equivalent to [`find_matching_bracket`]. +// +// If the cursor position is within a scope, the function searches +// for the surrounding scope that is surrounded by brackets and +// returns the position of the closing bracket for that scope. +// +// If no surrounding scope is found, the function returns `None`. +#[must_use] +pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option { + find_pair(syntax, doc, pos, true) +} + +fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option { + let tree = syntax.tree(); + let pos = doc.char_to_byte(pos); + + let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?; + + loop { + let (start_byte, end_byte) = surrounding_bytes(doc, &node)?; + let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte)); + + if is_valid_pair(doc, start_char, end_char) { + if end_byte == pos { + return Some(start_char); + } + // We return the end char if the cursor is either on the start char + // or at some arbitrary position between start and end char. + return Some(end_char); + } + + if traverse_parents { + node = node.parent()?; + } else { + return None; + } + } +} + +fn is_valid_bracket(c: char) -> bool { + PAIRS.iter().any(|(l, r)| *l == c || *r == c) +} + +fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool { + PAIRS.contains(&(doc.char(start_char), doc.char(end_char))) +} + +fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> { let len = doc.len_bytes(); + let start_byte = node.start_byte(); - let end_byte = node.end_byte().saturating_sub(1); // it's end exclusive + let end_byte = node.end_byte().saturating_sub(1); + if start_byte >= len || end_byte >= len { return None; } - let start_char = doc.byte_to_char(start_byte); - let end_char = doc.byte_to_char(end_byte); - - if PAIRS.contains(&(doc.char(start_char), doc.char(end_char))) { - if start_byte == byte_pos { - return Some(end_char); - } - - if end_byte == byte_pos { - return Some(start_char); - } - } - - None + Some((start_byte, end_byte)) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 431265cd5..e70773eba 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4954,7 +4954,9 @@ fn match_brackets(cx: &mut Context) { if let Some(syntax) = doc.syntax() { let text = doc.text().slice(..); let selection = doc.selection(view.id).clone().transform(|range| { - if let Some(pos) = match_brackets::find(syntax, doc.text(), range.anchor) { + if let Some(pos) = + match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.anchor) + { range.put_cursor(text, pos, doc.mode == Mode::Select) } else { range diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 03cd04748..27d33d225 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -377,7 +377,7 @@ impl EditorView { use helix_core::match_brackets; let pos = doc.selection(view.id).primary().cursor(text); - let pos = match_brackets::find(syntax, doc.text(), pos) + let pos = match_brackets::find_matching_bracket(syntax, doc.text(), pos) .and_then(|pos| view.screen_coords_at_pos(doc, text, pos)); if let Some(pos) = pos { From 05c6cb1d0b576547c14b204e0df543650c93892f Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 20 Nov 2021 09:17:38 -0500 Subject: [PATCH 02/36] Solarized theme: fix popup colors, adjust menu (#1124) * fix popup colors, adjust menu * fix hardcoded horizontal rule color --- helix-term/src/ui/markdown.rs | 6 ++++-- runtime/themes/solarized_dark.toml | 12 ++++++------ runtime/themes/solarized_light.toml | 12 ++++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 61630d555..649703b51 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -55,7 +55,7 @@ fn parse<'a>( fn to_span(text: pulldown_cmark::CowStr) -> Span { use std::ops::Deref; Span::raw::>(match text { - CowStr::Borrowed(s) => s.to_string().into(), // could retain borrow + CowStr::Borrowed(s) => s.into(), CowStr::Boxed(s) => s.to_string().into(), CowStr::Inlined(s) => s.deref().to_owned().into(), }) @@ -179,7 +179,9 @@ fn parse<'a>( spans.push(Span::raw(" ")); } Event::Rule => { - lines.push(Spans::from("---")); + let mut span = Span::raw("---"); + span.style = code_style; + lines.push(Spans::from(span)); lines.push(Spans::default()); } // TaskListMarker(bool) true if checked diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml index afcafd541..984c86ee8 100644 --- a/runtime/themes/solarized_dark.toml +++ b/runtime/themes/solarized_dark.toml @@ -28,18 +28,18 @@ # 行号栏 "ui.linenr" = { fg = "base0", bg = "base02" } # 当前行号栏 -"ui.linenr.selected" = { fg = "red", modifiers = ["bold"] } +"ui.linenr.selected" = { fg = "blue", modifiers = ["bold"] } # 状态栏 -"ui.statusline" = { fg = "base02", bg = "base1" } +"ui.statusline" = { fg = "base03", bg = "base0" } # 非活动状态栏 -"ui.statusline.inactive" = { fg = "base02", bg = "base00" } +"ui.statusline.inactive" = { fg = "base1", bg = "base01" } # 补全窗口, preview窗口 -"ui.popup" = { bg = "base1" } +"ui.popup" = { bg = "base02" } # 影响 补全选中 cmd弹出信息选中 -"ui.menu.selected" = { fg = "base02", bg = "violet"} -"ui.menu" = { fg = "base02" } +"ui.menu.selected" = { fg = "base02", bg = "base2"} +"ui.menu" = { fg = "base1" } # ?? "ui.window" = { fg = "base3" } # 命令行 补全的帮助信息 diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml index aec5bf48c..0ab1b9626 100644 --- a/runtime/themes/solarized_light.toml +++ b/runtime/themes/solarized_light.toml @@ -28,18 +28,18 @@ # 行号栏 "ui.linenr" = { fg = "base0", bg = "base02" } # 当前行号栏 -"ui.linenr.selected" = { fg = "red", modifiers = ["bold"] } +"ui.linenr.selected" = { fg = "blue", modifiers = ["bold"] } # 状态栏 -"ui.statusline" = { fg = "base02", bg = "base1" } +"ui.statusline" = { fg = "base03", bg = "base0" } # 非活动状态栏 -"ui.statusline.inactive" = { fg = "base02", bg = "base00" } +"ui.statusline.inactive" = { fg = "base1", bg = "base01" } # 补全窗口, preview窗口 -"ui.popup" = { bg = "base1" } +"ui.popup" = { bg = "base02" } # 影响 补全选中 cmd弹出信息选中 -"ui.menu.selected" = { fg = "base02", bg = "violet"} -"ui.menu" = { fg = "base02" } +"ui.menu.selected" = { fg = "base02", bg = "base2"} +"ui.menu" = { fg = "base1" } # ?? "ui.window" = { fg = "base3" } # 命令行 补全的帮助信息 From 6a4d9693ba12feed5b6d6b1b34a4ff56cb9f9fd7 Mon Sep 17 00:00:00 2001 From: Dan Nases Sha <70554613+dannasessha@users.noreply.github.com> Date: Sat, 20 Nov 2021 14:23:36 +0000 Subject: [PATCH 03/36] File picker config (#988) * squashed WIP commits * hide_gitignore working with config * pass reference to new config parameter of file_picker() * update config option name to match name on walk builder * add comments to config and documentation of option to book * add git_ignore option to WalkBuilder within prompt in commands.rs * WIP: add FilePickerConfig struct * WIP: cleanup * WIP: add more options including max_depth * WIP: changed defaults to match ignore crate defaults * WIP: change WalkBuilder in global_search() to use config options * WIP: removed follow_links, changed max_depth to follow config setting * WIP: update book with file-picker inline table notation * update documentation for file-picker config in book * adjusted to [editor.file-picker] in book configuration.md * adjust comments in editor.rs to be doc comments, cleanup * adjust comments * adjust book --- book/src/configuration.md | 12 ++++++ helix-term/src/application.rs | 2 +- helix-term/src/commands.rs | 77 +++++++++++++++++++++-------------- helix-term/src/ui/mod.rs | 11 ++++- helix-view/src/editor.rs | 42 +++++++++++++++++++ 5 files changed, 111 insertions(+), 33 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index be25441f5..2ed48d51f 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -24,6 +24,18 @@ To override global configuration parameters, create a `config.toml` file located | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `auto-info` | Whether to display infoboxes | `true` | +`[editor.filepicker]` section of the config. Sets options for file picker and global search. All but the last key listed in the default file-picker configuration below are IgnoreOptions: whether hidden files and files listed within ignore files are ignored by (not visible in) the helix file picker and global search. There is also one other key, `max-depth` available, which is not defined by default. + +| Key | Description | Default | +|--|--|---------| +|`hidden` | Enables ignoring hidden files. | true +|`parents` | Enables reading ignore files from parent directories. | true +|`ignore` | Enables reading `.ignore` files. | true +|`git-ignore` | Enables reading `.gitignore` files. | true +|`git-global` | Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. | true +|`git-exclude` | Enables reading `.git/info/exclude` files. | true +|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`. + ## LSP To display all language server messages in the status line add the following to your `config.toml`: diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 78b93cd96..a795a56e8 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -120,7 +120,7 @@ impl Application { if first.is_dir() { std::env::set_current_dir(&first)?; editor.new_file(Action::VerticalSplit); - compositor.push(Box::new(ui::file_picker(".".into()))); + compositor.push(Box::new(ui::file_picker(".".into(), &config.editor))); } else { let nr_of_files = args.files.len(); editor.open(first.to_path_buf(), Action::VerticalSplit)?; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e70773eba..fde505fdc 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1440,6 +1440,7 @@ fn global_search(cx: &mut Context) { let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); let smart_case = cx.editor.config.smart_case; + let file_picker_config = cx.editor.config.file_picker.clone(); let completions = search_completions(cx, None); let prompt = ui::regex_prompt( @@ -1468,41 +1469,55 @@ fn global_search(cx: &mut Context) { let search_root = std::env::current_dir() .expect("Global search error: Failed to get current dir"); - WalkBuilder::new(search_root).build_parallel().run(|| { - let mut searcher_cl = searcher.clone(); - let matcher_cl = matcher.clone(); - let all_matches_sx_cl = all_matches_sx.clone(); - Box::new(move |dent: Result| -> WalkState { - let dent = match dent { - Ok(dent) => dent, - Err(_) => return WalkState::Continue, - }; + WalkBuilder::new(search_root) + .hidden(file_picker_config.hidden) + .parents(file_picker_config.parents) + .ignore(file_picker_config.ignore) + .git_ignore(file_picker_config.git_ignore) + .git_global(file_picker_config.git_global) + .git_exclude(file_picker_config.git_exclude) + .max_depth(file_picker_config.max_depth) + .build_parallel() + .run(|| { + let mut searcher_cl = searcher.clone(); + let matcher_cl = matcher.clone(); + let all_matches_sx_cl = all_matches_sx.clone(); + Box::new(move |dent: Result| -> WalkState { + let dent = match dent { + Ok(dent) => dent, + Err(_) => return WalkState::Continue, + }; - match dent.file_type() { - Some(fi) => { - if !fi.is_file() { - return WalkState::Continue; + match dent.file_type() { + Some(fi) => { + if !fi.is_file() { + return WalkState::Continue; + } } + None => return WalkState::Continue, } - None => return WalkState::Continue, - } - let result_sink = sinks::UTF8(|line_num, _| { - match all_matches_sx_cl - .send((line_num as usize - 1, dent.path().to_path_buf())) - { - Ok(_) => Ok(true), - Err(_) => Ok(false), + let result_sink = sinks::UTF8(|line_num, _| { + match all_matches_sx_cl + .send((line_num as usize - 1, dent.path().to_path_buf())) + { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } + }); + let result = + searcher_cl.search_path(&matcher_cl, dent.path(), result_sink); + + if let Err(err) = result { + log::error!( + "Global search error: {}, {}", + dent.path().display(), + err + ); } - }); - let result = searcher_cl.search_path(&matcher_cl, dent.path(), result_sink); - - if let Err(err) = result { - log::error!("Global search error: {}, {}", dent.path().display(), err); - } - WalkState::Continue - }) - }); + WalkState::Continue + }) + }); } else { // Otherwise do nothing // log::warn!("Global Search Invalid Pattern") @@ -2742,7 +2757,7 @@ fn command_mode(cx: &mut Context) { fn file_picker(cx: &mut Context) { let root = find_root(None).unwrap_or_else(|| PathBuf::from("./")); - let picker = ui::file_picker(root); + let picker = ui::file_picker(root, &cx.editor.config); cx.push_layer(Box::new(picker)); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 62da0dce8..cdf423110 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -93,13 +93,22 @@ pub fn regex_prompt( ) } -pub fn file_picker(root: PathBuf) -> FilePicker { +pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker { use ignore::{types::TypesBuilder, WalkBuilder}; use std::time; // We want to exclude files that the editor can't handle yet let mut type_builder = TypesBuilder::new(); let mut walk_builder = WalkBuilder::new(&root); + walk_builder + .hidden(config.file_picker.hidden) + .parents(config.file_picker.parents) + .ignore(config.file_picker.ignore) + .git_ignore(config.file_picker.git_ignore) + .git_global(config.file_picker.git_global) + .git_exclude(config.file_picker.git_exclude) + .max_depth(config.file_picker.max_depth); + let walk_builder = match type_builder.add( "compressed", "*.{zip,gz,bz2,zst,lzo,sz,tgz,tbz2,lz,lz4,lzma,lzo,z,Z,xz,7z,rar,cab}", diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 364865d9c..1ce33760e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -35,6 +35,46 @@ where Ok(Duration::from_millis(millis)) } +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct FilePickerConfig { + /// IgnoreOptions + /// Enables ignoring hidden files. + /// Whether to hide hidden files in file picker and global search results. Defaults to true. + pub hidden: bool, + /// Enables reading ignore files from parent directories. Defaults to true. + pub parents: bool, + /// Enables reading `.ignore` files. + /// Whether to hide files listed in .ignore in file picker and global search results. Defaults to true. + pub ignore: bool, + /// Enables reading `.gitignore` files. + /// Whether to hide files listed in .gitignore in file picker and global search results. Defaults to true. + pub git_ignore: bool, + /// Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. + /// Whether to hide files listed in global .gitignore in file picker and global search results. Defaults to true. + pub git_global: bool, + /// Enables reading `.git/info/exclude` files. + /// Whether to hide files listed in .git/info/exclude in file picker and global search results. Defaults to true. + pub git_exclude: bool, + /// WalkBuilder options + /// Maximum Depth to recurse directories in file picker and global search. Defaults to `None`. + pub max_depth: Option, +} + +impl Default for FilePickerConfig { + fn default() -> Self { + Self { + hidden: true, + parents: true, + ignore: true, + git_ignore: true, + git_global: true, + git_exclude: true, + max_depth: None, + } + } +} + #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { @@ -62,6 +102,7 @@ pub struct Config { pub completion_trigger_len: u8, /// Whether to display infoboxes. Defaults to true. pub auto_info: bool, + pub file_picker: FilePickerConfig, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -93,6 +134,7 @@ impl Default for Config { idle_timeout: Duration::from_millis(400), completion_trigger_len: 2, auto_info: true, + file_picker: FilePickerConfig::default(), } } } From 17249307656d716a56cb994b11add82468dd4753 Mon Sep 17 00:00:00 2001 From: NNB Date: Mon, 22 Nov 2021 05:16:26 +0700 Subject: [PATCH 04/36] Fix "good first issue" link (#1140) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index faf5851ed..3f4087b9f 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Contributors are very welcome! **No contribution is too small and all contributi Some suggestions to get started: -- You can look at the [good first issue](https://github.com/helix-editor/helix/labels/E-easy) label on the issue tracker. +- You can look at the [good first issue](https://github.com/helix-editor/helix/issues?q=is%3Aopen+label%3AE-easy+label%3AE-good-first-issue) label on the issue tracker. - Help with packaging on various distributions needed! - To use print debugging to the [Helix log file](https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file), you must: * Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`) From 4238a843f7a3be573967d0392905fb272f1f4e86 Mon Sep 17 00:00:00 2001 From: NexiNov Date: Mon, 22 Nov 2021 20:14:42 +0530 Subject: [PATCH 05/36] Add link to Keymap page in book. (#1137) --- book/src/remapping.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/remapping.md b/book/src/remapping.md index 532f502ae..fffd189b7 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -53,4 +53,5 @@ Control, Shift and Alt modifiers are encoded respectively with the prefixes Keys can be disabled by binding them to the `no_op` command. -Commands can be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) +Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands. +> Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `commands!` macro. From 8fec8c079177b4255551f394c4248e790a9b37bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:55:20 +0900 Subject: [PATCH 06/36] build(deps): bump tree-sitter from 0.20.0 to 0.20.1 (#1145) Bumps [tree-sitter](https://github.com/tree-sitter/tree-sitter) from 0.20.0 to 0.20.1. - [Release notes](https://github.com/tree-sitter/tree-sitter/releases) - [Commits](https://github.com/tree-sitter/tree-sitter/compare/v0.20.0...v0.20.1) --- updated-dependencies: - dependency-name: tree-sitter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b5c9b6d0..080ca2ca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,9 +1120,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ec02a07a782abef91279b72fe8fd2bee4c168a22112cedec5d3b0d49b9e4f9" +checksum = "9394e9dbfe967b5f3d6ab79e302e78b5fb7b530c368d634ff3b8d67ede138bf1" dependencies = [ "cc", "regex", From a2f301ee4f923027a304cc6ad2254e073aedc02d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:56:35 +0900 Subject: [PATCH 07/36] build(deps): bump anyhow from 1.0.46 to 1.0.48 (#1144) Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.46 to 1.0.48. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.46...1.0.48) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 080ca2ca0..70bd72310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.46" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa828229c44c0293dd7d4d2300bdfc4d2883ffdba934c069a6b968957a81f70" +checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e" [[package]] name = "arc-swap" From 2cc19bd8e4321135f01a5a0fb986670403117c00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:56:58 +0900 Subject: [PATCH 08/36] build(deps): bump serde_json from 1.0.70 to 1.0.71 (#1147) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.70 to 1.0.71. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.70...v1.0.71) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70bd72310..ccdc0fdc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,9 +897,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" +checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" dependencies = [ "itoa", "ryu", From f24e5a3c414363c6847fd7e806b434f8a6772ef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:07:59 +0900 Subject: [PATCH 09/36] build(deps): bump tokio from 1.13.1 to 1.14.0 (#1146) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.13.1 to 1.14.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.13.1...tokio-1.14.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- helix-lsp/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccdc0fdc0..8d2b4562d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,9 +1069,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52963f91310c08d91cb7bff5786dfc8b79642ab839e188187e92105dbfb9d2c8" +checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" dependencies = [ "autocfg", "bytes", @@ -1089,9 +1089,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" +checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" dependencies = [ "proc-macro2", "quote", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 920ce0371..5e4619ef8 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -23,5 +23,5 @@ lsp-types = { version = "0.91", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.13", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } +tokio = { version = "1.14", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio-stream = "0.1.8" From 21143e8d22c13ead8c1835063acb518aa5a42822 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 23 Nov 2021 22:08:05 +0800 Subject: [PATCH 10/36] Align selections via & (#1101) * align lines * remove log statement * use selections to align * fix a clippy issue * only accept 1,2,3 as user count * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * return if user count is not correct * add doc Co-authored-by: Ivan Tham --- book/src/keymap.md | 1 + helix-term/src/commands.rs | 71 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index c88ed767e..fbe77267d 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -92,6 +92,7 @@ | `s` | Select all regex matches inside selections | `select_regex` | | `S` | Split selection into subselections on regex matches | `split_selection` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` | +| `&` | Align selection in columns | `align_selections` | | `_` | Trim whitespace from the selection | `trim_selections` | | `;` | Collapse selection onto a single cursor | `collapse_selection` | | `Alt-;` | Flip selection cursor and anchor | `flip_selections` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fde505fdc..a7179c303 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -314,6 +314,7 @@ impl Command { join_selections, "Join lines inside selection", keep_selections, "Keep selections matching regex", remove_selections, "Remove selections matching regex", + align_selections, "Align selections in column", keep_primary_selection, "Keep primary selection", remove_primary_selection, "Remove primary selection", completion, "Invoke completion popup", @@ -657,6 +658,76 @@ fn trim_selections(cx: &mut Context) { }; } +// align text in selection +fn align_selections(cx: &mut Context) { + let align_style = cx.count(); + if align_style > 3 { + cx.editor.set_error( + "align only accept 1,2,3 as count to set left/center/right align".to_string(), + ); + return; + } + + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + let selection = doc.selection(view.id); + let mut column_widths = vec![]; + let mut last_line = text.len_lines(); + let mut column = 0; + // first of all, we need compute all column's width, let use max width of the selections in a column + for sel in selection { + let (l1, l2) = sel.line_range(text); + if l1 != l2 { + cx.editor + .set_error("align cannot work with multi line selections".to_string()); + return; + } + // if the selection is not in the same line with last selection, we set the column to 0 + column = if l1 != last_line { 0 } else { column + 1 }; + last_line = l1; + + if column < column_widths.len() { + if sel.to() - sel.from() > column_widths[column] { + column_widths[column] = sel.to() - sel.from(); + } + } else { + // a new column, current selection width is the temp width of the column + column_widths.push(sel.to() - sel.from()); + } + } + last_line = text.len_lines(); + // once we get the with of each column, we transform each selection with to it's column width based on the align style + let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + let l = range.cursor_line(text); + column = if l != last_line { 0 } else { column + 1 }; + last_line = l; + + ( + range.from(), + range.to(), + Some( + align_fragment_to_width(&range.fragment(text), column_widths[column], align_style) + .into(), + ), + ) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) -> String { + let trimed = fragment.trim_matches(|c| c == ' '); + let mut s = " ".repeat(width - trimed.chars().count()); + match align_style { + 1 => s.insert_str(0, trimed), // left align + 2 => s.insert_str(s.len() / 2, trimed), // center align + 3 => s.push_str(trimed), // right align + n => unimplemented!("{}", n), + } + s +} + fn goto_window(cx: &mut Context, align: Align) { let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index bf3b594e8..7f6d0c6b9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -604,7 +604,7 @@ impl Default for Keymaps { // "q" => record_macro, // "Q" => replay_macro, - // & align selections + "&" => align_selections, "_" => trim_selections, "(" => rotate_selections_backward, From 57c14d4a9315aecc1be0c8f268e7dd3d541c1480 Mon Sep 17 00:00:00 2001 From: Martin Junghanns Date: Tue, 23 Nov 2021 22:26:55 -0800 Subject: [PATCH 11/36] Add `:` and `:goto ` commands (#1128) * Add typable `goto` command * Support `:` on prompt * Rename function according to convention * Directly call into goto_line_number function --- helix-term/src/commands.rs | 42 +++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a7179c303..2c556281a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2460,6 +2460,22 @@ mod cmd { Ok(()) } + pub(super) fn goto_line_number( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let line = args[0].parse::()?; + + goto_line_impl(&mut cx.editor, NonZeroUsize::new(line)); + + let (view, doc) = current!(cx.editor); + + view.ensure_cursor_in_view(doc, line); + + Ok(()) + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -2741,6 +2757,13 @@ mod cmd { fun: tutor, completer: None, }, + TypableCommand { + name: "goto", + aliases: &["g"], + doc: "Go to line number.", + fun: goto_line_number, + completer: None, + } ]; pub static COMMANDS: Lazy> = Lazy::new(|| { @@ -2803,6 +2826,15 @@ fn command_mode(cx: &mut Context) { return; } + // If command is numeric, interpret as line number and go there. + if parts.len() == 1 && parts[0].parse::().ok().is_some() { + if let Err(e) = cmd::goto_line_number(cx, &parts[0..], event) { + cx.editor.set_error(format!("{}", e)); + } + return; + } + + // Handle typable commands if let Some(cmd) = cmd::COMMANDS.get(parts[0]) { if let Err(e) = (cmd.fun)(cx, &parts[1..], event) { cx.editor.set_error(format!("{}", e)); @@ -3436,10 +3468,14 @@ fn push_jump(editor: &mut Editor) { } fn goto_line(cx: &mut Context) { - if let Some(count) = cx.count { - push_jump(cx.editor); + goto_line_impl(&mut cx.editor, cx.count) +} - let (view, doc) = current!(cx.editor); +fn goto_line_impl(editor: &mut Editor, count: Option) { + if let Some(count) = count { + push_jump(editor); + + let (view, doc) = current!(editor); let max_line = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 { // If the last line is blank, don't jump to it. doc.text().len_lines().saturating_sub(2) From 72f606ee19a20eca7901ac1647e8efa58ea4f8b8 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Wed, 24 Nov 2021 08:46:40 +0100 Subject: [PATCH 12/36] Implement no-yank delete/change (#1099) --- book/src/keymap.md | 72 ++++++++++++++++++++------------------ helix-term/src/commands.rs | 66 ++++++++++++++++++++++------------ helix-term/src/keymap.rs | 4 +-- 3 files changed, 82 insertions(+), 60 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index fbe77267d..4d5428005 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -45,44 +45,46 @@ ### Changes -| Key | Description | Command | -| ----- | ----------- | ------- | -| `r` | Replace with a character | `replace` | -| `R` | Replace with yanked text | `replace_with_yanked` | -| `~` | Switch case of the selected text | `switch_case` | -| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | -| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | -| `i` | Insert before selection | `insert_mode` | -| `a` | Insert after selection (append) | `append_mode` | -| `I` | Insert at the start of the line | `prepend_to_line` | -| `A` | Insert at the end of the line | `append_to_line` | -| `o` | Open new line below selection | `open_below` | -| `O` | Open new line above selection | `open_above` | -| `.` | Repeat last change | N/A | -| `u` | Undo change | `undo` | -| `U` | Redo change | `redo` | -| `Alt-u` | Move backward in history | `earlier` | -| `Alt-U` | Move forward in history | `later` | -| `y` | Yank selection | `yank` | -| `p` | Paste after selection | `paste_after` | -| `P` | Paste before selection | `paste_before` | -| `"` `` | Select a register to yank to or paste from | `select_register` | -| `>` | Indent selection | `indent` | -| `<` | Unindent selection | `unindent` | -| `=` | Format selection (**LSP**) | `format_selections` | -| `d` | Delete selection | `delete_selection` | -| `c` | Change selection (delete and enter insert mode) | `change_selection` | -| `Ctrl-a` | Increment object (number) under cursor | `increment` | -| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `r` | Replace with a character | `replace` | +| `R` | Replace with yanked text | `replace_with_yanked` | +| `~` | Switch case of the selected text | `switch_case` | +| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | +| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | +| `i` | Insert before selection | `insert_mode` | +| `a` | Insert after selection (append) | `append_mode` | +| `I` | Insert at the start of the line | `prepend_to_line` | +| `A` | Insert at the end of the line | `append_to_line` | +| `o` | Open new line below selection | `open_below` | +| `O` | Open new line above selection | `open_above` | +| `.` | Repeat last change | N/A | +| `u` | Undo change | `undo` | +| `U` | Redo change | `redo` | +| `Alt-u` | Move backward in history | `earlier` | +| `Alt-U` | Move forward in history | `later` | +| `y` | Yank selection | `yank` | +| `p` | Paste after selection | `paste_after` | +| `P` | Paste before selection | `paste_before` | +| `"` `` | Select a register to yank to or paste from | `select_register` | +| `>` | Indent selection | `indent` | +| `<` | Unindent selection | `unindent` | +| `=` | Format selection (**LSP**) | `format_selections` | +| `d` | Delete selection | `delete_selection` | +| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` | +| `c` | Change selection (delete and enter insert mode) | `change_selection` | +| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | +| `Ctrl-a` | Increment object (number) under cursor | `increment` | +| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | #### Shell -| Key | Description | Command | -| ------ | ----------- | ------- | -| | | Pipe each selection through shell command, replacing with output | `shell_pipe` | -| A-| | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | -| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | -| `A-!` | Run shell command, appending output after each selection | `shell_append_output` | +| Key | Description | Command | +| ------ | ----------- | ------- | +| | | Pipe each selection through shell command, replacing with output | `shell_pipe` | +| Alt-| | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | +| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | +| `Alt-!` | Run shell command, appending output after each selection | `shell_append_output` | ### Selection manipulation diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2c556281a..b5b6ac6de 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -9,7 +9,6 @@ use helix_core::{ numbers::NumberIncrementor, object, pos_at_coords, regex::{self, Regex, RegexBuilder}, - register::Register, search, selection, surround, textobject, unicode::width::UnicodeWidthChar, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, @@ -232,7 +231,9 @@ impl Command { extend_line, "Select current line, if already selected, extend to next line", extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)", delete_selection, "Delete selection", + delete_selection_noyank, "Delete selection, without yanking", change_selection, "Change selection (delete and enter insert mode)", + change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)", collapse_selection, "Collapse selection onto a single cursor", flip_selections, "Flip selection cursor and anchor", insert_mode, "Insert before selection", @@ -1693,19 +1694,42 @@ fn extend_to_line_bounds(cx: &mut Context) { ); } -fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) { - let text = doc.text().slice(..); - let selection = doc.selection(view_id); +enum Operation { + Delete, + Change, +} - // first yank the selection - let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); - reg.write(values); +fn delete_selection_impl(cx: &mut Context, op: Operation) { + let (view, doc) = current!(cx.editor); + + let text = doc.text().slice(..); + let selection = doc.selection(view.id); + + if cx.register != Some('_') { + // first yank the selection + let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); + let reg_name = cx.register.unwrap_or('"'); + let registers = &mut cx.editor.registers; + let reg = registers.get_mut(reg_name); + reg.write(values); + }; // then delete let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { (range.from(), range.to(), None) }); - doc.apply(&transaction, view_id); + doc.apply(&transaction, view.id); + + match op { + Operation::Delete => { + doc.append_changes_to_history(view.id); + // exit select mode, if currently in select mode + exit_select_mode(cx); + } + Operation::Change => { + enter_insert_mode(doc); + } + } } #[inline] @@ -1720,25 +1744,21 @@ fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Sel } fn delete_selection(cx: &mut Context) { - let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - let reg = registers.get_mut(reg_name); - delete_selection_impl(reg, doc, view.id); + delete_selection_impl(cx, Operation::Delete); +} - doc.append_changes_to_history(view.id); - - // exit select mode, if currently in select mode - exit_select_mode(cx); +fn delete_selection_noyank(cx: &mut Context) { + cx.register = Some('_'); + delete_selection_impl(cx, Operation::Delete); } fn change_selection(cx: &mut Context) { - let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - let reg = registers.get_mut(reg_name); - delete_selection_impl(reg, doc, view.id); - enter_insert_mode(doc); + delete_selection_impl(cx, Operation::Change); +} + +fn change_selection_noyank(cx: &mut Context) { + cx.register = Some('_'); + delete_selection_impl(cx, Operation::Change); } fn collapse_selection(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 7f6d0c6b9..0062e636c 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -537,9 +537,9 @@ impl Default for Keymaps { "O" => open_above, "d" => delete_selection, - // TODO: also delete without yanking + "A-d" => delete_selection_noyank, "c" => change_selection, - // TODO: also change delete without yanking + "A-c" => change_selection_noyank, "C" => copy_selection_on_next_line, "A-C" => copy_selection_on_prev_line, From 1eecd9a2ac97a3a1190753107f8e0da72a5e5e21 Mon Sep 17 00:00:00 2001 From: shenlebantongying Date: Wed, 24 Nov 2021 08:47:12 -0500 Subject: [PATCH 13/36] Add language: racket (#1143) --- languages.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/languages.toml b/languages.toml index 2ce9321e0..5326f9176 100644 --- a/languages.toml +++ b/languages.toml @@ -396,3 +396,12 @@ shebangs = ["perl"] roots = [] comment-token = "#" indent = { tab-width = 2, unit = " " } + +[[language]] +name = "racket" +scope = "source.rkt" +roots = [] +file-types = ["rkt"] +shebangs = ["racket"] +comment-token = ";" +language-server = { command = "racket", args = ["-l", "racket-langserver"] } From 95f392b18db27117b26c4be732a4298353bdc27e Mon Sep 17 00:00:00 2001 From: Thanabodee Charoenpiriyakij Date: Wed, 24 Nov 2021 22:28:25 +0700 Subject: [PATCH 14/36] Fix bug report template use wrong hx version (#1158) --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9b7c22e7a..958407bb8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,7 +17,7 @@ Please search on the issue tracker before creating one. --> ### Environment - Platform: -- Helix version: +- Helix version:
~/.cache/helix/helix.log From e8f800a141af11b4f2f4c838cd4fd1f8aa7fa971 Mon Sep 17 00:00:00 2001 From: Thanabodee Charoenpiriyakij Date: Thu, 25 Nov 2021 09:02:51 +0700 Subject: [PATCH 15/36] Do not crash when run goto command without line number (#1160) * Do not crash when run goto command without line number Report an error when running goto command without entering a line number. Fixes #1159 * Use is_empty() instead check len zero --- helix-term/src/commands.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b5b6ac6de..ff8d7a4ff 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2485,6 +2485,10 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { + if args.is_empty() { + bail!("Line number required"); + } + let line = args[0].parse::()?; goto_line_impl(&mut cx.editor, NonZeroUsize::new(line)); From 67bf4250caf14d3a632abdbfa367c04b318d782c Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Thu, 25 Nov 2021 10:07:23 +0800 Subject: [PATCH 16/36] Optimize space for DocumentId with NonZeroUsize (#1097) Now Option uses one byte rather than two --- helix-view/src/editor.rs | 29 +++++++++++++++-------------- helix-view/src/lib.rs | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1ce33760e..725dc1b82 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -11,6 +11,7 @@ use futures_util::future; use std::{ collections::BTreeMap, io::stdin, + num::NonZeroUsize, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -154,7 +155,7 @@ impl std::fmt::Debug for Motion { #[derive(Debug)] pub struct Editor { pub tree: Tree, - pub next_document_id: usize, + pub next_document_id: DocumentId, pub documents: BTreeMap, pub count: Option, pub selected_register: Option, @@ -198,7 +199,8 @@ impl Editor { Self { tree: Tree::new(area), - next_document_id: 0, + // Safety: 1 is non-zero + next_document_id: DocumentId::default(), documents: BTreeMap::new(), count: None, selected_register: None, @@ -367,16 +369,19 @@ impl Editor { self._refresh(); } - fn new_document(&mut self, mut document: Document) -> DocumentId { - let id = DocumentId(self.next_document_id); - self.next_document_id += 1; - document.id = id; - self.documents.insert(id, document); + /// Generate an id for a new document and register it. + fn new_document(&mut self, mut doc: Document) -> DocumentId { + let id = self.next_document_id; + // Safety: adding 1 from 1 is fine, probably impossible to reach usize max + self.next_document_id = + DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) }); + doc.id = id; + self.documents.insert(id, doc); id } - fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId { - let id = self.new_document(document); + fn new_file_from_document(&mut self, action: Action, doc: Document) -> DocumentId { + let id = self.new_document(doc); self.switch(id, action); id } @@ -435,11 +440,7 @@ impl Editor { doc.set_language_server(Some(language_server)); } - let id = DocumentId(self.next_document_id); - self.next_document_id += 1; - doc.id = id; - self.documents.insert(id, doc); - id + self.new_document(doc) }; self.switch(id, action); diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 3e779356c..4d19ee2e0 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -12,8 +12,18 @@ pub mod theme; pub mod tree; pub mod view; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub struct DocumentId(usize); +use std::num::NonZeroUsize; + +// uses NonZeroUsize so Option use a byte rather than two +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct DocumentId(NonZeroUsize); + +impl Default for DocumentId { + fn default() -> DocumentId { + // Safety: 1 is non-zero + DocumentId(unsafe { NonZeroUsize::new_unchecked(1) }) + } +} slotmap::new_key_type! { pub struct ViewId; From 6e62c3de47ff14df394c1c17fec103c134aa045c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 26 Nov 2021 18:26:22 +0900 Subject: [PATCH 17/36] Simplify some code in editor.rs --- helix-term/src/application.rs | 2 +- helix-view/src/editor.rs | 54 +++++++++++++++-------------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index a795a56e8..90330751a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -265,7 +265,7 @@ impl Application { use crate::commands::{insert::idle_completion, Context}; use helix_view::document::Mode; - if doc_mut!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion { + if doc!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion { return; } let editor_view = self diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 725dc1b82..b93d8126a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -19,7 +19,7 @@ use std::{ use tokio::time::{sleep, Duration, Instant, Sleep}; -use anyhow::Error; +use anyhow::{bail, Context, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; @@ -188,8 +188,8 @@ pub enum Action { impl Editor { pub fn new( mut area: Rect, - themes: Arc, - config_loader: Arc, + theme_loader: Arc, + syn_loader: Arc, config: Config, ) -> Self { let language_servers = helix_lsp::Registry::new(); @@ -199,15 +199,14 @@ impl Editor { Self { tree: Tree::new(area), - // Safety: 1 is non-zero next_document_id: DocumentId::default(), documents: BTreeMap::new(), count: None, selected_register: None, - theme: themes.default(), + theme: theme_loader.default(), language_servers, - syn_loader: config_loader, - theme_loader: themes, + syn_loader, + theme_loader, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), status_msg: None, @@ -264,7 +263,6 @@ impl Editor { } pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> { - use anyhow::Context; let theme = self .theme_loader .load(theme.as_ref()) @@ -343,23 +341,22 @@ impl Editor { } Action::Load => { let view_id = view!(self).id; - if let Some(doc) = self.document_mut(id) { - if doc.selections().is_empty() { - doc.selections.insert(view_id, Selection::point(0)); - } + let doc = self.documents.get_mut(&id).unwrap(); + if doc.selections().is_empty() { + doc.selections.insert(view_id, Selection::point(0)); } return; } - Action::HorizontalSplit => { + Action::HorizontalSplit | Action::VerticalSplit => { let view = View::new(id); - let view_id = self.tree.split(view, Layout::Horizontal); - // initialize selection for view - let doc = self.documents.get_mut(&id).unwrap(); - doc.selections.insert(view_id, Selection::point(0)); - } - Action::VerticalSplit => { - let view = View::new(id); - let view_id = self.tree.split(view, Layout::Vertical); + let view_id = self.tree.split( + view, + match action { + Action::HorizontalSplit => Layout::Horizontal, + Action::VerticalSplit => Layout::Vertical, + _ => unreachable!(), + }, + ); // initialize selection for view let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); @@ -397,11 +394,7 @@ impl Editor { pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; - - let id = self - .documents() - .find(|doc| doc.path() == Some(&path)) - .map(|doc| doc.id); + let id = self.document_by_path(&path).map(|doc| doc.id); let id = if let Some(id) = id { id @@ -463,11 +456,11 @@ impl Editor { pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> { let doc = match self.documents.get(&doc_id) { Some(doc) => doc, - None => anyhow::bail!("document does not exist"), + None => bail!("document does not exist"), }; if !force && doc.is_modified() { - anyhow::bail!( + bail!( "buffer {:?} is modified", doc.relative_path() .map(|path| path.to_string_lossy().to_string()) @@ -500,7 +493,7 @@ impl Editor { // If the document we removed was visible in all views, we will have no more views. We don't // want to close the editor just for a simple buffer close, so we need to create a new view // containing either an existing document, or a brand new document. - if self.tree.views().peekable().peek().is_none() { + if self.tree.views().next().is_none() { let doc_id = self .documents .iter() @@ -585,8 +578,7 @@ impl Editor { } pub fn cursor(&self) -> (Option, CursorKind) { - let view = view!(self); - let doc = &self.documents[&view.doc]; + let (view, doc) = current_ref!(self); let cursor = doc .selection(view.id) .primary() From 4ec20eaeffa6144f01ae8d5a95b12ea1593f3080 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Fri, 26 Nov 2021 21:19:40 -0500 Subject: [PATCH 18/36] Add language support for WGSL (#1166) --- .gitmodules | 4 + helix-syntax/languages/tree-sitter-wgsl | 1 + languages.toml | 8 ++ runtime/queries/wgsl/highlights.scm | 102 ++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 160000 helix-syntax/languages/tree-sitter-wgsl create mode 100644 runtime/queries/wgsl/highlights.scm diff --git a/.gitmodules b/.gitmodules index bf596bdc1..039a3ee3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -142,3 +142,7 @@ path = helix-syntax/languages/tree-sitter-perl url = https://github.com/ganezdragon/tree-sitter-perl shallow = true +[submodule "helix-syntax/languages/tree-sitter-wgsl"] + path = helix-syntax/languages/tree-sitter-wgsl + url = https://github.com/szebniok/tree-sitter-wgsl + shallow = true diff --git a/helix-syntax/languages/tree-sitter-wgsl b/helix-syntax/languages/tree-sitter-wgsl new file mode 160000 index 000000000..f00ff5225 --- /dev/null +++ b/helix-syntax/languages/tree-sitter-wgsl @@ -0,0 +1 @@ +Subproject commit f00ff52251edbd58f4d39c9c3204383253032c11 diff --git a/languages.toml b/languages.toml index 5326f9176..5dcbd8727 100644 --- a/languages.toml +++ b/languages.toml @@ -405,3 +405,11 @@ file-types = ["rkt"] shebangs = ["racket"] comment-token = ";" language-server = { command = "racket", args = ["-l", "racket-langserver"] } + +[[language]] +name = "wgsl" +scope = "source.wgsl" +file-types = ["wgsl"] +roots = [] +comment-token = "//" +indent = { tab-width = 4, unit = " " } diff --git a/runtime/queries/wgsl/highlights.scm b/runtime/queries/wgsl/highlights.scm new file mode 100644 index 000000000..7fbc87d82 --- /dev/null +++ b/runtime/queries/wgsl/highlights.scm @@ -0,0 +1,102 @@ +(const_literal) @constant.numeric + +(type_declaration) @type + +(function_declaration + (identifier) @function) + +(struct_declaration + (identifier) @type) + +(type_constructor_or_function_call_expression + (type_declaration) @function) + +(parameter + (variable_identifier_declaration (identifier) @variable.parameter)) + +[ + "struct" + "bitcast" + ; "block" + "discard" + "enable" + "fallthrough" + "fn" + "let" + "private" + "read" + "read_write" + "return" + "storage" + "type" + "uniform" + "var" + "workgroup" + "write" + (texel_format) +] @keyword ; TODO reserved keywords + +[ + (true) + (false) +] @constant.builtin.boolean + +[ "," "." ":" ";" ] @punctuation.delimiter + +;; brackets +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +[ + "loop" + "for" + "break" + "continue" + "continuing" +] @keyword.control.repeat + +[ + "if" + "else" + "elseif" + "switch" + "case" + "default" +] @keyword.control.conditional + +[ + "&" + "&&" + "/" + "!" + "=" + "==" + "!=" + ">" + ">=" + ">>" + "<" + "<=" + "<<" + "%" + "-" + "+" + "|" + "||" + "*" + "~" + "^" +] @operator + +(attribute + (identifier) @variable.other.member) + +(comment) @comment + +(ERROR) @error From 3b2b7341a5d3d616496e8f13e6522d08c7864fb4 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sun, 28 Nov 2021 02:18:25 +0100 Subject: [PATCH 19/36] Fix next char delete key documentation for prompt (#1180) --- book/src/keymap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 4d5428005..5e861cfb9 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -317,7 +317,7 @@ Keys to use within prompt, Remapping currently not supported. | `Ctrl-u` | Delete to start of line | | `Ctrl-k` | Delete to end of line | | `backspace`, `Ctrl-h` | Delete previous char | -| `delete`, `Ctrl-d` | Delete previous char | +| `delete`, `Ctrl-d` | Delete next char | | `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later | | `Ctrl-p`, `Up` | Select previous history | | `Ctrl-n`, `Down` | Select next history | From 103b5125e4bc51965d98a38cdbeed2151ed816fa Mon Sep 17 00:00:00 2001 From: RustyStriker <61246040+RustyStriker@users.noreply.github.com> Date: Sun, 28 Nov 2021 03:19:54 +0200 Subject: [PATCH 20/36] Detect filetype on :write (#1141) fixes #1136 * removed a log::info * removed temp.rs * cargo clippy no longer complains * new get_lang_server function * get_lang_server is now launch_language_server * launch_lang_server will now close the previous one * better code readability * remove resfresh_ls(and a wrong comment) --- helix-term/src/commands.rs | 7 +++- helix-view/src/editor.rs | 79 +++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ff8d7a4ff..8e57ef30f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1907,7 +1907,7 @@ mod cmd { let jobs = &mut cx.jobs; let (_, doc) = current!(cx.editor); - if let Some(path) = path { + if let Some(ref path) = path { doc.set_path(Some(path.as_ref())) .context("invalid filepath")?; } @@ -1927,6 +1927,11 @@ mod cmd { }); let future = doc.format_and_save(fmt); cx.jobs.add(Job::new(future).wait_before_exiting()); + + if path.is_some() { + let id = doc.id(); + let _ = cx.editor.refresh_language_server(id); + } Ok(()) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b93d8126a..77cea7835 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -271,6 +271,53 @@ impl Editor { Ok(()) } + /// Refreshes the language server for a given document + pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { + let doc = self.documents.get_mut(&doc_id)?; + doc.detect_language(Some(&self.theme), &self.syn_loader); + Self::launch_language_server(&mut self.language_servers, doc) + } + + /// Launch a language server for a given document + fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> Option<()> { + // try to find a language server based on the language name + let language_server = doc.language.as_ref().and_then(|language| { + ls.get(language) + .map_err(|e| { + log::error!( + "Failed to initialize the LSP for `{}` {{ {} }}", + language.scope(), + e + ) + }) + .ok() + }); + if let Some(language_server) = language_server { + // only spawn a new lang server if the servers aren't the same + if Some(language_server.id()) != doc.language_server().map(|server| server.id()) { + if let Some(language_server) = doc.language_server() { + tokio::spawn(language_server.text_document_did_close(doc.identifier())); + } + let language_id = doc + .language() + .and_then(|s| s.split('.').last()) // source.rust + .map(ToOwned::to_owned) + .unwrap_or_default(); + + // TODO: this now races with on_init code if the init happens too quickly + tokio::spawn(language_server.text_document_did_open( + doc.url().unwrap(), + doc.version(), + doc.text(), + language_id, + )); + + doc.set_language_server(Some(language_server)); + } + } + Some(()) + } + fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { let doc = &self.documents[&view.doc]; @@ -401,37 +448,7 @@ impl Editor { } else { let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?; - // try to find a language server based on the language name - let language_server = doc.language.as_ref().and_then(|language| { - self.language_servers - .get(language) - .map_err(|e| { - log::error!( - "Failed to initialize the LSP for `{}` {{ {} }}", - language.scope(), - e - ) - }) - .ok() - }); - - if let Some(language_server) = language_server { - let language_id = doc - .language() - .and_then(|s| s.split('.').last()) // source.rust - .map(ToOwned::to_owned) - .unwrap_or_default(); - - // TODO: this now races with on_init code if the init happens too quickly - tokio::spawn(language_server.text_document_did_open( - doc.url().unwrap(), - doc.version(), - doc.text(), - language_id, - )); - - doc.set_language_server(Some(language_server)); - } + let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); self.new_document(doc) }; From 1d773bcefb40f69fe31dc048bfbdd83601fe0e62 Mon Sep 17 00:00:00 2001 From: ath3 <45574139+ath3@users.noreply.github.com> Date: Sun, 28 Nov 2021 02:21:40 +0100 Subject: [PATCH 21/36] Implement black hole register (#1165) --- book/src/usage.md | 2 ++ helix-core/src/register.rs | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/book/src/usage.md b/book/src/usage.md index 6b7cbc415..cf7d9d488 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -23,8 +23,10 @@ If there is a selected register before invoking a change or delete command, the | `/` | Last search | | `:` | Last executed command | | `"` | Last yanked text | +| `_` | Black hole | > There is no special register for copying to system clipboard, instead special commands and keybindings are provided. See the [keymap](keymap.md#space-mode) for the specifics. +> The black hole register works as a no-op register, meaning no data will be written to / read from it. ## Surround diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs index c5444eb73..b9eb497df 100644 --- a/helix-core/src/register.rs +++ b/helix-core/src/register.rs @@ -15,7 +15,11 @@ impl Register { } pub fn new_with_values(name: char, values: Vec) -> Self { - Self { name, values } + if name == '_' { + Self::new(name) + } else { + Self { name, values } + } } pub const fn name(&self) -> char { @@ -27,11 +31,15 @@ impl Register { } pub fn write(&mut self, values: Vec) { - self.values = values; + if self.name != '_' { + self.values = values; + } } pub fn push(&mut self, value: String) { - self.values.push(value); + if self.name != '_' { + self.values.push(value); + } } } From dc53e65b9e9be71c49eaa86e0f4dabb69f586e2e Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Mon, 29 Nov 2021 07:03:53 +0530 Subject: [PATCH 22/36] Fix surround cursor position calculation (#1183) Fixes #1077. This was caused by the assumption that a block cursor is represented as zero width internally and simply rendered to be a single width selection, where as in reality a block cursor is an actual single width selection in form and function. Behavioural changes: 1. Surround selection no longer works when cursor is _on_ a surround character that has matching pairs (like `'` or `"`). This was the intended behaviour from the start but worked till now because of the cursor position calculation mismatch. --- helix-core/src/selection.rs | 6 +- helix-core/src/surround.rs | 148 ++++++++++++++++++++--------------- helix-core/src/textobject.rs | 10 ++- 3 files changed, 92 insertions(+), 72 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index b4d1dffa5..116a1c7c0 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -308,10 +308,10 @@ impl Range { } impl From<(usize, usize)> for Range { - fn from(tuple: (usize, usize)) -> Self { + fn from((anchor, head): (usize, usize)) -> Self { Self { - anchor: tuple.0, - head: tuple.1, + anchor, + head, horiz: None, } } diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs index 32161b700..b53b0a78c 100644 --- a/helix-core/src/surround.rs +++ b/helix-core/src/surround.rs @@ -1,4 +1,4 @@ -use crate::{search, Selection}; +use crate::{search, Range, Selection}; use ropey::RopeSlice; pub const PAIRS: &[(char, char)] = &[ @@ -35,33 +35,27 @@ pub fn get_pair(ch: char) -> (char, char) { pub fn find_nth_pairs_pos( text: RopeSlice, ch: char, - pos: usize, + range: Range, n: usize, ) -> Option<(usize, usize)> { - let (open, close) = get_pair(ch); - - if text.len_chars() < 2 || pos >= text.len_chars() { + if text.len_chars() < 2 || range.to() >= text.len_chars() { return None; } + let (open, close) = get_pair(ch); + let pos = range.cursor(text); + if open == close { if Some(open) == text.get_char(pos) { - // Special case: cursor is directly on a matching char. - match pos { - 0 => Some((pos, search::find_nth_next(text, close, pos + 1, n)?)), - _ if (pos + 1) == text.len_chars() => { - Some((search::find_nth_prev(text, open, pos, n)?, pos)) - } - // We return no match because there's no way to know which - // side of the char we should be searching on. - _ => None, - } - } else { - Some(( - search::find_nth_prev(text, open, pos, n)?, - search::find_nth_next(text, close, pos, n)?, - )) + // Cursor is directly on match char. We return no match + // because there's no way to know which side of the char + // we should be searching on. + return None; } + Some(( + search::find_nth_prev(text, open, pos, n)?, + search::find_nth_next(text, close, pos, n)?, + )) } else { Some(( find_nth_open_pair(text, open, close, pos, n)?, @@ -160,8 +154,8 @@ pub fn get_surround_pos( ) -> Option> { let mut change_pos = Vec::new(); - for range in selection { - let (open_pos, close_pos) = find_nth_pairs_pos(text, ch, range.head, skip)?; + for &range in selection { + let (open_pos, close_pos) = find_nth_pairs_pos(text, ch, range, skip)?; if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) { return None; } @@ -178,67 +172,91 @@ mod test { use ropey::Rope; use smallvec::SmallVec; - #[test] - fn test_find_nth_pairs_pos() { - let doc = Rope::from("some (text) here"); + fn check_find_nth_pair_pos( + text: &str, + cases: Vec<(usize, char, usize, Option<(usize, usize)>)>, + ) { + let doc = Rope::from(text); let slice = doc.slice(..); - // cursor on [t]ext - assert_eq!(find_nth_pairs_pos(slice, '(', 6, 1), Some((5, 10))); - assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 10))); - // cursor on so[m]e - assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None); - // cursor on bracket itself - assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 10))); - assert_eq!(find_nth_pairs_pos(slice, '(', 10, 1), Some((5, 10))); + for (cursor_pos, ch, n, expected_range) in cases { + let range = find_nth_pairs_pos(slice, ch, (cursor_pos, cursor_pos + 1).into(), n); + assert_eq!( + range, expected_range, + "Expected {:?}, got {:?}", + expected_range, range + ); + } + } + + #[test] + fn test_find_nth_pairs_pos() { + check_find_nth_pair_pos( + "some (text) here", + vec![ + // cursor on [t]ext + (6, '(', 1, Some((5, 10))), + (6, ')', 1, Some((5, 10))), + // cursor on so[m]e + (2, '(', 1, None), + // cursor on bracket itself + (5, '(', 1, Some((5, 10))), + (10, '(', 1, Some((5, 10))), + ], + ); } #[test] fn test_find_nth_pairs_pos_skip() { - let doc = Rope::from("(so (many (good) text) here)"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 15))); - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 21))); - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27))); + check_find_nth_pair_pos( + "(so (many (good) text) here)", + vec![ + // cursor on go[o]d + (13, '(', 1, Some((10, 15))), + (13, '(', 2, Some((4, 21))), + (13, '(', 3, Some((0, 27))), + ], + ); } #[test] fn test_find_nth_pairs_pos_same() { - let doc = Rope::from("'so 'many 'good' text' here'"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15))); - assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21))); - assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27))); - // cursor on the quotes - assert_eq!(find_nth_pairs_pos(slice, '\'', 10, 1), None); - // this is the best we can do since opening and closing pairs are same - assert_eq!(find_nth_pairs_pos(slice, '\'', 0, 1), Some((0, 4))); - assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 27))); + check_find_nth_pair_pos( + "'so 'many 'good' text' here'", + vec![ + // cursor on go[o]d + (13, '\'', 1, Some((10, 15))), + (13, '\'', 2, Some((4, 21))), + (13, '\'', 3, Some((0, 27))), + // cursor on the quotes + (10, '\'', 1, None), + ], + ) } #[test] fn test_find_nth_pairs_pos_step() { - let doc = Rope::from("((so)((many) good (text))(here))"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24))); - assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31))); + check_find_nth_pair_pos( + "((so)((many) good (text))(here))", + vec![ + // cursor on go[o]d + (15, '(', 1, Some((5, 24))), + (15, '(', 2, Some((0, 31))), + ], + ) } #[test] fn test_find_nth_pairs_pos_mixed() { - let doc = Rope::from("(so [many {good} text] here)"); - let slice = doc.slice(..); - - // cursor on go[o]d - assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 15))); - assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 21))); - assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 27))); + check_find_nth_pair_pos( + "(so [many {good} text] here)", + vec![ + // cursor on go[o]d + (13, '{', 1, Some((10, 15))), + (13, '[', 1, Some((4, 21))), + (13, '(', 1, Some((0, 27))), + ], + ) } #[test] diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 24f063d44..21ceec04f 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -114,7 +114,7 @@ pub fn textobject_surround( ch: char, count: usize, ) -> Range { - surround::find_nth_pairs_pos(slice, ch, range.head, count) + surround::find_nth_pairs_pos(slice, ch, range, count) .map(|(anchor, head)| match textobject { TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head), TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)), @@ -170,7 +170,7 @@ mod test { #[test] fn test_textobject_word() { - // (text, [(cursor position, textobject, final range), ...]) + // (text, [(char position, textobject, final range), ...]) let tests = &[ ( "cursor at beginning of doc", @@ -269,7 +269,9 @@ mod test { let slice = doc.slice(..); for &case in scenario { let (pos, objtype, expected_range) = case; - let result = textobject_word(slice, Range::point(pos), objtype, 1, false); + // cursor is a single width selection + let range = Range::new(pos, pos + 1); + let result = textobject_word(slice, range, objtype, 1, false); assert_eq!( result, expected_range.into(), @@ -283,7 +285,7 @@ mod test { #[test] fn test_textobject_surround() { - // (text, [(cursor position, textobject, final range, count), ...]) + // (text, [(cursor position, textobject, final range, surround char, count), ...]) let tests = &[ ( "simple (single) surround pairs", From 6f1a7b1220c2ae565a2542fb089eb6d87f0a9421 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:38:17 -0500 Subject: [PATCH 23/36] Add llvm grammar (#1167) --- .gitmodules | 4 ++++ book/src/themes.md | 5 +++-- languages.toml | 8 ++++++++ runtime/queries/llvm/highlights.scm | 14 ++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 runtime/queries/llvm/highlights.scm diff --git a/.gitmodules b/.gitmodules index 039a3ee3d..6295b9e95 100644 --- a/.gitmodules +++ b/.gitmodules @@ -146,3 +146,7 @@ path = helix-syntax/languages/tree-sitter-wgsl url = https://github.com/szebniok/tree-sitter-wgsl shallow = true +[submodule "helix-syntax/tree-sitter-llvm"] + path = helix-syntax/languages/tree-sitter-llvm + url = https://github.com/benwilliamgraham/tree-sitter-llvm + shallow = true diff --git a/book/src/themes.md b/book/src/themes.md index ecbbb6e97..fd3f5b1eb 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -145,11 +145,12 @@ We use a similar set of scopes as - `conditional` - `if`, `else` - `repeat` - `for`, `while`, `loop` - `import` - `import`, `export` - - (TODO: return?) + - `return` + - `operator` - `or`, `in` - `directive` - Preprocessor directives (`#if` in C) - `function` - `fn`, `func` -- `operator` - `||`, `+=`, `>`, `or` +- `operator` - `||`, `+=`, `>` - `function` - `builtin` diff --git a/languages.toml b/languages.toml index 5dcbd8727..4208e4b68 100644 --- a/languages.toml +++ b/languages.toml @@ -413,3 +413,11 @@ file-types = ["wgsl"] roots = [] comment-token = "//" indent = { tab-width = 4, unit = " " } + +[[language]] +name = "llvm" +scope = "source.llvm" +roots = [] +file-types = ["ll"] +comment-token = ";" +indent = { tab-width = 2, unit = " " } diff --git a/runtime/queries/llvm/highlights.scm b/runtime/queries/llvm/highlights.scm new file mode 100644 index 000000000..73afe85ed --- /dev/null +++ b/runtime/queries/llvm/highlights.scm @@ -0,0 +1,14 @@ +(type) @type +(statement) @keyword.operator +(number) @constant.numeric.integer +(comment) @comment +(string) @string +(label) @label +(keyword) @keyword +"ret" @keyword.control.return +(boolean) @constant.builtin.boolean +(float) @constant.numeric.float +(constant) @constant +(identifier) @variable +(symbol) @punctuation.delimiter +(bracket) @punctuation.bracket From 4f9390a435a44578bcedf1da63ea1fad92185afc Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 29 Nov 2021 09:53:29 +0800 Subject: [PATCH 24/36] gf as goto_file (#1102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * goto_file * support goto_file under current cursor * add C-w f/F * sync space w with window mode * Update helix-term/src/commands.rs Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 3 +++ helix-term/src/commands.rs | 46 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 5 +++++ 3 files changed, 54 insertions(+) diff --git a/book/src/keymap.md b/book/src/keymap.md index 5e861cfb9..865a700b4 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -160,6 +160,7 @@ Jumps to various locations. | ----- | ----------- | ------- | | `g` | Go to the start of the file | `goto_file_start` | | `e` | Go to the end of the file | `goto_last_line` | +| `f` | Go to files in the selection | `goto_file` | | `h` | Go to the start of the line | `goto_line_start` | | `l` | Go to the end of the line | `goto_line_end` | | `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` | @@ -202,6 +203,8 @@ This layer is similar to vim keybindings as kakoune does not support window. | `v`, `Ctrl-v` | Vertical right split | `vsplit` | | `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | | `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` | +| `f` | Go to files in the selection in horizontal splits | `goto_file` | +| `F` | Go to files in the selection in vertical splits | `goto_file` | | `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` | | `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` | | `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8e57ef30f..aafeb4766 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -259,6 +259,9 @@ impl Command { goto_implementation, "Goto implementation", goto_file_start, "Goto file start/line", goto_file_end, "Goto file end", + goto_file, "Goto files in the selection", + goto_file_hsplit, "Goto files in the selection in horizontal splits", + goto_file_vsplit, "Goto files in the selection in vertical splits", goto_reference, "Goto references", goto_window_top, "Goto window top", goto_window_middle, "Goto window middle", @@ -835,6 +838,49 @@ fn goto_file_end(cx: &mut Context) { doc.set_selection(view.id, selection); } +fn goto_file(cx: &mut Context) { + goto_file_impl(cx, Action::Replace); +} + +fn goto_file_hsplit(cx: &mut Context) { + goto_file_impl(cx, Action::HorizontalSplit); +} + +fn goto_file_vsplit(cx: &mut Context) { + goto_file_impl(cx, Action::VerticalSplit); +} + +fn goto_file_impl(cx: &mut Context, action: Action) { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text(); + let selections = doc.selection(view.id); + let mut paths: Vec<_> = selections + .iter() + .map(|r| text.slice(r.from()..r.to()).to_string()) + .collect(); + let primary = selections.primary(); + if selections.len() == 1 && primary.to() - primary.from() == 1 { + let current_word = movement::move_next_long_word_start( + text.slice(..), + movement::move_prev_long_word_start(text.slice(..), primary, 1), + 1, + ); + paths.clear(); + paths.push( + text.slice(current_word.from()..current_word.to()) + .to_string(), + ); + } + for sel in paths { + let p = sel.trim(); + if !p.is_empty() { + if let Err(e) = cx.editor.open(PathBuf::from(p), action) { + cx.editor.set_error(format!("Open file failed: {:?}", e)); + } + } + } +} + fn extend_word_impl(cx: &mut Context, extend_fn: F) where F: Fn(RopeSlice, Range, usize) -> Range, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 0062e636c..06639dcd9 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -512,6 +512,7 @@ impl Default for Keymaps { "g" => { "Goto" "g" => goto_file_start, "e" => goto_last_line, + "f" => goto_file, "h" => goto_line_start, "l" => goto_line_end, "s" => goto_first_nonwhitespace, @@ -622,6 +623,8 @@ impl Default for Keymaps { "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, @@ -650,6 +653,8 @@ impl Default for Keymaps { "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, From 42fde95223a62bded340a1737e8be50ef94af4af Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 29 Nov 2021 09:58:21 +0800 Subject: [PATCH 25/36] Accept count for goto_window (#1033) * accept count for goto_window also fix view is not fullfilled issue * fix fulfilled mispell * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * fix merge issue * revert line computation logic Co-authored-by: Ivan Tham --- helix-term/src/commands.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index aafeb4766..b4cc9ae92 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -733,10 +733,12 @@ fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) -> } fn goto_window(cx: &mut Context, align: Align) { + let count = cx.count() - 1; let (view, doc) = current!(cx.editor); let height = view.inner_area().height as usize; + // respect user given count if any // - 1 so we have at least one gap in the middle. // a height of 6 with padding of 3 on each side will keep shifting the view back and forth // as we type @@ -745,11 +747,12 @@ fn goto_window(cx: &mut Context, align: Align) { let last_line = view.last_line(doc); let line = match align { - Align::Top => (view.offset.row + scrolloff), - Align::Center => (view.offset.row + (height / 2)), - Align::Bottom => last_line.saturating_sub(scrolloff), + Align::Top => (view.offset.row + scrolloff + count), + Align::Center => (view.offset.row + ((last_line - view.offset.row) / 2)), + Align::Bottom => last_line.saturating_sub(scrolloff + count), } - .min(last_line.saturating_sub(scrolloff)); + .min(last_line.saturating_sub(scrolloff)) + .max(view.offset.row + scrolloff); let pos = doc.text().line_to_char(line); From 30171416cb5b801086da69566a82462fca16ea14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 24 Sep 2021 10:29:41 +0900 Subject: [PATCH 26/36] Gutter functions --- helix-core/src/diagnostic.rs | 4 +- helix-term/src/ui/editor.rs | 150 +++++++++++++++++++++-------------- helix-view/src/editor.rs | 2 +- 3 files changed, 94 insertions(+), 62 deletions(-) diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index ad1ba16ab..4fcf51c9c 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,7 +1,7 @@ //! LSP diagnostic utility types. /// Describes the severity level of a [`Diagnostic`]. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Severity { Error, Warning, @@ -17,7 +17,7 @@ pub struct Range { } /// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Diagnostic { pub range: Range, pub line: usize, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 27d33d225..8c4ea9ccb 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -17,7 +17,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, - editor::LineNumber, + editor::{Config, LineNumber}, graphics::{CursorKind, Modifier, Rect, Style}, info::Info, input::KeyEvent, @@ -412,22 +412,6 @@ impl EditorView { let text = doc.text().slice(..); let last_line = view.last_line(doc); - let linenr = theme.get("ui.linenr"); - let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); - - let warning = theme.get("warning"); - let error = theme.get("error"); - let info = theme.get("info"); - let hint = theme.get("hint"); - - // Whether to draw the line number for the last line of the - // document or not. We only draw it if it's not an empty line. - let draw_last = text.line_to_byte(last_line) < text.len_bytes(); - - let current_line = doc - .text() - .char_to_line(doc.selection(view.id).primary().cursor(text)); - // it's used inside an iterator so the collect isn't needless: // https://github.com/rust-lang/rust-clippy/issues/6164 #[allow(clippy::needless_collect)] @@ -437,51 +421,99 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { - use helix_core::diagnostic::Severity; - if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) { - surface.set_stringn( - viewport.x, - viewport.y + i as u16, - "●", - 1, - match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }, - ); - } + fn diagnostic( + doc: &Document, + _view: &View, + theme: &Theme, + _config: &Config, + _is_focused: bool, + _width: usize, + ) -> GutterFn { + let warning = theme.get("warning"); + let error = theme.get("error"); + let info = theme.get("info"); + let hint = theme.get("hint"); + let diagnostics = doc.diagnostics().to_vec(); // TODO - let selected = cursors.contains(&line); + Box::new(move |line: usize, _selected: bool| { + use helix_core::diagnostic::Severity; + if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { + return Some(( + "●".to_string(), + match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }, + )); + } + None + }) + } - let text = if line == last_line && !draw_last { - " ~".into() - } else { - let line = match config.line_number { - LineNumber::Absolute => line + 1, - LineNumber::Relative => { - if current_line == line { - line + 1 - } else { - abs_diff(current_line, line) - } - } - }; - format!("{:>5}", line) - }; - surface.set_stringn( - viewport.x + 1, - viewport.y + i as u16, - text, - 5, - if selected && is_focused { - linenr_select + fn line_number( + doc: &Document, + view: &View, + theme: &Theme, + config: &Config, + is_focused: bool, + width: usize, + ) -> GutterFn { + let text = doc.text().slice(..); + let last_line = view.last_line(doc); + // Whether to draw the line number for the last line of the + // document or not. We only draw it if it's not an empty line. + let draw_last = text.line_to_byte(last_line) < text.len_bytes(); + + let linenr = theme.get("ui.linenr"); + let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); + + let current_line = doc + .text() + .char_to_line(doc.selection(view.id).primary().cursor(text)); + + let config = config.line_number; + + Box::new(move |line: usize, selected: bool| { + if line == last_line && !draw_last { + Some((format!("{:>1$}", '~', width), linenr)) } else { - linenr - }, - ); + let line = match config { + LineNumber::Absolute => line + 1, + LineNumber::Relative => { + if current_line == line { + line + 1 + } else { + abs_diff(current_line, line) + } + } + }; + let style = if selected && is_focused { + linenr_select + } else { + linenr + }; + Some((format!("{:>1$}", line, width), style)) + } + }) + } + + type GutterFn = Box Option<(String, Style)>>; + type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn; + let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; + + let mut offset = 0; + for (constructor, width) in gutters { + let gutter = constructor(doc, view, theme, config, is_focused, *width); + for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { + let selected = cursors.contains(&line); + + if let Some((text, style)) = gutter(line, selected) { + surface.set_stringn(viewport.x + offset, viewport.y + i as u16, text, 5, style); + } + } + offset += *width as u16; } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 77cea7835..d5913a51d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -106,7 +106,7 @@ pub struct Config { pub file_picker: FilePickerConfig, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { /// Show absolute line number From c71c9f69e21a80c5c9c744bddbde7d5041da99a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 7 Oct 2021 10:26:28 +0900 Subject: [PATCH 27/36] TODO --- helix-term/src/ui/markdown.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 649703b51..ca8303dd9 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -228,6 +228,7 @@ impl Component for Markdown { return None; } let contents = parse(&self.contents, None, &self.config_loader); + // TODO: account for tab width let max_text_width = (viewport.0 - padding).min(120); let mut text_width = 0; let mut height = padding; From ba45db84d4b49913836a949472366a30a620e67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 22 Nov 2021 16:45:52 +0900 Subject: [PATCH 28/36] Tie the GutterFn lifetime to the doc so we can avoid cloning data --- helix-term/src/ui/editor.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 8c4ea9ccb..2cc212eab 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -421,19 +421,19 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - fn diagnostic( - doc: &Document, + fn diagnostic<'doc>( + doc: &'doc Document, _view: &View, theme: &Theme, _config: &Config, _is_focused: bool, _width: usize, - ) -> GutterFn { + ) -> GutterFn<'doc> { let warning = theme.get("warning"); let error = theme.get("error"); let info = theme.get("info"); let hint = theme.get("hint"); - let diagnostics = doc.diagnostics().to_vec(); // TODO + let diagnostics = doc.diagnostics(); Box::new(move |line: usize, _selected: bool| { use helix_core::diagnostic::Severity; @@ -452,14 +452,14 @@ impl EditorView { }) } - fn line_number( - doc: &Document, + fn line_number<'doc>( + doc: &'doc Document, view: &View, theme: &Theme, config: &Config, is_focused: bool, width: usize, - ) -> GutterFn { + ) -> GutterFn<'doc> { let text = doc.text().slice(..); let last_line = view.last_line(doc); // Whether to draw the line number for the last line of the @@ -499,8 +499,9 @@ impl EditorView { }) } - type GutterFn = Box Option<(String, Style)>>; - type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn; + type GutterFn<'doc> = Box Option<(String, Style)> + 'doc>; + type Gutter = + for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>; let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; let mut offset = 0; From 27c1a84f053d1282ed09d64ec737a46f55685d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 22 Nov 2021 17:02:46 +0900 Subject: [PATCH 29/36] Reuse a text buffer for each gutter line --- helix-term/src/ui/editor.rs | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2cc212eab..82fb8fbf6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -421,6 +421,8 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); + use std::fmt::Write; + fn diagnostic<'doc>( doc: &'doc Document, _view: &View, @@ -435,18 +437,16 @@ impl EditorView { let hint = theme.get("hint"); let diagnostics = doc.diagnostics(); - Box::new(move |line: usize, _selected: bool| { + Box::new(move |line: usize, _selected: bool, out: &mut String| { use helix_core::diagnostic::Severity; if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { - return Some(( - "●".to_string(), - match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }, - )); + write!(out, "●").unwrap(); + return Some(match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }); } None }) @@ -475,9 +475,10 @@ impl EditorView { let config = config.line_number; - Box::new(move |line: usize, selected: bool| { + Box::new(move |line: usize, selected: bool, out: &mut String| { if line == last_line && !draw_last { - Some((format!("{:>1$}", '~', width), linenr)) + write!(out, "{:>1$}", '~', width).unwrap(); + Some(linenr) } else { let line = match config { LineNumber::Absolute => line + 1, @@ -494,25 +495,38 @@ impl EditorView { } else { linenr }; - Some((format!("{:>1$}", line, width), style)) + write!(out, "{:>1$}", line, width).unwrap(); + Some(style) } }) } - type GutterFn<'doc> = Box Option<(String, Style)> + 'doc>; + type GutterFn<'doc> = Box Option