use std::borrow::Cow;
use crate::{
core::text::Text,
error::{Error, Result},
utils::trim_eol_from_end,
};
#[derive(Clone, Debug, PartialEq)]
pub enum Change<'a> {
Delete { start: GridIndex, end: GridIndex },
Insert { at: GridIndex, text: Cow<'a, str> },
Replace {
start: GridIndex,
end: GridIndex,
text: Cow<'a, str>,
},
ReplaceFull(Cow<'a, str>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct GridIndex {
pub row: usize,
pub col: usize,
}
#[cfg_attr(docsrs, doc(cfg(feature = "tree-sitter")))]
#[cfg(feature = "tree-sitter")]
mod ts {
use std::cmp::Ordering;
use tree_sitter::Point;
use super::GridIndex;
impl PartialEq<Point> for GridIndex {
fn eq(&self, other: &Point) -> bool {
self.row == other.row && self.col == other.column
}
}
impl PartialOrd<Point> for GridIndex {
fn partial_cmp(&self, other: &Point) -> Option<std::cmp::Ordering> {
match self.row.cmp(&other.row) {
Ordering::Equal => self.col.partial_cmp(&other.column),
s => Some(s),
}
}
}
impl From<Point> for GridIndex {
fn from(value: Point) -> Self {
GridIndex {
row: value.row,
col: value.column,
}
}
}
impl From<GridIndex> for Point {
fn from(value: GridIndex) -> Self {
Point {
row: value.row,
column: value.col,
}
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "lsp-types")))]
#[cfg(feature = "lsp-types")]
mod lspt {
use lsp_types::{Position, TextDocumentContentChangeEvent};
use super::{Change, GridIndex};
impl From<Position> for GridIndex {
fn from(value: Position) -> Self {
GridIndex {
row: value.line as usize,
col: value.character as usize,
}
}
}
impl From<GridIndex> for Position {
fn from(value: GridIndex) -> Self {
Position {
line: value.row as u32,
character: value.col as u32,
}
}
}
impl From<TextDocumentContentChangeEvent> for Change<'static> {
fn from(value: TextDocumentContentChangeEvent) -> Self {
let Some(range) = value.range else {
return Change::ReplaceFull(value.text.into());
};
if value.text.is_empty() {
return Change::Delete {
start: range.start.into(),
end: range.end.into(),
};
}
if range.start == range.end {
return Change::Insert {
at: range.start.into(),
text: value.text.into(),
};
}
Change::Replace {
start: range.start.into(),
end: range.end.into(),
text: value.text.into(),
}
}
}
impl<'a> From<&'a TextDocumentContentChangeEvent> for Change<'a> {
fn from(value: &'a TextDocumentContentChangeEvent) -> Self {
let Some(range) = value.range else {
return Change::ReplaceFull((&value.text).into());
};
if value.text.is_empty() {
return Change::Delete {
start: range.start.into(),
end: range.end.into(),
};
}
if range.start == range.end {
return Change::Insert {
at: range.start.into(),
text: (&value.text).into(),
};
}
Change::Replace {
start: range.start.into(),
end: range.end.into(),
text: (&value.text).into(),
}
}
}
}
impl GridIndex {
pub fn normalize(&mut self, text: &Text) -> Result<()> {
let br_indexes = &text.br_indexes;
let row_count = br_indexes.row_count();
if self.row == row_count.get() {
return Err(Error::oob_row(row_count, self.row));
}
let pure_line = resolve_pure_line(text, self.row)?;
self.col = (text.encoding[0])(pure_line, self.col)?;
Ok(())
}
pub fn denormalize(&mut self, text: &Text) -> Result<()> {
let pure_line = resolve_pure_line(text, self.row)?;
self.col = (text.encoding[1])(pure_line, self.col)?;
Ok(())
}
}
pub(crate) fn correct_positions(start: &mut GridIndex, end: &mut GridIndex) {
if start.row > end.row || (start.row == end.row && start.col > end.col) {
start.col = start.col.saturating_add(1);
end.col += 1;
std::mem::swap(start, end);
}
}
pub(crate) fn resolve_pure_line(text: &Text, row: usize) -> Result<&str> {
let br_indexes = &text.br_indexes;
let row_count = br_indexes.row_count();
let row_start = br_indexes
.row_start(row)
.ok_or(Error::oob_row(row_count, row))?;
if !br_indexes.is_last_row(row) && row_count.get() > 1 {
let row_end = br_indexes
.row_start(row + 1)
.ok_or(Error::oob_row(row_count, row))?;
Ok(trim_eol_from_end(&text.text[row_start..row_end]))
} else {
Ok(&text.text[row_start..])
}
}