Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--helix-core/src/auto_pairs.rs19
-rw-r--r--helix-core/src/transaction.rs64
-rw-r--r--helix-term/src/commands.rs33
3 files changed, 89 insertions, 27 deletions
diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs
index 2fd373de..467d55d9 100644
--- a/helix-core/src/auto_pairs.rs
+++ b/helix-core/src/auto_pairs.rs
@@ -110,17 +110,22 @@ impl Default for AutoPairs {
// middle of triple quotes, and more exotic pairs like Jinja's {% %}
#[must_use]
-pub fn hook(doc: &Rope, range: &Range, ch: char, pairs: &AutoPairs) -> Option<(Change, Range)> {
+pub fn hook_insert(
+ doc: &Rope,
+ range: &Range,
+ ch: char,
+ pairs: &AutoPairs,
+) -> Option<(Change, Range)> {
log::trace!("autopairs hook range: {:#?}", range);
if let Some(pair) = pairs.get(ch) {
if pair.same() {
- return handle_same(doc, range, pair);
+ return handle_insert_same(doc, range, pair);
} else if pair.open == ch {
- return handle_open(doc, range, pair);
+ return handle_insert_open(doc, range, pair);
} else if pair.close == ch {
// && char_at pos == close
- return handle_close(doc, range, pair);
+ return handle_insert_close(doc, range, pair);
}
}
@@ -243,7 +248,7 @@ fn get_next_range(doc: &Rope, start_range: &Range, len_inserted: usize) -> Range
Range::new(end_anchor, end_head)
}
-fn handle_open(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)> {
+fn handle_insert_open(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)> {
let cursor = range.cursor(doc.slice(..));
let next_char = doc.get_char(cursor);
let len_inserted;
@@ -271,7 +276,7 @@ fn handle_open(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)
Some(result)
}
-fn handle_close(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)> {
+fn handle_insert_close(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)> {
let cursor = range.cursor(doc.slice(..));
let next_char = doc.get_char(cursor);
@@ -291,7 +296,7 @@ fn handle_close(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range
}
/// handle cases where open and close is the same, or in triples ("""docstring""")
-fn handle_same(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)> {
+fn handle_insert_same(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)> {
let cursor = range.cursor(doc.slice(..));
let mut len_inserted = 0;
let next_char = doc.get_char(cursor);
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index d2f5be20..f973a475 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -748,16 +748,6 @@ impl Transaction {
)
}
- /// Generate a transaction with a deletion per selection range.
- /// Compared to using `change_by_selection` directly these ranges may overlap.
- /// In that case they are merged
- pub fn delete_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
- where
- F: FnMut(&Range) -> Deletion,
- {
- Self::delete(doc, selection.iter().map(f))
- }
-
/// Generate a transaction with a change per selection range, which
/// generates a new selection as well. Each range is operated upon by
/// the given function and can optionally produce a new range. If none
@@ -811,6 +801,60 @@ impl Transaction {
transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
}
+ /// Generate a transaction with a deletion per selection range.
+ /// Compared to using `change_by_selection` directly these ranges may overlap.
+ /// In that case they are merged.
+ pub fn delete_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
+ where
+ F: FnMut(&Range) -> Deletion,
+ {
+ Self::delete(doc, selection.iter().map(f))
+ }
+
+ /// Generate a transaction with a delete per selection range, which
+ /// generates a new selection as well. Each range is operated upon by
+ /// the given function and can optionally produce a new range. If none
+ /// is returned by the function, that range is mapped through the change
+ /// as usual.
+ ///
+ /// Compared to using `change_by_and_with_selection` directly these ranges
+ /// may overlap. In that case they are merged.
+ pub fn delete_by_and_with_selection<F>(doc: &Rope, selection: &Selection, mut f: F) -> Self
+ where
+ F: FnMut(&Range) -> (Deletion, Option<Range>),
+ {
+ let mut end_ranges = SmallVec::with_capacity(selection.len());
+ let mut offset = 0;
+
+ let transaction = Transaction::delete_by_selection(doc, selection, |start_range| {
+ let ((from, to), end_range) = f(start_range);
+ let change_size = to - from;
+
+ if let Some(end_range) = end_range {
+ let offset_range = Range::new(
+ end_range.anchor.saturating_sub(offset),
+ end_range.head.saturating_sub(offset),
+ );
+
+ log::trace!("end range {:?} offset to: {:?}", end_range, offset_range);
+
+ end_ranges.push(offset_range);
+ } else {
+ let changeset = ChangeSet::from_change(doc, (from, to, None));
+ let end_range = start_range.map(&changeset);
+ end_ranges.push(end_range);
+ }
+
+ offset += change_size;
+
+ log::trace!("delete from: {}, to: {}, offset: {}", from, to, offset);
+
+ (from, to)
+ });
+
+ transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
+ }
+
/// Insert text at each selection head.
pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
Self::change_by_selection(doc, selection, |range| {
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index bd489d3e..f5870109 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -4146,7 +4146,7 @@ pub mod insert {
auto_pairs
.as_ref()
.and_then(|ap| {
- auto_pairs::hook(text, range, c, ap)
+ auto_pairs::hook_insert(text, range, c, ap)
.map(|(change, range)| (change, Some(range)))
.or(Some(insert_char(*range, c)))
})
@@ -4406,11 +4406,13 @@ pub mod insert {
let indent_width = doc.indent_width();
let auto_pairs = doc.auto_pairs(cx.editor);
- let transaction =
- Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
+ let transaction = Transaction::delete_by_and_with_selection(
+ doc.text(),
+ doc.selection(view.id),
+ |range| {
let pos = range.cursor(text);
if pos == 0 {
- return (pos, pos);
+ return ((pos, pos), None);
}
let line_start_pos = text.line_to_char(range.cursor_line(text));
// consider to delete by indent level if all characters before `pos` are indent units.
@@ -4418,7 +4420,10 @@ pub mod insert {
if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
if text.get_char(pos.saturating_sub(1)) == Some('\t') {
// fast path, delete one char
- (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos)
+ (
+ (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos),
+ None,
+ )
} else {
let width: usize = fragment
.chars()
@@ -4445,7 +4450,7 @@ pub mod insert {
_ => break,
}
}
- (start, pos) // delete!
+ ((start, pos), None) // delete!
}
} else {
match (
@@ -4461,18 +4466,26 @@ pub mod insert {
// delete both autopaired characters
{
(
- graphemes::nth_prev_grapheme_boundary(text, pos, count),
- graphemes::nth_next_grapheme_boundary(text, pos, count),
+ (
+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
+ graphemes::nth_next_grapheme_boundary(text, pos, count),
+ ),
+ None,
)
}
_ =>
// delete 1 char
{
- (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos)
+ (
+ (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos),
+ None,
+ )
}
}
}
- });
+ },
+ );
+
let (view, doc) = current!(cx.editor);
doc.apply(&transaction, view.id);
}