use std::ops::Range; use implicit_fn::implicit_fn; use lsp_types::Position; use ropey::Rope; pub trait RopeExt { fn position(&self, r: Range) -> Option<[(usize, usize); 2]>; /// number of lines fn l(&self) -> usize; // input: char, output: utf8 fn x_bytes(&self, c: usize) -> Option; fn y(&self, c: usize) -> Option; fn x(&self, c: usize) -> Option; fn xy(&self, c: usize) -> Option<(usize, usize)>; fn beginning_of_line(&self, c: usize) -> Option; fn indentation_of(&self, n: usize) -> usize; /// or eof fn eol(&self, li: usize) -> usize; fn l_pos_to_char(&self, p: Position) -> Option<(usize, usize)>; fn l_position(&self, p: Position) -> Option; fn to_l_position(&self, l: usize) -> Option; fn l_range(&self, r: lsp_types::Range) -> Option>; fn to_l_range(&self, r: Range) -> Option; } impl RopeExt for Rope { fn position( &self, Range { start, end }: Range, ) -> Option<[(usize, usize); 2]> { let y1 = self.try_char_to_line(start).ok()?; let y2 = self.try_char_to_line(end).ok()?; let x1 = start - self.try_line_to_char(y1).ok()?; let x2 = end - self.try_line_to_char(y2).ok()?; [(x1, y1), (x2, y2)].into() } /// number of lines fn l(&self) -> usize { self.len_lines() } // input: char, output: utf8 fn x_bytes(&self, c: usize) -> Option { let y = self.try_char_to_line(c).ok()?; let x = self .try_char_to_byte(c) .ok()? .checked_sub(self.try_line_to_byte(y).ok()?)?; Some(x) } fn y(&self, c: usize) -> Option { self.try_char_to_line(c).ok() } fn xy(&self, c: usize) -> Option<(usize, usize)> { let y = self.try_char_to_line(c).ok()?; let x = c.checked_sub(self.try_line_to_char(y).ok()?)?; Some((x, y)) } fn x(&self, c: usize) -> Option { self.xy(c).map(|x| x.0) } fn beginning_of_line(&self, c: usize) -> Option { self.y(c).and_then(|x| self.try_line_to_char(x).ok()) } #[implicit_fn] fn indentation_of(&self, n: usize) -> usize { let Some(l) = self.get_line(n) else { return 0 }; l.chars().filter(*_ != '\n').take_while(_.is_whitespace()).count() } /// or eof #[lower::apply(saturating)] fn eol(&self, li: usize) -> usize { self.try_line_to_char(li) .map(|l| { l + self .get_line(li) .map(|x| x.len_chars() - 1) .unwrap_or_default() }) .unwrap_or(usize::MAX) .min(self.len_chars()) } fn l_pos_to_char(&self, p: Position) -> Option<(usize, usize)> { self.l_position(p).and_then(|x| self.xy(x)) } fn l_position(&self, p: Position) -> Option { self.try_byte_to_char( self.try_line_to_byte(p.line as _).ok()? + (p.character as usize) .min(self.get_line(p.line as _)?.len_bytes()), ) .ok() } fn to_l_position(&self, l: usize) -> Option { Some(Position { line: self.y(l)? as _, character: self.x_bytes(l)? as _, }) } fn l_range(&self, r: lsp_types::Range) -> Option> { Some(self.l_position(r.start)?..self.l_position(r.end)?) } fn to_l_range(&self, r: Range) -> Option { Some(lsp_types::Range { start: self.to_l_position(r.start)?, end: self.to_l_position(r.end)?, }) } }