Add Transaction::change_by_and_with_selection

Adds `Transaction::change_by_and_with_selection` which centralizes
logic for producing change sets with a potentially new selection that
is applied incrementally, rather than all at once at the end with
`with_selection`. It also centralizes the offset tracking logic so that
the caller can construct a new selection with ranges as if they were
operating on the text as-is.
pull/7269/head
Skyler Hawthorne 2 years ago
parent 38faf74feb
commit f1b459bd00

@ -4,8 +4,6 @@
use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction}; use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
use std::collections::HashMap; use std::collections::HashMap;
use smallvec::SmallVec;
// Heavily based on https://github.com/codemirror/closebrackets/ // Heavily based on https://github.com/codemirror/closebrackets/
pub const DEFAULT_PAIRS: &[(char, char)] = &[ pub const DEFAULT_PAIRS: &[(char, char)] = &[
('(', ')'), ('(', ')'),
@ -146,7 +144,7 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
} }
/// calculate what the resulting range should be for an auto pair insertion /// calculate what the resulting range should be for an auto pair insertion
fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted: usize) -> Range { fn get_next_range(doc: &Rope, start_range: &Range, len_inserted: usize) -> Range {
// When the character under the cursor changes due to complete pair // When the character under the cursor changes due to complete pair
// insertion, we must look backward a grapheme and then add the length // insertion, we must look backward a grapheme and then add the length
// of the insertion to put the resulting cursor in the right place, e.g. // of the insertion to put the resulting cursor in the right place, e.g.
@ -165,10 +163,7 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
// inserting at the very end of the document after the last newline // inserting at the very end of the document after the last newline
if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() { if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() {
return Range::new( return Range::new(start_range.anchor + 1, start_range.head + 1);
start_range.anchor + offset + 1,
start_range.head + offset + 1,
);
} }
let doc_slice = doc.slice(..); let doc_slice = doc.slice(..);
@ -177,7 +172,7 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
// just skip over graphemes // just skip over graphemes
if len_inserted == 0 { if len_inserted == 0 {
let end_anchor = if single_grapheme { let end_anchor = if single_grapheme {
graphemes::next_grapheme_boundary(doc_slice, start_range.anchor) + offset graphemes::next_grapheme_boundary(doc_slice, start_range.anchor)
// even for backward inserts with multiple grapheme selections, // even for backward inserts with multiple grapheme selections,
// we want the anchor to stay where it is so that the relative // we want the anchor to stay where it is so that the relative
@ -185,42 +180,38 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
// //
// foo([) wor]d -> insert ) -> foo()[ wor]d // foo([) wor]d -> insert ) -> foo()[ wor]d
} else { } else {
start_range.anchor + offset start_range.anchor
}; };
return Range::new( return Range::new(
end_anchor, end_anchor,
graphemes::next_grapheme_boundary(doc_slice, start_range.head) + offset, graphemes::next_grapheme_boundary(doc_slice, start_range.head),
); );
} }
// trivial case: only inserted a single-char opener, just move the selection // trivial case: only inserted a single-char opener, just move the selection
if len_inserted == 1 { if len_inserted == 1 {
let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward { let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward {
start_range.anchor + offset + 1 start_range.anchor + 1
} else { } else {
start_range.anchor + offset start_range.anchor
}; };
return Range::new(end_anchor, start_range.head + offset + 1); return Range::new(end_anchor, start_range.head + 1);
} }
// If the head = 0, then we must be in insert mode with a backward // If the head = 0, then we must be in insert mode with a backward
// cursor, which implies the head will just move // cursor, which implies the head will just move
let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward { let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward {
start_range.head + offset + 1 start_range.head + 1
} else { } else {
// We must have a forward cursor, which means we must move to the // We must have a forward cursor, which means we must move to the
// other end of the grapheme to get to where the new characters // other end of the grapheme to get to where the new characters
// are inserted, then move the head to where it should be // are inserted, then move the head to where it should be
let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head); let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
log::trace!( log::trace!("prev_bound: {}, len_inserted: {}", prev_bound, len_inserted);
"prev_bound: {}, offset: {}, len_inserted: {}",
prev_bound, prev_bound + len_inserted
offset,
len_inserted
);
prev_bound + offset + len_inserted
}; };
let end_anchor = match (start_range.len(), start_range.direction()) { let end_anchor = match (start_range.len(), start_range.direction()) {
@ -239,7 +230,7 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
// if we are appending, the anchor stays where it is; only offset // if we are appending, the anchor stays where it is; only offset
// for multiple range insertions // for multiple range insertions
} else { } else {
start_range.anchor + offset start_range.anchor
} }
} }
@ -248,13 +239,11 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
// if we're backward, then the head is at the first char // if we're backward, then the head is at the first char
// of the typed char, so we need to add the length of // of the typed char, so we need to add the length of
// the closing char // the closing char
graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor) graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor) + len_inserted
+ len_inserted
+ offset
} else { } else {
// when we are inserting in front of a selection, we need to move // when we are inserting in front of a selection, we need to move
// the anchor over by however many characters were inserted overall // the anchor over by however many characters were inserted overall
start_range.anchor + offset + len_inserted start_range.anchor + len_inserted
} }
} }
}; };
@ -263,10 +252,7 @@ fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted:
} }
fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction { fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
let mut end_ranges = SmallVec::with_capacity(selection.len()); let transaction = Transaction::change_by_and_with_selection(doc, selection, |start_range| {
let mut offs = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
let cursor = start_range.cursor(doc.slice(..)); let cursor = start_range.cursor(doc.slice(..));
let next_char = doc.get_char(cursor); let next_char = doc.get_char(cursor);
let len_inserted; let len_inserted;
@ -289,23 +275,17 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
} }
}; };
let next_range = get_next_range(doc, start_range, offs, len_inserted); let next_range = get_next_range(doc, start_range, len_inserted);
end_ranges.push(next_range);
offs += len_inserted;
change (change, Some(next_range))
}); });
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); log::debug!("auto pair transaction: {:#?}", transaction);
log::debug!("auto pair transaction: {:#?}", t); transaction
t
} }
fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction { fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
let mut end_ranges = SmallVec::with_capacity(selection.len()); let transaction = Transaction::change_by_and_with_selection(doc, selection, |start_range| {
let mut offs = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
let cursor = start_range.cursor(doc.slice(..)); let cursor = start_range.cursor(doc.slice(..));
let next_char = doc.get_char(cursor); let next_char = doc.get_char(cursor);
let mut len_inserted = 0; let mut len_inserted = 0;
@ -320,25 +300,18 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
(cursor, cursor, Some(tendril)) (cursor, cursor, Some(tendril))
}; };
let next_range = get_next_range(doc, start_range, offs, len_inserted); let next_range = get_next_range(doc, start_range, len_inserted);
end_ranges.push(next_range);
offs += len_inserted;
change (change, Some(next_range))
}); });
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); log::debug!("auto pair transaction: {:#?}", transaction);
log::debug!("auto pair transaction: {:#?}", t); transaction
t
} }
/// handle cases where open and close is the same, or in triples ("""docstring""") /// handle cases where open and close is the same, or in triples ("""docstring""")
fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction { fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
let mut end_ranges = SmallVec::with_capacity(selection.len()); let transaction = Transaction::change_by_and_with_selection(doc, selection, |start_range| {
let mut offs = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
let cursor = start_range.cursor(doc.slice(..)); let cursor = start_range.cursor(doc.slice(..));
let mut len_inserted = 0; let mut len_inserted = 0;
let next_char = doc.get_char(cursor); let next_char = doc.get_char(cursor);
@ -360,14 +333,12 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
(cursor, cursor, Some(pair_str)) (cursor, cursor, Some(pair_str))
}; };
let next_range = get_next_range(doc, start_range, offs, len_inserted); let next_range = get_next_range(doc, start_range, len_inserted);
end_ranges.push(next_range);
offs += len_inserted;
change (change, Some(next_range))
}); });
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); log::debug!("auto pair transaction: {:#?}", transaction);
log::debug!("auto pair transaction: {:#?}", t);
t transaction
} }

@ -736,6 +736,55 @@ impl Transaction {
Self::delete(doc, selection.iter().map(f)) 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
/// is returned by the function, that range is dropped from the resulting
/// selection.
pub fn change_by_and_with_selection<F>(doc: &Rope, selection: &Selection, mut f: F) -> Self
where
F: FnMut(&Range) -> (Change, Option<Range>),
{
let mut end_ranges = SmallVec::with_capacity(selection.len());
let mut offset = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
let ((from, to, replacement), end_range) = f(start_range);
let mut change_size = to as isize - from as isize;
if let Some(ref text) = replacement {
change_size = text.chars().count() as isize - change_size;
} else {
change_size = -change_size;
}
if let Some(end_range) = end_range {
let offset_range = Range::new(
(end_range.anchor as isize + offset) as usize,
(end_range.head as isize + offset) as usize,
);
log::trace!("end range {:?} offset to: {:?}", end_range, offset_range);
end_ranges.push(offset_range);
}
offset += change_size;
log::trace!(
"from: {}, to: {}, replacement: {:?}, offset: {}",
from,
to,
replacement,
offset
);
(from, to, replacement)
});
transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
}
/// Insert text at each selection head. /// Insert text at each selection head.
pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self { pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
Self::change_by_selection(doc, selection, |range| { Self::change_by_selection(doc, selection, |range| {

Loading…
Cancel
Save