use std::cmp::min;
use std::str::Chars;
use itertools::{Itertools, MultiPeek};
use lsp_types::{Position, Range};
#[path = "cursor_test.rs"]
mod test;
pub fn cursors(text: &str) -> (String, Cursors) {
let text = text.trim();
let mut cursors = Cursors::new();
let mut output_text = String::with_capacity(text.len());
let mut position = Position::new(0, 0);
let mut it = text.chars().multipeek();
while let Some(ch) = it.next() {
match ch {
'<' if peek(&mut it, "caret") => {
eat(&mut it, "caret>");
cursors.add(position);
}
_ => {
output_text.push(ch);
if ch == '\n' {
position.line += 1;
position.character = 0;
} else {
position.character += 1;
}
}
}
}
return (output_text, cursors);
fn peek(it: &mut MultiPeek<Chars<'_>>, needle: &str) -> bool {
let mut matched = true;
for needle_ch in needle.chars() {
let Some(&haystack_ch) = it.peek() else {
matched = false;
break;
};
if needle_ch != haystack_ch {
matched = false;
break;
}
}
it.reset_peek();
matched
}
fn eat(it: &mut MultiPeek<Chars<'_>>, needle: &str) {
for needle_ch in needle.chars() {
let haystack_ch = it.next();
assert_eq!(haystack_ch, Some(needle_ch));
}
}
}
pub struct Cursors {
cursors: Vec<Position>,
}
impl Cursors {
fn new() -> Self {
Self { cursors: Vec::new() }
}
fn add(&mut self, cursor: Position) {
self.cursors.push(cursor);
}
pub fn caret(&self, idx: usize) -> Position {
*self.cursors.get(idx).unwrap_or_else(|| panic!("cursor not found: {idx}"))
}
pub fn carets(&self) -> Vec<Position> {
self.cursors.clone()
}
}
pub fn peek_caret(text: &str, position: Position) -> String {
let mut snippet = text.to_owned();
snippet.insert_str(index_in_text(text, position), "<caret>");
snippet.lines().nth(position.line as usize).unwrap().to_owned() + "\n"
}
pub fn peek_selection(text: &str, range: &Range) -> String {
let mut snippet = text.to_owned();
assert!(range.start <= range.end);
snippet.insert_str(index_in_text(text, range.start), "<sel>");
snippet.insert_str(index_in_text(text, range.end) + "<sel>".len(), "</sel>");
snippet
.lines()
.skip(range.start.line as usize)
.take(range.end.line as usize - range.start.line as usize + 1)
.join("\n")
+ "\n"
}
fn index_in_text(text: &str, position: Position) -> usize {
let mut offset = 0;
let mut lines = text.lines();
for line in lines.by_ref().take(position.line as usize) {
offset += line.len() + "\n".len();
}
if let Some(line) = lines.next() {
offset += min(position.character as usize, line.len());
}
offset
}