diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index 0b630ea49f6fca0a38f34815bdf1800df06877bc..b5b68c22c9e4d4d6f8ae2e52efa600a1c373b793 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -1,5 +1,7 @@ use std::{cmp::Ordering, collections::HashMap, ops::Range}; +/// Construct a string and a list of offsets within that string using a single +/// string containing embedded position markers. pub fn marked_text_offsets_by( marked_text: &str, markers: Vec, @@ -19,6 +21,12 @@ pub fn marked_text_offsets_by( (unmarked_text, extracted_markers) } +/// Construct a string and a list of ranges within that string using a single +/// string containing embedded range markers, using arbitrary characters as +/// range markers. By using multiple different range markers, you can construct +/// ranges that overlap each other. +/// +/// The returned ranges will be grouped by their range marking characters. pub fn marked_text_ranges_by( marked_text: &str, markers: Vec, @@ -72,67 +80,94 @@ pub fn marked_text_ranges_by( (unmarked_text, range_lookup) } -pub fn marked_text_ranges(input_text: &str, indicate_cursors: bool) -> (String, Vec>) { - let mut output_text = String::with_capacity(input_text.len()); +/// Construct a string and a list of ranges within that string using a single +/// string containing embedded range markers. The characters used to mark the +/// ranges are as follows: +/// +/// 1. To mark a range of text, surround it with the `«` and `»` angle brackets, +/// which can be typed on a US keyboard with the `alt-|` and `alt-shift-|` keys. +/// +/// ``` +/// foo «selected text» bar +/// ``` +/// +/// 2. To mark a single position in the text, use the `ˇ` caron, +/// which can be typed on a US keyboard with the `alt-shift-t` key. +/// +/// ``` +/// the cursors are hereˇ and hereˇ. +/// ``` +/// +/// 3. To mark a range whose direction is meaningful (like a selection), +/// put a caron character beside one of its bounds, on the inside: +/// +/// ``` +/// one «ˇreversed» selection and one «forwardˇ» selection +/// ``` +pub fn marked_text_ranges( + marked_text: &str, + ranges_are_directed: bool, +) -> (String, Vec>) { + let mut unmarked_text = String::with_capacity(marked_text.len()); let mut ranges = Vec::new(); - let mut prev_input_ix = 0; + let mut prev_marked_ix = 0; let mut current_range_start = None; let mut current_range_cursor = None; - for (input_ix, marker) in input_text.match_indices(&['«', '»', 'ˇ']) { - output_text.push_str(&input_text[prev_input_ix..input_ix]); - let output_len = output_text.len(); + for (marked_ix, marker) in marked_text.match_indices(&['«', '»', 'ˇ']) { + unmarked_text.push_str(&marked_text[prev_marked_ix..marked_ix]); + let unmarked_len = unmarked_text.len(); let len = marker.len(); - prev_input_ix = input_ix + len; + prev_marked_ix = marked_ix + len; match marker { "ˇ" => { if current_range_start.is_some() { if current_range_cursor.is_some() { - panic!("duplicate point marker 'ˇ' at index {input_ix}"); + panic!("duplicate point marker 'ˇ' at index {marked_ix}"); } else { - current_range_cursor = Some(output_len); + current_range_cursor = Some(unmarked_len); } } else { - ranges.push(output_len..output_len); + ranges.push(unmarked_len..unmarked_len); } } "«" => { if current_range_start.is_some() { - panic!("unexpected range start marker '«' at index {input_ix}"); + panic!("unexpected range start marker '«' at index {marked_ix}"); } - current_range_start = Some(output_len); + current_range_start = Some(unmarked_len); } "»" => { let current_range_start = if let Some(start) = current_range_start.take() { start } else { - panic!("unexpected range end marker '»' at index {input_ix}"); + panic!("unexpected range end marker '»' at index {marked_ix}"); }; let mut reversed = false; if let Some(current_range_cursor) = current_range_cursor.take() { if current_range_cursor == current_range_start { reversed = true; - } else if current_range_cursor != output_len { + } else if current_range_cursor != unmarked_len { panic!("unexpected 'ˇ' marker in the middle of a range"); } - } else if indicate_cursors { + } else if ranges_are_directed { panic!("missing 'ˇ' marker to indicate range direction"); } ranges.push(if reversed { - output_len..current_range_start + unmarked_len..current_range_start } else { - current_range_start..output_len + current_range_start..unmarked_len }); } _ => unreachable!(), } } - output_text.push_str(&input_text[prev_input_ix..]); - (output_text, ranges) + unmarked_text.push_str(&marked_text[prev_marked_ix..]); + (unmarked_text, ranges) } pub fn marked_text_offsets(marked_text: &str) -> (String, Vec) { @@ -150,11 +185,11 @@ pub fn marked_text_offsets(marked_text: &str) -> (String, Vec) { } pub fn generate_marked_text( - output_text: &str, + unmarked_text: &str, ranges: &[Range], indicate_cursors: bool, ) -> String { - let mut marked_text = output_text.to_string(); + let mut marked_text = unmarked_text.to_string(); for range in ranges.iter().rev() { if indicate_cursors { match range.start.cmp(&range.end) {