aboutsummaryrefslogtreecommitdiff
path: root/helix-core/src/selection.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-core/src/selection.rs')
-rw-r--r--helix-core/src/selection.rs96
1 files changed, 60 insertions, 36 deletions
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index c44685ee..91f1d0de 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -7,9 +7,11 @@ use crate::{
ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
prev_grapheme_boundary,
},
+ line_ending::get_line_ending,
movement::Direction,
Assoc, ChangeSet, RopeGraphemes, RopeSlice,
};
+use helix_stdx::rope::{self, RopeSliceExt};
use smallvec::{smallvec, SmallVec};
use std::borrow::Cow;
@@ -708,12 +710,12 @@ impl IntoIterator for Selection {
pub fn keep_or_remove_matches(
text: RopeSlice,
selection: &Selection,
- regex: &crate::regex::Regex,
+ regex: &rope::Regex,
remove: bool,
) -> Option<Selection> {
let result: SmallVec<_> = selection
.iter()
- .filter(|range| regex.is_match(&range.fragment(text)) ^ remove)
+ .filter(|range| regex.is_match(text.regex_input_at(range.from()..range.to())) ^ remove)
.copied()
.collect();
@@ -724,25 +726,20 @@ pub fn keep_or_remove_matches(
None
}
+// TODO: support to split on capture #N instead of whole match
pub fn select_on_matches(
text: RopeSlice,
selection: &Selection,
- regex: &crate::regex::Regex,
+ regex: &rope::Regex,
) -> Option<Selection> {
let mut result = SmallVec::with_capacity(selection.len());
for sel in selection {
- // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
- let fragment = sel.fragment(text);
-
- let sel_start = sel.from();
- let start_byte = text.char_to_byte(sel_start);
-
- for mat in regex.find_iter(&fragment) {
+ for mat in regex.find_iter(text.regex_input_at(sel.from()..sel.to())) {
// TODO: retain range direction
- let start = text.byte_to_char(start_byte + mat.start());
- let end = text.byte_to_char(start_byte + mat.end());
+ let start = text.byte_to_char(mat.start());
+ let end = text.byte_to_char(mat.end());
let range = Range::new(start, end);
// Make sure the match is not right outside of the selection.
@@ -761,12 +758,7 @@ pub fn select_on_matches(
None
}
-// TODO: support to split on capture #N instead of whole match
-pub fn split_on_matches(
- text: RopeSlice,
- selection: &Selection,
- regex: &crate::regex::Regex,
-) -> Selection {
+pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection {
let mut result = SmallVec::with_capacity(selection.len());
for sel in selection {
@@ -776,21 +768,47 @@ pub fn split_on_matches(
continue;
}
- // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
- let fragment = sel.fragment(text);
-
let sel_start = sel.from();
let sel_end = sel.to();
- let start_byte = text.char_to_byte(sel_start);
+ let mut start = sel_start;
+ for mat in sel.slice(text).lines() {
+ let len = mat.len_chars();
+ let line_end_len = get_line_ending(&mat).map(|le| le.len_chars()).unwrap_or(0);
+ // TODO: retain range direction
+ result.push(Range::new(start, start + len - line_end_len));
+ start += len;
+ }
+
+ if start < sel_end {
+ result.push(Range::new(start, sel_end));
+ }
+ }
+
+ // TODO: figure out a new primary index
+ Selection::new(result, 0)
+}
+
+pub fn split_on_matches(text: RopeSlice, selection: &Selection, regex: &rope::Regex) -> Selection {
+ let mut result = SmallVec::with_capacity(selection.len());
+
+ for sel in selection {
+ // Special case: zero-width selection.
+ if sel.from() == sel.to() {
+ result.push(*sel);
+ continue;
+ }
+
+ let sel_start = sel.from();
+ let sel_end = sel.to();
let mut start = sel_start;
- for mat in regex.find_iter(&fragment) {
+ for mat in regex.find_iter(text.regex_input_at(sel_start..sel_end)) {
// TODO: retain range direction
- let end = text.byte_to_char(start_byte + mat.start());
+ let end = text.byte_to_char(mat.start());
result.push(Range::new(start, end));
- start = text.byte_to_char(start_byte + mat.end());
+ start = text.byte_to_char(mat.end());
}
if start < sel_end {
@@ -1021,14 +1039,12 @@ mod test {
#[test]
fn test_select_on_matches() {
- use crate::regex::{Regex, RegexBuilder};
-
let r = Rope::from_str("Nobody expects the Spanish inquisition");
let s = r.slice(..);
let selection = Selection::single(0, r.len_chars());
assert_eq!(
- select_on_matches(s, &selection, &Regex::new(r"[A-Z][a-z]*").unwrap()),
+ select_on_matches(s, &selection, &rope::Regex::new(r"[A-Z][a-z]*").unwrap()),
Some(Selection::new(
smallvec![Range::new(0, 6), Range::new(19, 26)],
0
@@ -1038,8 +1054,14 @@ mod test {
let r = Rope::from_str("This\nString\n\ncontains multiple\nlines");
let s = r.slice(..);
- let start_of_line = RegexBuilder::new(r"^").multi_line(true).build().unwrap();
- let end_of_line = RegexBuilder::new(r"$").multi_line(true).build().unwrap();
+ let start_of_line = rope::RegexBuilder::new()
+ .syntax(rope::Config::new().multi_line(true))
+ .build(r"^")
+ .unwrap();
+ let end_of_line = rope::RegexBuilder::new()
+ .syntax(rope::Config::new().multi_line(true))
+ .build(r"$")
+ .unwrap();
// line without ending
assert_eq!(
@@ -1077,9 +1099,9 @@ mod test {
select_on_matches(
s,
&Selection::single(0, s.len_chars()),
- &RegexBuilder::new(r"^[a-z ]*$")
- .multi_line(true)
- .build()
+ &rope::RegexBuilder::new()
+ .syntax(rope::Config::new().multi_line(true))
+ .build(r"^[a-z ]*$")
.unwrap()
),
Some(Selection::new(
@@ -1171,13 +1193,15 @@ mod test {
#[test]
fn test_split_on_matches() {
- use crate::regex::Regex;
-
let text = Rope::from(" abcd efg wrs xyz 123 456");
let selection = Selection::new(smallvec![Range::new(0, 9), Range::new(11, 20),], 0);
- let result = split_on_matches(text.slice(..), &selection, &Regex::new(r"\s+").unwrap());
+ let result = split_on_matches(
+ text.slice(..),
+ &selection,
+ &rope::Regex::new(r"\s+").unwrap(),
+ );
assert_eq!(
result.ranges(),