A simple CPU rendered GUI IDE experience.
Diffstat (limited to 'src/text/rope_ext.rs')
-rw-r--r--src/text/rope_ext.rs118
1 files changed, 118 insertions, 0 deletions
diff --git a/src/text/rope_ext.rs b/src/text/rope_ext.rs
new file mode 100644
index 0000000..d4f7398
--- /dev/null
+++ b/src/text/rope_ext.rs
@@ -0,0 +1,118 @@
+use std::ops::Range;
+
+use implicit_fn::implicit_fn;
+use lsp_types::Position;
+use ropey::Rope;
+
+pub trait RopeExt {
+ fn position(&self, r: Range<usize>) -> Option<[(usize, usize); 2]>;
+ /// number of lines
+ fn l(&self) -> usize;
+
+ // input: char, output: utf8
+ fn x_bytes(&self, c: usize) -> Option<usize>;
+ fn y(&self, c: usize) -> Option<usize>;
+ fn x(&self, c: usize) -> Option<usize>;
+ fn xy(&self, c: usize) -> Option<(usize, usize)>;
+
+ fn beginning_of_line(&self, c: usize) -> Option<usize>;
+
+ 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<usize>;
+ fn to_l_position(&self, l: usize) -> Option<lsp_types::Position>;
+ fn l_range(&self, r: lsp_types::Range) -> Option<Range<usize>>;
+ fn to_l_range(&self, r: Range<usize>) -> Option<lsp_types::Range>;
+}
+impl RopeExt for Rope {
+ fn position(
+ &self,
+ Range { start, end }: Range<usize>,
+ ) -> 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<usize> {
+ 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<usize> {
+ 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<usize> {
+ self.xy(c).map(|x| x.0)
+ }
+ fn beginning_of_line(&self, c: usize) -> Option<usize> {
+ 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<usize> {
+ 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<lsp_types::Position> {
+ Some(Position {
+ line: self.y(l)? as _,
+ character: self.x_bytes(l)? as _,
+ })
+ }
+ fn l_range(&self, r: lsp_types::Range) -> Option<Range<usize>> {
+ Some(self.l_position(r.start)?..self.l_position(r.end)?)
+ }
+ fn to_l_range(&self, r: Range<usize>) -> Option<lsp_types::Range> {
+ Some(lsp_types::Range {
+ start: self.to_l_position(r.start)?,
+ end: self.to_l_position(r.end)?,
+ })
+ }
+}