#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LspPosition {
pub line: u32,
pub character: u32,
}
#[derive(Debug, Clone)]
pub struct LspDocument {
pub uri: String,
pub language_id: String,
pub version: i32,
}
impl LspDocument {
pub fn new(uri: impl Into<String>, language_id: impl Into<String>) -> Self {
Self { uri: uri.into(), language_id: language_id.into(), version: 0 }
}
}
#[derive(Debug, Clone, Copy)]
pub struct LspRange {
pub start: LspPosition,
pub end: LspPosition,
}
#[derive(Debug, Clone)]
pub struct LspTextChange {
pub range: LspRange,
pub text: String,
}
pub trait LspClient {
fn did_open(&mut self, _document: &LspDocument, _text: &str) {}
fn did_change(
&mut self,
_document: &LspDocument,
_changes: &[LspTextChange],
) {
}
fn did_save(&mut self, _document: &LspDocument, _text: &str) {}
fn did_close(&mut self, _document: &LspDocument) {}
fn request_hover(
&mut self,
_document: &LspDocument,
_position: LspPosition,
) {
}
fn request_completion(
&mut self,
_document: &LspDocument,
_position: LspPosition,
) {
}
fn request_definition(
&mut self,
_document: &LspDocument,
_position: LspPosition,
) {
}
}
pub fn compute_text_change(old: &str, new: &str) -> Option<LspTextChange> {
if old == new {
return None;
}
let old_chars: Vec<char> = old.chars().collect();
let new_chars: Vec<char> = new.chars().collect();
let old_len = old_chars.len();
let new_len = new_chars.len();
let mut prefix = 0;
while prefix < old_len
&& prefix < new_len
&& old_chars[prefix] == new_chars[prefix]
{
prefix += 1;
}
let mut suffix = 0;
while suffix < old_len.saturating_sub(prefix)
&& suffix < new_len.saturating_sub(prefix)
&& old_chars[old_len - 1 - suffix] == new_chars[new_len - 1 - suffix]
{
suffix += 1;
}
let removed_len = old_len.saturating_sub(prefix + suffix);
let inserted: String =
new_chars[prefix..new_len.saturating_sub(suffix)].iter().collect();
let start = position_for_char_index(old, prefix);
let end = position_for_char_index(old, prefix + removed_len);
Some(LspTextChange { range: LspRange { start, end }, text: inserted })
}
fn position_for_char_index(text: &str, target_index: usize) -> LspPosition {
let mut line: u32 = 0;
let mut character: u32 = 0;
for (index, ch) in text.chars().enumerate() {
if index == target_index {
return LspPosition { line, character };
}
if ch == '\n' {
line = line.saturating_add(1);
character = 0;
} else {
character = character.saturating_add(1);
}
}
LspPosition { line, character }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_text_change_none_when_equal() {
let change = compute_text_change("abc", "abc");
assert!(change.is_none());
}
#[test]
fn test_compute_text_change_insertion() {
let change = compute_text_change("abc", "abXc");
assert!(change.is_some());
if let Some(change) = change {
assert_eq!(change.text, "X");
assert_eq!(
change.range.start,
LspPosition { line: 0, character: 2 }
);
assert_eq!(change.range.end, LspPosition { line: 0, character: 2 });
}
}
#[test]
fn test_compute_text_change_deletion_across_lines() {
let change = compute_text_change("a\nbc", "a\nc");
assert!(change.is_some());
if let Some(change) = change {
assert_eq!(change.text, "");
assert_eq!(
change.range.start,
LspPosition { line: 1, character: 0 }
);
assert_eq!(change.range.end, LspPosition { line: 1, character: 1 });
}
}
#[test]
fn test_position_for_char_index_end_of_text() {
let pos = position_for_char_index("a\nb", 3);
assert_eq!(pos, LspPosition { line: 1, character: 1 });
}
}