From 119dee2980708f75150e39c19f92de029d92dad0 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 2 Dec 2021 23:49:54 +0900 Subject: fix: Correctly detect empty transactions Fixes #1221 --- helix-core/src/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-core/src/transaction.rs') diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index dfc18fbe..9c07be2c 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -330,7 +330,7 @@ impl ChangeSet { /// `true` when the set is empty. #[inline] pub fn is_empty(&self) -> bool { - self.changes.is_empty() + self.changes.is_empty() || self.changes == [Operation::Retain(self.len)] } /// Map a position through the changes. -- cgit v1.2.3-70-g09d2 From 01f7a312d0bdf53184fb579bf41c619230449cce Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 3 Dec 2021 10:02:44 +0900 Subject: Address new lint on 1.57 --- helix-core/src/transaction.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'helix-core/src/transaction.rs') diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 9c07be2c..b62f4a9b 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -22,7 +22,7 @@ pub enum Assoc { } // ChangeSpec = Change | ChangeSet | Vec -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct ChangeSet { pub(crate) changes: Vec, /// The required document length. Will refuse to apply changes unless it matches. @@ -30,16 +30,6 @@ pub struct ChangeSet { len_after: usize, } -impl Default for ChangeSet { - fn default() -> Self { - Self { - changes: Vec::new(), - len: 0, - len_after: 0, - } - } -} - impl ChangeSet { pub fn with_capacity(capacity: usize) -> Self { Self { -- cgit v1.2.3-70-g09d2 From 94535fa013469abf6e5ab2fa52f3deced9d46de0 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Mon, 13 Dec 2021 10:58:58 -0500 Subject: Add auto pairs for same-char pairs (#1219) * Add auto pairs for same-char pairs * Add unit tests for all existing functionality * Add auto pairs for same-char pairs (quotes, etc). Account for apostrophe in prose by requiring both sides of the cursor to be non-pair chars or whitespace. This also incidentally will work for avoiding a double single quote in lifetime annotations, at least until <> is added * Slight factor of moving the cursor transform of the selection to inside the hooks. This will enable doing auto pairing with selections, and fixing the bug where auto pairs destroy the selection. Fixes #1014--- helix-core/src/auto_pairs.rs | 421 ++++++++++++++++++++++++++++++++++-------- helix-core/src/transaction.rs | 2 +- helix-term/src/commands.rs | 7 +- 3 files changed, 351 insertions(+), 79 deletions(-) (limited to 'helix-core/src/transaction.rs') diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index cc966852..c037afef 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -2,6 +2,7 @@ //! this module provides the functionality to insert the paired closing character. use crate::{Range, Rope, Selection, Tendril, Transaction}; +use log::debug; use smallvec::SmallVec; // Heavily based on https://github.com/codemirror/closebrackets/ @@ -15,7 +16,9 @@ pub const PAIRS: &[(char, char)] = &[ ('`', '`'), ]; -const CLOSE_BEFORE: &str = ")]}'\":;> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; // includes space and newlines +// [TODO] build this dynamically in language config. see #992 +const OPEN_BEFORE: &str = "([{'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; +const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; // includes space and newlines // insert hook: // Fn(doc, selection, char) => Option @@ -25,40 +28,44 @@ const CLOSE_BEFORE: &str = ")]}'\":;> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{202 // // to simplify, maybe return Option and just reimplement the default -// TODO: delete implementation where it erases the whole bracket (|) -> | +// [TODO] +// * delete implementation where it erases the whole bracket (|) -> | +// * do not reduce to cursors; use whole selections, and surround with pair +// * change to multi character pairs to handle cases like placing the cursor in the +// middle of triple quotes, and more exotic pairs like Jinja's {% %} #[must_use] pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option { + debug!("autopairs hook selection: {:#?}", selection); + + let cursors = selection.clone().cursors(doc.slice(..)); + for &(open, close) in PAIRS { if open == ch { if open == close { - return handle_same(doc, selection, open); + return Some(handle_same(doc, &cursors, open, CLOSE_BEFORE, OPEN_BEFORE)); } else { - return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE)); + return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE)); } } if close == ch { // && char_at pos == close - return Some(handle_close(doc, selection, open, close)); + return Some(handle_close(doc, &cursors, open, close)); } } None } -// TODO: special handling for lifetimes in rust: if preceeded by & or < don't auto close ' -// for example "&'a mut", or "fn<'a>" - -fn next_char(doc: &Rope, pos: usize) -> Option { - if pos >= doc.len_chars() { +fn prev_char(doc: &Rope, pos: usize) -> Option { + if pos == 0 { return None; } - Some(doc.char(pos)) + + doc.get_char(pos - 1) } -// TODO: selections should be extended if range, moved if point. -// TODO: if not cursor but selection, wrap on both sides of selection (surround) fn handle_open( doc: &Rope, selection: &Selection, @@ -66,98 +73,362 @@ fn handle_open( close: char, close_before: &str, ) -> Transaction { - let mut ranges = SmallVec::with_capacity(selection.len()); + let mut end_ranges = SmallVec::with_capacity(selection.len()); let mut offs = 0; - let transaction = Transaction::change_by_selection(doc, selection, |range| { - let pos = range.head; - let next = next_char(doc, pos); + let transaction = Transaction::change_by_selection(doc, selection, |start_range| { + let start_head = start_range.head; - let head = pos + offs + open.len_utf8(); - // if selection, retain anchor, if cursor, move over - ranges.push(Range::new( - if range.is_empty() { - head - } else { - range.anchor + offs - }, - head, - )); + let next = doc.get_char(start_head); + let end_head = start_head + offs + open.len_utf8(); + + let end_anchor = if start_range.is_empty() { + end_head + } else { + start_range.anchor + offs + }; + + end_ranges.push(Range::new(end_anchor, end_head)); match next { Some(ch) if !close_before.contains(ch) => { - offs += 1; - // TODO: else return (use default handler that inserts open) - (pos, pos, Some(Tendril::from_char(open))) + offs += open.len_utf8(); + (start_head, start_head, Some(Tendril::from_char(open))) } // None | Some(ch) if close_before.contains(ch) => {} _ => { // insert open & close - let mut pair = Tendril::with_capacity(2); - pair.push_char(open); - pair.push_char(close); - - offs += 2; - - (pos, pos, Some(pair)) + let pair = Tendril::from_iter([open, close]); + offs += open.len_utf8() + close.len_utf8(); + (start_head, start_head, Some(pair)) } } }); - transaction.with_selection(Selection::new(ranges, selection.primary_index())) + let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); + debug!("auto pair transaction: {:#?}", t); + t } fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> Transaction { - let mut ranges = SmallVec::with_capacity(selection.len()); + let mut end_ranges = SmallVec::with_capacity(selection.len()); let mut offs = 0; - let transaction = Transaction::change_by_selection(doc, selection, |range| { - let pos = range.head; - let next = next_char(doc, pos); + let transaction = Transaction::change_by_selection(doc, selection, |start_range| { + let start_head = start_range.head; + let next = doc.get_char(start_head); + let end_head = start_head + offs + close.len_utf8(); - let head = pos + offs + close.len_utf8(); - // if selection, retain anchor, if cursor, move over - ranges.push(Range::new( - if range.is_empty() { - head - } else { - range.anchor + offs - }, - head, - )); + let end_anchor = if start_range.is_empty() { + end_head + } else { + start_range.anchor + offs + }; + + end_ranges.push(Range::new(end_anchor, end_head)); if next == Some(close) { - // return transaction that moves past close - (pos, pos, None) // no-op + // return transaction that moves past close + (start_head, start_head, None) // no-op } else { offs += close.len_utf8(); + (start_head, start_head, Some(Tendril::from_char(close))) + } + }); + + transaction.with_selection(Selection::new(end_ranges, selection.primary_index())) +} - // TODO: else return (use default handler that inserts close) - (pos, pos, Some(Tendril::from_char(close))) +/// handle cases where open and close is the same, or in triples ("""docstring""") +fn handle_same( + doc: &Rope, + selection: &Selection, + token: char, + close_before: &str, + open_before: &str, +) -> Transaction { + let mut end_ranges = SmallVec::with_capacity(selection.len()); + + let mut offs = 0; + + let transaction = Transaction::change_by_selection(doc, selection, |start_range| { + let start_head = start_range.head; + let end_head = start_head + offs + token.len_utf8(); + + // if selection, retain anchor, if cursor, move over + let end_anchor = if start_range.is_empty() { + end_head + } else { + start_range.anchor + offs + }; + + end_ranges.push(Range::new(end_anchor, end_head)); + + let next = doc.get_char(start_head); + let prev = prev_char(doc, start_head); + + if next == Some(token) { + // return transaction that moves past close + (start_head, start_head, None) // no-op + } else { + let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32); + pair.push_char(token); + + // for equal pairs, don't insert both open and close if either + // side has a non-pair char + if (next.is_none() || close_before.contains(next.unwrap())) + && (prev.is_none() || open_before.contains(prev.unwrap())) + { + pair.push_char(token); + } + + offs += pair.len(); + (start_head, start_head, Some(pair)) } }); - transaction.with_selection(Selection::new(ranges, selection.primary_index())) + transaction.with_selection(Selection::new(end_ranges, selection.primary_index())) } -// handle cases where open and close is the same, or in triples ("""docstring""") -fn handle_same(_doc: &Rope, _selection: &Selection, _token: char) -> Option { - // if not cursor but selection, wrap - // let next = next char - - // if next == bracket { - // // if start of syntax node, insert token twice (new pair because node is complete) - // // elseif colsedBracketAt - // // is_triple == allow triple && next 3 is equal - // // cursor jump over - // } - //} else if allow_triple && followed by triple { - //} - //} else if next != word char && prev != bracket && prev != word char { - // // condition checks for cases like I' where you don't want I'' (or I'm) - // insert pair ("") - //} - None +#[cfg(test)] +mod test { + use super::*; + use smallvec::smallvec; + + fn differing_pairs() -> impl Iterator { + PAIRS.iter().filter(|(open, close)| open != close) + } + + fn matching_pairs() -> impl Iterator { + PAIRS.iter().filter(|(open, close)| open == close) + } + + fn test_hooks( + in_doc: &Rope, + in_sel: &Selection, + ch: char, + expected_doc: &Rope, + expected_sel: &Selection, + ) { + let trans = hook(&in_doc, &in_sel, ch).unwrap(); + let mut actual_doc = in_doc.clone(); + assert!(trans.apply(&mut actual_doc)); + assert_eq!(expected_doc, &actual_doc); + assert_eq!(expected_sel, trans.selection().unwrap()); + } + + fn test_hooks_with_pairs( + in_doc: &Rope, + in_sel: &Selection, + pairs: I, + get_expected_doc: F, + actual_sel: &Selection, + ) where + I: IntoIterator, + F: Fn(char, char) -> R, + R: Into, + Rope: From, + { + pairs.into_iter().for_each(|(open, close)| { + test_hooks( + in_doc, + in_sel, + *open, + &Rope::from(get_expected_doc(*open, *close)), + actual_sel, + ) + }); + } + + // [] indicates range + + /// [] -> insert ( -> ([]) + #[test] + fn test_insert_blank() { + test_hooks_with_pairs( + &Rope::new(), + &Selection::single(1, 0), + PAIRS, + |open, close| format!("{}{}", open, close), + &Selection::single(1, 1), + ); + } + + /// [] ([]) + /// [] -> insert -> ([]) + /// [] ([]) + #[test] + fn test_insert_blank_multi_cursor() { + test_hooks_with_pairs( + &Rope::from("\n\n\n"), + &Selection::new( + smallvec!(Range::new(1, 0), Range::new(2, 1), Range::new(3, 2),), + 0, + ), + PAIRS, + |open, close| { + format!( + "{open}{close}\n{open}{close}\n{open}{close}\n", + open = open, + close = close + ) + }, + &Selection::new( + smallvec!(Range::point(1), Range::point(4), Range::point(7),), + 0, + ), + ); + } + + // [TODO] broken until it works with selections + /// fo[o] -> append ( -> fo[o(]) + #[ignore] + #[test] + fn test_append() { + test_hooks_with_pairs( + &Rope::from("foo"), + &Selection::single(2, 4), + PAIRS, + |open, close| format!("foo{}{}", open, close), + &Selection::single(2, 5), + ); + } + + /// ([]) -> insert ) -> ()[] + #[test] + fn test_insert_close_inside_pair() { + for (open, close) in PAIRS { + let doc = Rope::from(format!("{}{}", open, close)); + + test_hooks( + &doc, + &Selection::single(2, 1), + *close, + &doc, + &Selection::point(2), + ); + } + } + + /// ([]) ()[] + /// ([]) -> insert ) -> ()[] + /// ([]) ()[] + #[test] + fn test_insert_close_inside_pair_multi_cursor() { + let sel = Selection::new( + smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),), + 0, + ); + + let expected_sel = Selection::new( + // smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),), + smallvec!(Range::point(2), Range::point(5), Range::point(8),), + 0, + ); + + for (open, close) in PAIRS { + let doc = Rope::from(format!( + "{open}{close}\n{open}{close}\n{open}{close}\n", + open = open, + close = close + )); + + test_hooks(&doc, &sel, *close, &doc, &expected_sel); + } + } + + /// ([]) -> insert ( -> (([])) + #[test] + fn test_insert_open_inside_pair() { + let sel = Selection::single(2, 1); + let expected_sel = Selection::point(2); + + for (open, close) in differing_pairs() { + let doc = Rope::from(format!("{}{}", open, close)); + let expected_doc = Rope::from(format!( + "{open}{open}{close}{close}", + open = open, + close = close + )); + + test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel); + } + } + + /// ([]) -> insert " -> ("[]") + #[test] + fn test_insert_nested_open_inside_pair() { + let sel = Selection::single(2, 1); + let expected_sel = Selection::point(2); + + for (outer_open, outer_close) in differing_pairs() { + let doc = Rope::from(format!("{}{}", outer_open, outer_close,)); + + for (inner_open, inner_close) in matching_pairs() { + let expected_doc = Rope::from(format!( + "{}{}{}{}", + outer_open, inner_open, inner_close, outer_close + )); + + test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel); + } + } + } + + /// []word -> insert ( -> ([]word + #[test] + fn test_insert_open_before_non_pair() { + test_hooks_with_pairs( + &Rope::from("word"), + &Selection::single(1, 0), + PAIRS, + |open, _| format!("{}word", open), + &Selection::point(1), + ) + } + + // [TODO] broken until it works with selections + /// [wor]d -> insert ( -> ([wor]d + #[test] + #[ignore] + fn test_insert_open_with_selection() { + test_hooks_with_pairs( + &Rope::from("word"), + &Selection::single(0, 4), + PAIRS, + |open, _| format!("{}word", open), + &Selection::single(1, 5), + ) + } + + /// we want pairs that are *not* the same char to be inserted after + /// a non-pair char, for cases like functions, but for pairs that are + /// the same char, we want to *not* insert a pair to handle cases like "I'm" + /// + /// word[] -> insert ( -> word([]) + /// word[] -> insert ' -> word'[] + #[test] + fn test_insert_open_after_non_pair() { + let doc = Rope::from("word"); + let sel = Selection::single(5, 4); + let expected_sel = Selection::point(5); + + test_hooks_with_pairs( + &doc, + &sel, + differing_pairs(), + |open, close| format!("word{}{}", open, close), + &expected_sel, + ); + + test_hooks_with_pairs( + &doc, + &sel, + matching_pairs(), + |open, _| format!("word{}", open), + &expected_sel, + ); + } } diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index b62f4a9b..d8d389f3 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -409,7 +409,7 @@ impl ChangeSet { /// Transaction represents a single undoable unit of changes. Several changes can be grouped into /// a single transaction. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Transaction { changes: ChangeSet, selection: Option, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 22c23043..8552f638 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4199,8 +4199,9 @@ pub mod insert { // The default insert hook: simply insert the character #[allow(clippy::unnecessary_wraps)] // need to use Option<> because of the Hook signature fn insert(doc: &Rope, selection: &Selection, ch: char) -> Option { + let cursors = selection.clone().cursors(doc.slice(..)); let t = Tendril::from_char(ch); - let transaction = Transaction::insert(doc, selection, t); + let transaction = Transaction::insert(doc, &cursors, t); Some(transaction) } @@ -4215,11 +4216,11 @@ pub mod insert { }; let text = doc.text(); - let selection = doc.selection(view.id).clone().cursors(text.slice(..)); + let selection = doc.selection(view.id); // run through insert hooks, stopping on the first one that returns Some(t) for hook in hooks { - if let Some(transaction) = hook(text, &selection, c) { + if let Some(transaction) = hook(text, selection, c) { doc.apply(&transaction, view.id); break; } -- cgit v1.2.3-70-g09d2 From f064894e5778071ef8cc563380f48cfdd9acba59 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sun, 23 Jan 2022 02:37:23 -0500 Subject: Fix Clippy lints in tests (#1563) Co-authored-by: Blaž Hrastnik --- helix-core/src/auto_pairs.rs | 4 ++-- helix-core/src/chars.rs | 9 ++++----- helix-core/src/diff.rs | 2 +- helix-core/src/history.rs | 4 ++-- helix-core/src/indent.rs | 4 ++-- helix-core/src/line_ending.rs | 2 +- helix-core/src/selection.rs | 16 ++++++++-------- helix-core/src/surround.rs | 1 + helix-core/src/transaction.rs | 2 +- helix-tui/src/widgets/reflow.rs | 4 ++-- helix-view/src/view.rs | 4 ++-- 11 files changed, 26 insertions(+), 26 deletions(-) (limited to 'helix-core/src/transaction.rs') diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 9d1152bc..40802680 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -322,7 +322,7 @@ mod test { use super::*; use smallvec::smallvec; - const LINE_END: &'static str = crate::DEFAULT_LINE_ENDING.as_str(); + const LINE_END: &str = crate::DEFAULT_LINE_ENDING.as_str(); fn differing_pairs() -> impl Iterator { PAIRS.iter().filter(|(open, close)| open != close) @@ -339,7 +339,7 @@ mod test { expected_doc: &Rope, expected_sel: &Selection, ) { - let trans = hook(&in_doc, &in_sel, ch).unwrap(); + let trans = hook(in_doc, in_sel, ch).unwrap(); let mut actual_doc = in_doc.clone(); assert!(trans.apply(&mut actual_doc)); assert_eq!(expected_doc, &actual_doc); diff --git a/helix-core/src/chars.rs b/helix-core/src/chars.rs index c8e5efbd..54991574 100644 --- a/helix-core/src/chars.rs +++ b/helix-core/src/chars.rs @@ -91,12 +91,11 @@ mod test { #[test] fn test_categorize() { - const EOL_TEST_CASE: &'static str = "\n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; - const WORD_TEST_CASE: &'static str = - "_hello_world_あいうえおー12345678901234567890"; - const PUNCTUATION_TEST_CASE: &'static str = + const EOL_TEST_CASE: &str = "\n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; + const WORD_TEST_CASE: &str = "_hello_world_あいうえおー12345678901234567890"; + const PUNCTUATION_TEST_CASE: &str = "!\"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~!”#$%&’()*+、。:;<=>?@「」^`{|}~"; - const WHITESPACE_TEST_CASE: &'static str = "      "; + const WHITESPACE_TEST_CASE: &str = "      "; for ch in EOL_TEST_CASE.chars() { assert_eq!(CharCategory::Eol, categorize_char(ch)); diff --git a/helix-core/src/diff.rs b/helix-core/src/diff.rs index 3349213d..6960c679 100644 --- a/helix-core/src/diff.rs +++ b/helix-core/src/diff.rs @@ -58,7 +58,7 @@ mod tests { let mut old = Rope::from(a); let new = Rope::from(b); compare_ropes(&old, &new).apply(&mut old); - old.to_string() == new.to_string() + old == new } } } diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 4b1c8d3b..bb95213c 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -448,8 +448,8 @@ mod test { change: crate::transaction::Change, instant: Instant, ) { - let txn = Transaction::change(&state.doc, vec![change.clone()].into_iter()); - history.commit_revision_at_timestamp(&txn, &state, instant); + let txn = Transaction::change(&state.doc, vec![change].into_iter()); + history.commit_revision_at_timestamp(&txn, state, instant); txn.apply(&mut state.doc); } diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index ac2a1208..a8ea3012 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -416,7 +416,7 @@ where ", ); - let doc = Rope::from(doc); + let doc = doc; use crate::diagnostic::Severity; use crate::syntax::{ Configuration, IndentationConfiguration, LanguageConfiguration, Loader, @@ -454,7 +454,7 @@ where let language_config = loader.language_config_for_scope("source.rust").unwrap(); let highlight_config = language_config.highlight_config(&[]).unwrap(); - let syntax = Syntax::new(&doc, highlight_config.clone(), std::sync::Arc::new(loader)); + let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader)); let text = doc.slice(..); let tab_width = 4; diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index 3541305c..8eb426e1 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -250,7 +250,7 @@ mod line_ending_tests { assert_eq!(get_line_ending_of_str(&text[..6]), Some(LineEnding::CR)); assert_eq!(get_line_ending_of_str(&text[..12]), Some(LineEnding::LF)); assert_eq!(get_line_ending_of_str(&text[..17]), Some(LineEnding::Crlf)); - assert_eq!(get_line_ending_of_str(&text[..]), None); + assert_eq!(get_line_ending_of_str(text), None); } #[test] diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 1515c4fc..c6eceb4b 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -766,16 +766,16 @@ mod test { fn test_contains() { let range = Range::new(10, 12); - assert_eq!(range.contains(9), false); - assert_eq!(range.contains(10), true); - assert_eq!(range.contains(11), true); - assert_eq!(range.contains(12), false); - assert_eq!(range.contains(13), false); + assert!(!range.contains(9)); + assert!(range.contains(10)); + assert!(range.contains(11)); + assert!(!range.contains(12)); + assert!(!range.contains(13)); let range = Range::new(9, 6); - assert_eq!(range.contains(9), false); - assert_eq!(range.contains(7), true); - assert_eq!(range.contains(6), true); + assert!(!range.contains(9)); + assert!(range.contains(7)); + assert!(range.contains(6)); } #[test] diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs index b53b0a78..58eb23cf 100644 --- a/helix-core/src/surround.rs +++ b/helix-core/src/surround.rs @@ -172,6 +172,7 @@ mod test { use ropey::Rope; use smallvec::SmallVec; + #[allow(clippy::type_complexity)] fn check_find_nth_pair_pos( text: &str, cases: Vec<(usize, char, usize, Option<(usize, usize)>)>, diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index d8d389f3..30995e8c 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -761,7 +761,7 @@ mod test { #[test] fn combine_with_utf8() { - const TEST_CASE: &'static str = "Hello, これはヘリックスエディターです!"; + const TEST_CASE: &str = "Hello, これはヘリックスエディターです!"; let empty = Rope::from(""); let a = ChangeSet::new(&empty); diff --git a/helix-tui/src/widgets/reflow.rs b/helix-tui/src/widgets/reflow.rs index 21847783..33e52bb4 100644 --- a/helix-tui/src/widgets/reflow.rs +++ b/helix-tui/src/widgets/reflow.rs @@ -404,8 +404,8 @@ mod test { let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\ では、"; let (word_wrapper, word_wrapper_width) = - run_composer(Composer::WordWrapper { trim: true }, &text, width); - let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width); + run_composer(Composer::WordWrapper { trim: true }, text, width); + let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width); assert_eq!(line_truncator, vec!["コンピュータ上で文字"]); let wrapped = vec![ "コンピュータ上で文字", diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 89a6c196..adfcd071 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -356,7 +356,7 @@ mod tests { let text = rope.slice(..); assert_eq!( - view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET + 0, 4), + view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET, 4), Some(0) ); @@ -389,7 +389,7 @@ mod tests { let text = rope.slice(..); assert_eq!( - view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET + 0, 4), + view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET, 4), Some(0) ); -- cgit v1.2.3-70-g09d2 From f88c077f992bbfc5ea3623441a9e2b2a0e9ca2b2 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 7 Feb 2022 14:03:04 +0900 Subject: Replace tendril with smartstring Slightly smaller API surface, less dependencies. --- Cargo.lock | 56 ++++++++++------------------------- helix-core/Cargo.toml | 2 +- helix-core/src/auto_pairs.rs | 14 +++++---- helix-core/src/increment/date_time.rs | 2 +- helix-core/src/increment/number.rs | 10 +++---- helix-core/src/lib.rs | 5 +++- helix-core/src/transaction.rs | 24 ++++++++------- helix-lsp/src/client.rs | 2 +- helix-term/src/commands.rs | 32 +++++++++++--------- xtask/src/main.rs | 2 +- 10 files changed, 69 insertions(+), 80 deletions(-) (limited to 'helix-core/src/transaction.rs') diff --git a/Cargo.lock b/Cargo.lock index 3253e6cd..360ebcfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,16 +246,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures-core" version = "0.3.21" @@ -383,7 +373,7 @@ dependencies = [ "similar", "slotmap", "smallvec", - "tendril", + "smartstring", "toml", "tree-sitter", "unicode-general-category", @@ -606,12 +596,6 @@ dependencies = [ "url", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "matches" version = "0.1.9" @@ -655,12 +639,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "ntapi" version = "0.3.6" @@ -1010,6 +988,21 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "smartstring" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa6a31c0c2b21327ce875f7e8952322acfcfd0c27569a6e18a647281352c9b" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str-buf" version = "1.0.5" @@ -1027,17 +1020,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tendril" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" -dependencies = [ - "futf", - "mac", - "utf-8", -] - [[package]] name = "thiserror" version = "1.0.30" @@ -1212,12 +1194,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "version_check" version = "0.9.4" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 3a1f9b3e..7ff91cfd 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -17,7 +17,7 @@ helix-syntax = { version = "0.6", path = "../helix-syntax" } ropey = "1.3" smallvec = "1.8" -tendril = "0.4.2" +smartstring = "0.2.9" unicode-segmentation = "1.9" unicode-width = "0.1" unicode-general-category = "0.5" diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 40802680..f4359a34 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -214,7 +214,9 @@ fn handle_open( let change = match next_char { Some(ch) if !close_before.contains(ch) => { len_inserted = open.len_utf8(); - (cursor, cursor, Some(Tendril::from_char(open))) + let mut tendril = Tendril::new(); + tendril.push(open); + (cursor, cursor, Some(tendril)) } // None | Some(ch) if close_before.contains(ch) => {} _ => { @@ -252,7 +254,9 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> (cursor, cursor, None) // no-op } else { len_inserted += close.len_utf8(); - (cursor, cursor, Some(Tendril::from_char(close))) + let mut tendril = Tendril::new(); + tendril.push(close); + (cursor, cursor, Some(tendril)) }; let next_range = get_next_range(doc, start_range, offs, close, len_inserted); @@ -290,15 +294,15 @@ fn handle_same( // return transaction that moves past close (cursor, cursor, None) // no-op } else { - let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32); - pair.push_char(token); + let mut pair = Tendril::new(); + pair.push(token); // for equal pairs, don't insert both open and close if either // side has a non-pair char if (next_char.is_none() || close_before.contains(next_char.unwrap())) && (prev_char.is_none() || open_before.contains(prev_char.unwrap())) { - pair.push_char(token); + pair.push(token); } len_inserted += pair.len(); diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 1703c3ba..91fa5963 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -451,7 +451,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } diff --git a/helix-core/src/increment/number.rs b/helix-core/src/increment/number.rs index a19b7e75..57171f67 100644 --- a/helix-core/src/increment/number.rs +++ b/helix-core/src/increment/number.rs @@ -371,7 +371,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -398,7 +398,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -426,7 +426,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -472,7 +472,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -500,7 +500,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 7fd23b97..0b03ead8 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -212,7 +212,10 @@ use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; pub use ropey::{Rope, RopeBuilder, RopeSlice}; -pub use tendril::StrTendril as Tendril; +// pub use tendril::StrTendril as Tendril; +pub use smartstring::SmartString; + +pub type Tendril = SmartString; #[doc(inline)] pub use {regex, tree_sitter}; diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 30995e8c..2e34a986 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -85,7 +85,7 @@ impl ChangeSet { let new_last = match self.changes.as_mut_slice() { [.., Insert(prev)] | [.., Insert(prev), Delete(_)] => { - prev.push_tendril(&fragment); + prev.push_str(&fragment); return; } [.., last @ Delete(_)] => std::mem::replace(last, Insert(fragment)), @@ -189,7 +189,7 @@ impl ChangeSet { // TODO: cover this with a test // figure out the byte index of the truncated string end let (pos, _) = s.char_indices().nth(j).unwrap(); - s.pop_front(pos as u32); + s.replace_range(0..pos, ""); head_a = Some(Insert(s)); head_b = changes_b.next(); } @@ -211,9 +211,11 @@ impl ChangeSet { Ordering::Greater => { // figure out the byte index of the truncated string end let (pos, _) = s.char_indices().nth(j).unwrap(); - let pos = pos as u32; - changes.insert(s.subtendril(0, pos)); - head_a = Some(Insert(s.subtendril(pos, s.len() as u32 - pos))); + let mut before = s; + let after = before.split_off(pos); + + changes.insert(before); + head_a = Some(Insert(after)); head_b = changes_b.next(); } } @@ -277,7 +279,7 @@ impl ChangeSet { } Delete(n) => { let text = Cow::from(original_doc.slice(pos..pos + *n)); - changes.insert(Tendril::from_slice(&text)); + changes.insert(Tendril::from(text.as_ref())); pos += n; } Insert(s) => { @@ -710,19 +712,19 @@ mod test { #[test] fn optimized_composition() { let mut state = State::new("".into()); - let t1 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('h')); + let t1 = Transaction::insert(&state.doc, &state.selection, Tendril::from("h")); t1.apply(&mut state.doc); state.selection = state.selection.clone().map(t1.changes()); - let t2 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('e')); + let t2 = Transaction::insert(&state.doc, &state.selection, Tendril::from("e")); t2.apply(&mut state.doc); state.selection = state.selection.clone().map(t2.changes()); - let t3 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('l')); + let t3 = Transaction::insert(&state.doc, &state.selection, Tendril::from("l")); t3.apply(&mut state.doc); state.selection = state.selection.clone().map(t3.changes()); - let t4 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('l')); + let t4 = Transaction::insert(&state.doc, &state.selection, Tendril::from("l")); t4.apply(&mut state.doc); state.selection = state.selection.clone().map(t4.changes()); - let t5 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('o')); + let t5 = Transaction::insert(&state.doc, &state.selection, Tendril::from("o")); t5.apply(&mut state.doc); state.selection = state.selection.clone().map(t5.changes()); diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index c80f70b5..15cbca0e 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -438,7 +438,7 @@ impl Client { changes.push(lsp::TextDocumentContentChangeEvent { range: Some(lsp::Range::new(start, end)), - text: s.into(), + text: s.to_string(), range_length: None, }); } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d0882728..9918d26c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2769,7 +2769,7 @@ pub mod cmd { let mut fragments: Vec<_> = selection .fragments(text) - .map(|fragment| Tendril::from_slice(&fragment)) + .map(|fragment| Tendril::from(fragment.as_ref())) .collect(); fragments.sort_by(match reverse { @@ -3921,7 +3921,7 @@ fn try_restore_indent(doc: &mut Document, view_id: ViewId) { if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] = changes { - move_pos + inserted_str.len32() as usize == pos + move_pos + inserted_str.len() == pos && inserted_str.starts_with('\n') && inserted_str.chars().skip(1).all(char_is_whitespace) && pos == line_end_pos // ensure no characters exists after current position @@ -4513,7 +4513,8 @@ pub mod insert { #[allow(clippy::unnecessary_wraps)] // need to use Option<> because of the Hook signature fn insert(doc: &Rope, selection: &Selection, ch: char) -> Option { let cursors = selection.clone().cursors(doc.slice(..)); - let t = Tendril::from_char(ch); + let mut t = Tendril::new(); + t.push(ch); let transaction = Transaction::insert(doc, &cursors, t); Some(transaction) } @@ -5015,12 +5016,12 @@ fn replace_with_yanked(cx: &mut Context) { let repeat = std::iter::repeat( values .last() - .map(|value| Tendril::from_slice(&value.repeat(count))) + .map(|value| Tendril::from(&value.repeat(count))) .unwrap(), ); let mut values = values .iter() - .map(|value| Tendril::from_slice(&value.repeat(count))) + .map(|value| Tendril::from(&value.repeat(count))) .chain(repeat); let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { @@ -5530,7 +5531,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) { let selection = doc.selection(view.id); let mut fragments: Vec<_> = selection .fragments(text) - .map(|fragment| Tendril::from_slice(&fragment)) + .map(|fragment| Tendril::from(fragment.as_ref())) .collect(); let group = count @@ -5891,8 +5892,12 @@ fn surround_add(cx: &mut Context) { let mut changes = Vec::with_capacity(selection.len() * 2); for range in selection.iter() { - changes.push((range.from(), range.from(), Some(Tendril::from_char(open)))); - changes.push((range.to(), range.to(), Some(Tendril::from_char(close)))); + 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))); } let transaction = Transaction::change(doc.text(), changes.into_iter()); @@ -5921,11 +5926,9 @@ fn surround_replace(cx: &mut Context) { let transaction = Transaction::change( doc.text(), change_pos.iter().enumerate().map(|(i, &pos)| { - ( - pos, - pos + 1, - Some(Tendril::from_char(if i % 2 == 0 { open } else { close })), - ) + let mut t = Tendril::new(); + t.push(if i % 2 == 0 { open } else { close }); + (pos, pos + 1, Some(t)) }), ); doc.apply(&transaction, view.id); @@ -6065,8 +6068,9 @@ fn shell_impl( log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr)); } - let tendril = Tendril::try_from_byte_slice(&output.stdout) + let str = std::str::from_utf8(&output.stdout) .map_err(|_| anyhow!("Process did not output valid UTF-8"))?; + let tendril = Tendril::from(str); Ok((tendril, output.status.success())) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 7256653a..d24a29cc 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -120,7 +120,7 @@ pub mod md_gen { } fn md_table_row(cols: &[String]) -> String { - "| ".to_owned() + &cols.join(" | ") + " |\n" + format!("| {} |\n", cols.join(" | ")) } fn md_mono(s: &str) -> String { -- cgit v1.2.3-70-g09d2