From 3cb95be452491a72d18c98ebc619b0d2abb1b746 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 6 Sep 2021 13:18:16 +0900 Subject: Update tree-sitter to 0.20 0.20 includes querying improvements, we no longer have to convert fragments to strings but can return an iterator of chunks instead. --- helix-core/Cargo.toml | 2 +- helix-core/src/syntax.rs | 79 +++++++++++++++++++++++++----------------------- 2 files changed, 42 insertions(+), 39 deletions(-) (limited to 'helix-core') diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 8c83816c..5d2db642 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -22,7 +22,7 @@ unicode-segmentation = "1.8" unicode-width = "0.1" unicode-general-category = "0.4" # slab = "0.4.2" -tree-sitter = "0.19" +tree-sitter = "0.20" once_cell = "1.8" arc-swap = "1" regex = "1" diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 64b921e6..a7a5d022 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -276,16 +276,6 @@ fn byte_range_to_str(range: std::ops::Range, source: RopeSlice) -> Cow(node: Node, source: RopeSlice<'a>) -> Cow<'a, [u8]> { - let start_char = source.byte_to_char(node.start_byte()); - let end_char = source.byte_to_char(node.end_byte()); - let fragment = source.slice(start_char..end_char); - match fragment.as_str() { - Some(fragment) => Cow::Borrowed(fragment.as_bytes()), - None => Cow::Owned(String::from(fragment).into_bytes()), - } -} - impl Syntax { // buffer, grammar, config, grammars, sync_timeout? pub fn new( @@ -380,14 +370,11 @@ impl Syntax { // TODO: if reusing cursors this might need resetting if let Some(range) = &range { - cursor_ref.set_byte_range(range.start, range.end); + cursor_ref.set_byte_range(range.clone()); } let captures = cursor_ref - .captures(query_ref, tree_ref.root_node(), move |n: Node| { - // &source[n.byte_range()] - node_to_bytes(n, source) - }) + .captures(query_ref, tree_ref.root_node(), RopeProvider(source)) .peekable(); // manually craft the root layer based on the existing tree @@ -501,10 +488,7 @@ impl LanguageLayer { // let mut injections_by_pattern_index = // vec![(None, Vec::new(), false); combined_injections_query.pattern_count()]; // let matches = - // cursor.matches(combined_injections_query, tree.root_node(), |n: Node| { - // // &source[n.byte_range()] - // node_to_bytes(n, source) - // }); + // cursor.matches(combined_injections_query, tree.root_node(), RopeProvider(source)); // for mat in matches { // let entry = &mut injections_by_pattern_index[mat.pattern_index]; // let (language_name, content_node, include_children) = @@ -716,7 +700,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::{iter, mem, ops, str, usize}; use tree_sitter::{ Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError, - QueryMatch, Range, Tree, + QueryMatch, Range, TextProvider, Tree, }; const CANCELLATION_CHECK_INTERVAL: usize = 100; @@ -776,7 +760,7 @@ struct LocalScope<'a> { } #[derive(Debug)] -struct HighlightIter<'a, 'tree: 'a, F> +struct HighlightIter<'a, F> where F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, { @@ -784,16 +768,41 @@ where byte_offset: usize, injection_callback: F, cancellation_flag: Option<&'a AtomicUsize>, - layers: Vec>, + layers: Vec>, iter_count: usize, next_event: Option, last_highlight_range: Option<(usize, usize, usize)>, } -struct HighlightIterLayer<'a, 'tree: 'a> { +// Adapter to convert rope chunks to bytes +struct ChunksBytes<'a> { + chunks: ropey::iter::Chunks<'a>, +} +impl<'a> Iterator for ChunksBytes<'a> { + type Item = &'a [u8]; + fn next(&mut self) -> Option { + self.chunks.next().map(str::as_bytes) + } +} + +struct RopeProvider<'a>(RopeSlice<'a>); +impl<'a> TextProvider<'a> for RopeProvider<'a> { + type I = ChunksBytes<'a>; + + fn text(&mut self, node: Node) -> Self::I { + let start_char = self.0.byte_to_char(node.start_byte()); + let end_char = self.0.byte_to_char(node.end_byte()); + let fragment = self.0.slice(start_char..end_char); + ChunksBytes { + chunks: fragment.chunks(), + } + } +} + +struct HighlightIterLayer<'a> { _tree: Option, cursor: QueryCursor, - captures: iter::Peekable>>, + captures: iter::Peekable>>, config: &'a HighlightConfiguration, highlight_end_stack: Vec, scope_stack: Vec>, @@ -801,7 +810,7 @@ struct HighlightIterLayer<'a, 'tree: 'a> { depth: usize, } -impl<'a, 'tree: 'a> fmt::Debug for HighlightIterLayer<'a, 'tree> { +impl<'a> fmt::Debug for HighlightIterLayer<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HighlightIterLayer").finish() } @@ -972,7 +981,7 @@ impl HighlightConfiguration { } } -impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> { +impl<'a> HighlightIterLayer<'a> { /// Create a new 'layer' of highlighting for this document. /// /// In the even that the new layer contains "combined injections" (injections where multiple @@ -1029,10 +1038,7 @@ impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> { let matches = cursor.matches( combined_injections_query, tree.root_node(), - |n: Node| { - // &source[n.byte_range()] - node_to_bytes(n, source) - }, + RopeProvider(source), ); for mat in matches { let entry = &mut injections_by_pattern_index[mat.pattern_index]; @@ -1079,10 +1085,7 @@ impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> { let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; let captures = cursor_ref - .captures(&config.query, tree_ref.root_node(), move |n: Node| { - // &source[n.byte_range()] - node_to_bytes(n, source) - }) + .captures(&config.query, tree_ref.root_node(), RopeProvider(source)) .peekable(); result.push(HighlightIterLayer { @@ -1236,7 +1239,7 @@ impl<'a, 'tree: 'a> HighlightIterLayer<'a, 'tree> { } } -impl<'a, 'tree: 'a, F> HighlightIter<'a, 'tree, F> +impl<'a, F> HighlightIter<'a, F> where F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, { @@ -1287,7 +1290,7 @@ where } } - fn insert_layer(&mut self, mut layer: HighlightIterLayer<'a, 'tree>) { + fn insert_layer(&mut self, mut layer: HighlightIterLayer<'a>) { if let Some(sort_key) = layer.sort_key() { let mut i = 1; while i < self.layers.len() { @@ -1306,7 +1309,7 @@ where } } -impl<'a, 'tree: 'a, F> Iterator for HighlightIter<'a, 'tree, F> +impl<'a, F> Iterator for HighlightIter<'a, F> where F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, { @@ -1570,7 +1573,7 @@ where fn injection_for_match<'a>( config: &HighlightConfiguration, query: &'a Query, - query_match: &QueryMatch<'a>, + query_match: &QueryMatch<'a, 'a>, source: RopeSlice<'a>, ) -> (Option>, Option>, bool) { let content_capture_index = config.injection_content_capture_index; -- cgit v1.2.3-70-g09d2 From 585e3ce83066bfe0235598660d32a6e171cc60cb Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Sep 2021 15:11:27 +0900 Subject: fix: tree-sitter-scopes would infinitely loop --- helix-core/src/indent.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'helix-core') diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index f5f36aca..55802059 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -316,8 +316,12 @@ pub fn suggested_indent_for_pos( pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> { let mut scopes = Vec::new(); if let Some(syntax) = syntax { - let byte_start = text.char_to_byte(pos); - let node = match get_highest_syntax_node_at_bytepos(syntax, byte_start) { + let pos = text.char_to_byte(pos); + let mut node = match syntax + .tree() + .root_node() + .descendant_for_byte_range(pos, pos) + { Some(node) => node, None => return scopes, }; @@ -325,7 +329,8 @@ pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<& scopes.push(node.kind()); while let Some(parent) = node.parent() { - scopes.push(parent.kind()) + scopes.push(parent.kind()); + node = parent; } } -- cgit v1.2.3-70-g09d2 From 4ac29434cb99b517dba3752a287c0edfe8679e7e Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 6 Sep 2021 18:13:52 +0900 Subject: syntax: Add go & rust locals, improve tree-sitter error message --- helix-core/src/syntax.rs | 8 ++++++-- runtime/queries/go/highlights.scm | 6 +++--- runtime/queries/go/locals.scm | 30 ++++++++++++++++++++++++++++++ runtime/queries/rust/locals.scm | 17 +++++++++++++++++ theme.toml | 2 +- 5 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 runtime/queries/go/locals.scm create mode 100644 runtime/queries/rust/locals.scm (limited to 'helix-core') diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index a7a5d022..1afe0e25 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -144,8 +144,12 @@ impl LanguageConfiguration { &highlights_query, &injections_query, &locals_query, - ) - .unwrap(); // TODO: no unwrap + ); + + let config = match config { + Ok(config) => config, + Err(err) => panic!("{}", err), + }; // TODO: avoid panic config.configure(scopes); Some(Arc::new(config)) } diff --git a/runtime/queries/go/highlights.scm b/runtime/queries/go/highlights.scm index 6e5d65a0..3129c4b2 100644 --- a/runtime/queries/go/highlights.scm +++ b/runtime/queries/go/highlights.scm @@ -21,14 +21,14 @@ (const_spec name: (identifier) @constant) +(parameter_declaration (identifier) @variable.parameter) +(variadic_parameter_declaration (identifier) @variable.parameter) + (type_identifier) @type (field_identifier) @property (identifier) @variable (package_identifier) @variable -(parameter_declaration (identifier) @variable.parameter) -(variadic_parameter_declaration (identifier) @variable.parameter) - ; Operators diff --git a/runtime/queries/go/locals.scm b/runtime/queries/go/locals.scm new file mode 100644 index 00000000..d240e2b7 --- /dev/null +++ b/runtime/queries/go/locals.scm @@ -0,0 +1,30 @@ +; Scopes + +(block) @local.scope + +; Definitions + +(parameter_declaration (identifier) @local.definition) +(variadic_parameter_declaration (identifier) @local.definition) + +(short_var_declaration + left: (expression_list + (identifier) @local.definition)) + +(var_spec + name: (identifier) @local.definition) + +(for_statement + (range_clause + left: (expression_list + (identifier) @local.definition))) + +(const_declaration + (const_spec + name: (identifier) @local.definition)) + +; References + +(identifier) @local.reference +(field_identifier) @local.reference + diff --git a/runtime/queries/rust/locals.scm b/runtime/queries/rust/locals.scm new file mode 100644 index 00000000..6428f9b4 --- /dev/null +++ b/runtime/queries/rust/locals.scm @@ -0,0 +1,17 @@ +; Scopes + +(block) @local.scope + +; Definitions + +(parameter + (identifier) @local.definition) + +(let_declaration + pattern: (identifier) @local.definition) + +(closure_parameters (identifier)) @local.definition + +; References +(identifier) @local.reference + diff --git a/theme.toml b/theme.toml index 3166b2d6..867a0d2c 100644 --- a/theme.toml +++ b/theme.toml @@ -9,7 +9,7 @@ special = "honey" property = "white" variable = "lavender" # variable = "almond" # TODO: metavariables only -"variable.parameter" = "lavender" +"variable.parameter" = { fg = "lavender", modifiers = ["underlined"] } "variable.builtin" = "mint" type = "white" "type.builtin" = "white" # TODO: distinguish? -- cgit v1.2.3-70-g09d2 From 066367c0a4fcfc1e7c2e926171672afef26736b4 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 9 Sep 2021 14:32:37 +0900 Subject: fix: Need to reset set_byte_range in case cursor_ref is reused. --- helix-core/src/syntax.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'helix-core') diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 1afe0e25..93da869b 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -372,10 +372,8 @@ impl Syntax { let config_ref = unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) }; - // TODO: if reusing cursors this might need resetting - if let Some(range) = &range { - cursor_ref.set_byte_range(range.clone()); - } + // if reusing cursors & no range this resets to whole range + cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX)); let captures = cursor_ref .captures(query_ref, tree_ref.root_node(), RopeProvider(source)) -- cgit v1.2.3-70-g09d2 From 51b7f40da1e564d9ecb5263421306d5846d98566 Mon Sep 17 00:00:00 2001 From: dependabot[bot] Date: Tue, 14 Sep 2021 09:21:35 +0900 Subject: build(deps): bump similar from 1.3.0 to 2.0.0 (#754) Bumps [similar](https://github.com/mitsuhiko/similar) from 1.3.0 to 2.0.0. - [Release notes](https://github.com/mitsuhiko/similar/releases) - [Changelog](https://github.com/mitsuhiko/similar/blob/main/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/similar/compare/1.3.0...2.0.0) --- updated-dependencies: - dependency-name: similar dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>--- Cargo.lock | 4 ++-- helix-core/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'helix-core') diff --git a/Cargo.lock b/Cargo.lock index c0011a24..93b456a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -875,9 +875,9 @@ dependencies = [ [[package]] name = "similar" -version = "1.3.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" +checksum = "6bf11003835e462f07851028082d2a1c89d956180ce4b4b50e07fb085ec4131a" [[package]] name = "slab" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 5d2db642..2b963676 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -30,7 +30,7 @@ regex = "1" serde = { version = "1.0", features = ["derive"] } toml = "0.5" -similar = "1.3" +similar = "2.0" etcetera = "0.3" -- cgit v1.2.3-70-g09d2 From dd0b15e1f1540d6ca8c58594be302c66005d755c Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 16 Sep 2021 15:47:51 +0900 Subject: syntax: Properly handle injection-regex for language injections --- helix-core/src/syntax.rs | 36 +++++++++++++++++++++++++++++++++++- helix-term/src/ui/editor.rs | 3 +-- helix-term/src/ui/markdown.rs | 2 +- 3 files changed, 37 insertions(+), 4 deletions(-) (limited to 'helix-core') diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 93da869b..8bbf363f 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -21,6 +21,15 @@ use std::{ use once_cell::sync::{Lazy, OnceCell}; use serde::{Deserialize, Serialize}; +fn deserialize_regex<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + Option::::deserialize(deserializer)? + .map(|buf| Regex::new(&buf).map_err(serde::de::Error::custom)) + .transpose() +} + #[derive(Debug, Serialize, Deserialize)] pub struct Configuration { pub language: Vec, @@ -42,7 +51,8 @@ pub struct LanguageConfiguration { pub auto_format: bool, // content_regex - // injection_regex + #[serde(default, skip_serializing, deserialize_with = "deserialize_regex")] + injection_regex: Option, // first_line_regex // #[serde(skip)] @@ -243,6 +253,30 @@ impl Loader { .cloned() } + pub fn language_configuration_for_injection_string( + &self, + string: &str, + ) -> Option> { + let mut best_match_length = 0; + let mut best_match_position = None; + for (i, configuration) in self.language_configs.iter().enumerate() { + if let Some(injection_regex) = &configuration.injection_regex { + if let Some(mat) = injection_regex.find(string) { + let length = mat.end() - mat.start(); + if length > best_match_length { + best_match_position = Some(i); + best_match_length = length; + } + } + } + } + + if let Some(i) = best_match_position { + let configuration = &self.language_configs[i]; + return Some(configuration.clone()); + } + None + } pub fn language_configs_iter(&self) -> impl Iterator> { self.language_configs.iter() } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 52cf3d2b..0605e2c7 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -147,8 +147,7 @@ impl EditorView { let scopes = theme.scopes(); syntax .highlight_iter(text.slice(..), Some(range), None, |language| { - loader - .language_config_for_scope(&format!("source.{}", language)) + loader.language_configuration_for_injection_string(language) .and_then(|language_config| { let config = language_config.highlight_config(scopes)?; let config_ref = config.as_ref(); diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 87b35a2d..4144ed3c 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -88,7 +88,7 @@ fn parse<'a>( if let Some(theme) = theme { let rope = Rope::from(text.as_ref()); let syntax = loader - .language_config_for_scope(&format!("source.{}", language)) + .language_configuration_for_injection_string(language) .and_then(|config| config.highlight_config(theme.scopes())) .map(|config| Syntax::new(&rope, config)); -- cgit v1.2.3-70-g09d2 From d8b94ba85fdbed9fb0e9ebc60e19e6353efdbf7a Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 16 Sep 2021 15:54:43 +0900 Subject: Fix broken test --- TODO.md | 4 ---- helix-core/src/indent.rs | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'helix-core') diff --git a/TODO.md b/TODO.md index f6a9ef09..d81cf302 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,8 @@ - tree sitter: - - lua - markdown - - zig - regex - - vue - kotlin - - julia - clojure - erlang diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 55802059..d9a0155f 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -454,6 +454,7 @@ where highlight_config: OnceCell::new(), config: None, // + injection_regex: None, roots: vec![], comment_token: None, auto_format: false, -- cgit v1.2.3-70-g09d2 From 64e8f0017c9d8d8fd02b3e86378522974f6437b3 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 16 Sep 2021 16:04:32 +0900 Subject: ... --- helix-core/src/syntax.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-core') diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 8bbf363f..547b2572 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -52,7 +52,7 @@ pub struct LanguageConfiguration { // content_regex #[serde(default, skip_serializing, deserialize_with = "deserialize_regex")] - injection_regex: Option, + pub injection_regex: Option, // first_line_regex // #[serde(skip)] -- cgit v1.2.3-70-g09d2 From 2e0803c8d9ec0028c0d018be251c7c2b781247b3 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 22 Sep 2021 00:51:49 +0900 Subject: Implement 'remove_primary_selection' as Alt-, This allows removing search matches from the selection Fixes #713 --- helix-core/src/selection.rs | 9 +++++++++ helix-term/src/commands.rs | 17 +++++++++++++++++ helix-term/src/keymap.rs | 1 + 3 files changed, 27 insertions(+) (limited to 'helix-core') diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index a3ea2ed4..755ee679 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -360,6 +360,15 @@ impl Selection { self.normalize() } + /// Adds a new range to the selection and makes it the primary range. + pub fn remove(mut self, index: usize) -> Self { + self.ranges.remove(index); + if index < self.primary_index || self.primary_index == self.ranges.len() { + self.primary_index -= 1; + } + self + } + /// Map selections over a set of changes. Useful for adjusting the selection position after /// applying changes to a document. pub fn map(self, changes: &ChangeSet) -> Self { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e3c351f6..025639a5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -290,6 +290,7 @@ impl Command { join_selections, "Join lines inside selection", keep_selections, "Keep selections matching regex", keep_primary_selection, "Keep primary selection", + remove_primary_selection, "Remove primary selection", completion, "Invoke completion popup", hover, "Show docs for item under cursor", toggle_comments, "Comment/uncomment selections", @@ -4016,11 +4017,27 @@ fn keep_selections(cx: &mut Context) { fn keep_primary_selection(cx: &mut Context) { let (view, doc) = current!(cx.editor); + // TODO: handle count let range = doc.selection(view.id).primary(); doc.set_selection(view.id, Selection::single(range.anchor, range.head)); } +fn remove_primary_selection(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + // TODO: handle count + + let selection = doc.selection(view.id); + if selection.len() == 1 { + cx.editor.set_error("no selections remaining".to_owned()); + return; + } + let index = selection.primary_index(); + let selection = selection.clone().remove(index); + + doc.set_selection(view.id, selection); +} + fn completion(cx: &mut Context) { // trigger on trigger char, or if user calls it // (or on word char typing??) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 4343a0b6..cd4d3a32 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -499,6 +499,7 @@ impl Default for Keymaps { // TODO: and another method for inverse "," => keep_primary_selection, + "A-," => remove_primary_selection, // "q" => record_macro, // "Q" => replay_macro, -- cgit v1.2.3-70-g09d2