From 1766bdb9d4c3df24a010041cd014b6fecb7641fa Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 18 Oct 2021 23:08:06 -0500 Subject: clean up combined-injections comment (#880) --- helix-core/src/syntax.rs | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 9c433f3d..0929e38f 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -527,39 +527,7 @@ impl LanguageLayer { self.tree.as_ref(), ) .ok_or(Error::Cancelled)?; - // unsafe { syntax.parser.set_cancellation_flag(None) }; - // let mut cursor = syntax.cursors.pop().unwrap_or_else(QueryCursor::new); - - // Process combined injections. (ERB, EJS, etc https://github.com/tree-sitter/tree-sitter/pull/526) - // if let Some(combined_injections_query) = &config.combined_injections_query { - // let mut injections_by_pattern_index = - // vec![(None, Vec::new(), false); combined_injections_query.pattern_count()]; - // let matches = - // cursor.matches(combined_injections_query, tree.root_node(), RopeProvider(source)); - // for mat in matches { - // let entry = &mut injections_by_pattern_index[mat.pattern_index]; - // let (language_name, content_node, include_children) = - // injection_for_match(config, combined_injections_query, &mat, source); - // if language_name.is_some() { - // entry.0 = language_name; - // } - // if let Some(content_node) = content_node { - // entry.1.push(content_node); - // } - // entry.2 = include_children; - // } - // for (lang_name, content_nodes, includes_children) in injections_by_pattern_index { - // if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) { - // if let Some(next_config) = (injection_callback)(lang_name) { - // let ranges = - // Self::intersect_ranges(&ranges, &content_nodes, includes_children); - // if !ranges.is_empty() { - // queue.push((next_config, depth + 1, ranges)); - // } - // } - // } - // } - // } + self.tree = Some(tree) } Ok(()) -- cgit v1.2.3-70-g09d2 From 4ee92cad19cc94f0751f91fa9391d1899353d740 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Sat, 23 Oct 2021 08:11:19 +0530 Subject: Add treesitter textobjects (#728) * Add treesitter textobject queries Only for Go, Python and Rust for now. * Add tree-sitter textobjects Only has functions and class objects as of now. * Fix tests * Add docs for tree-sitter textobjects * Add guide for creating new textobject queries * Add parameter textobject Only parameter.inside is implemented now, parameter.around will probably require custom predicates akin to nvim' `make-range` since we want to select a trailing comma too (a comma will be an anonymous node and matching against them doesn't work similar to named nodes) * Simplify TextObject cell init--- book/src/SUMMARY.md | 2 ++ book/src/guides/README.md | 4 +++ book/src/guides/textobject.md | 30 ++++++++++++++++++++ book/src/usage.md | 13 +++++++-- helix-core/src/indent.rs | 1 + helix-core/src/syntax.rs | 43 ++++++++++++++++++++++++++-- helix-core/src/textobject.rs | 51 ++++++++++++++++++++++++++++++++++ helix-term/src/commands.rs | 19 +++++++++++++ runtime/queries/go/textobjects.scm | 21 ++++++++++++++ runtime/queries/python/textobjects.scm | 14 ++++++++++ runtime/queries/rust/textobjects.scm | 26 +++++++++++++++++ 11 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 book/src/guides/README.md create mode 100644 book/src/guides/textobject.md create mode 100644 runtime/queries/go/textobjects.scm create mode 100644 runtime/queries/python/textobjects.scm create mode 100644 runtime/queries/rust/textobjects.scm (limited to 'helix-core/src') diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 3fa8e067..56f50e21 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -8,3 +8,5 @@ - [Keymap](./keymap.md) - [Key Remapping](./remapping.md) - [Hooks](./hooks.md) +- [Guides](./guides/README.md) + - [Adding Textobject Queries](./guides/textobject.md) diff --git a/book/src/guides/README.md b/book/src/guides/README.md new file mode 100644 index 00000000..96e62978 --- /dev/null +++ b/book/src/guides/README.md @@ -0,0 +1,4 @@ +# Guides + +This section contains guides for adding new language server configurations, +tree-sitter grammers, textobject queries, etc. diff --git a/book/src/guides/textobject.md b/book/src/guides/textobject.md new file mode 100644 index 00000000..50b3b574 --- /dev/null +++ b/book/src/guides/textobject.md @@ -0,0 +1,30 @@ +# Adding Textobject Queries + +Textobjects that are language specific ([like functions, classes, etc][textobjects]) +require an accompanying tree-sitter grammar and a `textobjects.scm` query file +to work properly. Tree-sitter allows us to query the source code syntax tree +and capture specific parts of it. The queries are written in a lisp dialect. +More information on how to write queries can be found in the [official tree-sitter +documentation](tree-sitter-queries). + +Query files should be placed in `runtime/queries/{language}/textobjects.scm` +when contributing. Note that to test the query files locally you should put +them under your local runtime directory (`~/.config/helix/runtime` on Linux +for example). + +The following [captures][tree-sitter-captures] are recognized: + +| Capture Name | +| --- | +| `function.inside` | +| `function.around` | +| `class.inside` | +| `class.around` | +| `parameter.inside` | + +[Example query files][textobject-examples] can be found in the helix GitHub repository. + +[textobjects]: ../usage.md#textobjects +[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax +[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes +[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l= diff --git a/book/src/usage.md b/book/src/usage.md index 2de8d01a..d31e03a1 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -51,9 +51,10 @@ Multiple characters are currently not supported, but planned. ## Textobjects -Currently supported: `word`, `surround`. +Currently supported: `word`, `surround`, `function`, `class`, `parameter`. ![textobject-demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif) +![textobject-treesitter-demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif) - `ma` - Select around the object (`va` in vim, `` in kakoune) - `mi` - Select inside the object (`vi` in vim, `` in kakoune) @@ -62,5 +63,11 @@ Currently supported: `word`, `surround`. | --- | --- | | `w` | Word | | `(`, `[`, `'`, etc | Specified surround pairs | - -Textobjects based on treesitter, like `function`, `class`, etc are planned. +| `f` | Function | +| `c` | Class | +| `p` | Parameter | + +Note: `f`, `c`, etc need a tree-sitter grammar active for the current +document and a special tree-sitter query file to work properly. [Only +some grammars](https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=) +currently have the query file implemented. Contributions are welcome ! diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index d9a0155f..20f034ea 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -464,6 +464,7 @@ where unit: String::from(" "), }), indent_query: OnceCell::new(), + textobject_query: OnceCell::new(), }], }); diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 0929e38f..f4b4535b 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -49,7 +49,7 @@ pub struct Configuration { #[serde(rename_all = "kebab-case")] pub struct LanguageConfiguration { #[serde(rename = "name")] - pub(crate) language_id: String, + pub language_id: String, pub scope: String, // source.rust pub file_types: Vec, // filename ends_with? pub roots: Vec, // these indicate project roots <.git, Cargo.toml> @@ -76,6 +76,8 @@ pub struct LanguageConfiguration { #[serde(skip)] pub(crate) indent_query: OnceCell>, + #[serde(skip)] + pub(crate) textobject_query: OnceCell>, } #[derive(Debug, Serialize, Deserialize)] @@ -105,6 +107,32 @@ pub struct IndentQuery { pub outdent: HashSet, } +#[derive(Debug)] +pub struct TextObjectQuery { + pub query: Query, +} + +impl TextObjectQuery { + /// Run the query on the given node and return sub nodes which match given + /// capture ("function.inside", "class.around", etc). + pub fn capture_nodes<'a>( + &'a self, + capture_name: &str, + node: Node<'a>, + slice: RopeSlice<'a>, + cursor: &'a mut QueryCursor, + ) -> Option>> { + let capture_idx = self.query.capture_index_for_name(capture_name)?; + let captures = cursor.captures(&self.query, node, RopeProvider(slice)); + + captures + .filter_map(move |(mat, idx)| { + (mat.captures[idx].index == capture_idx).then(|| mat.captures[idx].node) + }) + .into() + } +} + fn load_runtime_file(language: &str, filename: &str) -> Result { let path = crate::RUNTIME_DIR .join("queries") @@ -153,7 +181,6 @@ impl LanguageConfiguration { // highlights_query += "\n(ERROR) @error"; let injections_query = read_query(&language, "injections.scm"); - let locals_query = read_query(&language, "locals.scm"); if highlights_query.is_empty() { @@ -203,6 +230,18 @@ impl LanguageConfiguration { .as_ref() } + pub fn textobject_query(&self) -> Option<&TextObjectQuery> { + self.textobject_query + .get_or_init(|| -> Option { + let lang_name = self.language_id.to_ascii_lowercase(); + let query_text = read_query(&lang_name, "textobjects.scm"); + let lang = self.highlight_config.get()?.as_ref()?.language; + let query = Query::new(lang, &query_text).ok()?; + Some(TextObjectQuery { query }) + }) + .as_ref() + } + pub fn scope(&self) -> &str { &self.scope } diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index b965f6df..975ed115 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -1,9 +1,13 @@ +use std::fmt::Display; + use ropey::RopeSlice; +use tree_sitter::{Node, QueryCursor}; use crate::chars::{categorize_char, char_is_whitespace, CharCategory}; use crate::graphemes::next_grapheme_boundary; use crate::movement::Direction; use crate::surround; +use crate::syntax::LanguageConfiguration; use crate::Range; fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize { @@ -51,6 +55,15 @@ pub enum TextObject { Inside, } +impl Display for TextObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Around => "around", + Self::Inside => "inside", + }) + } +} + // count doesn't do anything yet pub fn textobject_word( slice: RopeSlice, @@ -108,6 +121,44 @@ pub fn textobject_surround( .unwrap_or(range) } +/// Transform the given range to select text objects based on tree-sitter. +/// `object_name` is a query capture base name like "function", "class", etc. +/// `slice_tree` is the tree-sitter node corresponding to given text slice. +pub fn textobject_treesitter( + slice: RopeSlice, + range: Range, + textobject: TextObject, + object_name: &str, + slice_tree: Node, + lang_config: &LanguageConfiguration, + _count: usize, +) -> Range { + let get_range = move || -> Option { + let byte_pos = slice.char_to_byte(range.cursor(slice)); + + let capture_name = format!("{}.{}", object_name, textobject); // eg. function.inner + let mut cursor = QueryCursor::new(); + let node = lang_config + .textobject_query()? + .capture_nodes(&capture_name, slice_tree, slice, &mut cursor)? + .filter(|node| node.byte_range().contains(&byte_pos)) + .min_by_key(|node| node.byte_range().len())?; + + let len = slice.len_bytes(); + let start_byte = node.start_byte(); + let end_byte = node.end_byte(); + if start_byte >= len || end_byte >= len { + return None; + } + + let start_char = slice.byte_to_char(start_byte); + let end_char = slice.byte_to_char(end_byte); + + Some(Range::new(start_char, end_char)) + }; + get_range().unwrap_or(range) +} + #[cfg(test)] mod test { use super::TextObject::*; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9f54292d..272a9d9a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4465,9 +4465,28 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); + let textobject_treesitter = |obj_name: &str, range: Range| -> Range { + let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) { + Some(t) => t, + None => return range, + }; + textobject::textobject_treesitter( + text, + range, + objtype, + obj_name, + syntax.tree().root_node(), + lang_config, + count, + ) + }; + let selection = doc.selection(view.id).clone().transform(|range| { match ch { 'w' => textobject::textobject_word(text, range, objtype, count), + 'c' => textobject_treesitter("class", range), + 'f' => textobject_treesitter("function", range), + 'p' => textobject_treesitter("parameter", range), // TODO: cancel new ranges if inconsistent surround matches across lines ch if !ch.is_ascii_alphanumeric() => { textobject::textobject_surround(text, range, objtype, ch, count) diff --git a/runtime/queries/go/textobjects.scm b/runtime/queries/go/textobjects.scm new file mode 100644 index 00000000..9bcfc690 --- /dev/null +++ b/runtime/queries/go/textobjects.scm @@ -0,0 +1,21 @@ +(function_declaration + body: (block)? @function.inside) @function.around + +(func_literal + (_)? @function.inside) @function.around + +(method_declaration + body: (block)? @function.inside) @function.around + +;; struct and interface declaration as class textobject? +(type_declaration + (type_spec (type_identifier) (struct_type (field_declaration_list (_)?) @class.inside))) @class.around + +(type_declaration + (type_spec (type_identifier) (interface_type (method_spec_list (_)?) @class.inside))) @class.around + +(parameter_list + (_) @parameter.inside) + +(argument_list + (_) @parameter.inside) diff --git a/runtime/queries/python/textobjects.scm b/runtime/queries/python/textobjects.scm new file mode 100644 index 00000000..a52538af --- /dev/null +++ b/runtime/queries/python/textobjects.scm @@ -0,0 +1,14 @@ +(function_definition + body: (block)? @function.inside) @function.around + +(class_definition + body: (block)? @class.inside) @class.around + +(parameters + (_) @parameter.inside) + +(lambda_parameters + (_) @parameter.inside) + +(argument_list + (_) @parameter.inside) diff --git a/runtime/queries/rust/textobjects.scm b/runtime/queries/rust/textobjects.scm new file mode 100644 index 00000000..e3132687 --- /dev/null +++ b/runtime/queries/rust/textobjects.scm @@ -0,0 +1,26 @@ +(function_item + body: (_) @function.inside) @function.around + +(struct_item + body: (_) @class.inside) @class.around + +(enum_item + body: (_) @class.inside) @class.around + +(union_item + body: (_) @class.inside) @class.around + +(trait_item + body: (_) @class.inside) @class.around + +(impl_item + body: (_) @class.inside) @class.around + +(parameters + (_) @parameter.inside) + +(closure_parameters + (_) @parameter.inside) + +(arguments + (_) @parameter.inside) -- cgit v1.2.3-70-g09d2 From 0cb5e0b2caba61bbcf6f57ce58506882766d5eea Mon Sep 17 00:00:00 2001 From: Kirawi Date: Sat, 23 Oct 2021 08:52:18 -0400 Subject: log syntax highlighting init errors (#895) --- Cargo.lock | 1 + helix-core/Cargo.toml | 1 + helix-core/src/syntax.rs | 4 +++- helix-term/src/main.rs | 7 ++++++- helix-view/src/clipboard.rs | 22 ++++++---------------- helix-view/src/editor.rs | 6 +++++- 6 files changed, 22 insertions(+), 19 deletions(-) (limited to 'helix-core/src') diff --git a/Cargo.lock b/Cargo.lock index aa6fb141..ad12adf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,6 +363,7 @@ dependencies = [ "arc-swap", "etcetera", "helix-syntax", + "log", "once_cell", "quickcheck", "regex", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 93ebb133..84d029d2 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -27,6 +27,7 @@ once_cell = "1.8" arc-swap = "1" regex = "1" +log = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.5" diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index f4b4535b..281a70f9 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -186,7 +186,9 @@ impl LanguageConfiguration { if highlights_query.is_empty() { None } else { - let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?; + let language = get_language(&crate::RUNTIME_DIR, &self.language_id) + .map_err(|e| log::info!("{}", e)) + .ok()?; let config = HighlightConfiguration::new( language, &highlights_query, diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 180dacd1..2589a375 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -16,6 +16,11 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { }; // Separate file config so we can include year, month and day in file logs + let file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(logpath)?; let file_config = fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( @@ -26,7 +31,7 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { message )) }) - .chain(fern::log_file(logpath)?); + .chain(file); base_config.chain(file_config).apply()?; diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index a11224ac..a492652d 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -116,7 +116,7 @@ pub fn get_clipboard_provider() -> Box { } } else { #[cfg(target_os = "windows")] - return Box::new(provider::WindowsProvider::new()); + return Box::new(provider::WindowsProvider::default()); #[cfg(not(target_os = "windows"))] return Box::new(provider::NopProvider::new()); @@ -145,15 +145,15 @@ mod provider { use anyhow::{bail, Context as _, Result}; use std::borrow::Cow; + #[cfg(not(target_os = "windows"))] #[derive(Debug)] pub struct NopProvider { buf: String, primary_buf: String, } + #[cfg(not(target_os = "windows"))] impl NopProvider { - #[allow(dead_code)] - // Only dead_code on Windows. pub fn new() -> Self { Self { buf: String::new(), @@ -162,6 +162,7 @@ mod provider { } } + #[cfg(not(target_os = "windows"))] impl ClipboardProvider for NopProvider { fn name(&self) -> Cow { Cow::Borrowed("none") @@ -186,19 +187,8 @@ mod provider { } #[cfg(target_os = "windows")] - #[derive(Debug)] - pub struct WindowsProvider { - selection_buf: String, - } - - #[cfg(target_os = "windows")] - impl WindowsProvider { - pub fn new() -> Self { - Self { - selection_buf: String::new(), - } - } - } + #[derive(Default, Debug)] + pub struct WindowsProvider; #[cfg(target_os = "windows")] impl ClipboardProvider for WindowsProvider { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 813c86fd..51fe8a42 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -294,7 +294,11 @@ impl Editor { self.language_servers .get(language) .map_err(|e| { - log::error!("Failed to get LSP, {}, for `{}`", e, language.scope()) + log::error!( + "Failed to initialize the LSP for `{}` {{ {} }}", + language.scope(), + e + ) }) .ok() }); -- cgit v1.2.3-70-g09d2 From cee7ad781e5f6de249d728425a6283a26bb62dc3 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Sun, 24 Oct 2021 17:28:29 +0900 Subject: Mark a few functions as `const` --- helix-core/src/line_ending.rs | 6 +++--- helix-core/src/register.rs | 4 ++-- helix-term/src/ui/editor.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index 18ea5f9f..3541305c 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -20,7 +20,7 @@ pub enum LineEnding { impl LineEnding { #[inline] - pub fn len_chars(&self) -> usize { + pub const fn len_chars(&self) -> usize { match self { Self::Crlf => 2, _ => 1, @@ -28,7 +28,7 @@ impl LineEnding { } #[inline] - pub fn as_str(&self) -> &'static str { + pub const fn as_str(&self) -> &'static str { match self { Self::Crlf => "\u{000D}\u{000A}", Self::LF => "\u{000A}", @@ -42,7 +42,7 @@ impl LineEnding { } #[inline] - pub fn from_char(ch: char) -> Option { + pub const fn from_char(ch: char) -> Option { match ch { '\u{000A}' => Some(LineEnding::LF), '\u{000B}' => Some(LineEnding::VT), diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs index c3e6652e..c5444eb7 100644 --- a/helix-core/src/register.rs +++ b/helix-core/src/register.rs @@ -7,7 +7,7 @@ pub struct Register { } impl Register { - pub fn new(name: char) -> Self { + pub const fn new(name: char) -> Self { Self { name, values: Vec::new(), @@ -18,7 +18,7 @@ impl Register { Self { name, values } } - pub fn name(&self) -> char { + pub const fn name(&self) -> char { self.name } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 9234bb96..692696a6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1106,7 +1106,7 @@ fn canonicalize_key(key: &mut KeyEvent) { } #[inline] -fn abs_diff(a: usize, b: usize) -> usize { +const fn abs_diff(a: usize, b: usize) -> usize { if a > b { a - b } else { -- cgit v1.2.3-70-g09d2 From bfb6cff5a9b0ff5c37085086f895d3f14eaa5782 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 25 Oct 2021 11:01:21 +0900 Subject: fix: Compose where changes.compose(empty_other) --- helix-core/src/transaction.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'helix-core/src') diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index d682f058..0e49fbe3 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -132,6 +132,9 @@ impl ChangeSet { if self.changes.is_empty() { return other; } + if other.changes.is_empty() { + return self; + } let len = self.changes.len(); -- cgit v1.2.3-70-g09d2 From 3edca7854e66cbdb0c4baca25962a4f390fede55 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 25 Oct 2021 11:03:18 +0900 Subject: completion: fully revert state before apply & insertText common prefix --- helix-core/src/transaction.rs | 7 ++++ helix-term/src/ui/completion.rs | 71 +++++++++++++++++------------------------ helix-term/src/ui/editor.rs | 13 ++++++-- helix-view/src/document.rs | 24 +++++++++++++- 4 files changed, 71 insertions(+), 44 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 0e49fbe3..dfc18fbe 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -468,6 +468,13 @@ impl Transaction { } } + pub fn compose(mut self, other: Self) -> Self { + self.changes = self.changes.compose(other.changes); + // Other selection takes precedence + self.selection = other.selection; + self + } + pub fn with_selection(mut self, selection: Selection) -> Self { self.selection = Some(selection); self diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c75b24f1..44879fcf 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface; use std::borrow::Cow; use helix_core::Transaction; -use helix_view::{graphics::Rect, Document, Editor, View}; +use helix_view::{graphics::Rect, Document, Editor}; use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -83,13 +83,13 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Self { - // let items: Vec = Vec::new(); let menu = Menu::new(items, move |editor: &mut Editor, item, event| { fn item_to_transaction( doc: &Document, - view: &View, item: &CompletionItem, offset_encoding: helix_lsp::OffsetEncoding, + start_offset: usize, + trigger_offset: usize, ) -> Transaction { if let Some(edit) = &item.text_edit { let edit = match edit { @@ -105,63 +105,52 @@ impl Completion { ) } else { let text = item.insert_text.as_ref().unwrap_or(&item.label); - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); + // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯ + // in these cases we need to check for a common prefix and remove it + let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset)); + let text = text.trim_start_matches::<&str>(&prefix); Transaction::change( doc.text(), - vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), + vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), ) } } + let (view, doc) = current!(editor); + + // if more text was entered, remove it + doc.restore(view.id); + match event { PromptEvent::Abort => {} PromptEvent::Update => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); + + // initialize a savepoint + doc.savepoint(); - let transaction = item_to_transaction(doc, view, item, offset_encoding); doc.apply(&transaction, view.id); } PromptEvent::Validate => { - let (view, doc) = current!(editor); - // always present here let item = item.unwrap(); - // if more text was entered, remove it - // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes - let cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - if trigger_offset < cursor { - let remove = Transaction::change( - doc.text(), - vec![(trigger_offset, cursor, None)].into_iter(), - ); - doc.apply(&remove, view.id); - } - - let transaction = item_to_transaction(doc, view, item, offset_encoding); + let transaction = item_to_transaction( + doc, + item, + offset_encoding, + start_offset, + trigger_offset, + ); doc.apply(&transaction, view.id); if let Some(additional_edits) = &item.additional_text_edits { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 692696a6..850fec0f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -13,7 +13,7 @@ use helix_core::{ syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, + LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ document::Mode, @@ -721,7 +721,7 @@ impl EditorView { pub fn set_completion( &mut self, - editor: &Editor, + editor: &mut Editor, items: Vec, offset_encoding: helix_lsp::OffsetEncoding, start_offset: usize, @@ -736,6 +736,9 @@ impl EditorView { return; } + // Immediately initialize a savepoint + doc_mut!(editor).savepoint(); + // TODO : propagate required size on resize to completion too completion.required_size((size.width, size.height)); self.completion = Some(completion); @@ -945,6 +948,9 @@ impl Component for EditorView { if callback.is_some() { // assume close_fn self.completion = None; + // Clear any savepoints + let (_, doc) = current!(cxt.editor); + doc.savepoint = None; cxt.editor.clear_idle_timer(); // don't retrigger } } @@ -959,6 +965,9 @@ impl Component for EditorView { completion.update(&mut cxt); if completion.is_empty() { self.completion = None; + // Clear any savepoints + let (_, doc) = current!(cxt.editor); + doc.savepoint = None; cxt.editor.clear_idle_timer(); // don't retrigger } } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 8804681b..23c2dbc6 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -97,6 +97,9 @@ pub struct Document { // it back as it separated from the edits. We could split out the parts manually but that will // be more troublesome. history: Cell, + + pub savepoint: Option, + last_saved_revision: usize, version: i32, // should be usize? @@ -328,6 +331,7 @@ impl Document { text, selections: HashMap::default(), indent_style: DEFAULT_INDENT, + line_ending: DEFAULT_LINE_ENDING, mode: Mode::Normal, restore_cursor: false, syntax: None, @@ -337,9 +341,9 @@ impl Document { diagnostics: Vec::new(), version: 0, history: Cell::new(History::default()), + savepoint: None, last_saved_revision: 0, language_server: None, - line_ending: DEFAULT_LINE_ENDING, } } @@ -635,6 +639,14 @@ impl Document { if !transaction.changes().is_empty() { self.version += 1; + // generate revert to savepoint + if self.savepoint.is_some() { + take_with(&mut self.savepoint, |prev_revert| { + let revert = transaction.invert(&old_doc); + Some(revert.compose(prev_revert.unwrap())) + }); + } + // update tree-sitter syntax tree if let Some(syntax) = &mut self.syntax { // TODO: no unwrap @@ -724,6 +736,16 @@ impl Document { } } + pub fn savepoint(&mut self) { + self.savepoint = Some(Transaction::new(self.text())); + } + + pub fn restore(&mut self, view_id: ViewId) { + if let Some(revert) = self.savepoint.take() { + self.apply(&revert, view_id); + } + } + /// Undo modifications to the [`Document`] according to `uk`. pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) { let txns = self.history.get_mut().earlier(uk); -- cgit v1.2.3-70-g09d2 From 92c2d5d3bffb52a165fad75b6db2120de37b5ae4 Mon Sep 17 00:00:00 2001 From: Kirawi Date: Mon, 25 Oct 2021 12:02:16 -0400 Subject: Document more of helix-core (#904) --- helix-core/src/auto_pairs.rs | 3 ++ helix-core/src/chars.rs | 2 + helix-core/src/comment.rs | 3 ++ helix-core/src/diagnostic.rs | 5 +++ helix-core/src/graphemes.rs | 4 +- helix-core/src/history.rs | 105 ++++++++++++++++++++++++++----------------- 6 files changed, 81 insertions(+), 41 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 9b901e9b..cc966852 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -1,3 +1,6 @@ +//! When typing the opening character of one of the possible pairs defined below, +//! this module provides the functionality to insert the paired closing character. + use crate::{Range, Rope, Selection, Tendril, Transaction}; use smallvec::SmallVec; diff --git a/helix-core/src/chars.rs b/helix-core/src/chars.rs index 24133dd3..c8e5efbd 100644 --- a/helix-core/src/chars.rs +++ b/helix-core/src/chars.rs @@ -1,3 +1,5 @@ +//! Utility functions to categorize a `char`. + use crate::LineEnding; #[derive(Debug, Eq, PartialEq)] diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index 3d8e1ce3..4072a532 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -1,3 +1,6 @@ +//! This module contains the functionality toggle comments on lines over the selection +//! using the comment character defined in the user's `languages.toml` + use crate::{ find_first_non_whitespace_char, Change, Rope, RopeSlice, Selection, Tendril, Transaction, }; diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index e08a71e7..ab47e075 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,3 +1,6 @@ +//! LSP diagnostic utility types. + +/// Describes the severity level of a [`Diagnostic`]. #[derive(Debug, Eq, PartialEq)] pub enum Severity { Error, @@ -6,12 +9,14 @@ pub enum Severity { Hint, } +/// A range of `char`s within the text. #[derive(Debug)] pub struct Range { pub start: usize, pub end: usize, } +/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) #[derive(Debug)] pub struct Diagnostic { pub range: Range, diff --git a/helix-core/src/graphemes.rs b/helix-core/src/graphemes.rs index 0465fe51..56f5bacb 100644 --- a/helix-core/src/graphemes.rs +++ b/helix-core/src/graphemes.rs @@ -1,4 +1,6 @@ -// Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs +//! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents. +//! +//! Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice}; use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; use unicode_width::UnicodeWidthStr; diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 67ded166..cf62708a 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -4,48 +4,50 @@ use regex::Regex; use std::num::NonZeroUsize; use std::time::{Duration, Instant}; -// Stores the history of changes to a buffer. -// -// Currently the history is represented as a vector of revisions. The vector -// always has at least one element: the empty root revision. Each revision -// with the exception of the root has a parent revision, a [Transaction] -// that can be applied to its parent to transition from the parent to itself, -// and an inversion of that transaction to transition from the parent to its -// latest child. -// -// When using `u` to undo a change, an inverse of the stored transaction will -// be applied which will transition the buffer to the parent state. -// -// Each revision with the exception of the last in the vector also has a -// last child revision. When using `U` to redo a change, the last child transaction -// will be applied to the current state of the buffer. -// -// The current revision is the one currently displayed in the buffer. -// -// Commiting a new revision to the history will update the last child of the -// current revision, and push a new revision to the end of the vector. -// -// Revisions are commited with a timestamp. :earlier and :later can be used -// to jump to the closest revision to a moment in time relative to the timestamp -// of the current revision plus (:later) or minus (:earlier) the duration -// given to the command. If a single integer is given, the editor will instead -// jump the given number of revisions in the vector. -// -// Limitations: -// * Changes in selections currently don't commit history changes. The selection -// will only be updated to the state after a commited buffer change. -// * The vector of history revisions is currently unbounded. This might -// cause the memory consumption to grow significantly large during long -// editing sessions. -// * Because delete transactions currently don't store the text that they -// delete, we also store an inversion of the transaction. +/// Stores the history of changes to a buffer. +/// +/// Currently the history is represented as a vector of revisions. The vector +/// always has at least one element: the empty root revision. Each revision +/// with the exception of the root has a parent revision, a [Transaction] +/// that can be applied to its parent to transition from the parent to itself, +/// and an inversion of that transaction to transition from the parent to its +/// latest child. +/// +/// When using `u` to undo a change, an inverse of the stored transaction will +/// be applied which will transition the buffer to the parent state. +/// +/// Each revision with the exception of the last in the vector also has a +/// last child revision. When using `U` to redo a change, the last child transaction +/// will be applied to the current state of the buffer. +/// +/// The current revision is the one currently displayed in the buffer. +/// +/// Commiting a new revision to the history will update the last child of the +/// current revision, and push a new revision to the end of the vector. +/// +/// Revisions are commited with a timestamp. :earlier and :later can be used +/// to jump to the closest revision to a moment in time relative to the timestamp +/// of the current revision plus (:later) or minus (:earlier) the duration +/// given to the command. If a single integer is given, the editor will instead +/// jump the given number of revisions in the vector. +/// +/// Limitations: +/// * Changes in selections currently don't commit history changes. The selection +/// will only be updated to the state after a commited buffer change. +/// * The vector of history revisions is currently unbounded. This might +/// cause the memory consumption to grow significantly large during long +/// editing sessions. +/// * Because delete transactions currently don't store the text that they +/// delete, we also store an inversion of the transaction. +/// +/// Using time to navigate the history: https://github.com/helix-editor/helix/pull/194 #[derive(Debug)] pub struct History { revisions: Vec, current: usize, } -// A single point in history. See [History] for more information. +/// A single point in history. See [History] for more information. #[derive(Debug)] struct Revision { parent: usize, @@ -111,6 +113,7 @@ impl History { self.current == 0 } + /// Undo the last edit. pub fn undo(&mut self) -> Option<&Transaction> { if self.at_root() { return None; @@ -121,6 +124,7 @@ impl History { Some(¤t_revision.inversion) } + /// Redo the last edit. pub fn redo(&mut self) -> Option<&Transaction> { let current_revision = &self.revisions[self.current]; let last_child = current_revision.last_child?; @@ -147,8 +151,8 @@ impl History { } } - // List of nodes on the way from `n` to 'a`. Doesn`t include `a`. - // Includes `n` unless `a == n`. `a` must be an ancestor of `n`. + /// List of nodes on the way from `n` to 'a`. Doesn`t include `a`. + /// Includes `n` unless `a == n`. `a` must be an ancestor of `n`. fn path_up(&self, mut n: usize, a: usize) -> Vec { let mut path = Vec::new(); while n != a { @@ -158,6 +162,7 @@ impl History { path } + /// Create a [`Transaction`] that will jump to a specific revision in the history. fn jump_to(&mut self, to: usize) -> Vec { let lca = self.lowest_common_ancestor(self.current, to); let up = self.path_up(self.current, lca); @@ -171,10 +176,12 @@ impl History { up_txns.chain(down_txns).collect() } + /// Creates a [`Transaction`] that will undo `delta` revisions. fn jump_backward(&mut self, delta: usize) -> Vec { self.jump_to(self.current.saturating_sub(delta)) } + /// Creates a [`Transaction`] that will redo `delta` revisions. fn jump_forward(&mut self, delta: usize) -> Vec { self.jump_to( self.current @@ -183,7 +190,7 @@ impl History { ) } - // Helper for a binary search case below. + /// Helper for a binary search case below. fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize { let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp); let dur_i = self.revisions[i].timestamp.duration_since(instant); @@ -194,6 +201,8 @@ impl History { } } + /// Creates a [`Transaction`] that will match a revision created at around + /// `instant`. fn jump_instant(&mut self, instant: Instant) -> Vec { let search_result = self .revisions @@ -209,6 +218,8 @@ impl History { self.jump_to(revision) } + /// Creates a [`Transaction`] that will match a revision created `duration` ago + /// from the timestamp of current revision. fn jump_duration_backward(&mut self, duration: Duration) -> Vec { match self.revisions[self.current].timestamp.checked_sub(duration) { Some(instant) => self.jump_instant(instant), @@ -216,6 +227,8 @@ impl History { } } + /// Creates a [`Transaction`] that will match a revision created `duration` in + /// the future from the timestamp of the current revision. fn jump_duration_forward(&mut self, duration: Duration) -> Vec { match self.revisions[self.current].timestamp.checked_add(duration) { Some(instant) => self.jump_instant(instant), @@ -223,6 +236,7 @@ impl History { } } + /// Creates an undo [`Transaction`]. pub fn earlier(&mut self, uk: UndoKind) -> Vec { use UndoKind::*; match uk { @@ -231,6 +245,7 @@ impl History { } } + /// Creates a redo [`Transaction`]. pub fn later(&mut self, uk: UndoKind) -> Vec { use UndoKind::*; match uk { @@ -240,13 +255,14 @@ impl History { } } +/// Whether to undo by a number of edits or a duration of time. #[derive(Debug, PartialEq)] pub enum UndoKind { Steps(usize), TimePeriod(std::time::Duration), } -// A subset of sytemd.time time span syntax units. +/// A subset of sytemd.time time span syntax units. const TIME_UNITS: &[(&[&str], &str, u64)] = &[ (&["seconds", "second", "sec", "s"], "seconds", 1), (&["minutes", "minute", "min", "m"], "minutes", 60), @@ -254,11 +270,20 @@ const TIME_UNITS: &[(&[&str], &str, u64)] = &[ (&["days", "day", "d"], "days", 24 * 60 * 60), ]; +/// Checks if the duration input can be turned into a valid duration. It must be a +/// positive integer and denote the [unit of time.](`TIME_UNITS`) +/// Examples of valid durations: +/// * `5 sec` +/// * `5 min` +/// * `5 hr` +/// * `5 days` static DURATION_VALIDATION_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap()); +/// Captures both the number and unit as separate capture groups. static NUMBER_UNIT_REGEX: Lazy = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap()); +/// Parse a string (e.g. "5 sec") and try to convert it into a [`Duration`]. fn parse_human_duration(s: &str) -> Result { if !DURATION_VALIDATION_REGEX.is_match(s) { return Err("duration should be composed \ -- cgit v1.2.3-70-g09d2 From 0a38983ee3d4a38a026dfb44ae9cd99f66145d3d Mon Sep 17 00:00:00 2001 From: Gygaxis Vainhardt Date: Wed, 27 Oct 2021 22:24:11 -0300 Subject: Remove three transmutes from helix-core syntax.rs (#923) --- helix-core/src/syntax.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 281a70f9..f3e3f238 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -437,7 +437,7 @@ impl Syntax { /// Iterate over the highlighted regions for a given slice of source code. pub fn highlight_iter<'a>( - &self, + &'a self, source: RopeSlice<'a>, range: Option>, cancellation_flag: Option<&'a AtomicUsize>, @@ -452,11 +452,10 @@ impl Syntax { let highlighter = &mut ts_parser.borrow_mut(); highlighter.cursors.pop().unwrap_or_else(QueryCursor::new) }); - let tree_ref = unsafe { mem::transmute::<_, &'static Tree>(self.tree()) }; + let tree_ref = self.tree(); let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) }; - let query_ref = unsafe { mem::transmute::<_, &'static Query>(&self.config.query) }; - let config_ref = - unsafe { mem::transmute::<_, &'static HighlightConfiguration>(self.config.as_ref()) }; + let query_ref = &self.config.query; + let config_ref = self.config.as_ref(); // if reusing cursors & no range this resets to whole range cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX)); -- cgit v1.2.3-70-g09d2 From 45fadf61518b36c2e020f7262f136588dc96b03e Mon Sep 17 00:00:00 2001 From: Omnikar Date: Thu, 28 Oct 2021 20:55:15 -0400 Subject: Add hyperlinks to fix `cargo doc` warn (#931) --- helix-core/src/graphemes.rs | 2 +- helix-core/src/history.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/graphemes.rs b/helix-core/src/graphemes.rs index 56f5bacb..c6398875 100644 --- a/helix-core/src/graphemes.rs +++ b/helix-core/src/graphemes.rs @@ -1,6 +1,6 @@ //! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents. //! -//! Based on https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs +//! Based on use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice}; use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete}; use unicode_width::UnicodeWidthStr; diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index cf62708a..b53c01fe 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -40,7 +40,7 @@ use std::time::{Duration, Instant}; /// * Because delete transactions currently don't store the text that they /// delete, we also store an inversion of the transaction. /// -/// Using time to navigate the history: https://github.com/helix-editor/helix/pull/194 +/// Using time to navigate the history: #[derive(Debug)] pub struct History { revisions: Vec, -- cgit v1.2.3-70-g09d2 From befecc8a9a087436a9dbc6942e328646c3391874 Mon Sep 17 00:00:00 2001 From: cossonleo Date: Thu, 28 Oct 2021 13:31:44 +0800 Subject: select smaller range on some case --- helix-core/src/object.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index d9558dd8..717c5994 100644 --- a/helix-core/src/object.rs +++ b/helix-core/src/object.rs @@ -13,8 +13,13 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) let parent = match tree .root_node() .descendant_for_byte_range(from, to) - .and_then(|node| node.parent()) - { + .and_then(|node| { + if node.child_count() == 0 || (node.start_byte() == from && node.end_byte() == to) { + node.parent() + } else { + Some(node) + } + }) { Some(parent) => parent, None => return range, }; -- cgit v1.2.3-70-g09d2 From ee889aaa854d0036da3bae16252bc382e50b0df6 Mon Sep 17 00:00:00 2001 From: Kirawi Date: Tue, 2 Nov 2021 23:00:52 -0400 Subject: Updated tree-sitter query scopes (#896) * updated theme scopes variable.property -> variable.field property -> variable.field * updated theme scopes * update book and themes updated book and themes to reflect scope changes * wip * update more queries * update dark_plus.toml--- book/src/themes.md | 10 +++--- helix-core/src/lib.rs | 1 + runtime/queries/bash/highlights.scm | 4 +-- runtime/queries/c-sharp/highlights.scm | 10 +++--- runtime/queries/c/highlights.scm | 6 ++-- runtime/queries/css/highlights.scm | 14 ++++---- runtime/queries/elixir/highlights.scm | 19 +++++------ runtime/queries/go/highlights.scm | 6 ++-- runtime/queries/haskell/highlights.scm | 6 ++-- runtime/queries/java/highlights.scm | 11 ++++--- runtime/queries/javascript/highlights.scm | 4 +-- runtime/queries/json/highlights.scm | 19 ++++++++--- runtime/queries/julia/highlights.scm | 12 +++---- runtime/queries/ledger/highlights.scm | 4 +-- runtime/queries/lua/highlights.scm | 6 ++-- runtime/queries/nix/highlights.scm | 12 +++---- runtime/queries/ocaml/highlights.scm | 8 ++--- runtime/queries/php/highlights.scm | 12 +++---- runtime/queries/protobuf/highlights.scm | 10 +++--- runtime/queries/python/highlights.scm | 11 +++---- runtime/queries/ruby/highlights.scm | 6 ++-- runtime/queries/rust/highlights.scm | 35 +++++++++++++------- runtime/queries/svelte/highlights.scm | 2 +- runtime/queries/toml/highlights.scm | 8 ++--- runtime/queries/tsq/highlights.scm | 4 +-- runtime/queries/yaml/highlights.scm | 10 +++--- runtime/queries/zig/highlights.scm | 8 ++--- runtime/themes/base16_default_dark.toml | 6 ++-- runtime/themes/bogster.toml | 6 ++-- runtime/themes/dark_plus.toml | 53 ++++++++++++++++++++----------- runtime/themes/everforest_dark.toml | 7 ++-- runtime/themes/gruvbox.toml | 7 ++-- runtime/themes/ingrid.toml | 6 ++-- runtime/themes/monokai.toml | 7 ++-- runtime/themes/nord.toml | 6 ++-- runtime/themes/rose_pine.toml | 4 +-- theme.toml | 2 +- 37 files changed, 198 insertions(+), 164 deletions(-) (limited to 'helix-core/src') diff --git a/book/src/themes.md b/book/src/themes.md index 5a4d0403..ecbbb6e9 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -103,8 +103,6 @@ We use a similar set of scopes as [SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also [TextMate](https://macromates.com/manual/en/language_grammars) scopes. -- `escape` (TODO: rename to (constant).character.escape) - - `type` - Types - `builtin` - Primitive types provided by the language (`int`, `usize`) @@ -112,8 +110,11 @@ We use a similar set of scopes as - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc) - `boolean` - `character` + - `escape` + - `numeric` (numbers) + - `integer` + - `float` -- `number` (TODO: rename to constant.number/.numeric.{integer, float, complex}) - `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)? - `regexp` - Regular expressions - `special` @@ -129,7 +130,8 @@ We use a similar set of scopes as - `variable` - Variables - `builtin` - Reserved language variables (`self`, `this`, `super`, etc) - `parameter` - Function parameters - - `property` + - `other` + - `member` - Fields of composite data types (e.g. structs, unions) - `function` (TODO: ?) - `label` diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index d971464a..96f88ee4 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -35,6 +35,7 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option { line.chars().position(|ch| !ch.is_whitespace()) } +/// Find `.git` root. pub fn find_root(root: Option<&str>) -> Option { let current_dir = std::env::current_dir().expect("unable to determine current directory"); diff --git a/runtime/queries/bash/highlights.scm b/runtime/queries/bash/highlights.scm index 754faeda..57898f27 100644 --- a/runtime/queries/bash/highlights.scm +++ b/runtime/queries/bash/highlights.scm @@ -7,7 +7,7 @@ (command_name) @function -(variable_name) @property +(variable_name) @variable.other.member [ "case" @@ -31,7 +31,7 @@ (function_definition name: (word) @function) -(file_descriptor) @number +(file_descriptor) @constant.numeric.integer [ (command_substitution) diff --git a/runtime/queries/c-sharp/highlights.scm b/runtime/queries/c-sharp/highlights.scm index b76f4e60..6e84ad83 100644 --- a/runtime/queries/c-sharp/highlights.scm +++ b/runtime/queries/c-sharp/highlights.scm @@ -20,16 +20,16 @@ ] @type.builtin ;; Enum -(enum_member_declaration (identifier) @variable.property) +(enum_member_declaration (identifier) @variable.other.member) ;; Literals [ (real_literal) (integer_literal) -] @number +] @constant.numeric.integer +(character_literal) @constant.character [ - (character_literal) (string_literal) (verbatim_string_literal) (interpolated_string_text) @@ -40,8 +40,8 @@ "$@\"" ] @string +(boolean_literal) @constant.builtin.boolean [ - (boolean_literal) (null_literal) (void_keyword) ] @constant.builtin @@ -98,7 +98,7 @@ ;; Keywords (modifier) @keyword (this_expression) @keyword -(escape_sequence) @keyword +(escape_sequence) @constant.character.escape [ "as" diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 2c42710f..918f3f66 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -60,8 +60,8 @@ (system_lib_string) @string (null) @constant -(number_literal) @number -(char_literal) @string +(number_literal) @constant.numeric.integer +(char_literal) @constant.character (call_expression function: (identifier) @function) @@ -73,7 +73,7 @@ (preproc_function_def name: (identifier) @function.special) -(field_identifier) @property +(field_identifier) @variable.other.member (statement_identifier) @label (type_identifier) @type (primitive_type) @type diff --git a/runtime/queries/css/highlights.scm b/runtime/queries/css/highlights.scm index 763661af..4dfc0c66 100644 --- a/runtime/queries/css/highlights.scm +++ b/runtime/queries/css/highlights.scm @@ -26,11 +26,11 @@ (pseudo_element_selector (tag_name) @attribute) (pseudo_class_selector (class_name) @attribute) -(class_name) @property -(id_name) @property -(namespace_name) @property -(property_name) @property -(feature_name) @property +(class_name) @variable.other.member +(id_name) @variable.other.member +(namespace_name) @variable.other.member +(property_name) @variable.other.member +(feature_name) @variable.other.member (attribute_name) @attribute @@ -55,8 +55,8 @@ (string_value) @string (color_value) @string.special -(integer_value) @number -(float_value) @number +(integer_value) @constant.numeric.integer +(float_value) @constant.numeric.float (unit) @type "#" @punctuation.delimiter diff --git a/runtime/queries/elixir/highlights.scm b/runtime/queries/elixir/highlights.scm index b7b0cab6..76fd2af9 100644 --- a/runtime/queries/elixir/highlights.scm +++ b/runtime/queries/elixir/highlights.scm @@ -39,13 +39,13 @@ ; * module attribute (unary_operator - operator: "@" @variable.property + operator: "@" @variable.other.member operand: [ - (identifier) @variable.property + (identifier) @variable.other.member (call - target: (identifier) @variable.property) - (boolean) @variable.property - (nil) @variable.property + target: (identifier) @variable.other.member) + (boolean) @variable.other.member + (nil) @variable.other.member ]) ; * capture operator @@ -79,11 +79,8 @@ (nil) @constant.builtin (boolean) @constant.builtin.boolean - -[ - (integer) - (float) -] @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (alias) @type @@ -97,7 +94,7 @@ (interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded -(escape_sequence) @escape +(escape_sequence) @constant.character.escape [ (atom) diff --git a/runtime/queries/go/highlights.scm b/runtime/queries/go/highlights.scm index 3129c4b2..56384d4d 100644 --- a/runtime/queries/go/highlights.scm +++ b/runtime/queries/go/highlights.scm @@ -25,7 +25,7 @@ (variadic_parameter_declaration (identifier) @variable.parameter) (type_identifier) @type -(field_identifier) @property +(field_identifier) @variable.other.member (identifier) @variable (package_identifier) @variable @@ -130,13 +130,13 @@ (rune_literal) ] @string -(escape_sequence) @escape +(escape_sequence) @constant.character.escape [ (int_literal) (float_literal) (imaginary_literal) -] @number +] @constant.numeric.integer [ (true) diff --git a/runtime/queries/haskell/highlights.scm b/runtime/queries/haskell/highlights.scm index dada80b6..72187876 100644 --- a/runtime/queries/haskell/highlights.scm +++ b/runtime/queries/haskell/highlights.scm @@ -13,9 +13,9 @@ (constraint class: (class_name (type)) @class) (class (class_head class: (class_name (type)) @class)) (instance (instance_head class: (class_name (type)) @class)) -(integer) @number -(exp_literal (float)) @number -(char) @literal +(integer) @constant.numeric.integer +(exp_literal (float)) @constant.numeric.float +(char) @constant.character (con_unit) @literal (con_list) @literal (tycon_arrow) @operator diff --git a/runtime/queries/java/highlights.scm b/runtime/queries/java/highlights.scm index e7d793df..77902fce 100644 --- a/runtime/queries/java/highlights.scm +++ b/runtime/queries/java/highlights.scm @@ -59,14 +59,15 @@ (hex_integer_literal) (decimal_integer_literal) (octal_integer_literal) +] @constant.numeric.integer + +[ (decimal_floating_point_literal) (hex_floating_point_literal) -] @number +] @constant.numeric.float -[ - (character_literal) - (string_literal) -] @string +(character_literal) @constant.character +(string_literal) @string [ (true) diff --git a/runtime/queries/javascript/highlights.scm b/runtime/queries/javascript/highlights.scm index e29829bf..6163b680 100644 --- a/runtime/queries/javascript/highlights.scm +++ b/runtime/queries/javascript/highlights.scm @@ -65,7 +65,7 @@ ; Properties ;----------- -(property_identifier) @property +(property_identifier) @variable.other.member ; Literals ;--------- @@ -88,7 +88,7 @@ ] @string (regex) @string.regexp -(number) @number +(number) @constant.numeric.integer ; Tokens ;------- diff --git a/runtime/queries/json/highlights.scm b/runtime/queries/json/highlights.scm index b08ea439..6df6c9eb 100644 --- a/runtime/queries/json/highlights.scm +++ b/runtime/queries/json/highlights.scm @@ -1,9 +1,20 @@ +[ + (true) + (false) +] @constant.builtin.boolean +(null) @constant.builtin +(number) @constant.numeric (pair key: (_) @keyword) (string) @string +(escape_sequence) @constant.character.escape +(ERROR) @error -(object - "{" @escape - (_) - "}" @escape) +"," @punctuation.delimiter +[ + "[" + "]" + "{" + "}" +] @punctuation.bracket diff --git a/runtime/queries/julia/highlights.scm b/runtime/queries/julia/highlights.scm index 7b7d426c..7c447985 100644 --- a/runtime/queries/julia/highlights.scm +++ b/runtime/queries/julia/highlights.scm @@ -15,7 +15,7 @@ (field_expression (identifier) - (identifier) @field .) + (identifier) @variable.other.member .) (function_definition name: (identifier) @function) @@ -80,14 +80,14 @@ (struct_definition name: (identifier) @type) -(number) @number +(number) @constant.numeric.integer (range_expression - (identifier) @number - (eq? @number "end")) + (identifier) @constant.numeric.integer + (eq? @constant.numeric.integer "end")) (range_expression (_ - (identifier) @number - (eq? @number "end"))) + (identifier) @constant.numeric.integer + (eq? @constant.numeric.integer "end"))) (coefficient_expression (number) (identifier) @constant.builtin) diff --git a/runtime/queries/ledger/highlights.scm b/runtime/queries/ledger/highlights.scm index 86c609c2..bdf5f2db 100644 --- a/runtime/queries/ledger/highlights.scm +++ b/runtime/queries/ledger/highlights.scm @@ -7,9 +7,9 @@ (date) (interval) (quantity) -] @number +] @constant.numeric.integer -((account) @field) +((account) @variable.other.member) ((commodity) @text.literal) "include" @include diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm index 40c2be70..e73b32d6 100644 --- a/runtime/queries/lua/highlights.scm +++ b/runtime/queries/lua/highlights.scm @@ -150,14 +150,14 @@ (table ["{" "}"] @constructor) (comment) @comment (string) @string -(number) @number +(number) @constant.numeric.integer (label_statement) @label ; A bit of a tricky one, this will only match field names -(field . (identifier) @property (_)) +(field . (identifier) @variable.other.member (_)) (shebang) @comment ;; Property -(property_identifier) @property +(property_identifier) @variable.other.member ;; Variable (identifier) @variable diff --git a/runtime/queries/nix/highlights.scm b/runtime/queries/nix/highlights.scm index 741b73b5..66719e87 100644 --- a/runtime/queries/nix/highlights.scm +++ b/runtime/queries/nix/highlights.scm @@ -33,16 +33,14 @@ (uri) @string.special.uri -[ - (integer) - (float) -] @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (interpolation "${" @punctuation.special "}" @punctuation.special) @embedded -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (function universal: (identifier) @variable.parameter @@ -66,8 +64,8 @@ (binary operator: _ @operator) -(attr_identifier) @property -(inherit attrs: (attrs_inherited (identifier) @property) ) +(attr_identifier) @variable.other.member +(inherit attrs: (attrs_inherited (identifier) @variable.other.member) ) [ ";" diff --git a/runtime/queries/ocaml/highlights.scm b/runtime/queries/ocaml/highlights.scm index 160f2cb4..15f46cc1 100644 --- a/runtime/queries/ocaml/highlights.scm +++ b/runtime/queries/ocaml/highlights.scm @@ -51,14 +51,14 @@ ; Properties ;----------- -[(label_name) (field_name) (instance_variable_name)] @property +[(label_name) (field_name) (instance_variable_name)] @variable.other.member ; Constants ;---------- [(boolean) (unit)] @constant -[(number) (signed_number)] @number +[(number) (signed_number)] @constant.numeric.integer (character) @constant.character @@ -66,7 +66,7 @@ (quoted_string "{" @string "}" @string) @string -(escape_sequence) @string.escape +(escape_sequence) @constant.character.escape [ (conversion_specification) @@ -145,7 +145,7 @@ ; Attributes ;----------- -(attribute_id) @property +(attribute_id) @variable.other.member ; Comments ;--------- diff --git a/runtime/queries/php/highlights.scm b/runtime/queries/php/highlights.scm index 02904555..46b5d26c 100644 --- a/runtime/queries/php/highlights.scm +++ b/runtime/queries/php/highlights.scm @@ -30,12 +30,12 @@ ; Member (property_element - (variable_name) @property) + (variable_name) @variable.other.member) (member_access_expression - name: (variable_name (name)) @property) + name: (variable_name (name)) @variable.other.member) (member_access_expression - name: (name) @property) + name: (name) @variable.other.member) ; Variables @@ -56,10 +56,10 @@ (string) @string (heredoc) @string -(boolean) @constant.builtin +(boolean) @constant.builtin.boolean (null) @constant.builtin -(integer) @number -(float) @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (comment) @comment "$" @operator diff --git a/runtime/queries/protobuf/highlights.scm b/runtime/queries/protobuf/highlights.scm index cd021be1..c35c430e 100644 --- a/runtime/queries/protobuf/highlights.scm +++ b/runtime/queries/protobuf/highlights.scm @@ -34,16 +34,14 @@ [ (fieldName) (optionName) -] @property +] @variable.other.member (enumVariantName) @type.enum.variant (fullIdent) @namespace -[ - (intLit) - (floatLit) -] @number -(boolLit) @constant.builtin +(intLit) @constant.numeric.integer +(floatLit) @constant.numeric.float +(boolLit) @constant.builtin.boolean (strLit) @string (constant) @constant diff --git a/runtime/queries/python/highlights.scm b/runtime/queries/python/highlights.scm index f64fecb2..9131acc5 100644 --- a/runtime/queries/python/highlights.scm +++ b/runtime/queries/python/highlights.scm @@ -29,7 +29,7 @@ name: (identifier) @function) (identifier) @variable -(attribute attribute: (identifier) @property) +(attribute attribute: (identifier) @variable.other.member) (type (identifier) @type) ; Literals @@ -40,14 +40,11 @@ (false) ] @constant.builtin -[ - (integer) - (float) -] @number - +(integer) @constant.numeric.integer +(float) @constant.numeric.float (comment) @comment (string) @string -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (interpolation "{" @punctuation.special diff --git a/runtime/queries/ruby/highlights.scm b/runtime/queries/ruby/highlights.scm index 8617d6f0..898f8f79 100644 --- a/runtime/queries/ruby/highlights.scm +++ b/runtime/queries/ruby/highlights.scm @@ -55,7 +55,7 @@ [ (class_variable) (instance_variable) -] @property +] @variable.other.member ((identifier) @constant.builtin (#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$")) @@ -101,12 +101,12 @@ ] @string.special.symbol (regex) @string.regexp -(escape_sequence) @escape +(escape_sequence) @constant.character.escape [ (integer) (float) -] @number +] @constant.numeric.integer [ (nil) diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm index 956a5dac..539d9550 100644 --- a/runtime/queries/rust/highlights.scm +++ b/runtime/queries/rust/highlights.scm @@ -15,15 +15,13 @@ ; Primitives ; --- -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (primitive_type) @type.builtin (boolean_literal) @constant.builtin.boolean +(integer_literal) @constant.numeric.integer +(float_literal) @constant.numeric.float +(char_literal) @constant.character [ - (integer_literal) - (float_literal) -] @number -[ - (char_literal) (string_literal) (raw_string_literal) ] @string @@ -40,10 +38,10 @@ (enum_variant (identifier) @type.enum.variant) (field_initializer - (field_identifier) @property) + (field_identifier) @variable.other.member) (shorthand_field_initializer - (identifier) @variable.property) -(shorthand_field_identifier) @variable.property + (identifier) @variable.other.member) +(shorthand_field_identifier) @variable.other.member (lifetime "'" @label @@ -81,9 +79,24 @@ ] @punctuation.bracket) ; --- -; Parameters +; Variables ; --- +(let_declaration + pattern: [ + ((identifier) @variable) + ((tuple_pattern + (identifier) @variable)) + ]) + +; It needs to be anonymous to not conflict with `call_expression` further below. +(_ + value: (field_expression + value: (identifier)? @variable + field: (field_identifier) @variable.other.member)) + +(arguments + (identifier) @variable.parameter) (parameter pattern: (identifier) @variable.parameter) (closure_parameters @@ -336,4 +349,4 @@ (type_identifier) @type (identifier) @variable -(field_identifier) @property +(field_identifier) @variable.other.member diff --git a/runtime/queries/svelte/highlights.scm b/runtime/queries/svelte/highlights.scm index 4c6f5f35..4fcdfd66 100644 --- a/runtime/queries/svelte/highlights.scm +++ b/runtime/queries/svelte/highlights.scm @@ -29,7 +29,7 @@ (#match? @_attr "^(href|src)$")) (tag_name) @tag -(attribute_name) @property +(attribute_name) @variable.other.member (erroneous_end_tag_name) @error (comment) @comment diff --git a/runtime/queries/toml/highlights.scm b/runtime/queries/toml/highlights.scm index e4d6966f..2742b2be 100644 --- a/runtime/queries/toml/highlights.scm +++ b/runtime/queries/toml/highlights.scm @@ -1,17 +1,17 @@ ; Properties ;----------- -(bare_key) @property +(bare_key) @variable.other.member (quoted_key) @string ; Literals ;--------- -(boolean) @constant.builtin +(boolean) @constant.builtin.boolean (comment) @comment (string) @string -(integer) @number -(float) @number +(integer) @constant.numeric.integer +(float) @constant.numeric.float (offset_date_time) @string.special (local_date_time) @string.special (local_date) @string.special diff --git a/runtime/queries/tsq/highlights.scm b/runtime/queries/tsq/highlights.scm index 9ba5699a..549895c1 100644 --- a/runtime/queries/tsq/highlights.scm +++ b/runtime/queries/tsq/highlights.scm @@ -35,12 +35,12 @@ (comment) @comment -(field_name) @property +(field_name) @variable.other.member (capture) @label (predicate_name) @function -(escape_sequence) @escape +(escape_sequence) @constant.character.escape (node_name) @variable diff --git a/runtime/queries/yaml/highlights.scm b/runtime/queries/yaml/highlights.scm index 2955a4ce..a7efb5e7 100644 --- a/runtime/queries/yaml/highlights.scm +++ b/runtime/queries/yaml/highlights.scm @@ -1,12 +1,12 @@ -(block_mapping_pair key: (_) @property) -(flow_mapping (_ key: (_) @property)) +(block_mapping_pair key: (_) @variable.other.member) +(flow_mapping (_ key: (_) @variable.other.member)) (boolean_scalar) @constant.builtin.boolean (null_scalar) @constant.builtin (double_quote_scalar) @string (single_quote_scalar) @string -(escape_sequence) @string.escape -(integer_scalar) @number -(float_scalar) @number +(escape_sequence) @constant.character.escape +(integer_scalar) @constant.numeric.integer +(float_scalar) @constant.numeric.float (comment) @comment (anchor_name) @type (alias_name) @type diff --git a/runtime/queries/zig/highlights.scm b/runtime/queries/zig/highlights.scm index 404a8682..34dbeacd 100644 --- a/runtime/queries/zig/highlights.scm +++ b/runtime/queries/zig/highlights.scm @@ -14,7 +14,7 @@ parameter: (IDENTIFIER) @variable.parameter [ field_member: (IDENTIFIER) field_access: (IDENTIFIER) -] @variable.property +] @variable.other.member ;; assume TitleCase is a type ( @@ -75,9 +75,9 @@ field_constant: (IDENTIFIER) @constant ((BUILTINIDENTIFIER) @keyword.control.import (#any-of? @keyword.control.import "@import" "@cImport")) -(INTEGER) @number +(INTEGER) @constant.numeric.integer -(FLOAT) @number +(FLOAT) @constant.numeric.float [ (LINESTRING) @@ -85,7 +85,7 @@ field_constant: (IDENTIFIER) @constant ] @string (CHAR_LITERAL) @constant.character -(EscapeSequence) @escape +(EscapeSequence) @constant.character.escape (FormatSequence) @string.special [ diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml index a5dc0852..d65995c0 100644 --- a/runtime/themes/base16_default_dark.toml +++ b/runtime/themes/base16_default_dark.toml @@ -16,14 +16,14 @@ "operator" = "base05" "ui.text.focus" = { fg = "base05" } "variable" = "base08" -"number" = "base09" +"constant.numeric" = "base09" "constant" = "base09" "attributes" = "base09" "type" = "base0A" "ui.cursor.match" = { fg = "base0A", modifiers = ["underlined"] } "strings" = "base0B" -"property" = "base0B" -"escape" = "base0C" +"variable.other.member" = "base0B" +"constant.character.escape" = "base0C" "function" = "base0D" "constructor" = "base0D" "special" = "base0D" diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml index 37b9adbf..86a6c34b 100644 --- a/runtime/themes/bogster.toml +++ b/runtime/themes/bogster.toml @@ -8,7 +8,7 @@ "punctuation.delimiter" = "#dc7759" "operator" = { fg = "#dc7759", modifiers = ["bold"] } "special" = "#7fdc59" -"property" = "#c6b8ad" +"variable.other.member" = "#c6b8ad" "variable" = "#c6b8ad" "variable.parameter" = "#c6b8ad" "type" = "#dc597f" @@ -22,8 +22,8 @@ "constant" = "#59dcb7" "constant.builtin" = "#59dcb7" "string" = "#59dcb7" -"number" = "#59c0dc" -"escape" = { fg = "#7fdc59", modifiers = ["bold"] } +"constant.numeric" = "#59c0dc" +"constant.character.escape" = { fg = "#7fdc59", modifiers = ["bold"] } "label" = "#59c0dc" "module" = "#d32c5d" diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index c48a7e28..0554f827 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -7,7 +7,7 @@ "type.builtin" = { fg = "type" } "type.enum.variant" = { fg = "constant" } "constructor" = { fg = "constant" } -"property" = { fg = "variable" } +"variable.other.member" = { fg = "variable" } "keyword" = { fg = "keyword" } "keyword.directive" = { fg = "keyword" } @@ -31,47 +31,64 @@ "function.macro" = { fg = "keyword" } "attribute" = { fg = "fn_declaration" } -"comment" = { fg = "#6A9955" } +"comment" = { fg = "dark_green" } -"string" = { fg = "#ce9178" } -"string.regexp" = { fg = "regex" } -"number" = { fg = "#b5cea8" } -"escape" = { fg = "#d7ba7d" } +"string" = { fg = "orange" } +"constant.character" = { fg = "orange" } +"string.regexp" = { fg = "gold" } +"constant.numeric" = { fg = "pale_green" } +"constant.character.escape" = { fg = "gold" } -"ui.background" = { fg = "#d4d4d4", bg = "#1e1e1e" } +"ui.background" = { fg = "light_gray", bg = "dark_gray2" } "ui.window" = { bg = "widget" } "ui.popup" = { bg = "widget" } "ui.help" = { bg = "widget" } "ui.menu.selected" = { bg = "widget" } +# TODO: Alternate bg colour for `ui.cursor.match` and `ui.selection`. "ui.cursor" = { fg = "cursor", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] } "ui.cursor.match" = { bg = "#3a3d41", modifiers = ["underlined"] } "ui.selection" = { bg = "#3a3d41" } -"ui.selection.primary" = { bg = "#264f78" } +"ui.selection.primary" = { bg = "dark_blue" } -"ui.linenr" = { fg = "#858585" } -"ui.linenr.selected" = { fg = "#c6c6c6" } +"ui.linenr" = { fg = "dark_gray" } +"ui.linenr.selected" = { fg = "light_gray2" } -"ui.statusline" = { fg = "#ffffff", bg = "#007acc" } -"ui.statusline.inactive" = { fg = "#ffffff", bg = "#007acc" } +"ui.statusline" = { fg = "white", bg = "blue" } +"ui.statusline.inactive" = { fg = "white", bg = "blue" } "ui.text" = { fg = "text", bg = "background" } -"ui.text.focus" = { fg = "#ffffff" } +"ui.text.focus" = { fg = "white" } -"warning" = { fg = "#cca700" } -"error" = { fg = "#ff1212" } -"info" = { fg = "#75beff" } -"hint" = { fg = "#eeeeeeb3" } +"warning" = { fg = "gold2" } +"error" = { fg = "red" } +"info" = { fg = "light_blue" } +"hint" = { fg = "light_gray3" } diagnostic = { modifiers = ["underlined"] } [palette] +white = "#ffffff" +orange = "#ce9178" +gold = "#d7ba7d" +gold2 = "#cca700" +pale_green = "#b5cea8" +dark_green = "#6A9955" +light_gray = "#d4d4d4" +light_gray2 = "#c6c6c6" +light_gray3 = "#eeeeee" +dark_gray = "#858585" +dark_gray2 = "#1e1e1e" +blue = "#007acc" +light_blue = "#75beff" +dark_blue = "#264f78" +red = "#ff1212" + type = "#4EC9B0" keyword = "#569CD6" -regex = "#CE9178" special = "#C586C0" variable = "#9CDCFE" fn_declaration = "#DCDCAA" diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml index 462c8265..bbd005e6 100644 --- a/runtime/themes/everforest_dark.toml +++ b/runtime/themes/everforest_dark.toml @@ -8,16 +8,16 @@ # Email: sainnhe@gmail.com # License: MIT License -"escape" = "orange" +"constant.character.escape" = "orange" "type" = "yellow" "constant" = "purple" -"number" = "purple" +"constant.numeric" = "purple" "string" = "grey2" "comment" = "grey0" "variable" = "fg" "variable.builtin" = "blue" "variable.parameter" = "fg" -"variable.property" = "fg" +"variable.other.member" = "fg" "label" = "aqua" "punctuation" = "grey2" "punctuation.delimiter" = "grey2" @@ -32,7 +32,6 @@ "attribute" = "aqua" "constructor" = "yellow" "module" = "blue" -"property" = "fg" "special" = "orange" "ui.background" = { bg = "bg0" } diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml index 0a6eec07..0ff039ea 100644 --- a/runtime/themes/gruvbox.toml +++ b/runtime/themes/gruvbox.toml @@ -9,8 +9,7 @@ "punctuation.delimiter" = "orange1" "operator" = "purple1" "special" = "purple0" -"property" = "blue1" -"variable.property" = "blue1" +"variable.other.member" = "blue1" "variable" = "fg1" "variable.builtin" = "orange1" "variable.parameter" = "fg2" @@ -24,8 +23,8 @@ "constant" = { fg = "purple1" } "constant.builtin" = { fg = "purple1", modifiers = ["bold"] } "string" = "green1" -"number" = "purple1" -"escape" = { fg = "fg2", modifiers = ["bold"] } +"constant.numeric" = "purple1" +"constant.character.escape" = { fg = "fg2", modifiers = ["bold"] } "label" = "aqua1" "module" = "aqua1" diff --git a/runtime/themes/ingrid.toml b/runtime/themes/ingrid.toml index 6a177ec7..30829475 100644 --- a/runtime/themes/ingrid.toml +++ b/runtime/themes/ingrid.toml @@ -8,7 +8,7 @@ "punctuation.delimiter" = "#C97270" "operator" = { fg = "#D74E50", modifiers = ["bold"] } "special" = "#D68482" -"property" = "#89BEB7" +"variable.other.member" = "#89BEB7" "variable" = "#A6B6CE" "variable.parameter" = "#89BEB7" "type" = { fg = "#A6B6CE", modifiers = ["bold"] } @@ -22,8 +22,8 @@ "constant" = "#D4A520" "constant.builtin" = "#D4A520" "string" = "#D74E50" -"number" = "#D74E50" -"escape" = { fg = "#D74E50", modifiers = ["bold"] } +"constant.numeric" = "#D74E50" +"constant.character.escape" = { fg = "#D74E50", modifiers = ["bold"] } "label" = "#D68482" "module" = "#839A53" diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml index a8f03ff3..38f9f170 100644 --- a/runtime/themes/monokai.toml +++ b/runtime/themes/monokai.toml @@ -7,7 +7,7 @@ "type.builtin" = { fg = "#66D9EF" } "type.enum.variant" = { fg = "text" } "constructor" = { fg = "text" } -"property" = { fg = "variable" } +"variable.other.member" = { fg = "variable" } "keyword" = { fg = "keyword" } "keyword.directive" = { fg = "keyword" } @@ -34,9 +34,10 @@ "comment" = { fg = "#88846F" } "string" = { fg = "#e6db74" } +"constant.character" = { fg = "#e6db74" } "string.regexp" = { fg = "regex" } -"number" = { fg = "#ae81ff" } -"escape" = { fg = "#ae81ff" } +"constant.numeric" = { fg = "#ae81ff" } +"constant.character.escape" = { fg = "#ae81ff" } "ui.background" = { fg = "text", bg = "background" } diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index 29f3875d..78736c3b 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -59,7 +59,7 @@ # nord9 - operator, tags, units, punctuations "punctuation.delimiter" = "nord9" "operator" = { fg = "nord9" } -"property" = "nord9" +"variable.other.member" = "nord9" # nord10 - keywords, special "keyword" = { fg = "nord10" } @@ -76,13 +76,13 @@ # nord13 - warnings, escape characters, regex "warning" = "nord13" -"escape" = { fg = "nord13" } +"constant.character.escape" = { fg = "nord13" } # nord14 - strings "string" = "nord14" # nord15 - integer, floating point -"number" = "nord15" +"constant.numeric" = "nord15" [palette] nord0 = "#2e3440" diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml index a982fd6e..53777008 100644 --- a/runtime/themes/rose_pine.toml +++ b/runtime/themes/rose_pine.toml @@ -16,14 +16,14 @@ "operator" = "rose" "ui.text.focus" = { fg = "base05" } "variable" = "text" -"number" = "iris" +"constant.numeric" = "iris" "constant" = "gold" "attributes" = "gold" "type" = "foam" "ui.cursor.match" = { fg = "gold", modifiers = ["underlined"] } "string" = "gold" "property" = "foam" -"escape" = "subtle" +"constant.character.escape" = "subtle" "function" = "rose" "function.builtin" = "rose" "function.method" = "foam" diff --git a/theme.toml b/theme.toml index 82b71a7d..3956e25e 100644 --- a/theme.toml +++ b/theme.toml @@ -6,7 +6,7 @@ punctuation = "lavender" "punctuation.delimiter" = "lavender" operator = "lilac" special = "honey" -property = "white" +variable.other.member = "white" variable = "lavender" # variable = "almond" # TODO: metavariables only # "variable.parameter" = { fg = "lavender", modifiers = ["underlined"] } -- cgit v1.2.3-70-g09d2 From 3eb829e2330fed5ad1c095f8bba44f62361b4943 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Wed, 3 Nov 2021 11:02:29 +0800 Subject: Ensure coords in screen depends on char width (#885) The issue affected files with lots of tabs at the start as well. Fix #840--- helix-core/src/lib.rs | 2 +- helix-core/src/movement.rs | 4 ++ helix-core/src/position.rs | 81 +++++++++++++++++++++++++++++++++++++---- helix-term/src/ui/completion.rs | 10 ++--- helix-view/src/view.rs | 8 ++-- 5 files changed, 87 insertions(+), 18 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 96f88ee4..d1720df0 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -194,7 +194,7 @@ pub use tendril::StrTendril as Tendril; pub use {regex, tree_sitter}; pub use graphemes::RopeGraphemes; -pub use position::{coords_at_pos, pos_at_coords, Position}; +pub use position::{coords_at_pos, pos_at_coords, visual_coords_at_pos, Position}; pub use selection::{Range, Selection}; pub use smallvec::SmallVec; pub use syntax::Syntax; diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 5d080545..9e85bd21 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -53,6 +53,10 @@ pub fn move_vertically( let pos = range.cursor(slice); // Compute the current position's 2d coordinates. + // TODO: switch this to use `visual_coords_at_pos` rather than + // `coords_at_pos` as this will cause a jerky movement when the visual + // position does not match, like moving from a line with tabs/CJK to + // a line without let Position { row, col } = coords_at_pos(slice, pos); let horiz = range.horiz.unwrap_or(col as u32); diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index 08a8aed5..c6018ce6 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -2,6 +2,7 @@ use crate::{ chars::char_is_line_ending, graphemes::{ensure_grapheme_boundary_prev, RopeGraphemes}, line_ending::line_end_char_index, + unicode::width::UnicodeWidthChar, RopeSlice, }; @@ -54,11 +55,8 @@ impl From for tree_sitter::Point { } /// Convert a character index to (line, column) coordinates. /// -/// TODO: this should be split into two methods: one for visual -/// row/column, and one for "objective" row/column (possibly with -/// the column specified in `char`s). The former would be used -/// for cursor movement, and the latter would be used for e.g. the -/// row:column display in the status line. +/// column in `char` count which can be used for row:column display in +/// status line. See [`visual_coords_at_pos`] for a visual one. pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { let line = text.char_to_line(pos); @@ -69,6 +67,28 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { Position::new(line, col) } +/// Convert a character index to (line, column) coordinates visually. +/// +/// Takes \t, double-width characters (CJK) into account as well as text +/// not in the document in the future. +/// See [`coords_at_pos`] for an "objective" one. +pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position { + let line = text.char_to_line(pos); + + let line_start = text.line_to_char(line); + let pos = ensure_grapheme_boundary_prev(text, pos); + let col = text + .slice(line_start..pos) + .chars() + .flat_map(|c| match c { + '\t' => Some(tab_width), + c => UnicodeWidthChar::width(c), + }) + .sum(); + + Position::new(line, col) +} + /// Convert (line, column) coordinates to a character index. /// /// If the `line` coordinate is beyond the end of the file, the EOF @@ -130,7 +150,6 @@ mod test { assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d // Test with wide characters. - // TODO: account for character width. let text = Rope::from("今日はいい\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -151,7 +170,6 @@ mod test { assert_eq!(coords_at_pos(slice, 9), (1, 0).into()); // Test with wide-character grapheme clusters. - // TODO: account for character width. let text = Rope::from("किमपि\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -161,7 +179,6 @@ mod test { assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // Test with tabs. - // Todo: account for tab stops. let text = Rope::from("\tHello\n"); let slice = text.slice(..); assert_eq!(coords_at_pos(slice, 0), (0, 0).into()); @@ -169,6 +186,54 @@ mod test { assert_eq!(coords_at_pos(slice, 2), (0, 2).into()); } + #[test] + fn test_visual_coords_at_pos() { + let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); // position on \n + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); // position on w + assert_eq!(visual_coords_at_pos(slice, 7, 8), (1, 1).into()); // position on o + assert_eq!(visual_coords_at_pos(slice, 10, 8), (1, 4).into()); // position on d + + // Test with wide characters. + let text = Rope::from("今日はいい\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 4).into()); + assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 6).into()); + assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 8).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 10).into()); + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); + + // Test with grapheme clusters. + let text = Rope::from("a̐éö̲\r\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 1).into()); + assert_eq!(visual_coords_at_pos(slice, 4, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 7, 8), (0, 3).into()); + assert_eq!(visual_coords_at_pos(slice, 9, 8), (1, 0).into()); + + // Test with wide-character grapheme clusters. + // TODO: account for cluster. + let text = Rope::from("किमपि\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 2).into()); + assert_eq!(visual_coords_at_pos(slice, 3, 8), (0, 3).into()); + assert_eq!(visual_coords_at_pos(slice, 5, 8), (0, 5).into()); + assert_eq!(visual_coords_at_pos(slice, 6, 8), (1, 0).into()); + + // Test with tabs. + let text = Rope::from("\tHello\n"); + let slice = text.slice(..); + assert_eq!(visual_coords_at_pos(slice, 0, 8), (0, 0).into()); + assert_eq!(visual_coords_at_pos(slice, 1, 8), (0, 8).into()); + assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 9).into()); + } + #[test] fn test_pos_at_coords() { let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index dcb2bfd8..dd782d29 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -264,12 +264,10 @@ impl Component for Completion { .language() .and_then(|scope| scope.strip_prefix("source.")) .unwrap_or(""); - let cursor_pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row - - view.offset.row) as u16; + let text = doc.text().slice(..); + let cursor_pos = doc.selection(view.id).primary().cursor(text); + let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width()); + let cursor_pos = (coords.row - view.offset.row) as u16; let mut markdown_doc = match &option.documentation { Some(lsp::Documentation::String(contents)) | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 01f18c71..11f30155 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -2,10 +2,9 @@ use std::borrow::Cow; use crate::{graphics::Rect, Document, DocumentId, ViewId}; use helix_core::{ - coords_at_pos, graphemes::{grapheme_width, RopeGraphemes}, line_ending::line_end_char_index, - Position, RopeSlice, Selection, + visual_coords_at_pos, Position, RopeSlice, Selection, }; type Jump = (DocumentId, Selection); @@ -91,7 +90,10 @@ impl View { .selection(self.id) .primary() .cursor(doc.text().slice(..)); - let Position { col, row: line } = coords_at_pos(doc.text().slice(..), cursor); + + let Position { col, row: line } = + visual_coords_at_pos(doc.text().slice(..), cursor, doc.tab_width()); + let inner_area = self.inner_area(); let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1); -- cgit v1.2.3-70-g09d2 From 6431b26a6a5fa4be5b91008f21537721d2ff4ba2 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Sat, 6 Nov 2021 17:37:45 +0900 Subject: Implement Selection::replace to replace a single range Fixes #985 Co-authored-by: Daniel S Poulin --- helix-core/src/selection.rs | 11 +++++++++++ helix-term/src/commands.rs | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'helix-core/src') diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 18af4d08..f3b5d2c8 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -362,6 +362,11 @@ impl Selection { /// Adds a new range to the selection and makes it the primary range. pub fn remove(mut self, index: usize) -> Self { + assert!( + self.ranges.len() > 1, + "can't remove the last range from a selection!" + ); + self.ranges.remove(index); if index < self.primary_index || self.primary_index == self.ranges.len() { self.primary_index -= 1; @@ -369,6 +374,12 @@ impl Selection { self } + /// Replace a range in the selection with a new range. + pub fn replace(mut self, index: usize, range: Range) -> Self { + self.ranges[index] = range; + self.normalize() + } + /// Map selections over a set of changes. Useful for adjusting the selection position after /// applying changes to a document. pub fn map(self, changes: &ChangeSet) -> Self { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c8f64531..e3ebd128 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1227,8 +1227,7 @@ fn search_impl( Movement::Extend => selection.clone().push(Range::new(start, end)), Movement::Move => selection .clone() - .remove(selection.primary_index()) - .push(Range::new(start, end)), + .replace(selection.primary_index(), Range::new(start, end)), }; doc.set_selection(view.id, selection); -- cgit v1.2.3-70-g09d2 From e80708eba7e9959fa720e2563231bba542570295 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Sat, 6 Nov 2021 18:58:40 +0900 Subject: Make sure document diagnostics are sorted --- helix-core/src/diagnostic.rs | 2 +- helix-view/src/document.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'helix-core/src') diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index ab47e075..ad1ba16a 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -10,7 +10,7 @@ pub enum Severity { } /// A range of `char`s within the text. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] pub struct Range { pub start: usize, pub end: usize, diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 0d86143b..ce5df8ee 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -918,6 +918,9 @@ impl Document { pub fn set_diagnostics(&mut self, diagnostics: Vec) { self.diagnostics = diagnostics; + // sort by range + self.diagnostics + .sort_unstable_by_key(|diagnostic| diagnostic.range); } } -- cgit v1.2.3-70-g09d2 From f979bdc442ab3150a369ff8bee0703e90e32e2a4 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Sat, 6 Nov 2021 23:57:42 +0900 Subject: Specify capacity on toggle_line_comments --- helix-core/src/comment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-core/src') diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index 4072a532..b22a95a6 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -63,7 +63,7 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st let token = token.unwrap_or("//"); let comment = Tendril::from(format!("{} ", token)); - let mut lines: Vec = Vec::new(); + let mut lines: Vec = Vec::with_capacity(selection.len()); let mut min_next_line = 0; for selection in selection { -- cgit v1.2.3-70-g09d2