From b2251870dabd343fe78299b02182e1af0f33c77a Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Wed, 1 Feb 2023 22:17:59 +0100 Subject: [PATCH 001/191] Add ayu_evolve theme (#5638) * Add ayu_evolve theme * ayu_evolve: fix typo + raw markdown highlight * Update runtime/themes/ayu_evolve.toml typo Co-authored-by: Michael Davis --------- Co-authored-by: Michael Davis --- runtime/themes/ayu_evolve.toml | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 runtime/themes/ayu_evolve.toml diff --git a/runtime/themes/ayu_evolve.toml b/runtime/themes/ayu_evolve.toml new file mode 100644 index 000000000000..b8abedec3503 --- /dev/null +++ b/runtime/themes/ayu_evolve.toml @@ -0,0 +1,36 @@ +inherits = 'ayu_dark' + +"keyword.control" = "orange" +"keyword.storage" = "yellow" +"keyword.storage.modifier" = "magenta" +"variable.other.member" = "gray" +"variable" = "light_gray" +"constructor" = "magenta" +"type.builtin" = { fg = "blue", modifiers = ["italic"] } + +# Gutters and editing area +"error" = "red" +"diagnostic.error" = { underline = { color = "red", style = "curl" } } +"warning" = "vibrant_orange" +"diagnostic.warning" = { underline = { color = "vibrant_orange", style = "curl" } } +"hint" = "vibrant_yellow" +"diagnostic.hint" = { underline = { color = "vibrant_yellow", style = "curl" } } +"info" = "white" +"diagnostic.info" = { underline = { color = "white", style = "curl" } } + +"markup.raw.block" = { bg = "black" } +"markup.raw.inline" = { bg = "black" } + +"ui.cursor" = { fg = "dark_gray", bg = "light_gray" } +"ui.cursor.primary" = { fg = "dark_gray", bg = "orange" } +"ui.cursor.primary.select" = { fg = "dark_gray", bg = "magenta" } +"ui.cursor.primary.insert" = { fg = "dark_gray", bg = "green" } +"ui.text.inactive" = "gray" + +[palette] +background = '#020202' +black = "#0D0D0D" +light_gray = "#dedede" +red = "#DD3E25" +vibrant_yellow = "#CFCA0D" +vibrant_orange = "#FF8732" From d5f17d3f6947abbfd0684246a440751355e48f9d Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 1 Feb 2023 15:28:56 -0600 Subject: [PATCH 002/191] Fix initial highlight layer sort order (#5196) The purpose of this change is to remove the mutable self borrow on `HighlightIterLayer::sort_key` so that we can sort layers with the correct ordering using the `Vec::sort` function family. `HighlightIterLayer::sort_key` needs `&mut self` since it calls `Peekable::peek` which needs `&mut self`. `Vec::sort` functions only give immutable borrows of the elements to ensure the correctness of the sort. We could instead approach this by creating an eager Peekable and using that instead of `std::iter::Peekable` to wrap `QueryCaptures`: ```rust struct EagerPeekable { iter: I, peeked: Option, } impl EagerPeekable { fn new(mut iter: I) -> Self { let peeked = iter.next(); Self { iter, peeked } } fn peek(&self) -> Option<&I::Item> { self.peeked.as_ref() } } impl Iterator for EagerPeekable { type Item = I::Item; fn next(&mut self) -> Option { std::mem::replace(&mut self.peeked, self.iter.next()) } } ``` This would be a cleaner approach (notice how `EagerPeekable::peek` takes `&self` rather than `&mut self`), however this doesn't work in practice because the Items emitted by the `tree_sitter::QueryCaptures` Iterator must be consumed before the next Item is returned. `Iterator::next` on `tree_sitter::QueryCaptures` modifies the `QueryMatch` returned by the last call of `next`. This behavior is not currently reflected in the lifetimes/structure of `QueryCaptures`. This fixes an issue with layers being out of order when using combined injections since the old code only checked the first range in the layer. Layers being out of order could cause missing highlights for combined-injections content. --- helix-core/src/syntax.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 41ab23e1343a..ca4da3dcd56d 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1092,21 +1092,14 @@ impl Syntax { }], cursor, _tree: None, - captures, + captures: RefCell::new(captures), config: layer.config.as_ref(), // TODO: just reuse `layer` depth: layer.depth, // TODO: just reuse `layer` - ranges: &layer.ranges, // TODO: temp }) }) .collect::>(); - // HAXX: arrange layers by byte range, with deeper layers positioned first - layers.sort_by_key(|layer| { - ( - layer.ranges.first().cloned(), - std::cmp::Reverse(layer.depth), - ) - }); + layers.sort_unstable_by_key(|layer| layer.sort_key()); let mut result = HighlightIter { source, @@ -1424,12 +1417,11 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> { struct HighlightIterLayer<'a> { _tree: Option, cursor: QueryCursor, - captures: iter::Peekable>>, + captures: RefCell>>>, config: &'a HighlightConfiguration, highlight_end_stack: Vec, scope_stack: Vec>, depth: u32, - ranges: &'a [Range], } impl<'a> fmt::Debug for HighlightIterLayer<'a> { @@ -1610,10 +1602,11 @@ impl<'a> HighlightIterLayer<'a> { // First, sort scope boundaries by their byte offset in the document. At a // given position, emit scope endings before scope beginnings. Finally, emit // scope boundaries from deeper layers first. - fn sort_key(&mut self) -> Option<(usize, bool, isize)> { + fn sort_key(&self) -> Option<(usize, bool, isize)> { let depth = -(self.depth as isize); let next_start = self .captures + .borrow_mut() .peek() .map(|(m, i)| m.captures[*i].node.start_byte()); let next_end = self.highlight_end_stack.last().cloned(); @@ -1838,7 +1831,8 @@ impl<'a> Iterator for HighlightIter<'a> { // Get the next capture from whichever layer has the earliest highlight boundary. let range; let layer = &mut self.layers[0]; - if let Some((next_match, capture_index)) = layer.captures.peek() { + let captures = layer.captures.get_mut(); + if let Some((next_match, capture_index)) = captures.peek() { let next_capture = next_match.captures[*capture_index]; range = next_capture.node.byte_range(); @@ -1861,7 +1855,7 @@ impl<'a> Iterator for HighlightIter<'a> { return self.emit_event(self.source.len_bytes(), None); }; - let (mut match_, capture_index) = layer.captures.next().unwrap(); + let (mut match_, capture_index) = captures.next().unwrap(); let mut capture = match_.captures[capture_index]; // Remove from the local scope stack any local scopes that have already ended. @@ -1937,11 +1931,11 @@ impl<'a> Iterator for HighlightIter<'a> { } // Continue processing any additional matches for the same node. - if let Some((next_match, next_capture_index)) = layer.captures.peek() { + if let Some((next_match, next_capture_index)) = captures.peek() { let next_capture = next_match.captures[*next_capture_index]; if next_capture.node == capture.node { capture = next_capture; - match_ = layer.captures.next().unwrap().0; + match_ = captures.next().unwrap().0; continue; } } @@ -1964,11 +1958,11 @@ impl<'a> Iterator for HighlightIter<'a> { // highlighting patterns that are disabled for local variables. if definition_highlight.is_some() || reference_highlight.is_some() { while layer.config.non_local_variable_patterns[match_.pattern_index] { - if let Some((next_match, next_capture_index)) = layer.captures.peek() { + if let Some((next_match, next_capture_index)) = captures.peek() { let next_capture = next_match.captures[*next_capture_index]; if next_capture.node == capture.node { capture = next_capture; - match_ = layer.captures.next().unwrap().0; + match_ = captures.next().unwrap().0; continue; } } @@ -1983,10 +1977,10 @@ impl<'a> Iterator for HighlightIter<'a> { // for a given node are ordered by pattern index, so these subsequent // captures are guaranteed to be for highlighting, not injections or // local variables. - while let Some((next_match, next_capture_index)) = layer.captures.peek() { + while let Some((next_match, next_capture_index)) = captures.peek() { let next_capture = next_match.captures[*next_capture_index]; if next_capture.node == capture.node { - layer.captures.next(); + captures.next(); } else { break; } From 685cd383a3a56755b06b4146d5dc14872890a56e Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 1 Feb 2023 15:29:48 -0600 Subject: [PATCH 003/191] Surround with line-endings on `ms` (#4571) This change makes `ms` work similarly to `t` and related find commands: when the next event is a keypress of Enter, surround the selection with the document's line-endings. --- helix-term/src/commands.rs | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1cbdd0fb28ed..2ee3f6b9ee4a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4775,35 +4775,39 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { fn surround_add(cx: &mut Context) { cx.on_next_key(move |cx, event| { - let ch = match event.char() { - Some(ch) => ch, + let (view, doc) = current!(cx.editor); + // surround_len is the number of new characters being added. + let (open, close, surround_len) = match event.char() { + Some(ch) => { + let (o, c) = surround::get_pair(ch); + let mut open = Tendril::new(); + open.push(o); + let mut close = Tendril::new(); + close.push(c); + (open, close, 2) + } + None if event.code == KeyCode::Enter => ( + doc.line_ending.as_str().into(), + doc.line_ending.as_str().into(), + 2 * doc.line_ending.len_chars(), + ), None => return, }; - let (view, doc) = current!(cx.editor); - let selection = doc.selection(view.id); - let (open, close) = surround::get_pair(ch); - // The number of chars in get_pair - let surround_len = 2; + let selection = doc.selection(view.id); let mut changes = Vec::with_capacity(selection.len() * 2); let mut ranges = SmallVec::with_capacity(selection.len()); let mut offs = 0; for range in selection.iter() { - let mut o = Tendril::new(); - o.push(open); - let mut c = Tendril::new(); - c.push(close); - changes.push((range.from(), range.from(), Some(o))); - changes.push((range.to(), range.to(), Some(c))); - - // Add 2 characters to the range to select them + changes.push((range.from(), range.from(), Some(open.clone()))); + changes.push((range.to(), range.to(), Some(close.clone()))); + ranges.push( Range::new(offs + range.from(), offs + range.to() + surround_len) .with_direction(range.direction()), ); - // Add 2 characters to the offset for the next ranges offs += surround_len; } From 62d046fa219b927c536bf6726fcae1e825346e0e Mon Sep 17 00:00:00 2001 From: Mike Trinkala Date: Wed, 1 Feb 2023 14:07:42 -0800 Subject: [PATCH 004/191] Fix utf8 length handling for shellwords (#5738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the last argument to shellwords ends in a multibyte utf8 character the entire argument will be dropped. e.g. `:sh echo test1 test2π’€€` will only output `test1` Add additional tests based on the code review feedback --- helix-core/src/shellwords.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/helix-core/src/shellwords.rs b/helix-core/src/shellwords.rs index 9475f5e50cea..0883eb9172ae 100644 --- a/helix-core/src/shellwords.rs +++ b/helix-core/src/shellwords.rs @@ -129,8 +129,9 @@ impl<'a> From<&'a str> for Shellwords<'a> { DquoteEscaped => Dquoted, }; - if i >= input.len() - 1 && end == 0 { - end = i + 1; + let c_len = c.len_utf8(); + if i == input.len() - c_len && end == 0 { + end = i + c_len; } if end > 0 { @@ -333,4 +334,17 @@ mod test { assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]); assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\"]); } + + #[test] + fn test_multibyte_at_end() { + assert_eq!(Shellwords::from("π’€€").parts(), &["π’€€"]); + assert_eq!( + Shellwords::from(":sh echo π’€€").parts(), + &[":sh", "echo", "π’€€"] + ); + assert_eq!( + Shellwords::from(":sh echo π’€€ hello worldπ’€€").parts(), + &[":sh", "echo", "π’€€", "hello", "worldπ’€€"] + ); + } } From 6ed2348078a331bc2039a313bd7ad9f0bb1a00c2 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Feb 2023 18:14:02 +0100 Subject: [PATCH 005/191] Hide duplicate symlinks from the picker (#5658) * hide duplicate symlinks from the picker * Apply suggestions from code review Co-authored-by: g-re-g <123515925+g-re-g@users.noreply.github.com> * minor stylistic fix Co-authored-by: Michael Davis --------- Co-authored-by: g-re-g <123515925+g-re-g@users.noreply.github.com> Co-authored-by: Michael Davis --- book/src/configuration.md | 2 ++ helix-term/src/commands.rs | 13 +++++++++---- helix-term/src/lib.rs | 25 +++++++++++++++++++++++++ helix-term/src/ui/mod.rs | 18 ++++++++---------- helix-view/src/editor.rs | 3 +++ 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 528fafd048fa..87585ece0f9e 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -150,6 +150,8 @@ All git related options are only enabled in a git repository. | Key | Description | Default | |--|--|---------| |`hidden` | Enables ignoring hidden files. | true +|`follow-links` | Follow symlinks instead of ignoring them | true +|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true |`parents` | Enables reading ignore files from parent directories. | true |`ignore` | Enables reading `.ignore` files. | true |`git-ignore` | Enables reading `.gitignore` files. | true diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2ee3f6b9ee4a..4f27fc801f46 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -49,6 +49,7 @@ use movement::Movement; use crate::{ args, compositor::{self, Component, Compositor}, + filter_picker_entry, job::Callback, keymap::ReverseKeymap, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, @@ -2013,6 +2014,11 @@ fn global_search(cx: &mut Context) { let search_root = std::env::current_dir() .expect("Global search error: Failed to get current dir"); + let dedup_symlinks = file_picker_config.deduplicate_links; + let absolute_root = search_root + .canonicalize() + .unwrap_or_else(|_| search_root.clone()); + WalkBuilder::new(search_root) .hidden(file_picker_config.hidden) .parents(file_picker_config.parents) @@ -2022,10 +2028,9 @@ fn global_search(cx: &mut Context) { .git_global(file_picker_config.git_global) .git_exclude(file_picker_config.git_exclude) .max_depth(file_picker_config.max_depth) - // We always want to ignore the .git directory, otherwise if - // `ignore` is turned off above, we end up with a lot of noise - // in our picker. - .filter_entry(|entry| entry.file_name() != ".git") + .filter_entry(move |entry| { + filter_picker_entry(entry, &absolute_root, dedup_symlinks) + }) .build_parallel() .run(|| { let mut searcher = searcher.clone(); diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index a945b20dedaf..f0bc9129a139 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -10,6 +10,9 @@ pub mod health; pub mod job; pub mod keymap; pub mod ui; +use std::path::Path; + +use ignore::DirEntry; pub use keymap::macros::*; #[cfg(not(windows))] @@ -22,3 +25,25 @@ fn true_color() -> bool { fn true_color() -> bool { true } + +/// Function used for filtering dir entries in the various file pickers. +fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> bool { + // We always want to ignore the .git directory, otherwise if + // `ignore` is turned off, we end up with a lot of noise + // in our picker. + if entry.file_name() == ".git" { + return false; + } + + // We also ignore symlinks that point inside the current directory + // if `dedup_links` is enabled. + if dedup_symlinks && entry.path_is_symlink() { + return entry + .path() + .canonicalize() + .ok() + .map_or(false, |path| !path.starts_with(&root)); + } + + true +} diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 5e7f8c36f458..d7717f8cf59c 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -15,6 +15,7 @@ mod statusline; mod text; use crate::compositor::{Component, Compositor}; +use crate::filter_picker_entry; use crate::job::{self, Callback}; pub use completion::Completion; pub use editor::EditorView; @@ -163,6 +164,9 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi let now = Instant::now(); + let dedup_symlinks = config.file_picker.deduplicate_links; + let absolute_root = root.canonicalize().unwrap_or_else(|_| root.clone()); + let mut walk_builder = WalkBuilder::new(&root); walk_builder .hidden(config.file_picker.hidden) @@ -173,10 +177,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi .git_global(config.file_picker.git_global) .git_exclude(config.file_picker.git_exclude) .max_depth(config.file_picker.max_depth) - // We always want to ignore the .git directory, otherwise if - // `ignore` is turned off above, we end up with a lot of noise - // in our picker. - .filter_entry(|entry| entry.file_name() != ".git"); + .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks)); // We want to exclude files that the editor can't handle yet let mut type_builder = TypesBuilder::new(); @@ -195,14 +196,11 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi // We want files along with their modification date for sorting let files = walk_builder.build().filter_map(|entry| { let entry = entry.ok()?; - // This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir - let is_dir = entry.file_type().map_or(false, |ft| ft.is_dir()); - if is_dir { - // Will give a false positive if metadata cannot be read (eg. permission error) - None - } else { + if entry.file_type()?.is_file() { Some(entry.into_path()) + } else { + None } }); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 46511c62d73f..aabf9cde6f16 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -173,6 +173,8 @@ pub struct FilePickerConfig { /// Enables following symlinks. /// Whether to follow symbolic links in file picker and file or directory completions. Defaults to true. pub follow_symlinks: bool, + /// Hides symlinks that point into the current directory. Defaults to true. + pub deduplicate_links: bool, /// Enables reading ignore files from parent directories. Defaults to true. pub parents: bool, /// Enables reading `.ignore` files. @@ -197,6 +199,7 @@ impl Default for FilePickerConfig { Self { hidden: true, follow_symlinks: true, + deduplicate_links: true, parents: true, ignore: true, git_ignore: true, From 2949bb018c00e21d0465bab6970708407cc29ccd Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Feb 2023 19:04:41 +0100 Subject: [PATCH 006/191] fix position translation at EOF with softwrap (#5786) --- helix-term/src/ui/document.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index 663324100d47..56377d1ec455 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -207,16 +207,19 @@ pub fn render_text<'t>( it } else { let mut last_pos = formatter.visual_pos(); - last_pos.col -= 1; - // check if any positions translated on the fly (like cursor) are at the EOF - translate_positions( - char_pos + 1, - first_visible_char_idx, - translated_positions, - text_fmt, - renderer, - last_pos, - ); + if last_pos.row >= row_off { + last_pos.col -= 1; + last_pos.row -= row_off; + // check if any positions translated on the fly (like cursor) are at the EOF + translate_positions( + char_pos + 1, + first_visible_char_idx, + translated_positions, + text_fmt, + renderer, + last_pos, + ); + } break; }; From e31943c4c4a996da1fe8704db052c7d44984fcc4 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Fri, 3 Feb 2023 01:02:05 +0530 Subject: [PATCH 007/191] Tabulate buffer picker contents (#5777) --- helix-term/src/commands.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4f27fc801f46..d1e28bbeca64 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2430,20 +2430,15 @@ fn buffer_picker(cx: &mut Context) { None => SCRATCH_BUFFER_NAME, }; - let mut flags = Vec::new(); + let mut flags = String::new(); if self.is_modified { - flags.push("+"); + flags.push('+'); } if self.is_current { - flags.push("*"); + flags.push('*'); } - let flag = if flags.is_empty() { - "".into() - } else { - format!(" ({})", flags.join("")) - }; - format!("{} {}{}", self.id, path, flag).into() + Row::new([self.id.to_string(), flags, path.to_string()]) } } From f0c2e898b49b2392d6d1ca02680e88b90e62017e Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Feb 2023 20:48:16 +0100 Subject: [PATCH 008/191] add substring matching options to picker (#5114) --- helix-term/src/ui/fuzzy_match.rs | 237 ++++++++++++++++++++++++++----- helix-term/src/ui/picker.rs | 17 ++- 2 files changed, 212 insertions(+), 42 deletions(-) diff --git a/helix-term/src/ui/fuzzy_match.rs b/helix-term/src/ui/fuzzy_match.rs index e25d7328527d..e6a3f03a2fbc 100644 --- a/helix-term/src/ui/fuzzy_match.rs +++ b/helix-term/src/ui/fuzzy_match.rs @@ -4,41 +4,209 @@ use fuzzy_matcher::FuzzyMatcher; #[cfg(test)] mod test; +struct QueryAtom { + kind: QueryAtomKind, + atom: String, + ignore_case: bool, + inverse: bool, +} +impl QueryAtom { + fn new(atom: &str) -> Option { + let mut atom = atom.to_string(); + let inverse = atom.starts_with('!'); + if inverse { + atom.remove(0); + } + + let mut kind = match atom.chars().next() { + Some('^') => QueryAtomKind::Prefix, + Some('\'') => QueryAtomKind::Substring, + _ if inverse => QueryAtomKind::Substring, + _ => QueryAtomKind::Fuzzy, + }; + + if atom.starts_with(&['^', '\'']) { + atom.remove(0); + } + + if atom.is_empty() { + return None; + } + + if atom.ends_with('$') && !atom.ends_with("\\$") { + atom.pop(); + kind = if kind == QueryAtomKind::Prefix { + QueryAtomKind::Exact + } else { + QueryAtomKind::Postfix + } + } + + Some(QueryAtom { + kind, + atom: atom.replace('\\', ""), + // not ideal but fuzzy_matches only knows ascii uppercase so more consistent + // to behave the same + ignore_case: kind != QueryAtomKind::Fuzzy + && atom.chars().all(|c| c.is_ascii_lowercase()), + inverse, + }) + } + + fn indices(&self, matcher: &Matcher, item: &str, indices: &mut Vec) -> bool { + // for inverse there are no indicies to return + // just return whether we matched + if self.inverse { + return self.matches(matcher, item); + } + let buf; + let item = if self.ignore_case { + buf = item.to_ascii_lowercase(); + &buf + } else { + item + }; + let off = match self.kind { + QueryAtomKind::Fuzzy => { + if let Some((_, fuzzy_indices)) = matcher.fuzzy_indices(item, &self.atom) { + indices.extend_from_slice(&fuzzy_indices); + return true; + } else { + return false; + } + } + QueryAtomKind::Substring => { + if let Some(off) = item.find(&self.atom) { + off + } else { + return false; + } + } + QueryAtomKind::Prefix if item.starts_with(&self.atom) => 0, + QueryAtomKind::Postfix if item.ends_with(&self.atom) => item.len() - self.atom.len(), + QueryAtomKind::Exact if item == self.atom => 0, + _ => return false, + }; + + indices.extend(off..(off + self.atom.len())); + true + } + + fn matches(&self, matcher: &Matcher, item: &str) -> bool { + let buf; + let item = if self.ignore_case { + buf = item.to_ascii_lowercase(); + &buf + } else { + item + }; + let mut res = match self.kind { + QueryAtomKind::Fuzzy => matcher.fuzzy_match(item, &self.atom).is_some(), + QueryAtomKind::Substring => item.contains(&self.atom), + QueryAtomKind::Prefix => item.starts_with(&self.atom), + QueryAtomKind::Postfix => item.ends_with(&self.atom), + QueryAtomKind::Exact => item == self.atom, + }; + if self.inverse { + res = !res; + } + res + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum QueryAtomKind { + /// Item is a fuzzy match of this behaviour + /// + /// Usage: `foo` + Fuzzy, + /// Item contains query atom as a continous substring + /// + /// Usage `'foo` + Substring, + /// Item starts with query atom + /// + /// Usage: `^foo` + Prefix, + /// Item ends with query atom + /// + /// Usage: `foo$` + Postfix, + /// Item is equal to query atom + /// + /// Usage `^foo$` + Exact, +} + +#[derive(Default)] pub struct FuzzyQuery { - queries: Vec, + first_fuzzy_atom: Option, + query_atoms: Vec, +} + +fn query_atoms(query: &str) -> impl Iterator + '_ { + let mut saw_backslash = false; + query.split(move |c| { + saw_backslash = match c { + ' ' if !saw_backslash => return true, + '\\' => true, + _ => false, + }; + false + }) } impl FuzzyQuery { + pub fn refine(&self, query: &str, old_query: &str) -> (FuzzyQuery, bool) { + // TODO: we could be a lot smarter about this + let new_query = Self::new(query); + let mut is_refinement = query.starts_with(old_query); + + // if the last atom is an inverse atom adding more text to it + // will actually increase the number of matches and we can not refine + // the matches. + if is_refinement && !self.query_atoms.is_empty() { + let last_idx = self.query_atoms.len() - 1; + if self.query_atoms[last_idx].inverse + && self.query_atoms[last_idx].atom != new_query.query_atoms[last_idx].atom + { + is_refinement = false; + } + } + + (new_query, is_refinement) + } + pub fn new(query: &str) -> FuzzyQuery { - let mut saw_backslash = false; - let queries = query - .split(|c| { - saw_backslash = match c { - ' ' if !saw_backslash => return true, - '\\' => true, - _ => false, - }; - false - }) - .filter_map(|query| { - if query.is_empty() { + let mut first_fuzzy_query = None; + let query_atoms = query_atoms(query) + .filter_map(|atom| { + let atom = QueryAtom::new(atom)?; + if atom.kind == QueryAtomKind::Fuzzy && first_fuzzy_query.is_none() { + first_fuzzy_query = Some(atom.atom); None } else { - Some(query.replace("\\ ", " ")) + Some(atom) } }) .collect(); - FuzzyQuery { queries } + FuzzyQuery { + first_fuzzy_atom: first_fuzzy_query, + query_atoms, + } } pub fn fuzzy_match(&self, item: &str, matcher: &Matcher) -> Option { - // use the rank of the first query for the rank, because merging ranks is not really possible + // use the rank of the first fuzzzy query for the rank, because merging ranks is not really possible // this behaviour matches fzf and skim - let score = matcher.fuzzy_match(item, self.queries.get(0)?)?; + let score = self + .first_fuzzy_atom + .as_ref() + .map_or(Some(0), |atom| matcher.fuzzy_match(item, atom))?; if self - .queries + .query_atoms .iter() - .any(|query| matcher.fuzzy_match(item, query).is_none()) + .any(|atom| !atom.matches(matcher, item)) { return None; } @@ -46,29 +214,26 @@ impl FuzzyQuery { } pub fn fuzzy_indicies(&self, item: &str, matcher: &Matcher) -> Option<(i64, Vec)> { - if self.queries.len() == 1 { - return matcher.fuzzy_indices(item, &self.queries[0]); - } - - // use the rank of the first query for the rank, because merging ranks is not really possible - // this behaviour matches fzf and skim - let (score, mut indicies) = matcher.fuzzy_indices(item, self.queries.get(0)?)?; + let (score, mut indices) = self.first_fuzzy_atom.as_ref().map_or_else( + || Some((0, Vec::new())), + |atom| matcher.fuzzy_indices(item, atom), + )?; - // fast path for the common case of not using a space - // during matching this branch should be free thanks to branch prediction - if self.queries.len() == 1 { - return Some((score, indicies)); + // fast path for the common case of just a single atom + if self.query_atoms.is_empty() { + return Some((score, indices)); } - for query in &self.queries[1..] { - let (_, matched_indicies) = matcher.fuzzy_indices(item, query)?; - indicies.extend_from_slice(&matched_indicies); + for atom in &self.query_atoms { + if !atom.indices(matcher, item, &mut indices) { + return None; + } } // deadup and remove duplicate matches - indicies.sort_unstable(); - indicies.dedup(); + indices.sort_unstable(); + indices.dedup(); - Some((score, indicies)) + Some((score, indices)) } } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 5fa75136f104..5190fc531d55 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -407,7 +407,7 @@ pub struct Picker { cursor: usize, // pattern: String, prompt: Prompt, - previous_pattern: String, + previous_pattern: (String, FuzzyQuery), /// Whether to truncate the start (default true) pub truncate_start: bool, /// Whether to show the preview panel (default true) @@ -458,7 +458,7 @@ impl Picker { matches: Vec::new(), cursor: 0, prompt, - previous_pattern: String::new(), + previous_pattern: (String::new(), FuzzyQuery::default()), truncate_start: true, show_preview: true, callback_fn: Box::new(callback_fn), @@ -485,10 +485,15 @@ impl Picker { pub fn score(&mut self) { let pattern = self.prompt.line(); - if pattern == &self.previous_pattern { + if pattern == &self.previous_pattern.0 { return; } + let (query, is_refined) = self + .previous_pattern + .1 + .refine(pattern, &self.previous_pattern.0); + if pattern.is_empty() { // Fast path for no pattern. self.matches.clear(); @@ -501,8 +506,7 @@ impl Picker { len: text.chars().count(), } })); - } else if pattern.starts_with(&self.previous_pattern) { - let query = FuzzyQuery::new(pattern); + } else if is_refined { // optimization: if the pattern is a more specific version of the previous one // then we can score the filtered set. self.matches.retain_mut(|pmatch| { @@ -527,7 +531,8 @@ impl Picker { // reset cursor position self.cursor = 0; let pattern = self.prompt.line(); - self.previous_pattern.clone_from(pattern); + self.previous_pattern.0.clone_from(pattern); + self.previous_pattern.1 = query; } pub fn force_score(&mut self) { From 61e1e6160afe2c6d0f1b9060112929957bba4e5f Mon Sep 17 00:00:00 2001 From: Dylan Bulfin <31492860+DylanBulfin@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:49:17 -0500 Subject: [PATCH 009/191] Removing C-j and C-k from completion menu navigation (#5070) --- helix-term/src/ui/menu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index da00aa89f9b1..3ae007039294 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -252,12 +252,12 @@ impl Component for Menu { return EventResult::Consumed(close_fn); } // arrow up/ctrl-p/shift-tab prev completion choice (including updating the doc) - shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => { + shift!(Tab) | key!(Up) | ctrl!('p') => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); } - key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { + key!(Tab) | key!(Down) | ctrl!('n') => { // arrow down/ctrl-n/tab advances completion choice (including updating the doc) self.move_down(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); From 8ba0a4627439b944157c288f909517289d5aeb20 Mon Sep 17 00:00:00 2001 From: Filipe Azevedo Date: Thu, 2 Feb 2023 19:51:11 +0000 Subject: [PATCH 010/191] add picker: current view dir (#4666) --- helix-term/src/commands.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d1e28bbeca64..7d5f22415f30 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -276,6 +276,7 @@ impl MappableCommand { append_mode, "Append after selection", command_mode, "Enter command mode", file_picker, "Open file picker", + file_picker_in_current_buffer_directory, "Open file picker at current buffers's directory", file_picker_in_current_directory, "Open file picker at current working directory", code_action, "Perform code action", buffer_picker, "Open buffer picker", @@ -2401,6 +2402,22 @@ fn file_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } +fn file_picker_in_current_buffer_directory(cx: &mut Context) { + let doc_dir = doc!(cx.editor) + .path() + .and_then(|path| path.parent().map(|path| path.to_path_buf())); + + let path = match doc_dir { + Some(path) => path, + None => { + cx.editor.set_error("current buffer has no path or parent"); + return; + } + }; + + let picker = ui::file_picker(path, &cx.editor.config()); + cx.push_layer(Box::new(overlayed(picker))); +} fn file_picker_in_current_directory(cx: &mut Context) { let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./")); let picker = ui::file_picker(cwd, &cx.editor.config()); From 0e038fb80cd5f9bcdfd5fc057843cc1d79a7d7ea Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Fri, 21 Oct 2022 11:15:28 -0400 Subject: [PATCH 011/191] make clipboard message debug --- helix-view/src/clipboard.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 3c620c1466ac..96c87d3f5d74 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -41,7 +41,7 @@ macro_rules! command_provider { primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ; primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ; ) => {{ - log::info!( + log::debug!( "Using {} to interact with the system and selection (primary) clipboard", if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() } ); @@ -381,7 +381,7 @@ mod provider { impl ClipboardProvider for WindowsProvider { fn name(&self) -> Cow { - log::info!("Using clipboard-win to interact with the system clipboard"); + log::debug!("Using clipboard-win to interact with the system clipboard"); Cow::Borrowed("clipboard-win") } From 06d095f31cb83cde1a59b672c222277a42f6c9b6 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Tue, 16 Aug 2022 10:58:13 -0400 Subject: [PATCH 012/191] provide option to completely disable lsp --- book/src/configuration.md | 1 + helix-view/src/editor.rs | 33 +++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 87585ece0f9e..7514a3d0fcc3 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -115,6 +115,7 @@ The following statusline elements can be configured: | Key | Description | Default | | --- | ----------- | ------- | +| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` | | `display-messages` | Display LSP progress messages below statusline[^1] | `false` | | `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` | | `display-signature-help-docs` | Display docs under signature help popup | `true` | diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index aabf9cde6f16..042f5bdb4269 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -369,6 +369,8 @@ pub fn get_terminal_provider() -> Option { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case", deny_unknown_fields)] pub struct LspConfig { + /// Enables LSP + pub enable: bool, /// Display LSP progress messages below statusline pub display_messages: bool, /// Enable automatic pop up of signature help (parameter hints) @@ -380,6 +382,7 @@ pub struct LspConfig { impl Default for LspConfig { fn default() -> Self { Self { + enable: true, display_messages: false, auto_signature_help: true, display_signature_help_docs: true, @@ -1077,18 +1080,25 @@ impl Editor { /// Refreshes the language server for a given document pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { - let doc = self.documents.get_mut(&doc_id)?; - Self::launch_language_server(&mut self.language_servers, doc) + self.launch_language_server(doc_id) } /// Launch a language server for a given document - fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> Option<()> { + fn launch_language_server(&mut self, doc_id: DocumentId) -> Option<()> { + if !self.config().lsp.enable { + return None; + } + // if doc doesn't have a URL it's a scratch buffer, ignore it - let doc_url = doc.url()?; + let (lang, path) = { + let doc = self.document(doc_id)?; + (doc.language.clone(), doc.path().cloned()) + }; // try to find a language server based on the language name - let language_server = doc.language.as_ref().and_then(|language| { - ls.get(language, doc.path()) + let language_server = lang.as_ref().and_then(|language| { + self.language_servers + .get(language, path.as_ref()) .map_err(|e| { log::error!( "Failed to initialize the LSP for `{}` {{ {} }}", @@ -1099,6 +1109,10 @@ impl Editor { .ok() .flatten() }); + + let doc = self.document_mut(doc_id)?; + let doc_url = doc.url()?; + if let Some(language_server) = language_server { // only spawn a new lang server if the servers aren't the same if Some(language_server.id()) != doc.language_server().map(|server| server.id()) { @@ -1288,11 +1302,14 @@ impl Editor { self.config.clone(), )?; - let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); if let Some(diff_base) = self.diff_providers.get_diff_base(&path) { doc.set_diff_base(diff_base, self.redraw_handle.clone()); } - self.new_document(doc) + + let id = self.new_document(doc); + let _ = self.launch_language_server(id); + + id }; self.switch(id, action); From a5233cf5ad3c4f8e95228314463cf41d744fac04 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 22 Oct 2022 20:51:55 -0400 Subject: [PATCH 013/191] disable lsp in integration tests --- helix-term/tests/test/auto_indent.rs | 2 +- helix-term/tests/test/helpers.rs | 43 +++++++++++++--------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/helix-term/tests/test/auto_indent.rs b/helix-term/tests/test/auto_indent.rs index d5c220b7a1be..2d9082853dcf 100644 --- a/helix-term/tests/test/auto_indent.rs +++ b/helix-term/tests/test/auto_indent.rs @@ -7,7 +7,7 @@ async fn auto_indent_c() -> anyhow::Result<()> { files: vec![(PathBuf::from("foo.c"), Position::default())], ..Default::default() }, - Config::default(), + helpers::test_config(), helpers::test_syntax_conf(None), // switches to append mode? ( diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index a0f3a32e4cba..8755b60f06ca 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -8,8 +8,8 @@ use std::{ use anyhow::bail; use crossterm::event::{Event, KeyEvent}; use helix_core::{diagnostic::Severity, test, Selection, Transaction}; -use helix_term::{application::Application, args::Args, config::Config}; -use helix_view::{doc, input::parse_macro, Editor}; +use helix_term::{application::Application, args::Args, config::Config, keymap::merge_keys}; +use helix_view::{doc, editor::LspConfig, input::parse_macro, Editor}; use tempfile::NamedTempFile; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -118,7 +118,7 @@ pub async fn test_key_sequence_with_input_text>( let test_case = test_case.into(); let mut app = match app { Some(app) => app, - None => Application::new(Args::default(), Config::default(), test_syntax_conf(None))?, + None => Application::new(Args::default(), test_config(), test_syntax_conf(None))?, }; let (view, doc) = helix_view::current!(app.editor); @@ -143,27 +143,9 @@ pub async fn test_key_sequence_with_input_text>( /// Generates language configs that merge in overrides, like a user language /// config. The argument string must be a raw TOML document. -/// -/// By default, language server configuration is dropped from the languages.toml -/// document. If a language-server is necessary for a test, it must be explicitly -/// added in `overrides`. pub fn test_syntax_conf(overrides: Option) -> helix_core::syntax::Configuration { let mut lang = helix_loader::config::default_lang_config(); - for lang_config in lang - .as_table_mut() - .expect("Expected languages.toml to be a table") - .get_mut("language") - .expect("Expected languages.toml to have \"language\" keys") - .as_array_mut() - .expect("Expected an array of language configurations") - { - lang_config - .as_table_mut() - .expect("Expected language config to be a TOML table") - .remove("language-server"); - } - if let Some(overrides) = overrides { let override_toml = toml::from_str(&overrides).unwrap(); lang = helix_loader::merge_toml_values(lang, override_toml, 3); @@ -177,11 +159,12 @@ pub fn test_syntax_conf(overrides: Option) -> helix_core::syntax::Config /// want to verify the resulting document and selection. pub async fn test_with_config>( args: Args, - config: Config, + mut config: Config, syn_conf: helix_core::syntax::Configuration, test_case: T, ) -> anyhow::Result<()> { let test_case = test_case.into(); + config = helix_term::keymap::merge_keys(config); let app = Application::new(args, config, syn_conf)?; test_key_sequence_with_input_text( @@ -205,7 +188,7 @@ pub async fn test_with_config>( pub async fn test>(test_case: T) -> anyhow::Result<()> { test_with_config( Args::default(), - Config::default(), + test_config(), test_syntax_conf(None), test_case, ) @@ -226,6 +209,20 @@ pub fn temp_file_with_contents>( Ok(temp_file) } +/// Generates a config with defaults more suitable for integration tests +pub fn test_config() -> Config { + merge_keys(Config { + editor: helix_view::editor::Config { + lsp: LspConfig { + enable: false, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }) +} + /// Replaces all LF chars with the system's appropriate line feed /// character, and if one doesn't exist already, appends the system's /// appropriate line ending to the end of a string. From 350535208f134f1d8a6201833ad560905fea5abc Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 2 Feb 2023 20:56:52 +0100 Subject: [PATCH 014/191] always commit to history when pasting (#5790) --- helix-term/src/commands.rs | 1 + helix-term/src/commands/typed.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7d5f22415f30..d1dc92236cf0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3746,6 +3746,7 @@ fn paste_impl( } doc.apply(&transaction, view.id); + doc.append_changes_to_history(view); } pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index ec7100a63740..f2495d8ce63c 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -913,6 +913,7 @@ fn replace_selections_with_clipboard_impl( cx: &mut compositor::Context, clipboard_type: ClipboardType, ) -> anyhow::Result<()> { + let scrolloff = cx.editor.config().scrolloff; let (view, doc) = current!(cx.editor); match cx.editor.clipboard_provider.get_contents(clipboard_type) { @@ -924,6 +925,7 @@ fn replace_selections_with_clipboard_impl( doc.apply(&transaction, view.id); doc.append_changes_to_history(view); + view.ensure_cursor_in_view(doc, scrolloff); Ok(()) } Err(e) => Err(e.context("Couldn't get system clipboard contents")), @@ -1570,6 +1572,7 @@ fn sort_impl( _args: &[Cow], reverse: bool, ) -> anyhow::Result<()> { + let scrolloff = cx.editor.config().scrolloff; let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); @@ -1595,6 +1598,7 @@ fn sort_impl( doc.apply(&transaction, view.id); doc.append_changes_to_history(view); + view.ensure_cursor_in_view(doc, scrolloff); Ok(()) } From 10fee815c4d1ae952ef0863f3f75c3502e712edb Mon Sep 17 00:00:00 2001 From: Cemal Okten <60609268+cemalokten@users.noreply.github.com> Date: Thu, 2 Feb 2023 22:42:15 +0000 Subject: [PATCH 015/191] Add Jellybeans theme (#5719) --- runtime/themes/jellybeans.toml | 138 +++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 runtime/themes/jellybeans.toml diff --git a/runtime/themes/jellybeans.toml b/runtime/themes/jellybeans.toml new file mode 100644 index 000000000000..592db9a92e7c --- /dev/null +++ b/runtime/themes/jellybeans.toml @@ -0,0 +1,138 @@ +# +# __ _ _ _ +# \ \ ___| | |_ _| |__ ___ __ _ _ __ ___ +# \ \/ _ \ | | | | | _ \ / _ \/ _ | _ \/ __| +# /\_/ / __/ | | |_| | |_| | __/ |_| | | | \__ \ +# \___/ \___|_|_|\__ |____/ \___|\____|_| |_|___/ +# \___/ +# +# Jellybeans +# A take on the Jellybeans theme, please feel free to contribute! +# +# Original author: @nanotech +# Original repository: nanotech/jellybeans.vim +# Contributors: +# @cemalokten + +"attribute" = "green" +"type" = "light_blue" +"type.enum.variant" = "purple" +"constructor" = "yellow" +"constant" = "dark_orange" + +"constant.builtin.boolean" = "yellow" +"constant.character" = "yellow" +"constant.characted.escape" = "red_error" +"constant.numeric" = "dark_orange" +"string" = "dark_green" +"string.regexp" = "light_purple" +"string.special" = { fg = "yellow", modifiers = ["underlined"] } +"comment" = "light_gray" + +"variable" = "light_yellow" +"variable.builtin" = { fg = "dark_green", modifiers = ["underlined"] } +"variable.parameter" = "yellow" +"variable.other.member" = "light_purple" +"label" = "yellow" +"punctuation" = "mid_blue" +"keyword" = "mid_blue" +"keyword.control.exception" = "purple" +"operator" = "light_purple" +"function" = "yellow" +"function.macro" = "green" +"function.builtin" = "green" +"function.special" = "green" +"function.method" = "yellow" +"tag" = "light_blue" +"special" = "green" +"namespace" = "light_purple" + +"markup.bold" = { fg = "white", modifiers = ["bold"] } +"markup.italic" = { modifiers = ["italic"] } +"markup.strikethrough" = { modifiers = ["crossed_out"] } +"markup.heading" = { fg = "mid_blue", modifiers = ["bold"] } +"markup.list" = "dark_green" +"markup.list.numbered" = "mid_blue" +"markup.list.unnumbered" = "mid_blue" +"markup.link.url" = { fg = "dark_green", modifiers = ['italic', 'underlined'] } +"markup.link.text" = "mid_blue" +"markup.link.label" = "purple" +"markup.quote" = "dark_green" +"markup.raw" = "dark_green" +"markup.raw.inline" = "mid_blue" +"markup.raw.block" = "dark_green" + +"diff.plus" = "diff_plus" +"diff.minus" = "red_accent" +"diff.delta" = "blue_accent" + +# ui specific +"ui.background" = { bg = "background" } # .separator +"ui.cursor" = { bg = "background", modifiers = ["reversed"] } +"ui.cursor.insert" = { bg = "light_yellow", fg = "background" } +"ui.cursor.match" = { fg = "background", bg = "dark_orange" } +"ui.cursorline" = { bg = "darker" } +"ui.linenr" = "dark_gray" +"ui.linenr.selected" = { fg = "light_yellow", bg = "darker" } +"ui.statusline" = { fg = "light_yellow", bg = "darker" } +"ui.statusline.inactive" = { fg = "dark", bg = "darker" } +"ui.statusline.normal" = { fg = "light_yellow", bg = "darker" } +"ui.statusline.insert" = { fg = "darker", bg = "purple" } +"ui.statusline.select" = { fg = "selectionFG", bg = "selection" } +"ui.popup" = { fg = "light_yellow", bg = "darkest" } +"ui.window" = { fg = "dark", bg = "darkest" } +"ui.help" = { fg = "light_yellow", bg = "darkest" } +"ui.text" = "light_yellow" +"ui.text.focus" = { fg = "white", bg = "dark_blue" } +"ui.virtual" = "dark" +"ui.virtual.ruler" = { bg = "darker" } +"ui.menu" = { fg = "light_purple", bg = "darkest" } +"ui.menu.selected" = { fg = "white", bg = "dark_blue" } +"ui.selection" = { bg = "darker" } +"ui.selection.primary" = { bg = "selection", fg = "selectionFG" } +"hint" = "blue" +"info" = "yellow_accent" +"warning" = "orange_accent" +"error" = "red_error" +"diagnostic" = { modifiers = [] } +"diagnostic.hint" = { underline = { color = "light_purple", style = "line" } } +"diagnostic.info" = { underline = { color = "blue_accent", style = "line" } } +"diagnostic.warning" = { underline = { color = "yellow_accent", style = "line" } } +"diagnostic.error" = { underline = { color = "red_error", style = "line" } } + +[palette] +background = "#151515" +darkest = "#1e1e1e" +darker = "#292929" +dark = "#898989" +white = "#ffffff" +dark_gray = "#535353" +light_gray = "#6d6d6d" + +purple = "#a390f0" +light_purple = "#CDBEF0" + +blue = "#52a7f6" +light_blue = "#8fbfdc" +mid_blue = "#8197bf" +dark_blue = "#204474" +blue_accent = "#2197F3" + +green = "#99ad6a" +dark_green = "#84A775" + +red = "#CC7C8A" +red_error = "#902020" +red_accent = "#F44747" + +orange = "#efb080" +dark_orange = "#cf6a4c" +orange_accent = "#EE7F25" + +yellow = "#fad07a" +light_yellow = "#EBEBD8" +yellow_accent = "#DEA407" + +diff_plus = "#5A9F81" +selection = "#37232D" +selectionFG = "#F2AAC7" From 30412366be411335b7e2600e9b4178355c27da15 Mon Sep 17 00:00:00 2001 From: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Date: Thu, 2 Feb 2023 23:44:36 +0100 Subject: [PATCH 016/191] feat: Update `tree-sitter-sql` and migrate `highlights.scm` to match grammar (#5772) * Sort buildin functions alphabetically * fix: Query float type like other numeric types * Update tree-sitter-sql and update highlights.scm to match grammar --- languages.toml | 2 +- runtime/queries/sql/highlights.scm | 69 ++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/languages.toml b/languages.toml index 755f577ffa0e..09ba2a2a160c 100644 --- a/languages.toml +++ b/languages.toml @@ -1434,7 +1434,7 @@ injection-regex = "sql" [[grammar]] name = "sql" -source = { git = "https://github.com/DerekStride/tree-sitter-sql", rev = "4fe05b2d81565ddb689d2f415e07afdacc515c52" } +source = { git = "https://github.com/DerekStride/tree-sitter-sql", rev = "286e10c5bc5d1703ee8f9afb351165a9a6182be1" } [[language]] name = "gdscript" diff --git a/runtime/queries/sql/highlights.scm b/runtime/queries/sql/highlights.scm index aad5ed318cb8..a2d2f7feb911 100644 --- a/runtime/queries/sql/highlights.scm +++ b/runtime/queries/sql/highlights.scm @@ -1,19 +1,20 @@ -(keyword_gist) @function.builtin -(keyword_btree) @function.builtin (keyword_btree) @function.builtin (keyword_hash) @function.builtin +(keyword_gist) @function.builtin (keyword_spgist) @function.builtin (keyword_gin) @function.builtin (keyword_brin) @function.builtin -(keyword_float) @function.builtin -(invocation - name: (identifier) @function.builtin - parameter: [(field)]? @variable.other.member) +(cast + name: (identifier) @function.builtin) (count - name: (identifier) @function.builtin - parameter: [(field)]? @variable.other.member) + name: (identifier) @function.builtin) + +(keyword_group_concat) @function.builtin + +(invocation + name: (identifier) @function.builtin) (table_reference name: (identifier) @namespace) @@ -28,7 +29,6 @@ table_alias: (identifier) @variable.parameter name: (identifier) @variable.other.member) - (comment) @comment [ @@ -102,12 +102,16 @@ (keyword_as) (keyword_distinct) (keyword_constraint) + ; (keyword_cast) ; (keyword_count) + ; (keyword_group_concat) + (keyword_separator) (keyword_max) (keyword_min) (keyword_avg) (keyword_end) (keyword_force) + (keyword_ignore) (keyword_using) (keyword_use) (keyword_index) @@ -117,8 +121,6 @@ (keyword_auto_increment) (keyword_default) (keyword_cascade) - (keyword_between) - (keyword_window) (keyword_with) (keyword_no) (keyword_data) @@ -129,6 +131,7 @@ (keyword_owner) (keyword_temp) (keyword_temporary) + (keyword_unlogged) (keyword_union) (keyword_all) (keyword_except) @@ -138,8 +141,35 @@ (keyword_commit) (keyword_rollback) (keyword_transaction) - (keyword_group_concat) - (keyword_separator) + (keyword_over) + (keyword_nulls) + (keyword_first) + (keyword_last) + (keyword_window) + (keyword_range) + (keyword_rows) + (keyword_groups) + (keyword_between) + (keyword_unbounded) + (keyword_preceding) + (keyword_following) + (keyword_exclude) + (keyword_current) + (keyword_row) + (keyword_ties) + (keyword_others) + (keyword_only) + (keyword_unique) + (keyword_concurrently) + ; (keyword_btree) + ; (keyword_hash) + ; (keyword_gist) + ; (keyword_spgist) + ; (keyword_gin) + ; (keyword_brin) + (keyword_like) + (keyword_similar) + (keyword_preserve) ] @keyword [ @@ -159,6 +189,7 @@ [ (keyword_boolean) + (keyword_smallserial) (keyword_serial) (keyword_bigserial) @@ -170,6 +201,7 @@ (numeric) (keyword_real) (double) + (float) (keyword_money) @@ -194,4 +226,15 @@ (keyword_geography) (keyword_box2d) (keyword_box3d) + + (char) + (varchar) + (numeric) + + (keyword_oid) + (keyword_name) + (keyword_regclass) + (keyword_regnamespace) + (keyword_regproc) + (keyword_regtype) ] @type.builtin From d8f482e11e2dec544ede636464cf6a87e32bd1f7 Mon Sep 17 00:00:00 2001 From: Brett Lyons Date: Fri, 3 Feb 2023 07:24:22 -0700 Subject: [PATCH 017/191] Add MSBuild language based on XML grammar (#5793) --- book/src/generated/lang-support.md | 1 + languages.toml | 17 +++++++++++++++++ runtime/queries/msbuild/highlights.scm | 1 + runtime/queries/msbuild/indents.scm | 1 + runtime/queries/msbuild/injections.scm | 1 + 5 files changed, 21 insertions(+) create mode 100644 runtime/queries/msbuild/highlights.scm create mode 100644 runtime/queries/msbuild/indents.scm create mode 100644 runtime/queries/msbuild/injections.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 1711ec36d5bf..99fe82b33159 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -83,6 +83,7 @@ | mermaid | βœ“ | | | | | meson | βœ“ | | βœ“ | | | mint | | | | `mint` | +| msbuild | βœ“ | | βœ“ | | | nickel | βœ“ | | βœ“ | `nls` | | nix | βœ“ | | | `nil` | | nu | βœ“ | | | | diff --git a/languages.toml b/languages.toml index 09ba2a2a160c..8520b12d74f4 100644 --- a/languages.toml +++ b/languages.toml @@ -2117,3 +2117,20 @@ roots = [] comment-token = "#" indent = { tab-width = 4, unit = " " } grammar = "python" + +[[language]] +name = "msbuild" +scope = "source.msbuild" +injection-regex = "msbuild" +file-types = ["proj", "vbproj", "csproj", "fsproj", "targets", "props"] +indent = { tab-width = 2, unit = " " } +roots = [] +grammar = "xml" + +[language.auto-pairs] +'(' = ')' +'{' = '}' +'[' = ']' +'"' = '"' +"'" = "'" +"<" = ">" diff --git a/runtime/queries/msbuild/highlights.scm b/runtime/queries/msbuild/highlights.scm new file mode 100644 index 000000000000..27b10d95d8b6 --- /dev/null +++ b/runtime/queries/msbuild/highlights.scm @@ -0,0 +1 @@ +; inherits: xml diff --git a/runtime/queries/msbuild/indents.scm b/runtime/queries/msbuild/indents.scm new file mode 100644 index 000000000000..27b10d95d8b6 --- /dev/null +++ b/runtime/queries/msbuild/indents.scm @@ -0,0 +1 @@ +; inherits: xml diff --git a/runtime/queries/msbuild/injections.scm b/runtime/queries/msbuild/injections.scm new file mode 100644 index 000000000000..27b10d95d8b6 --- /dev/null +++ b/runtime/queries/msbuild/injections.scm @@ -0,0 +1 @@ +; inherits: xml From f7bd7b5eafac9f016a470cc77c780922f83e690b Mon Sep 17 00:00:00 2001 From: William Etheredge Date: Fri, 3 Feb 2023 08:24:46 -0600 Subject: [PATCH 018/191] Add :character-info command (#4000) --- book/src/generated/typable-cmd.md | 1 + helix-term/src/commands/typed.rs | 133 ++++++++++++++++++++++++++++++ helix-term/tests/test/commands.rs | 58 +++++++++++++ 3 files changed, 192 insertions(+) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 66e6ac039c26..0ff501a33fdb 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -43,6 +43,7 @@ | `:change-current-directory`, `:cd` | Change the current working directory. | | `:show-directory`, `:pwd` | Show the current working directory. | | `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. | +| `:character-info`, `:char` | Get info about the character under the primary cursor. | | `:reload` | Discard changes and reload from the source file. | | `:reload-all` | Discard changes and reload all documents from the source files. | | `:update` | Write changes only if the file has been modified. | diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index f2495d8ce63c..1fd11b65de5e 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -4,6 +4,7 @@ use crate::job::Job; use super::*; +use helix_core::encoding; use helix_view::editor::{Action, CloseError, ConfigEvent}; use ui::completers::{self, Completer}; @@ -1033,6 +1034,131 @@ fn set_encoding( } } +/// Shows info about the character under the primary cursor. +fn get_character_info( + cx: &mut compositor::Context, + _args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let (view, doc) = current_ref!(cx.editor); + let text = doc.text().slice(..); + + let grapheme_start = doc.selection(view.id).primary().cursor(text); + let grapheme_end = graphemes::next_grapheme_boundary(text, grapheme_start); + + if grapheme_start == grapheme_end { + return Ok(()); + } + + let grapheme = text.slice(grapheme_start..grapheme_end).to_string(); + let encoding = doc.encoding(); + + let printable = grapheme.chars().fold(String::new(), |mut s, c| { + match c { + '\0' => s.push_str("\\0"), + '\t' => s.push_str("\\t"), + '\n' => s.push_str("\\n"), + '\r' => s.push_str("\\r"), + _ => s.push(c), + } + + s + }); + + // Convert to Unicode codepoints if in UTF-8 + let unicode = if encoding == encoding::UTF_8 { + let mut unicode = " (".to_owned(); + + for (i, char) in grapheme.chars().enumerate() { + if i != 0 { + unicode.push(' '); + } + + unicode.push_str("U+"); + + let codepoint: u32 = if char.is_ascii() { + char.into() + } else { + // Not ascii means it will be multi-byte, so strip out the extra + // bits that encode the length & mark continuation bytes + + let s = String::from(char); + let bytes = s.as_bytes(); + + // First byte starts with 2-4 ones then a zero, so strip those off + let first = bytes[0]; + let codepoint = first & (0xFF >> (first.leading_ones() + 1)); + let mut codepoint = u32::from(codepoint); + + // Following bytes start with 10 + for byte in bytes.iter().skip(1) { + codepoint <<= 6; + codepoint += u32::from(*byte) & 0x3F; + } + + codepoint + }; + + unicode.push_str(&format!("{codepoint:0>4x}")); + } + + unicode.push(')'); + unicode + } else { + String::new() + }; + + // Give the decimal value for ascii characters + let dec = if encoding.is_ascii_compatible() && grapheme.len() == 1 { + format!(" Dec {}", grapheme.as_bytes()[0]) + } else { + String::new() + }; + + let hex = { + let mut encoder = encoding.new_encoder(); + let max_encoded_len = encoder + .max_buffer_length_from_utf8_without_replacement(grapheme.len()) + .unwrap(); + let mut bytes = Vec::with_capacity(max_encoded_len); + let mut current_byte = 0; + let mut hex = String::new(); + + for (i, char) in grapheme.chars().enumerate() { + if i != 0 { + hex.push_str(" +"); + } + + let (result, _input_bytes_read) = encoder.encode_from_utf8_to_vec_without_replacement( + &char.to_string(), + &mut bytes, + true, + ); + + if let encoding::EncoderResult::Unmappable(char) = result { + bail!("{char:?} cannot be mapped to {}", encoding.name()); + } + + for byte in &bytes[current_byte..] { + hex.push_str(&format!(" {byte:0>2x}")); + } + + current_byte = bytes.len(); + } + + hex + }; + + cx.editor + .set_status(format!("\"{printable}\"{unicode}{dec} Hex{hex}")); + + Ok(()) +} + /// Reload the [`Document`] from its source file. fn reload( cx: &mut compositor::Context, @@ -2131,6 +2257,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: set_encoding, completer: None, }, + TypableCommand { + name: "character-info", + aliases: &["char"], + doc: "Get info about the character under the primary cursor.", + fun: get_character_info, + completer: None, + }, TypableCommand { name: "reload", aliases: &[], diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 6e7275f5e411..da2e020efcee 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -354,3 +354,61 @@ async fn test_extend_line() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_character_info() -> anyhow::Result<()> { + // UTF-8, single byte + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some("ihh:char"), + Some(&|app| { + assert_eq!( + r#""h" (U+0068) Dec 104 Hex 68"#, + app.editor.get_status().unwrap().0 + ); + }), + false, + ) + .await?; + + // UTF-8, multi-byte + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some("iëh:char"), + Some(&|app| { + assert_eq!( + r#""ë" (U+0065 U+0308) Hex 65 + cc 88"#, + app.editor.get_status().unwrap().0 + ); + }), + false, + ) + .await?; + + // Multiple characters displayed as one, escaped characters + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some(":lineending crlf:char"), + Some(&|app| { + assert_eq!( + r#""\r\n" (U+000d U+000a) Hex 0d + 0a"#, + app.editor.get_status().unwrap().0 + ); + }), + false, + ) + .await?; + + // Non-UTF-8 + test_key_sequence( + &mut helpers::AppBuilder::new().build()?, + Some(":encoding asciiihh:char"), + Some(&|app| { + assert_eq!(r#""h" Dec 104 Hex 68"#, app.editor.get_status().unwrap().0); + }), + false, + ) + .await?; + + Ok(()) +} From 7b46a6cadaf4abf1ab0814a5195cc8352574385c Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Sat, 4 Feb 2023 00:46:57 +0500 Subject: [PATCH 019/191] Add Podfile and *.podspec to the file types for ruby (#5811) --- languages.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages.toml b/languages.toml index 8520b12d74f4..c82bc155e041 100644 --- a/languages.toml +++ b/languages.toml @@ -519,7 +519,7 @@ source = { git = "https://github.com/cstrahan/tree-sitter-nix", rev = "6b71a810c name = "ruby" scope = "source.ruby" injection-regex = "ruby" -file-types = ["rb", "rake", "rakefile", "irb", "gemfile", "gemspec", "Rakefile", "Gemfile", "rabl", "jbuilder", "jb"] +file-types = ["rb", "rake", "rakefile", "irb", "gemfile", "gemspec", "Rakefile", "Gemfile", "rabl", "jbuilder", "jb", "Podfile", "podspec"] shebangs = ["ruby"] roots = [] comment-token = "#" From d6e2434f735545ded269f228e3d93684d2be262a Mon Sep 17 00:00:00 2001 From: Alex <3957610+CptPotato@users.noreply.github.com> Date: Sat, 4 Feb 2023 17:33:53 +0100 Subject: [PATCH 020/191] Add `ui.virtual.wrap` to theme docs (#5823) --- book/src/themes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 0d0827fd18e3..9b7e97a1c9ff 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -270,7 +270,7 @@ These scopes are used for theming the editor interface. | `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.separator` | Separator character in statusline | -| `ui.popup` | Documentation popups (e.g Space + k) | +| `ui.popup` | Documentation popups (e.g Space + k) | | `ui.popup.info` | Prompt for multiple key options | | `ui.window` | Border lines separating splits | | `ui.help` | Description box for commands | @@ -279,8 +279,9 @@ These scopes are used for theming the editor interface. | `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) | | `ui.text.info` | The key: command text in `ui.popup.info` boxes | | `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) | -| `ui.virtual.whitespace` | Visible whitespace characters | +| `ui.virtual.whitespace` | Visible whitespace characters | | `ui.virtual.indent-guide` | Vertical indent width guides | +| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) | | `ui.menu` | Code and command completion menus | | `ui.menu.selected` | Selected autocomplete item | | `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | From b2e83f81e10089a0e81ce33c4beb51aefc29a62e Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 4 Feb 2023 16:20:23 -0500 Subject: [PATCH 021/191] enable rendering in integration tests (#5819) This will allow testing more of the code base, as well as enable UI- specific testing. Debug mode builds are prohibitively slow for the tests, mostly because of the concurrency write tests. So there is now a profile for integration tests that sets the optimization level to 2 for a few helix crates, and lowers the number of rounds of concurrent writes to 1000. --- .cargo/config.toml | 2 +- Cargo.toml | 6 ++++++ helix-term/src/application.rs | 4 ---- helix-term/tests/test/write.rs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 5d6155669a3a..b016eca31aec 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] xtask = "run --package xtask --" -integration-test = "test --features integration --workspace --test integration" +integration-test = "test --features integration --profile integration --workspace --test integration" diff --git a/Cargo.toml b/Cargo.toml index ecf6848e04a1..c7e254728c31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,9 @@ lto = "fat" codegen-units = 1 # strip = "debuginfo" # TODO: or strip = true opt-level = 3 + +[profile.integration] +inherits = "test" +package.helix-core.opt-level = 2 +package.helix-tui.opt-level = 2 +package.helix-term.opt-level = 2 diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 05ceb874e84d..a1685fcfa956 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -277,10 +277,6 @@ impl Application { Ok(app) } - #[cfg(feature = "integration")] - async fn render(&mut self) {} - - #[cfg(not(feature = "integration"))] async fn render(&mut self) { let mut cx = crate::compositor::Context { editor: &mut self.editor, diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs index d0128edcaddf..bbf14fc2096c 100644 --- a/helix-term/tests/test/write.rs +++ b/helix-term/tests/test/write.rs @@ -70,7 +70,7 @@ async fn test_write_quit() -> anyhow::Result<()> { async fn test_write_concurrent() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; let mut command = String::new(); - const RANGE: RangeInclusive = 1..=5000; + const RANGE: RangeInclusive = 1..=1000; let mut app = helpers::AppBuilder::new() .with_file(file.path(), None) .build()?; From 0eba0db4a012e9961cf0c5dddf0a4b73f95431b2 Mon Sep 17 00:00:00 2001 From: Jaeho Choi Date: Mon, 6 Feb 2023 04:31:20 +0900 Subject: [PATCH 022/191] docs: Fix PowerShell runtime linking command (#5822) --- README.md | 3 ++- book/src/install.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60125e11800b..a26673219cb7 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,9 @@ elevated privileges - i.e. PowerShell or Cmd must be run as administrator. **PowerShell:** ```powershell -New-Item -ItemType SymbolicLink -Target "runtime" -Path "$Env:AppData\helix\runtime" +New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime" ``` +Note: "runtime" must be absolute path to the runtime directory. **Cmd:** diff --git a/book/src/install.md b/book/src/install.md index 792799d7a7f9..7df9e6c778ea 100644 --- a/book/src/install.md +++ b/book/src/install.md @@ -123,8 +123,9 @@ elevated privileges - i.e. PowerShell or Cmd must be run as administrator. **PowerShell:** ```powershell -New-Item -ItemType SymbolicLink -Target "runtime" -Path "$Env:AppData\helix\runtime" +New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime" ``` +Note: "runtime" must be the absolute path to the runtime directory. **Cmd:** From 9c98043c1cd6a8b92f35214007a90bb0f287beda Mon Sep 17 00:00:00 2001 From: Ethan Budd Date: Sun, 5 Feb 2023 16:02:36 -0600 Subject: [PATCH 023/191] Recognize .C and .H file types as cpp (#5808) --- languages.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages.toml b/languages.toml index c82bc155e041..5cde4167d6ff 100644 --- a/languages.toml +++ b/languages.toml @@ -190,7 +190,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "7175a6dd name = "cpp" scope = "source.cpp" injection-regex = "cpp" -file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino"] +file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H"] roots = [] comment-token = "//" language-server = { command = "clangd" } From 093b37d7e73e137869984f8b062989755bd1e2a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:42:45 -0600 Subject: [PATCH 024/191] build(deps): bump cachix/install-nix-action from 18 to 19 (#5855) Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 18 to 19. - [Release notes](https://github.com/cachix/install-nix-action/releases) - [Commits](https://github.com/cachix/install-nix-action/compare/v18...v19) --- updated-dependencies: - dependency-name: cachix/install-nix-action 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> --- .github/workflows/cachix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cachix.yml b/.github/workflows/cachix.yml index 20035678707a..bcdea3197dec 100644 --- a/.github/workflows/cachix.yml +++ b/.github/workflows/cachix.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - name: Install nix - uses: cachix/install-nix-action@v18 + uses: cachix/install-nix-action@v19 - name: Authenticate with Cachix uses: cachix/cachix-action@v12 From b97462ea9b77a0cb4b174de3202c12b5d33886cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:43:08 -0600 Subject: [PATCH 025/191] build(deps): bump unicode-segmentation from 1.10.0 to 1.10.1 (#5856) Bumps [unicode-segmentation](https://github.com/unicode-rs/unicode-segmentation) from 1.10.0 to 1.10.1. - [Release notes](https://github.com/unicode-rs/unicode-segmentation/releases) - [Commits](https://github.com/unicode-rs/unicode-segmentation/compare/v1.10.0...v1.10.1) --- updated-dependencies: - dependency-name: unicode-segmentation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 066af52830ad..0f1d9b48f9c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2288,9 +2288,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" From f3f8660d363f385c54f0afa9199be993ce50ab57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:43:28 -0600 Subject: [PATCH 026/191] build(deps): bump serde_json from 1.0.91 to 1.0.92 (#5857) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.91 to 1.0.92. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.91...v1.0.92) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f1d9b48f9c1..905fc1b6ab67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1866,9 +1866,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" dependencies = [ "itoa", "ryu", From 9b69f5010d24b0f39c57f6d79e424601e7e2bfea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:43:48 -0600 Subject: [PATCH 027/191] build(deps): bump anyhow from 1.0.68 to 1.0.69 (#5858) Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.68 to 1.0.69. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.68...1.0.69) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 905fc1b6ab67..7f59ca1dffb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "arc-swap" From 7f6a91fd63254ba735e7070652c3170be070a37c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:44:26 -0600 Subject: [PATCH 028/191] build(deps): bump ropey from 1.5.1 to 1.6.0 (#5859) Bumps [ropey](https://github.com/cessen/ropey) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/cessen/ropey/releases) - [Changelog](https://github.com/cessen/ropey/blob/master/CHANGELOG.md) - [Commits](https://github.com/cessen/ropey/compare/v1.5.1...v1.6.0) --- updated-dependencies: - dependency-name: ropey dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- helix-core/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f59ca1dffb3..1a76beed265b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1803,9 +1803,9 @@ dependencies = [ [[package]] name = "ropey" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f832915525613e83f275694cb8c184f5df13ca26a9ef0ea6ce736921964c8e" +checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed" dependencies = [ "smallvec", "str_indices", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 10de738f80b8..62ec87b485ca 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -17,7 +17,7 @@ integration = [] [dependencies] helix-loader = { version = "0.6", path = "../helix-loader" } -ropey = { version = "1.5.1", default-features = false, features = ["simd"] } +ropey = { version = "1.6.0", default-features = false, features = ["simd"] } smallvec = "1.10" smartstring = "1.0.1" unicode-segmentation = "1.10" From fb149133db1269f14bc37ef8583fc762e7f19781 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Mon, 6 Feb 2023 18:47:51 -0500 Subject: [PATCH 029/191] Make `fleet-dark` accurate to the official theme (#5605) --- runtime/themes/fleet_dark.toml | 251 +++++++++++++++++++-------------- 1 file changed, 142 insertions(+), 109 deletions(-) diff --git a/runtime/themes/fleet_dark.toml b/runtime/themes/fleet_dark.toml index 0fc9e812d244..83caf8f30d66 100644 --- a/runtime/themes/fleet_dark.toml +++ b/runtime/themes/fleet_dark.toml @@ -4,128 +4,161 @@ # Original author: @krfl # Contributors: # @matoous +# @kirawi -"attribute" = "green" -"type" = "light_blue" -"type.enum.variant" = "purple" -"constructor" = "yellow" -"constant" = "cyan" +"attribute" = "Lime" + +"type" = "Blue" +"type.return" = "Blue Light" +"type.parameter" = "Blue Light" +"constructor" = "Yellow" + +"constant" = "Violet" # "constant.builtin" = {} # .boolean -"constant.builtin.boolean" = "yellow" -"constant.character" = "yellow" -"constant.characted.escape" = "light" -"constant.numeric" = "yellow" -"string" = "pink" -"string.regexp" = "light" -"string.special" = { fg = "yellow", modifiers = ["underlined"] } #.path / .url / .symbol -"comment" = "light_gray" # .line +"constant.builtin.boolean" = "Cyan" +"constant.character" = "Yellow" +"constant.character.escape" = "Cyan" +"constant.numeric" = "Yellow" +"string" = "Pink" +"string.regexp" = "Cyan" +"string.special" = { fg = "Yellow", modifiers = ["underlined"] } #.path / .url / .symbol + +"comment" = "Gray 90" # .line # "comment.block" = {} # .documentation -"variable" = "light" # .builtin -"variable.builtin" = { fg = "red", modifiers = ["underlined"] } -"variable.parameter" = "light" +"variable" = "Gray 120" # .builtin +"variable.builtin" = { fg = "Coral" } # "variable.other" = {} # .member -"variable.other.member" = "purple" -"label" = "yellow" -"punctuation" = "light" # .delimiter / .bracket -"keyword" = "cyan" # .operator / .directive / .function -# "keyword.control" = "cyan" # .conditional / .repeat / .import / .return / .exception -"keyword.control.exception" = "purple" -"operator" = "light" -"function" = "yellow" -"function.macro" = "green" -"function.builtin" = "green" -"function.special" = "green" -"function.method" = "light" +"variable.other.member" = "Violet" +"label" = "Yellow" +"keyword" = "Cyan" # .operator / .directive / .function +"function" = "Yellow" +"function.declaration" = "#EFEFEF" +"function.macro" = "Lime" +"function.builtin" = "Lime" +"function.special" = "Lime" #"function.declaration.method" = { fg = "lightest", modifiers = ["bold"] } #depends on #4892 -"tag" = "light_blue" -"special" = "green" -"namespace" = "light" +"tag" = "Blue" +"special" = "Lime" +"namespace" = "Blue" # used in theming # "markup" = {} # .normal / .quote / .raw # "markup.normal" = {} # .completion / .hover -"markup.bold" = { fg = "lightest", modifiers = ["bold"] } +"markup.bold" = { modifiers = ["bold"] } "markup.italic" = { modifiers = ["italic"] } "markup.strikethrough" = { modifiers = ["crossed_out"] } -"markup.heading" = { fg = "cyan", modifiers = ["bold"] } # .marker / .1 / .2 / .3 / .4 / .5 / .6 -"markup.list" = "pink" # .unnumbered / .numbered -"markup.list.numbered" = "cyan" -"markup.list.unnumbered" = "cyan" +"markup.heading" = { fg = "Cyan", modifiers = ["bold"] } # .marker / .1 / .2 / .3 / .4 / .5 / .6 +"markup.list" = "Pink" # .unnumbered / .numbered +"markup.list.numbered" = "Cyan" +"markup.list.unnumbered" = "Cyan" # "markup.link" = "green" -"markup.link.url" = { fg = "pink", modifiers = ['italic', 'underlined'] } -"markup.link.text" = "cyan" -"markup.link.label" = "purple" -"markup.quote" = "pink" -"markup.raw" = "pink" -"markup.raw.inline" = "cyan" # .completion / .hover -"markup.raw.block" = "pink" - -"diff.plus" = "diff_plus" -"diff.minus" = "red_accent" -"diff.delta" = "blue_accent" +"markup.link.url" = { fg = "Pink", modifiers = ['italic', 'underlined'] } +"markup.link.text" = "Cyan" +"markup.link.label" = "Purple 20" +"markup.quote" = "Pink" +"markup.raw" = "Pink" +"markup.raw.inline" = "Cyan" # .completion / .hover +"markup.raw.block" = "#EB83E2" + +"diff.plus" = "Green 50" +"diff.minus" = "Red 50" +"diff.delta" = "Blue 80" # ui specific -"ui.background" = { bg = "background" } # .separator -"ui.cursor" = { bg = "dark_gray", modifiers = ["reversed"] } # .insert / .select / .match / .primary -"ui.cursor.match" = { fg = "light", bg = "selection" } # .insert / .select / .match / .primary -"ui.cursorline" = { bg = "darker" } -"ui.linenr" = "dark_gray" -"ui.linenr.selected" = { fg = "light", bg = "darker" } -"ui.statusline" = { fg = "light", bg = "darker" } # .inactive / .normal / .insert / .select -"ui.statusline.inactive" = { fg = "dark", bg = "darker" } -"ui.statusline.normal" = { fg = "lightest", bg = "darker"} -"ui.statusline.insert" = { fg = "lightest", bg = "blue_accent" } -"ui.statusline.select" = { fg = "lightest", bg = "orange_accent" } -"ui.popup" = { fg = "light", bg = "darkest" } # .info -"ui.window" = { fg = "dark", bg = "darkest" } -"ui.help" = { fg = "light", bg = "darkest" } -"ui.text" = "light" # .focus / .info -"ui.text.focus" = { fg = "lightest", bg = "focus" } -"ui.virtual" = "dark" # .whitespace -"ui.virtual.ruler" = { bg = "darker"} -"ui.menu" = { fg = "light", bg = "darkest" } # .selected -"ui.menu.selected" = { fg = "lightest", bg = "focus" } # .selected -"ui.selection" = { bg = "darker" } # .primary -"ui.selection.primary" = { bg = "selection" } -"hint" = "blue" -"info" = "yellow_accent" -"warning" = "orange_accent" -"error" = "red_error" -"diagnostic" = { modifiers = [] } -"diagnostic.hint" = { underline = { color = "light", style = "curl" } } -"diagnostic.info" = { underline = { color = "blue_accent", style = "curl" } } -"diagnostic.warning" = { underline = { color = "yellow_accent", style = "curl" } } -"diagnostic.error" = { underline = { color = "red_error", style = "curl" } } +"ui.background" = { bg = "Gray 10" } # .separator +"ui.statusline" = { fg = "Gray 120", bg = "Gray 10" } # .inactive / .normal / .insert / .select +# "ui.statusline.normal" = { fg = "lightest", bg = "darker"} +# "ui.statusline.insert" = { fg = "lightest", bg = "blue_accent" } +# "ui.statusline.select" = { fg = "lightest", bg = "orange_accent" } + +"ui.cursor" = { modifiers = ["reversed"] } # .insert / .select / .match / .primary +"ui.cursor.match" = { bg = "Blue 30" } # .insert / .select / .match / .primary +"ui.selection" = { bg = "Gray 50" } # .primary +"ui.selection.primary" = { bg = "Blue 40" } + +"ui.cursorline" = { bg = "Gray 20" } +"ui.linenr" = "Gray 70" +"ui.linenr.selected" = "Gray 110" + +"ui.popup" = { fg = "Gray 120", bg = "Gray 20" } # .info +"ui.window" = { fg = "Gray 50" } +"ui.help" = { fg = "Gray 120", bg = "Gray 20" } +"ui.menu" = { fg = "Gray 120", bg = "Gray 20" } # .selected +"ui.menu.selected" = { fg = "White", bg = "Blue 40" } # .selected +# Calculated as #ffffff with 30% opacity +"ui.menu.scroll" = { fg = "#dfdfdf" } + +"ui.text" = "Gray 120" # .focus / .info +"ui.text.focus" = { fg = "White", bg = "Blue 40" } + +"ui.virtual" = "Gray 80" # .whitespace +# "ui.virtual.ruler" = { bg = "darker"} + +"hint" = "Gray 80" +"info" = "#A366C4" +"warning" = "#FACb66" +"error" = "#FF5269" + +"diagnostic.hint" = { underline = { color = "Gray 80", style = "line" } } +"diagnostic.info" = { underline = { color = "#A366C4", style = "line" } } +"diagnostic.warning" = { underline = { color = "#FACB66", style = "line" } } +"diagnostic.error" = { underline = { color = "#FF5269", style = "line" } } [palette] -background = "#181818" -darkest = "#1e1e1e" -darker = "#292929" -dark = "#898989" - -light = "#d6d6dd" -lightest = "#ffffff" - -dark_gray = "#535353" -light_gray = "#6d6d6d" -purple = "#a390f0" -light_blue = "#7dbeff" -blue = "#52a7f6" -pink = "#d898d8" -green = "#afcb85" -cyan = "#78d0bd" -orange = "#efb080" -yellow = "#e5c995" -red = "#CC7C8A" - -blue_accent = "#2197F3" -pink_accent = "#E44C7A" -green_accent = "#00AF99" -orange_accent = "#EE7F25" -yellow_accent = "#DEA407" -red_accent = "#F44747" - -red_error = "#EB5F6A" -selection = "#1F3661" -diff_plus = "#5A9F81" -focus = "#204474" +"White" = "#ffffff" +"Gray 120" = "#d1d1d1" +"Gray 110" = "#c2c2c2" +"Gray 100" = "#a0a0a0" +"Gray 90" = "#898989" +"Gray 80" = "#767676" +"Gray 70" = "#5d5d5d" +"Gray 60" = "#484848" +"Gray 50" = "#383838" +"Gray 40" = "#333333" +"Gray 30" = "#2d2d2d" +"Gray 20" = "#292929" +"Gray 10" = "#181818" +"Black" = "#000000" +"Blue 110" = "#6daaf7" +"Blue 100" = "#4d9bf8" +"Blue 90" = "#3691f9" +"Blue 80" = "#1a85f6" +"Blue 70" = "#0273eb" +"Blue 60" = "#0c6ddd" +"Blue 50" = "#195eb5" +"Blue 40" = "#194176" +"Blue 30" = "#163764" +"Blue 20" = "#132c4f" +"Blue 10" = "#0b1b32" +"Red 80" = "#ec7388" +"Red 70" = "#ea4b67" +"Red 60" = "#d93953" +"Red 50" = "#ce364d" +"Red 40" = "#c03248" +"Red 30" = "#a72a3f" +"Red 20" = "#761b2d" +"Red 10" = "#390813" +"Green 50" = "#4ca988" +"Green 40" = "#3ea17f" +"Green 30" = "#028764" +"Green 20" = "#134939" +"Green 10" = "#081f19" +"Yellow 60" = "#f8ab17" +"Yellow 50" = "#e1971b" +"Yellow 40" = "#b5791f" +"Yellow 30" = "#7c511a" +"Yellow 20" = "#5a3a14" +"Yellow 10" = "#281806" +"Purple 20" = "#c07bf3" +"Purple 10" = "#b35def" + +"Blue" = "#87C3FF" +"Blue Light" = "#ADD1DE" +"Coral" = "#CC7C8A" +"Cyan" = "#82D2CE" +"Cyan Dark" = "#779E9E" +"Lime" = "#A8CC7C" +"Orange" = "#E09B70" +"Pink" = "#E394DC" +"Violet" = "#AF9CFF" +"Yellow" = "#EBC88D" From f71f27f804016aa7a8e3517cebeef0861088fb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Gonz=C3=A1lez?= Date: Tue, 7 Feb 2023 00:39:49 -0300 Subject: [PATCH 030/191] Add Darcula Solid theme (#5778) * Add Darcula Solid theme. * Update darcula solid theme. * Derive darcula-solid theme from original darcula --- runtime/themes/darcula-solid.toml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 runtime/themes/darcula-solid.toml diff --git a/runtime/themes/darcula-solid.toml b/runtime/themes/darcula-solid.toml new file mode 100644 index 000000000000..5feac234eea9 --- /dev/null +++ b/runtime/themes/darcula-solid.toml @@ -0,0 +1,19 @@ +# Original source and more info: https://github.com/jesusmgg/darcula-solid-helix + +inherits = "darcula" + +"ui.background.separator" = { bg = "grey01" } +"ui.menu.scroll" = { fg = "grey02", bg = "grey00" } +"ui.popup" = { fg = "grey03", bg = "grey02" } +"ui.window" = { bg = "grey00" } +"ui.selection" = { bg = "blue" } +"ui.cursorline.secondary" = { bg = "grey03" } + +[palette] +grey00 = "#101010" +grey01 = "#1f1f1f" +grey02 = "#323232" +grey03 = "#555555" +grey04 = "#a8a8a8" + +blue = "#104158" From 23ed8c12f17c28ee888b5560d0ab2a9f9cd74dc9 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 6 Feb 2023 21:43:48 -0600 Subject: [PATCH 031/191] Select change range for goto_first/last_change commands (#5206) This matches the behavior from 42ad1a9e043e2e0fb148924ff79b9abbe06907ae but for the first and last change. The selection rules are the same as for goto_next/prev_change: additions and modifications select the added and modified range while deletions are represented with a point. --- helix-term/src/commands.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d1dc92236cf0..157d19f730ed 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2927,14 +2927,6 @@ fn exit_select_mode(cx: &mut Context) { } } -fn goto_pos(editor: &mut Editor, pos: usize) { - let (view, doc) = current!(editor); - - push_jump(view, doc); - doc.set_selection(view.id, Selection::point(pos)); - align_view(doc, view, Align::Center); -} - fn goto_first_diag(cx: &mut Context) { let (view, doc) = current!(cx.editor); let selection = match doc.diagnostics().first() { @@ -3012,7 +3004,7 @@ fn goto_last_change(cx: &mut Context) { fn goto_first_change_impl(cx: &mut Context, reverse: bool) { let editor = &mut cx.editor; - let (_, doc) = current!(editor); + let (view, doc) = current!(editor); if let Some(handle) = doc.diff_handle() { let hunk = { let hunks = handle.hunks(); @@ -3024,8 +3016,8 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) { hunks.nth_hunk(idx) }; if hunk != Hunk::NONE { - let pos = doc.text().line_to_char(hunk.after.start as usize); - goto_pos(editor, pos) + let range = hunk_range(hunk, doc.text().slice(..)); + doc.set_selection(view.id, Selection::single(range.anchor, range.head)); } } } @@ -3069,14 +3061,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) { return range; }; let hunk = hunks.nth_hunk(hunk_idx); - - let hunk_start = doc_text.line_to_char(hunk.after.start as usize); - let hunk_end = if hunk.after.is_empty() { - hunk_start + 1 - } else { - doc_text.line_to_char(hunk.after.end as usize) - }; - let new_range = Range::new(hunk_start, hunk_end); + let new_range = hunk_range(hunk, doc_text); if editor.mode == Mode::Select { let head = if new_range.head < range.anchor { new_range.anchor @@ -3096,6 +3081,20 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) { cx.editor.last_motion = Some(Motion(Box::new(motion))); } +/// Returns the [Range] for a [Hunk] in the given text. +/// Additions and modifications cover the added and modified ranges. +/// Deletions are represented as the point at the start of the deletion hunk. +fn hunk_range(hunk: Hunk, text: RopeSlice) -> Range { + let anchor = text.line_to_char(hunk.after.start as usize); + let head = if hunk.after.is_empty() { + anchor + 1 + } else { + text.line_to_char(hunk.after.end as usize) + }; + + Range::new(anchor, head) +} + pub mod insert { use super::*; pub type Hook = fn(&Rope, &Selection, char) -> Option; From c704701714236f9de9fdb03823b6adb9227be744 Mon Sep 17 00:00:00 2001 From: Mike Trinkala Date: Tue, 7 Feb 2023 15:05:27 -0800 Subject: [PATCH 032/191] Short-circuit the word and treesitter object movement commands (#5851) The loop always iterates the number of times the user specified even if the beginning/end of the document is reached. For an extreme demonstration try the following commands, Helix will hang for several seconds. 100000000w 100000000]c --- helix-core/src/movement.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 11c12a6f0b25..8e6b63066201 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -227,9 +227,15 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar }; // Do the main work. - (0..count).fold(start_range, |r, _| { - slice.chars_at(r.head).range_to_target(target, r) - }) + let mut range = start_range; + for _ in 0..count { + let next_range = slice.chars_at(range.head).range_to_target(target, range); + if range == next_range { + break; + } + range = next_range; + } + range } pub fn move_prev_paragraph( @@ -251,6 +257,7 @@ pub fn move_prev_paragraph( let mut lines = slice.lines_at(line); lines.reverse(); let mut lines = lines.map(rope_is_line_ending).peekable(); + let mut last_line = line; for _ in 0..count { while lines.next_if(|&e| e).is_some() { line -= 1; @@ -258,6 +265,10 @@ pub fn move_prev_paragraph( while lines.next_if(|&e| !e).is_some() { line -= 1; } + if line == last_line { + break; + } + last_line = line; } let head = slice.line_to_char(line); @@ -293,6 +304,7 @@ pub fn move_next_paragraph( line += 1; } let mut lines = slice.lines_at(line).map(rope_is_line_ending).peekable(); + let mut last_line = line; for _ in 0..count { while lines.next_if(|&e| !e).is_some() { line += 1; @@ -300,6 +312,10 @@ pub fn move_next_paragraph( while lines.next_if(|&e| e).is_some() { line += 1; } + if line == last_line { + break; + } + last_line = line; } let head = slice.line_to_char(line); let anchor = if behavior == Movement::Move { @@ -523,7 +539,14 @@ pub fn goto_treesitter_object( // head of range should be at beginning Some(Range::new(start_char, end_char)) }; - (0..count).fold(range, |range, _| get_range(range).unwrap_or(range)) + let mut last_range = range; + for _ in 0..count { + match get_range(last_range) { + Some(r) if r != last_range => last_range = r, + _ => break, + } + } + last_range } #[cfg(test)] From fce30c0da0eb222bf20091752b599df723ebf559 Mon Sep 17 00:00:00 2001 From: Surendrajat Date: Wed, 8 Feb 2023 04:47:48 +0530 Subject: [PATCH 033/191] xml: add mobileconfig & plist file types (#5863) --- languages.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages.toml b/languages.toml index 5cde4167d6ff..f407ce3496ee 100644 --- a/languages.toml +++ b/languages.toml @@ -1967,7 +1967,7 @@ source = { git = "https://github.com/Unoqwy/tree-sitter-kdl", rev = "e1cd292c6d1 name = "xml" scope = "source.xml" injection-regex = "xml" -file-types = ["xml"] +file-types = ["xml", "mobileconfig", "plist"] indent = { tab-width = 2, unit = " " } roots = [] From 05c52072653f4c276750e3c651b9c4e030e912d2 Mon Sep 17 00:00:00 2001 From: Matthew Toohey Date: Tue, 7 Feb 2023 18:24:26 -0500 Subject: [PATCH 034/191] feat: add pem language (#5797) --- book/src/generated/lang-support.md | 1 + languages.toml | 12 ++++++++++++ runtime/queries/pem/highlights.scm | 7 +++++++ 3 files changed, 20 insertions(+) create mode 100644 runtime/queries/pem/highlights.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 99fe82b33159..0ad6dedb683b 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -93,6 +93,7 @@ | openscad | βœ“ | | | `openscad-lsp` | | org | βœ“ | | | | | pascal | βœ“ | βœ“ | | `pasls` | +| pem | βœ“ | | | | | perl | βœ“ | βœ“ | βœ“ | | | php | βœ“ | βœ“ | βœ“ | `intelephense` | | ponylang | βœ“ | βœ“ | βœ“ | | diff --git a/languages.toml b/languages.toml index f407ce3496ee..2fa2c3f5f516 100644 --- a/languages.toml +++ b/languages.toml @@ -2134,3 +2134,15 @@ grammar = "xml" '"' = '"' "'" = "'" "<" = ">" + +[[language]] +name = "pem" +scope = "source.pem" +file-types = ["pem", "cert", "crt"] +injection-regex = "pem" +roots = [] +grammar = "pem" + +[[grammar]] +name = "pem" +source = { git = "https://github.com/mtoohey31/tree-sitter-pem", rev = "be67a4330a1aa507c7297bc322204f936ec1132c" } diff --git a/runtime/queries/pem/highlights.scm b/runtime/queries/pem/highlights.scm new file mode 100644 index 000000000000..ee7a40433366 --- /dev/null +++ b/runtime/queries/pem/highlights.scm @@ -0,0 +1,7 @@ +(label) @constant + +(preeb) @keyword +(posteb) @keyword + +(base64pad) @string.special.symbol +(laxbase64text) @string From 00ecc556a8d3f7efe115f3d826c916ffacf6ab2e Mon Sep 17 00:00:00 2001 From: gavincrawford <94875769+gavincrawford@users.noreply.github.com> Date: Wed, 8 Feb 2023 00:17:34 -0700 Subject: [PATCH 035/191] Fix Go select indenting (#5713) * Fix Go indenting * Fix bracket outdent predicate * Fix brace indenting (again) --- runtime/queries/go/indents.scm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/queries/go/indents.scm b/runtime/queries/go/indents.scm index f72ec9e827b0..e439a9055fca 100644 --- a/runtime/queries/go/indents.scm +++ b/runtime/queries/go/indents.scm @@ -20,8 +20,9 @@ ] @indent [ - "case" - "}" "]" ")" ] @outdent + +((_ "}" @outdent) @outer (#not-kind-eq? @outer "select_statement")) +(communication_case) @extend From f386ff795d4833cce02d57de921999284aadded3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Delafargue?= Date: Wed, 8 Feb 2023 17:09:19 +0100 Subject: [PATCH 036/191] Check for external file modifications when writing (#5805) `:write` and other file-saving commands now check the file modification time before writing to protect against overwriting external changes. Co-authored-by: Gustavo Noronha Silva Co-authored-by: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Co-authored-by: Pascal Kuthe --- helix-term/tests/test/commands.rs | 2 +- helix-term/tests/test/helpers.rs | 6 ++++++ helix-term/tests/test/write.rs | 36 +++++++++++++++++++++++++++++-- helix-view/src/document.rs | 25 ++++++++++++++++++++- 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index da2e020efcee..2ca9e3957a7b 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -67,7 +67,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> { const RANGE: RangeInclusive = 1..=1000; for i in RANGE { - let cmd = format!("%c{}:w", i); + let cmd = format!("%c{}:w!", i); command.push_str(&cmd); } diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 8755b60f06ca..fb12ef12cff2 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -319,6 +319,12 @@ impl AppBuilder { } } +pub async fn run_event_loop_until_idle(app: &mut Application) { + let (_, rx) = tokio::sync::mpsc::unbounded_channel(); + let mut rx_stream = UnboundedReceiverStream::new(rx); + app.event_loop_until_idle(&mut rx_stream).await; +} + pub fn assert_file_has_content(file: &mut File, content: &str) -> anyhow::Result<()> { file.flush()?; file.sync_all()?; diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs index bbf14fc2096c..81459b2fe846 100644 --- a/helix-term/tests/test/write.rs +++ b/helix-term/tests/test/write.rs @@ -1,5 +1,5 @@ use std::{ - io::{Read, Write}, + io::{Read, Seek, SeekFrom, Write}, ops::RangeInclusive, }; @@ -37,6 +37,38 @@ async fn test_write() -> anyhow::Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn test_overwrite_protection() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + let mut app = helpers::AppBuilder::new() + .with_file(file.path(), None) + .build()?; + + helpers::run_event_loop_until_idle(&mut app).await; + + file.as_file_mut() + .write_all(helpers::platform_line("extremely important content").as_bytes())?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + test_key_sequence(&mut app, Some(":x"), None, false).await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + file.seek(SeekFrom::Start(0))?; + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + + assert_eq!( + helpers::platform_line("extremely important content"), + file_content + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn test_write_quit() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; @@ -76,7 +108,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> { .build()?; for i in RANGE { - let cmd = format!("%c{}:w", i); + let cmd = format!("%c{}:w!", i); command.push_str(&cmd); } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 798b54006d97..d308d013ff5f 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -19,6 +19,7 @@ use std::future::Future; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; +use std::time::SystemTime; use helix_core::{ encoding, @@ -135,6 +136,10 @@ pub struct Document { pub savepoint: Option, + // Last time we wrote to the file. This will carry the time the file was last opened if there + // were no saves. + last_saved_time: SystemTime, + last_saved_revision: usize, version: i32, // should be usize? pub(crate) modified_since_accessed: bool, @@ -160,6 +165,7 @@ impl fmt::Debug for Document { .field("changes", &self.changes) .field("old_state", &self.old_state) // .field("history", &self.history) + .field("last_saved_time", &self.last_saved_time) .field("last_saved_revision", &self.last_saved_revision) .field("version", &self.version) .field("modified_since_accessed", &self.modified_since_accessed) @@ -382,6 +388,7 @@ impl Document { version: 0, history: Cell::new(History::default()), savepoint: None, + last_saved_time: SystemTime::now(), last_saved_revision: 0, modified_since_accessed: false, language_server: None, @@ -577,9 +584,11 @@ impl Document { let encoding = self.encoding; + let last_saved_time = self.last_saved_time; + // We encode the file according to the `Document`'s encoding. let future = async move { - use tokio::fs::File; + use tokio::{fs, fs::File}; if let Some(parent) = path.parent() { // TODO: display a prompt asking the user if the directories should be created if !parent.exists() { @@ -591,6 +600,17 @@ impl Document { } } + // Protect against overwriting changes made externally + if !force { + if let Ok(metadata) = fs::metadata(&path).await { + if let Ok(mtime) = metadata.modified() { + if last_saved_time < mtime { + bail!("file modified by an external process, use :w! to overwrite"); + } + } + } + } + let mut file = File::create(&path).await?; to_writer(&mut file, encoding, &text).await?; @@ -668,6 +688,8 @@ impl Document { self.append_changes_to_history(view); self.reset_modified(); + self.last_saved_time = SystemTime::now(); + self.detect_indent_and_line_ending(); match provider_registry.get_diff_base(&path) { @@ -1016,6 +1038,7 @@ impl Document { rev ); self.last_saved_revision = rev; + self.last_saved_time = SystemTime::now(); } /// Get the document's latest saved revision. From 882fa11d17c27fbda3426023b898c0e7acb6cd94 Mon Sep 17 00:00:00 2001 From: zSchoen Date: Wed, 8 Feb 2023 22:08:10 +0100 Subject: [PATCH 037/191] Add `Containerfile` file-type for dockerfile language (#5873) --- languages.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/languages.toml b/languages.toml index 2fa2c3f5f516..9ecd1b789b03 100644 --- a/languages.toml +++ b/languages.toml @@ -1072,8 +1072,8 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-scala", rev = "f6bb name = "dockerfile" scope = "source.dockerfile" injection-regex = "docker|dockerfile" -roots = ["Dockerfile"] -file-types = ["Dockerfile", "dockerfile"] +roots = ["Dockerfile", "Containerfile"] +file-types = ["Dockerfile", "dockerfile", "Containerfile", "containerfile"] comment-token = "#" indent = { tab-width = 2, unit = " " } language-server = { command = "docker-langserver", args = ["--stdio"] } From 8e2eab1a6fd90e3989ac807f675b022c540ad014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 9 Feb 2023 11:04:44 +0900 Subject: [PATCH 038/191] Also run build checks on merge queue --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d6fcb3e8426..8f3b778add26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + merge_group: schedule: - cron: '00 01 * * *' From bd14f5a72cec6932cf1792d62e420349eaec60db Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 9 Feb 2023 03:22:38 +0100 Subject: [PATCH 039/191] update bug report template (#5872) --- .github/ISSUE_TEMPLATE/bug_report.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c67deb69046f..47fd3fe8a51c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -32,7 +32,7 @@ body: id: helix-log attributes: label: Helix log - description: See `hx -h` for log file path + description: See `hx -h` for log file path. If you can reproduce the issue run `RUST_BACKTRACE=1 hx -vv` to generate a more detailed log file. value: |
~/.cache/helix/helix.log @@ -61,7 +61,8 @@ body: label: Helix Version description: > Helix version (`hx -V` if using a release, `git describe` if building - from master) - placeholder: "helix 0.6.0 (c0dbd6dc)" + from master). + **Make sure that you are using the [latest helix release](https://github.com/helix-editor/helix/releases) or a newer master build** + placeholder: "helix 22.12 (5eaa6d97)" validations: required: true From e474779c8729c36335b76badc98d8211829122d2 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 9 Feb 2023 03:24:31 +0100 Subject: [PATCH 040/191] bump msrv to 1.63 (#5570) * bump msrv to 1.63 * resolve new complex type clippy lints --- .github/workflows/build.yml | 16 ++++------------ .github/workflows/msrv-rust-toolchain.toml | 3 --- helix-term/src/commands.rs | 4 +++- helix-term/src/compositor.rs | 1 + helix-term/src/job.rs | 7 +++++-- helix-term/src/ui/editor.rs | 4 ++-- helix-term/src/ui/menu.rs | 4 +++- helix-term/src/ui/picker.rs | 8 ++++++-- helix-term/src/ui/prompt.rs | 11 +++++++---- rust-toolchain.toml | 2 +- 10 files changed, 32 insertions(+), 28 deletions(-) delete mode 100644 .github/workflows/msrv-rust-toolchain.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f3b778add26..44d267884d23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,19 +10,11 @@ on: jobs: check: - name: Check + name: Check (msrv) runs-on: ubuntu-latest - strategy: - matrix: - rust: [stable, msrv] steps: - name: Checkout sources uses: actions/checkout@v3 - - - name: Use MSRV rust toolchain - if: matrix.rust == 'msrv' - run: cp .github/workflows/msrv-rust-toolchain.toml rust-toolchain.toml - - name: Install stable toolchain uses: helix-editor/rust-toolchain@v1 with: @@ -45,7 +37,7 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.61 + uses: dtolnay/rust-toolchain@1.63 - uses: Swatinem/rust-cache@v2 @@ -74,7 +66,7 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.61 + uses: dtolnay/rust-toolchain@1.63 with: components: rustfmt, clippy @@ -99,7 +91,7 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.61 + uses: dtolnay/rust-toolchain@1.63 - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/msrv-rust-toolchain.toml b/.github/workflows/msrv-rust-toolchain.toml deleted file mode 100644 index b169d31e6f55..000000000000 --- a/.github/workflows/msrv-rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "1.61.0" -components = ["rustfmt", "rust-src"] diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 157d19f730ed..e6cbed2ce34c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -73,13 +73,15 @@ use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; use ignore::{DirEntry, WalkBuilder, WalkState}; use tokio_stream::wrappers::UnboundedReceiverStream; +pub type OnKeyCallback = Box; + pub struct Context<'a> { pub register: Option, pub count: Option, pub editor: &'a mut Editor, pub callback: Option, - pub on_next_key_callback: Option>, + pub on_next_key_callback: Option, pub jobs: &'a mut Jobs, } diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 2e4a2e20e9f4..bcb3e44904e4 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -7,6 +7,7 @@ use helix_view::graphics::{CursorKind, Rect}; use tui::buffer::Buffer as Surface; pub type Callback = Box; +pub type SyncCallback = Box; // Cursive-inspired pub enum EventResult { diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index 2888b6eb1565..19f2521a5231 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -5,9 +5,12 @@ use crate::compositor::Compositor; use futures_util::future::{BoxFuture, Future, FutureExt}; use futures_util::stream::{FuturesUnordered, StreamExt}; +pub type EditorCompositorCallback = Box; +pub type EditorCallback = Box; + pub enum Callback { - EditorCompositor(Box), - Editor(Box), + EditorCompositor(EditorCompositorCallback), + Editor(EditorCallback), } pub type JobFuture = BoxFuture<'static, anyhow::Result>>; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f297b44eb08b..493f8d501923 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1,5 +1,5 @@ use crate::{ - commands, + commands::{self, OnKeyCallback}, compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, @@ -37,7 +37,7 @@ use super::{document::LineDecoration, lsp::SignatureHelp}; pub struct EditorView { pub keymaps: Keymaps, - on_next_key: Option>, + on_next_key: Option, pseudo_pending: Vec, last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 3ae007039294..8aa10c08b919 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -43,6 +43,8 @@ impl Item for PathBuf { } } +pub type MenuCallback = Box, MenuEvent)>; + pub struct Menu { options: Vec, editor_data: T::Data, @@ -55,7 +57,7 @@ pub struct Menu { widths: Vec, - callback_fn: Box, MenuEvent)>, + callback_fn: MenuCallback, scroll: usize, size: (u16, u16), diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 5190fc531d55..803e2d65bb78 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -70,6 +70,8 @@ impl From for PathOrId { } } +type FileCallback = Box Option>; + /// File path and range of lines (used to align and highlight lines) pub type FileLocation = (PathOrId, Option<(usize, usize)>); @@ -80,7 +82,7 @@ pub struct FilePicker { preview_cache: HashMap, read_buffer: Vec, /// Given an item in the picker, return the file path and line number to display. - file_fn: Box Option>, + file_fn: FileCallback, } pub enum CachedPreview { @@ -394,6 +396,8 @@ impl Ord for PickerMatch { } } +type PickerCallback = Box; + pub struct Picker { options: Vec, editor_data: T::Data, @@ -415,7 +419,7 @@ pub struct Picker { /// Constraints for tabular formatting widths: Vec, - callback_fn: Box, + callback_fn: PickerCallback, } impl Picker { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 5fb6745a90e5..f438231fa9c0 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -14,8 +14,11 @@ use helix_view::{ Editor, }; -pub type Completion = (RangeFrom, Cow<'static, str>); type PromptCharHandler = Box; +pub type Completion = (RangeFrom, Cow<'static, str>); +type CompletionFn = Box Vec>; +type CallbackFn = Box; +pub type DocFn = Box Option>>; pub struct Prompt { prompt: Cow<'static, str>, @@ -25,9 +28,9 @@ pub struct Prompt { selection: Option, history_register: Option, history_pos: Option, - completion_fn: Box Vec>, - callback_fn: Box, - pub doc_fn: Box Option>>, + completion_fn: CompletionFn, + callback_fn: CallbackFn, + pub doc_fn: DocFn, next_char_handler: Option, } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b169d31e6f55..ace4f5f96e55 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.61.0" +channel = "1.63.0" components = ["rustfmt", "rust-src"] From 8a602995fa71bee009c0ab1e67b78828a731ca75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 9 Feb 2023 11:44:14 +0900 Subject: [PATCH 041/191] Address new clippy lints --- helix-term/src/commands/typed.rs | 5 +++-- helix-term/src/ui/document.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 1fd11b65de5e..0cc1b7432978 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,3 +1,4 @@ +use std::fmt::Write; use std::ops::Deref; use crate::job::Job; @@ -1103,7 +1104,7 @@ fn get_character_info( codepoint }; - unicode.push_str(&format!("{codepoint:0>4x}")); + write!(unicode, "{codepoint:0>4x}").unwrap(); } unicode.push(')'); @@ -1144,7 +1145,7 @@ fn get_character_info( } for byte in &bytes[current_byte..] { - hex.push_str(&format!(" {byte:0>2x}")); + write!(hex, " {byte:0>2x}").unwrap(); } current_byte = bytes.len(); diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index 56377d1ec455..ddbb2cf30670 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -419,7 +419,7 @@ impl<'a> TextRenderer<'a> { // TODO special rendering for other whitespaces? Grapheme::Other { ref g } if g == " " => &self.space, Grapheme::Other { ref g } if g == "\u{00A0}" => &self.nbsp, - Grapheme::Other { ref g } => &*g, + Grapheme::Other { ref g } => g, Grapheme::Newline => &self.newline, }; From 7ebcf4e919a27b60610ba8f3a24a213282eb9ee6 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 9 Feb 2023 08:19:29 +0100 Subject: [PATCH 042/191] properly handle LSP position encoding (#5711) * properly handle LSP position encoding * add debug assertion to Transaction::change * Apply suggestions from code review Co-authored-by: Michael Davis --------- Co-authored-by: Michael Davis --- helix-core/src/line_ending.rs | 7 +++ helix-core/src/transaction.rs | 5 ++ helix-lsp/src/client.rs | 2 +- helix-lsp/src/lib.rs | 112 ++++++++++++++++++++++++++++------ 4 files changed, 107 insertions(+), 19 deletions(-) diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index 09e9252306e7..953d567d5f10 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -203,6 +203,13 @@ pub fn line_end_char_index(slice: &RopeSlice, line: usize) -> usize { .unwrap_or(0) } +pub fn line_end_byte_index(slice: &RopeSlice, line: usize) -> usize { + slice.line_to_byte(line + 1) + - get_line_ending(&slice.line(line)) + .map(|le| le.as_str().len()) + .unwrap_or(0) +} + /// Fetches line `line_idx` from the passed rope slice, sans any line ending. pub fn line_without_line_ending<'a>(slice: &'a RopeSlice, line_idx: usize) -> RopeSlice<'a> { let start = slice.line_to_char(line_idx); diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 482fd6d97e5e..d2f4de07dbe7 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -481,6 +481,11 @@ impl Transaction { for (from, to, tendril) in changes { // Verify ranges are ordered and not overlapping debug_assert!(last <= from); + // Verify ranges are correct + debug_assert!( + from <= to, + "Edit end must end before it starts (should {from} <= {to})" + ); // Retain from last "to" to current "from" changeset.retain(from - last); diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 6827f568d986..365c6990177f 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -104,7 +104,7 @@ impl Client { server_tx, request_counter: AtomicU64::new(0), capabilities: OnceCell::new(), - offset_encoding: OffsetEncoding::Utf8, + offset_encoding: OffsetEncoding::Utf16, config, req_timeout, diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 8418896cbb73..bcaff10a91ef 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -57,6 +57,7 @@ pub enum OffsetEncoding { pub mod util { use super::*; + use helix_core::line_ending::{line_end_byte_index, line_end_char_index}; use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction}; /// Converts a diagnostic in the document to [`lsp::Diagnostic`]. @@ -117,7 +118,7 @@ pub mod util { /// Converts [`lsp::Position`] to a position in the document. /// - /// Returns `None` if position exceeds document length or an operation overflows. + /// Returns `None` if position.line is out of bounds or an overflow occurs pub fn lsp_pos_to_pos( doc: &Rope, pos: lsp::Position, @@ -128,22 +129,58 @@ pub mod util { return None; } - match offset_encoding { + // We need to be careful here to fully comply ith the LSP spec. + // Two relevant quotes from the spec: + // + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position + // > If the character value is greater than the line length it defaults back + // > to the line length. + // + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocuments + // > To ensure that both client and server split the string into the same + // > line representation the protocol specifies the following end-of-line sequences: + // > β€˜\n’, β€˜\r\n’ and β€˜\r’. Positions are line end character agnostic. + // > So you can not specify a position that denotes \r|\n or \n| where | represents the character offset. + // + // This means that while the line must be in bounds the `charater` + // must be capped to the end of the line. + // Note that the end of the line here is **before** the line terminator + // so we must use `line_end_char_index` istead of `doc.line_to_char(pos_line + 1)` + // + // FIXME: Helix does not fully comply with the LSP spec for line terminators. + // The LSP standard requires that line terminators are ['\n', '\r\n', '\r']. + // Without the unicode-linebreak feature disabled, the `\r` terminator is not handled by helix. + // With the unicode-linebreak feature, helix recognizes multiple extra line break chars + // which means that positions will be decoded/encoded incorrectly in their presence + + let line = match offset_encoding { OffsetEncoding::Utf8 => { - let line = doc.line_to_char(pos_line); - let pos = line.checked_add(pos.character as usize)?; - if pos <= doc.len_chars() { - Some(pos) - } else { - None - } + let line_start = doc.line_to_byte(pos_line); + let line_end = line_end_byte_index(&doc.slice(..), pos_line); + line_start..line_end } OffsetEncoding::Utf16 => { - let line = doc.line_to_char(pos_line); - let line_start = doc.char_to_utf16_cu(line); - let pos = line_start.checked_add(pos.character as usize)?; - doc.try_utf16_cu_to_char(pos).ok() + // TODO directly translate line index to char-idx + // ropey can do this just as easily as utf-8 byte translation + // but the functions are just missing. + // Translate to char first and then utf-16 as a workaround + let line_start = doc.line_to_char(pos_line); + let line_end = line_end_char_index(&doc.slice(..), pos_line); + doc.char_to_utf16_cu(line_start)..doc.char_to_utf16_cu(line_end) } + }; + + // The LSP spec demands that the offset is capped to the end of the line + let pos = line + .start + .checked_add(pos.character as usize) + .unwrap_or(line.end) + .min(line.end); + + // TODO prefer UTF32/char indices to avoid this step + match offset_encoding { + OffsetEncoding::Utf8 => doc.try_byte_to_char(pos).ok(), + OffsetEncoding::Utf16 => doc.try_utf16_cu_to_char(pos).ok(), } } @@ -158,8 +195,8 @@ pub mod util { match offset_encoding { OffsetEncoding::Utf8 => { let line = doc.char_to_line(pos); - let line_start = doc.line_to_char(line); - let col = pos - line_start; + let line_start = doc.line_to_byte(line); + let col = doc.char_to_byte(pos) - line_start; lsp::Position::new(line as u32, col as u32) } @@ -606,16 +643,55 @@ mod tests { } test_case!("", (0, 0) => Some(0)); - test_case!("", (0, 1) => None); + test_case!("", (0, 1) => Some(0)); test_case!("", (1, 0) => None); test_case!("\n\n", (0, 0) => Some(0)); test_case!("\n\n", (1, 0) => Some(1)); - test_case!("\n\n", (1, 1) => Some(2)); + test_case!("\n\n", (1, 1) => Some(1)); test_case!("\n\n", (2, 0) => Some(2)); test_case!("\n\n", (3, 0) => None); test_case!("test\n\n\n\ncase", (4, 3) => Some(11)); test_case!("test\n\n\n\ncase", (4, 4) => Some(12)); - test_case!("test\n\n\n\ncase", (4, 5) => None); + test_case!("test\n\n\n\ncase", (4, 5) => Some(12)); test_case!("", (u32::MAX, u32::MAX) => None); } + + #[test] + fn emoji_format_gh_4791() { + use lsp_types::{Position, Range, TextEdit}; + + let edits = vec![ + TextEdit { + range: Range { + start: Position { + line: 0, + character: 1, + }, + end: Position { + line: 1, + character: 0, + }, + }, + new_text: "\n ".to_string(), + }, + TextEdit { + range: Range { + start: Position { + line: 1, + character: 7, + }, + end: Position { + line: 2, + character: 0, + }, + }, + new_text: "\n ".to_string(), + }, + ]; + + let mut source = Rope::from_str("[\n\"πŸ‡ΊπŸ‡Έ\",\n\"πŸŽ„\",\n]"); + + let transaction = generate_transaction_from_edits(&source, edits, OffsetEncoding::Utf8); + assert!(transaction.apply(&mut source)); + } } From 9d73a0d1128f8237eef2d4bf475496d4db081df2 Mon Sep 17 00:00:00 2001 From: Mike Trinkala Date: Thu, 9 Feb 2023 06:28:33 -0800 Subject: [PATCH 043/191] Fix the infinite loop when copying the cursor to the top of the file (#5888) Example: ``` test testitem ``` Select line 2 with x, then type Alt-C; Helix will go into an infinite loop. The saturating_sub keeps the head_row and anchor_row pinned at 0, and a selection is never made since the first line is too short. --- helix-term/src/commands.rs | 4 ++ helix-term/tests/test/commands.rs | 64 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e6cbed2ce34c..d7d87b5a159e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1617,6 +1617,10 @@ fn copy_selection_on_line(cx: &mut Context, direction: Direction) { sels += 1; } + if anchor_row == 0 && head_row == 0 { + break; + } + i += 1; } } diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 2ca9e3957a7b..e8d16bfaf2be 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -132,6 +132,70 @@ async fn test_selection_duplication() -> anyhow::Result<()> { .as_str(), )) .await?; + + // Copy the selection to previous line, skipping the first line in the file + test(( + platform_line(indoc! {"\ + test + #[testitem|]# + "}) + .as_str(), + "", + platform_line(indoc! {"\ + test + #[testitem|]# + "}) + .as_str(), + )) + .await?; + + // Copy the selection to previous line, including the first line in the file + test(( + platform_line(indoc! {"\ + test + #[test|]# + "}) + .as_str(), + "", + platform_line(indoc! {"\ + #[test|]# + #(test|)# + "}) + .as_str(), + )) + .await?; + + // Copy the selection to next line, skipping the last line in the file + test(( + platform_line(indoc! {"\ + #[testitem|]# + test + "}) + .as_str(), + "C", + platform_line(indoc! {"\ + #[testitem|]# + test + "}) + .as_str(), + )) + .await?; + + // Copy the selection to next line, including the last line in the file + test(( + platform_line(indoc! {"\ + #[test|]# + test + "}) + .as_str(), + "C", + platform_line(indoc! {"\ + #(test|)# + #[test|]# + "}) + .as_str(), + )) + .await?; Ok(()) } From 8a3ec443f176218384db8c8610bbada9c43a6ea5 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Thu, 9 Feb 2023 23:27:08 +0100 Subject: [PATCH 044/191] Fix new clippy lints (#5892) --- helix-core/src/comment.rs | 2 +- helix-core/src/increment/integer.rs | 4 ++-- helix-core/src/indent.rs | 4 ++-- helix-core/src/syntax.rs | 2 +- helix-lsp/src/client.rs | 2 +- helix-term/src/commands.rs | 8 +++++--- helix-term/src/lib.rs | 2 +- helix-term/src/ui/document.rs | 12 ++++++------ helix-term/src/ui/editor.rs | 9 ++------- helix-term/src/ui/fuzzy_match.rs | 2 +- helix-vcs/src/git/test.rs | 2 +- helix-view/src/base64.rs | 2 +- helix-view/src/clipboard.rs | 2 +- helix-view/src/document.rs | 2 +- helix-view/src/gutter.rs | 8 +++----- helix-view/src/input.rs | 2 +- helix-view/src/view.rs | 2 +- xtask/src/themelint.rs | 2 +- 18 files changed, 32 insertions(+), 37 deletions(-) diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index ec5d7a45aba2..6241f7fd56f1 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -68,7 +68,7 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st let mut min_next_line = 0; for selection in selection { let (start, end) = selection.line_range(text); - let start = start.max(min_next_line).min(text.len_lines()); + let start = start.clamp(min_next_line, text.len_lines()); let end = (end + 1).min(text.len_lines()); lines.extend(start..end); diff --git a/helix-core/src/increment/integer.rs b/helix-core/src/increment/integer.rs index 30803e175b8c..0dfabc0d38af 100644 --- a/helix-core/src/increment/integer.rs +++ b/helix-core/src/increment/integer.rs @@ -69,8 +69,8 @@ pub fn increment(selected_text: &str, amount: i64) -> Option { let (lower_count, upper_count): (usize, usize) = number.chars().fold((0, 0), |(lower, upper), c| { ( - lower + c.is_ascii_lowercase().then(|| 1).unwrap_or(0), - upper + c.is_ascii_uppercase().then(|| 1).unwrap_or(0), + lower + c.is_ascii_lowercase() as usize, + upper + c.is_ascii_uppercase() as usize, ) }); if upper_count > lower_count { diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index d6aa5edb8b71..3aa59fa31aaf 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -604,7 +604,7 @@ pub fn treesitter_indent_for_pos( &mut cursor, text, query_range, - new_line.then(|| (line, byte_pos)), + new_line.then_some((line, byte_pos)), ); ts_parser.cursors.push(cursor); (query_result, deepest_preceding) @@ -624,7 +624,7 @@ pub fn treesitter_indent_for_pos( tab_width, ); } - let mut first_in_line = get_first_in_line(node, new_line.then(|| byte_pos)); + let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos)); let mut result = Indentation::default(); // We always keep track of all the indent changes on one line, in order to only indent once diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index ca4da3dcd56d..1b6c1b1dab3e 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -427,7 +427,7 @@ impl TextObjectQuery { let nodes: Vec<_> = mat .captures .iter() - .filter_map(|cap| (cap.index == capture_idx).then(|| cap.node)) + .filter_map(|cap| (cap.index == capture_idx).then_some(cap.node)) .collect(); if nodes.len() > 1 { diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 365c6990177f..cb6e5d815219 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -628,7 +628,7 @@ impl Client { Some(self.notify::( lsp::DidSaveTextDocumentParams { text_document, - text: include_text.then(|| text.into()), + text: include_text.then_some(text.into()), }, )) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d7d87b5a159e..84daaef4e40d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -959,9 +959,11 @@ fn goto_window(cx: &mut Context, align: Align) { Align::Bottom => { view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count) } - } - .max(view.offset.vertical_offset + scrolloff) - .min(view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff)); + }; + let visual_line = visual_line.clamp( + view.offset.vertical_offset + scrolloff, + view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff), + ); let pos = view .pos_at_visual_coords(doc, visual_line as u16, 0, false) diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index f0bc9129a139..2f6ec12b13fd 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -42,7 +42,7 @@ fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> b .path() .canonicalize() .ok() - .map_or(false, |path| !path.starts_with(&root)); + .map_or(false, |path| !path.starts_with(root)); } true diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index ddbb2cf30670..ed4b1de90e91 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -402,7 +402,7 @@ impl<'a> TextRenderer<'a> { is_in_indent_area: &mut bool, position: Position, ) { - let cut_off_start = self.col_offset.saturating_sub(position.col as usize); + let cut_off_start = self.col_offset.saturating_sub(position.col); let is_whitespace = grapheme.is_whitespace(); // TODO is it correct to apply the whitspace style to all unicode white spaces? @@ -413,7 +413,7 @@ impl<'a> TextRenderer<'a> { let width = grapheme.width(); let grapheme = match grapheme { Grapheme::Tab { width } => { - let grapheme_tab_width = char_to_byte_idx(&self.tab, width as usize); + let grapheme_tab_width = char_to_byte_idx(&self.tab, width); &self.tab[..grapheme_tab_width] } // TODO special rendering for other whitespaces? @@ -423,8 +423,8 @@ impl<'a> TextRenderer<'a> { Grapheme::Newline => &self.newline, }; - let in_bounds = self.col_offset <= (position.col as usize) - && (position.col as usize) < self.viewport.width as usize + self.col_offset; + let in_bounds = self.col_offset <= position.col + && position.col < self.viewport.width as usize + self.col_offset; if in_bounds { self.surface.set_string( @@ -433,10 +433,10 @@ impl<'a> TextRenderer<'a> { grapheme, style, ); - } else if cut_off_start != 0 && cut_off_start < width as usize { + } else if cut_off_start != 0 && cut_off_start < width { // partially on screen let rect = Rect::new( - self.viewport.x as u16, + self.viewport.x, self.viewport.y + position.row as u16, (width - cut_off_start) as u16, 1, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 493f8d501923..59f371bda5dc 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -206,7 +206,7 @@ impl EditorView { highlights, theme, &mut line_decorations, - &mut *translated_positions, + &mut translated_positions, ); Self::render_rulers(editor, doc, view, inner, surface, theme); @@ -723,12 +723,7 @@ impl EditorView { let viewport = view.area; let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| { - let area = Rect::new( - viewport.x, - viewport.y + pos.visual_line as u16, - viewport.width, - 1, - ); + let area = Rect::new(viewport.x, viewport.y + pos.visual_line, viewport.width, 1); if primary_line == pos.doc_line { renderer.surface.set_style(area, primary_style); } else if secondary_lines.binary_search(&pos.doc_line).is_ok() { diff --git a/helix-term/src/ui/fuzzy_match.rs b/helix-term/src/ui/fuzzy_match.rs index e6a3f03a2fbc..b406702ff95e 100644 --- a/helix-term/src/ui/fuzzy_match.rs +++ b/helix-term/src/ui/fuzzy_match.rs @@ -25,7 +25,7 @@ impl QueryAtom { _ => QueryAtomKind::Fuzzy, }; - if atom.starts_with(&['^', '\'']) { + if atom.starts_with(['^', '\'']) { atom.remove(0); } diff --git a/helix-vcs/src/git/test.rs b/helix-vcs/src/git/test.rs index d6e9af08868a..6b1aba7f56ec 100644 --- a/helix-vcs/src/git/test.rs +++ b/helix-vcs/src/git/test.rs @@ -89,7 +89,7 @@ fn directory() { std::fs::create_dir(&dir).expect(""); let file = dir.join("file.txt"); let contents = b"foo".as_slice(); - File::create(&file).unwrap().write_all(contents).unwrap(); + File::create(file).unwrap().write_all(contents).unwrap(); create_commit(temp_git.path(), true); diff --git a/helix-view/src/base64.rs b/helix-view/src/base64.rs index a0dc167fe6f8..13ee919d6b7a 100644 --- a/helix-view/src/base64.rs +++ b/helix-view/src/base64.rs @@ -36,7 +36,7 @@ const LOW_SIX_BITS: u32 = 0x3F; pub fn encode(input: &[u8]) -> String { let rem = input.len() % 3; let complete_chunks = input.len() / 3; - let remainder_chunk = if rem == 0 { 0 } else { 1 }; + let remainder_chunk = usize::from(rem != 0); let encoded_size = (complete_chunks + remainder_chunk) * 4; let mut output = vec![0; encoded_size]; diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 96c87d3f5d74..d43d632a9be7 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -258,7 +258,7 @@ pub mod provider { .args(args) .output() .ok() - .and_then(|out| out.status.success().then(|| ())) // TODO: use then_some when stabilized + .and_then(|out| out.status.success().then_some(())) .is_some() } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index d308d013ff5f..11a0dbf8b964 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1096,7 +1096,7 @@ impl Document { /// Language server if it has been initialized. pub fn language_server(&self) -> Option<&helix_lsp::Client> { let server = self.language_server.as_deref()?; - server.is_initialized().then(|| server) + server.is_initialized().then_some(server) } pub fn diff_handle(&self) -> Option<&DiffHandle> { diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 90c94d558a3e..cb9e43336f40 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -7,9 +7,8 @@ use crate::{ }; fn count_digits(n: usize) -> usize { - // NOTE: if int_log gets standardized in stdlib, can use checked_log10 - // (https://github.com/rust-lang/rust/issues/70887#issue) - std::iter::successors(Some(n), |&n| (n >= 10).then(|| n / 10)).count() + // TODO: use checked_log10 when MSRV reaches 1.67 + std::iter::successors(Some(n), |&n| (n >= 10).then_some(n / 10)).count() } pub type GutterFn<'doc> = Box Option