mirror of https://github.com/helix-editor/helix
add delete_by_and_with_selection
parent
d319dbfcc0
commit
e046bb75a6
|
@ -110,17 +110,22 @@ impl Default for AutoPairs {
|
||||||
// middle of triple quotes, and more exotic pairs like Jinja's {% %}
|
// middle of triple quotes, and more exotic pairs like Jinja's {% %}
|
||||||
|
|
||||||
#[must_use]
|
#[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);
|
log::trace!("autopairs hook range: {:#?}", range);
|
||||||
|
|
||||||
if let Some(pair) = pairs.get(ch) {
|
if let Some(pair) = pairs.get(ch) {
|
||||||
if pair.same() {
|
if pair.same() {
|
||||||
return handle_same(doc, range, pair);
|
return handle_insert_same(doc, range, pair);
|
||||||
} else if pair.open == ch {
|
} else if pair.open == ch {
|
||||||
return handle_open(doc, range, pair);
|
return handle_insert_open(doc, range, pair);
|
||||||
} else if pair.close == ch {
|
} else if pair.close == ch {
|
||||||
// && char_at pos == close
|
// && 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)
|
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 cursor = range.cursor(doc.slice(..));
|
||||||
let next_char = doc.get_char(cursor);
|
let next_char = doc.get_char(cursor);
|
||||||
let len_inserted;
|
let len_inserted;
|
||||||
|
@ -271,7 +276,7 @@ fn handle_open(doc: &Rope, range: &Range, pair: &Pair) -> Option<(Change, Range)
|
||||||
Some(result)
|
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 cursor = range.cursor(doc.slice(..));
|
||||||
let next_char = doc.get_char(cursor);
|
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""")
|
/// 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 cursor = 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);
|
||||||
|
|
|
@ -738,16 +738,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
|
/// Generate a transaction with a change per selection range, which
|
||||||
/// generates a new selection as well. Each range is operated upon by
|
/// generates a new selection as well. Each range is operated upon by
|
||||||
/// the given function and can optionally produce a new range. If none
|
/// the given function and can optionally produce a new range. If none
|
||||||
|
@ -801,6 +791,60 @@ impl Transaction {
|
||||||
transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
|
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.
|
/// 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| {
|
||||||
|
|
|
@ -4127,7 +4127,7 @@ pub mod insert {
|
||||||
auto_pairs
|
auto_pairs
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|ap| {
|
.and_then(|ap| {
|
||||||
auto_pairs::hook(text, range, c, ap)
|
auto_pairs::hook_insert(text, range, c, ap)
|
||||||
.map(|(change, range)| (change, Some(range)))
|
.map(|(change, range)| (change, Some(range)))
|
||||||
.or(Some(insert_char(*range, c)))
|
.or(Some(insert_char(*range, c)))
|
||||||
})
|
})
|
||||||
|
@ -4338,11 +4338,13 @@ pub mod insert {
|
||||||
let indent_width = doc.indent_width();
|
let indent_width = doc.indent_width();
|
||||||
let auto_pairs = doc.auto_pairs(cx.editor);
|
let auto_pairs = doc.auto_pairs(cx.editor);
|
||||||
|
|
||||||
let transaction =
|
let transaction = Transaction::delete_by_and_with_selection(
|
||||||
Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
|
doc.text(),
|
||||||
|
doc.selection(view.id),
|
||||||
|
|range| {
|
||||||
let pos = range.cursor(text);
|
let pos = range.cursor(text);
|
||||||
if pos == 0 {
|
if pos == 0 {
|
||||||
return (pos, pos);
|
return ((pos, pos), None);
|
||||||
}
|
}
|
||||||
let line_start_pos = text.line_to_char(range.cursor_line(text));
|
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.
|
// consider to delete by indent level if all characters before `pos` are indent units.
|
||||||
|
@ -4350,7 +4352,10 @@ pub mod insert {
|
||||||
if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
|
if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
|
||||||
if text.get_char(pos.saturating_sub(1)) == Some('\t') {
|
if text.get_char(pos.saturating_sub(1)) == Some('\t') {
|
||||||
// fast path, delete one char
|
// 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 {
|
} else {
|
||||||
let width: usize = fragment
|
let width: usize = fragment
|
||||||
.chars()
|
.chars()
|
||||||
|
@ -4377,7 +4382,7 @@ pub mod insert {
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(start, pos) // delete!
|
((start, pos), None) // delete!
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match (
|
match (
|
||||||
|
@ -4393,18 +4398,26 @@ pub mod insert {
|
||||||
// delete both autopaired characters
|
// 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
|
// 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);
|
let (view, doc) = current!(cx.editor);
|
||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue