lsp: edit events change ranges need to affect each other.

pull/8/head
Blaž Hrastnik 3 years ago
parent 9cac44c7c0
commit d5f9622e2e

@ -247,19 +247,47 @@ impl Client {
.await .await
} }
// TODO: this is dumb. TextEdit describes changes to the initial doc (concurrent), but
// TextDocumentContentChangeEvent describes a series of changes (sequential).
// So S -> S1 -> S2, meaning positioning depends on the previous edits.
//
// Calculation is therefore a bunch trickier.
pub fn changeset_to_changes( pub fn changeset_to_changes(
old_text: &Rope, old_text: &Rope,
new_text: &Rope,
changeset: &ChangeSet, changeset: &ChangeSet,
) -> Vec<lsp::TextDocumentContentChangeEvent> { ) -> Vec<lsp::TextDocumentContentChangeEvent> {
let mut iter = changeset.changes().iter().peekable(); let mut iter = changeset.changes().iter().peekable();
let mut old_pos = 0; let mut old_pos = 0;
let mut new_pos = 0;
let mut changes = Vec::new(); let mut changes = Vec::new();
use crate::util::pos_to_lsp_pos; use crate::util::pos_to_lsp_pos;
use helix_core::Operation::*; use helix_core::Operation::*;
// TODO: stolen from syntax.rs, share
use helix_core::RopeSlice;
fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position {
let lsp::Position {
mut line,
mut character,
} = pos;
// TODO: there should be a better way here
for ch in text.chars() {
if ch == '\n' {
line += 1;
character = 0;
} else {
character += ch.len_utf16() as u32;
}
}
lsp::Position { line, character }
}
let old_text = old_text.slice(..); let old_text = old_text.slice(..);
let new_text = new_text.slice(..);
// TODO: verify this function, specifically line num counting // TODO: verify this function, specifically line num counting
@ -271,10 +299,12 @@ impl Client {
let mut old_end = old_pos + len; let mut old_end = old_pos + len;
match change { match change {
Retain(_) => {} Retain(i) => {
new_pos += i;
}
Delete(_) => { Delete(_) => {
let start = pos_to_lsp_pos(&old_text, old_pos); let start = pos_to_lsp_pos(&new_text, new_pos);
let end = pos_to_lsp_pos(&old_text, old_end); let end = traverse(start, old_text.slice(old_pos..old_end));
// deletion // deletion
changes.push(lsp::TextDocumentContentChangeEvent { changes.push(lsp::TextDocumentContentChangeEvent {
@ -284,12 +314,14 @@ impl Client {
}); });
} }
Insert(s) => { Insert(s) => {
let start = pos_to_lsp_pos(&old_text, old_pos); let start = pos_to_lsp_pos(&new_text, new_pos);
new_pos += s.chars().count();
// a subsequent delete means a replace, consume it // a subsequent delete means a replace, consume it
let end = if let Some(Delete(len)) = iter.peek() { let end = if let Some(Delete(len)) = iter.peek() {
old_end = old_pos + len; old_end = old_pos + len;
let end = pos_to_lsp_pos(&old_text, old_end); let end = traverse(start, old_text.slice(old_pos..old_end));
iter.next(); iter.next();
@ -318,6 +350,7 @@ impl Client {
&self, &self,
text_document: lsp::VersionedTextDocumentIdentifier, text_document: lsp::VersionedTextDocumentIdentifier,
old_text: &Rope, old_text: &Rope,
new_text: &Rope,
changes: &ChangeSet, changes: &ChangeSet,
) -> Result<()> { ) -> Result<()> {
// figure out what kind of sync the server supports // figure out what kind of sync the server supports
@ -343,7 +376,9 @@ impl Client {
text: "".to_string(), text: "".to_string(),
}] // TODO: probably need old_state here too? }] // TODO: probably need old_state here too?
} }
lsp::TextDocumentSyncKind::Incremental => Self::changeset_to_changes(old_text, changes), lsp::TextDocumentSyncKind::Incremental => {
Self::changeset_to_changes(old_text, new_text, changes)
}
lsp::TextDocumentSyncKind::None => return Ok(()), lsp::TextDocumentSyncKind::None => return Ok(()),
}; };

@ -183,6 +183,7 @@ impl Document {
let notify = language_server.text_document_did_change( let notify = language_server.text_document_did_change(
self.versioned_identifier(), self.versioned_identifier(),
&old_doc, &old_doc,
self.text(),
transaction.changes(), transaction.changes(),
); );
@ -301,8 +302,8 @@ mod test {
let transaction = Transaction::insert(&doc.state, " world".into()); let transaction = Transaction::insert(&doc.state, " world".into());
let old_doc = doc.state.clone(); let old_doc = doc.state.clone();
let changes = Client::changeset_to_changes(&old_doc.doc, transaction.changes());
doc.apply(&transaction); doc.apply(&transaction);
let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
assert_eq!( assert_eq!(
changes, changes,
@ -320,8 +321,8 @@ mod test {
let transaction = transaction.invert(&old_doc); let transaction = transaction.invert(&old_doc);
let old_doc = doc.state.clone(); let old_doc = doc.state.clone();
let changes = Client::changeset_to_changes(&old_doc.doc, transaction.changes());
doc.apply(&transaction); doc.apply(&transaction);
let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
// line: 0-based. // line: 0-based.
// col: 0-based, gaps between chars. // col: 0-based, gaps between chars.
@ -343,22 +344,48 @@ mod test {
// replace // replace
// also tests that changes are layered, positions depend on previous changes.
doc.state.selection = Selection::single(0, 5); doc.state.selection = Selection::single(0, 5);
let transaction = Transaction::change_by_selection(&doc.state, |range| { let transaction = Transaction::change(
(range.from(), range.to(), Some("aeiou".into())) &doc.state,
}); vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
let changes = Client::changeset_to_changes(&doc.state.doc, transaction.changes()); );
// aeilou
doc.apply(&transaction);
let changes =
Client::changeset_to_changes(&doc.state.doc, doc.text(), transaction.changes());
assert_eq!( assert_eq!(
changes, changes,
&[lsp::TextDocumentContentChangeEvent { &[
range: Some(lsp::Range::new( // 0 1 2 3 4 5
lsp::Position::new(0, 0), // |h|e|l|l|o|
lsp::Position::new(0, 5) // ----
)), //
text: "aeiou".into(), // aeillo
range_length: None, lsp::TextDocumentContentChangeEvent {
}] range: Some(lsp::Range::new(
lsp::Position::new(0, 0),
lsp::Position::new(0, 2)
)),
text: "aei".into(),
range_length: None,
},
// 0 1 2 3 4 5 6
// |a|e|i|l|l|o|
// -----
//
// aeilou
lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(
lsp::Position::new(0, 4),
lsp::Position::new(0, 6)
)),
text: "ou".into(),
range_length: None,
}
]
); );
} }
} }

Loading…
Cancel
Save