Skip to content

Commit

Permalink
feat: add basic hard-coded implementation for replace tag
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitarevenco committed Nov 12, 2024
1 parent 3cb8519 commit abe3210
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 45 deletions.
68 changes: 68 additions & 0 deletions helix-core/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
text: RopeSlice,
char_matcher: M,
Expand Down Expand Up @@ -65,3 +99,37 @@ pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Opt

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)
}
110 changes: 107 additions & 3 deletions helix-core/src/surround.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,62 @@ pub fn get_surround_pos(
return Err(Error::CursorOverlap);
}
// 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)]);
}
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)]
mod test {
use super::*;
Expand All @@ -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]
fn test_get_surround_pos_bail_different_surround_chars() {
#[rustfmt::skip]
Expand Down Expand Up @@ -445,9 +511,9 @@ mod test {
)
}

// Create a Rope and a matching Selection using a specification language.
// ^ is a single-point selection.
// _ is an expected index. These are returned as a Vec<usize> for use in assertions.
/// Create a Rope and a matching Selection using a specification language.
/// ^ is a single-point selection.
/// _ is an expected index. These are returned as a Vec<usize> for use in assertions.
fn rope_with_selections_and_expectations(
text: &str,
spec: &str,
Expand All @@ -467,4 +533,42 @@ mod test {

(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)
}
}
91 changes: 49 additions & 42 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5715,57 +5715,64 @@ fn surround_replace(cx: &mut Context) {
let text = doc.text().slice(..);
let selection = doc.selection(view.id);

// the first character represents the index of the first change
// the second character represents the index of the second change
let change_pos =
// also interested in changing this
match surround::get_surround_pos(doc.syntax(), text, selection, surround_ch, layer) {
if false {
let change_pos = match surround::get_surround_pos_tag(text, selection, "lol", layer) {
Ok(c) => c,
Err(err) => {
cx.editor.set_error(err.to_string());
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 ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect();
doc.set_selection(
view.id,
Selection::new(ranges, selection.primary_index() * 2),
);
let selection = selection.clone();
let ranges: SmallVec<[Range; 1]> =
change_pos.iter().map(|&p| Range::new(p.0, p.1)).collect();

cx.on_next_key(move |cx, event| {
let (view, doc) = current!(cx.editor);
let to = match event.char() {
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::new(ranges, selection.primary_index() * 2),
);
doc.set_selection(view.id, selection);
doc.apply(&transaction, view.id);
exit_select_mode(cx);
});

cx.on_next_key(move |cx, event| {
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);
});
}
})
}

Expand Down

0 comments on commit abe3210

Please sign in to comment.