feat: add basic hard-coded implementation for replace tag

pull/12055/head
Nikita Revenco 2 weeks ago
parent 3cb8519ffd
commit abe32105f3

@ -17,6 +17,40 @@ impl<F: Fn(&char) -> bool> CharMatcher for F {
} }
} }
pub fn find_nth_next_tag(
text: RopeSlice,
tag: &str,
mut pos: usize,
n: usize,
) -> Option<Vec<usize>> {
if pos >= text.len_chars() || n == 0 {
return None;
}
let mut chars = text.chars_at(pos);
let tag = format!("</{tag}>");
let len = tag.len();
for _ in 0..n {
loop {
let c = chars.next()?;
let cloned_chars = chars.clone();
let stri: String = cloned_chars.take(len).collect();
pos += 1;
if stri == tag {
break;
}
}
}
let range: Vec<usize> = (pos - 1..pos + len - 1).collect();
Some(range)
}
pub fn find_nth_next<M: CharMatcher>( pub fn find_nth_next<M: CharMatcher>(
text: RopeSlice, text: RopeSlice,
char_matcher: M, char_matcher: M,
@ -65,3 +99,37 @@ pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Opt
Some(pos) Some(pos)
} }
pub fn find_nth_prev_tag(
text: RopeSlice,
tag: &str,
mut pos: usize,
n: usize,
) -> Option<Vec<usize>> {
if pos == 0 || n == 0 {
return None;
}
let mut chars = text.chars_at(pos).reversed();
let tag = format!("<{tag}>");
let len = tag.len();
for _ in 0..n {
loop {
let c = chars.next()?;
let cloned_chars = chars.clone();
let stri: String = cloned_chars.take(len).collect();
pos -= 1;
if stri == tag {
break;
}
}
}
let range: Vec<usize> = (pos..pos + len).collect();
Some(range)
}

@ -308,11 +308,62 @@ pub fn get_surround_pos(
return Err(Error::CursorOverlap); return Err(Error::CursorOverlap);
} }
// ensure the positions are always paired in the forward direction // ensure the positions are always paired in the forward direction
// e.g. [41, 214]
change_pos.extend_from_slice(&[open_pos.min(close_pos), close_pos.max(open_pos)]); change_pos.extend_from_slice(&[open_pos.min(close_pos), close_pos.max(open_pos)]);
} }
Ok(change_pos) Ok(change_pos)
} }
fn find_nth_close_tag(
text: RopeSlice,
tag: &str,
mut pos: usize,
n: usize,
) -> Result<(Vec<usize>, Vec<usize>)> {
// if text.len_chars() < 2 {
// return Err(Error::PairNotFound);
// }
// if range.to() >= text.len_chars() {
// return Err(Error::RangeExceedsText);
// }
//
Ok((vec![4, 7], vec![10, 13]))
}
/// like get_surround_pos, but for Tags
pub fn get_surround_pos_tag(
text: RopeSlice,
selection: &Selection,
tag: &str,
skip: usize,
) -> Result<Vec<usize>> {
let mut change_pos = Vec::new();
for &range in selection {
let (start1, end1, start2, end2) = {
let pos = range.cursor(text);
let range_raw = find_nth_close_tag(text, tag, pos, skip).unwrap();
let first = range_raw.0;
let second = range_raw.1;
let first_1 = first.first().unwrap();
let first_2 = second.last().unwrap();
let second_1 = first.first().unwrap();
let second_2 = second.last().unwrap();
let range_first = Range::new(*first_1, *first_2);
let range_last = Range::new(*second_1, *second_2);
(
range_first.from(),
range_first.to(),
range_last.from(),
range_last.to(),
)
};
change_pos.extend_from_slice(&[start1, end1, start2, end2]);
}
Ok(change_pos)
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -336,6 +387,21 @@ mod test {
); );
} }
#[test]
fn test_get_surround_pos_tag_simple() {
#[rustfmt::skip]
let (doc, selection, expectations) =
rope_with_selections_and_expectations_tags(
"<tag>hello</tag> <MegaDiv>lol</MegaDiv>",
" ___ ^ ___ _______ ^ _______ "
);
assert_eq!(
get_tag_surround_pos(None, doc.slice(..), &selection, 1).unwrap(),
expectations
);
}
#[test] #[test]
fn test_get_surround_pos_bail_different_surround_chars() { fn test_get_surround_pos_bail_different_surround_chars() {
#[rustfmt::skip] #[rustfmt::skip]
@ -445,9 +511,9 @@ mod test {
) )
} }
// Create a Rope and a matching Selection using a specification language. /// Create a Rope and a matching Selection using a specification language.
// ^ is a single-point selection. /// ^ is a single-point selection.
// _ is an expected index. These are returned as a Vec<usize> for use in assertions. /// _ is an expected index. These are returned as a Vec<usize> for use in assertions.
fn rope_with_selections_and_expectations( fn rope_with_selections_and_expectations(
text: &str, text: &str,
spec: &str, spec: &str,
@ -467,4 +533,42 @@ mod test {
(rope, Selection::new(selections, 0), expectations) (rope, Selection::new(selections, 0), expectations)
} }
fn rope_with_selections_and_expectations_tags(
text: &str,
spec: &str,
) -> (Rope, Selection, Vec<Vec<usize>>) {
if text.len() != spec.len() {
panic!("specification must match text length -- are newlines aligned?");
}
let rope = Rope::from(text);
let selections: SmallVec<[Range; 1]> = spec
.match_indices('^')
.map(|(i, _)| Range::point(i))
.collect();
let expectations: Vec<Vec<usize>> = spec
.char_indices()
.filter(|&(_, c)| c == '_')
.map(|(i, _)| i)
.fold(Vec::new(), |mut groups, idx| {
if let Some(last_group) = groups.last_mut() {
if last_group
.last()
.map_or(false, |&last_idx| last_idx + 1 == idx)
{
last_group.push(idx);
} else {
groups.push(vec![idx]);
}
} else {
groups.push(vec![idx]);
}
groups
});
(rope, Selection::new(selections, 0), expectations)
}
} }

@ -5715,57 +5715,64 @@ fn surround_replace(cx: &mut Context) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
// the first character represents the index of the first change if false {
// the second character represents the index of the second change let change_pos = match surround::get_surround_pos_tag(text, selection, "lol", layer) {
let change_pos =
// also interested in changing this
match surround::get_surround_pos(doc.syntax(), text, selection, surround_ch, layer) {
Ok(c) => c, Ok(c) => c,
Err(err) => { Err(err) => {
cx.editor.set_error(err.to_string()); cx.editor.set_error(err.to_string());
return; return;
} }
}; };
// TODO: add back the logic for other surround changes
} else {
// TODO: obtain these values from a function
let change_pos: Vec<(usize, usize)> = vec![(4, 10), (13, 20), (24, 30), (33, 40)];
let selection = selection.clone(); let selection = selection.clone();
let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect(); let ranges: SmallVec<[Range; 1]> =
doc.set_selection( change_pos.iter().map(|&p| Range::new(p.0, p.1)).collect();
view.id,
Selection::new(ranges, selection.primary_index() * 2),
);
cx.on_next_key(move |cx, event| { doc.set_selection(
let (view, doc) = current!(cx.editor); view.id,
let to = match event.char() { Selection::new(ranges, selection.primary_index() * 2),
Some(to) => to,
None => return doc.set_selection(view.id, selection),
};
// we are interested in changing this specifically
let (open, close) = match_brackets::get_pair(to);
// the changeset has to be sorted to allow nested surrounds
let mut sorted_pos: Vec<(usize, char)> = Vec::new();
for p in change_pos.chunks(2) {
sorted_pos.push((p[0], open));
sorted_pos.push((p[1], close));
}
sorted_pos.sort_unstable();
let transaction = Transaction::change(
doc.text(),
sorted_pos.iter().map(|&pos| {
// "pos" is the idx of the character we are replacing
// "t" is the replacement character
let mut t = Tendril::new();
t.push(pos.1);
log::error!("{:?}, {:?}", pos.0, Some(&t));
(pos.0, pos.0 + 1, Some(t))
}),
); );
doc.set_selection(view.id, selection);
doc.apply(&transaction, view.id); cx.on_next_key(move |cx, event| {
exit_select_mode(cx); let (view, doc) = current!(cx.editor);
});
// TODO: obtain this value from the input
let open = "<help>";
let close = "</help>";
// the changeset has to be sorted to allow nested surrounds
let mut sorted_pos: Vec<((usize, usize), &str)> = Vec::new();
// taking chunks of two at once to replace with <help> </help>
for p in change_pos.chunks(2) {
let line_opening = p[0];
let line_closing = p[1];
sorted_pos.push((line_opening, open));
sorted_pos.push((line_closing, close));
}
sorted_pos.sort_unstable();
let transaction = Transaction::change(
doc.text(),
sorted_pos.iter().map(|&(pos, change_tag)| {
let mut tag = Tendril::new();
tag.push_str(change_tag);
// replace from pos.0 to pos.1 with t
(pos.0, pos.1, Some(tag))
}),
);
// we don't need to touch this
doc.set_selection(view.id, selection);
doc.apply(&transaction, view.id);
exit_select_mode(cx);
});
}
}) })
} }

Loading…
Cancel
Save