use super::Position;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Edit {
Insert {
position: Position,
text: String,
},
Delete {
position: Position,
text: String,
},
}
impl Edit {
#[must_use]
pub fn insert(position: Position, text: impl Into<String>) -> Self {
Self::Insert {
position,
text: text.into(),
}
}
#[must_use]
pub fn delete(position: Position, text: impl Into<String>) -> Self {
Self::Delete {
position,
text: text.into(),
}
}
#[must_use]
pub fn inverse(&self) -> Self {
match self {
Self::Insert { position, text } => Self::Delete {
position: *position,
text: text.clone(),
},
Self::Delete { position, text } => Self::Insert {
position: *position,
text: text.clone(),
},
}
}
#[must_use]
pub const fn position(&self) -> Position {
match self {
Self::Insert { position, .. } | Self::Delete { position, .. } => *position,
}
}
#[must_use]
pub fn text(&self) -> &str {
match self {
Self::Insert { text, .. } | Self::Delete { text, .. } => text,
}
}
#[must_use]
pub const fn is_insert(&self) -> bool {
matches!(self, Self::Insert { .. })
}
#[must_use]
pub const fn is_delete(&self) -> bool {
matches!(self, Self::Delete { .. })
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.text().is_empty()
}
#[must_use]
pub fn transform(&self, against: &Self) -> Self {
match self {
Self::Insert { position, text } => Self::Insert {
position: transform_position(*position, against),
text: text.clone(),
},
Self::Delete { position, text } => Self::Delete {
position: transform_position(*position, against),
text: text.clone(),
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TextDimensions {
pub line_count: usize,
pub last_line_len: usize,
}
#[must_use]
pub fn text_dimensions(text: &str) -> TextDimensions {
let line_count = text.chars().filter(|&c| c == '\n').count();
let last_line_len = text.rsplit('\n').next().map_or(0, |s| s.chars().count());
TextDimensions {
line_count,
last_line_len,
}
}
#[must_use]
pub fn delete_end(pos: Position, text: &str) -> Position {
let dims = text_dimensions(text);
if dims.line_count == 0 {
Position::new(pos.line, pos.column + dims.last_line_len)
} else {
Position::new(pos.line + dims.line_count, dims.last_line_len)
}
}
#[must_use]
pub fn transform_position(pos: Position, against: &Edit) -> Position {
if against.is_empty() {
return pos;
}
match against {
Edit::Insert {
position: ins_pos,
text,
} => transform_position_against_insert(pos, *ins_pos, text),
Edit::Delete {
position: del_pos,
text,
} => transform_position_against_delete(pos, *del_pos, text),
}
}
fn transform_position_against_insert(pos: Position, ins_pos: Position, text: &str) -> Position {
if pos < ins_pos {
return pos;
}
let dims = text_dimensions(text);
if pos.line == ins_pos.line {
if dims.line_count == 0 {
Position::new(pos.line, pos.column + dims.last_line_len)
} else {
Position::new(
pos.line + dims.line_count,
pos.column - ins_pos.column + dims.last_line_len,
)
}
} else {
Position::new(pos.line + dims.line_count, pos.column)
}
}
fn transform_position_against_delete(pos: Position, del_pos: Position, text: &str) -> Position {
if pos <= del_pos {
return pos;
}
let del_end = delete_end(del_pos, text);
let dims = text_dimensions(text);
if pos <= del_end {
return del_pos;
}
if pos.line == del_end.line {
if dims.line_count == 0 {
Position::new(pos.line, pos.column - dims.last_line_len)
} else {
Position::new(del_pos.line, del_pos.column + pos.column - del_end.column)
}
} else {
Position::new(pos.line - dims.line_count, pos.column)
}
}