Skip to content

Commit 6c0f1a9

Browse files
committed
handle out-of-order document updates
1 parent 29139da commit 6c0f1a9

File tree

3 files changed

+75
-12
lines changed

3 files changed

+75
-12
lines changed

extensions/positron-r/amalthea/crates/ark/src/lsp/backend.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use std::collections::HashSet;
1111
use std::path::Path;
1212
use std::sync::Arc;
13+
use std::time::Duration;
1314

1415
use crossbeam::channel::Sender;
1516
use dashmap::DashMap;
@@ -278,11 +279,10 @@ impl LanguageServer for Backend {
278279
return;
279280
});
280281

281-
// update the document
282-
for change in params.content_changes.iter() {
283-
if let Err(error) = doc.update(change) {
284-
backend_trace!(self, "doc.update(): unexpected error {}", error);
285-
}
282+
// respond to document updates
283+
if let Err(error) = doc.on_did_change(&params) {
284+
backend_trace!(self, "did_change(): unexpected error applying updates {}", error);
285+
return;
286286
}
287287

288288
// update index

extensions/positron-r/amalthea/crates/ark/src/lsp/documents.rs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use anyhow::*;
1111
use dashmap::DashMap;
1212
use lazy_static::lazy_static;
1313
use ropey::Rope;
14+
use tower_lsp::lsp_types::DidChangeTextDocumentParams;
1415
use tower_lsp::lsp_types::TextDocumentContentChangeEvent;
1516
use tower_lsp::lsp_types::Url;
1617
use tree_sitter::InputEdit;
@@ -60,6 +61,13 @@ pub struct Document {
6061
// The document's textual contents.
6162
pub contents: Rope,
6263

64+
// A set of pending changes for this document.
65+
pub pending: Vec<DidChangeTextDocumentParams>,
66+
67+
// The version of the document we last synchronized with.
68+
// None if the document hasn't been synchronized yet.
69+
pub version: Option<i32>,
70+
6371
// The parser used to generate the AST.
6472
pub parser: Parser,
6573

@@ -86,11 +94,62 @@ impl Document {
8694
parser.set_language(tree_sitter_r::language()).expect("failed to create parser");
8795
let ast = parser.parse(contents, None).unwrap();
8896

97+
let pending = Vec::new();
98+
let version = None;
99+
89100
// return generated document
90-
Self { contents: document, parser, ast }
101+
Self { contents: document, pending, version, parser, ast }
91102
}
92103

93-
pub fn update(&mut self, change: &TextDocumentContentChangeEvent) -> Result<()> {
104+
pub fn on_did_change(&mut self, params: &DidChangeTextDocumentParams) -> Result<i32> {
105+
106+
// Add pending changes.
107+
self.pending.push(params.clone());
108+
109+
// Check the version of this update.
110+
//
111+
// If we receive version {n + 2} before {n + 1}, then we'll
112+
// bail here, and handle the {n + 2} change after we received
113+
// version {n + 1}.
114+
//
115+
// TODO: What if an intermediate document change is somehow dropped or lost?
116+
// Do we need a way to recover (e.g. reset the document state)?
117+
if let Some(old_version) = self.version {
118+
let new_version = params.text_document.version;
119+
if new_version > old_version + 1 {
120+
log::info!("on_did_change(): received out-of-order document changes; deferring");
121+
return Ok(old_version);
122+
}
123+
}
124+
125+
// Get pending updates, sort by version, and then apply them one-by-one.
126+
self.pending.sort_by(|lhs, rhs| {
127+
let lhs = lhs.text_document.version;
128+
let rhs = rhs.text_document.version;
129+
lhs.cmp(&rhs)
130+
});
131+
132+
// Get the maximum version.
133+
let version = self.pending.last().unwrap().text_document.version;
134+
135+
// Take the changes, and apply them one-by-one.
136+
let changes = std::mem::take(&mut self.pending);
137+
for change in changes {
138+
for event in change.content_changes {
139+
if let Err(error) = self.update(&event) {
140+
log::error!("error updating document: {}", error);
141+
}
142+
}
143+
}
144+
145+
// Updates successfully applied; update cached document version.
146+
self.version = Some(version);
147+
148+
Ok(version)
149+
150+
}
151+
152+
fn update(&mut self, change: &TextDocumentContentChangeEvent) -> Result<()> {
94153

95154
// Extract edit range. Nothing to do if there wasn't an edit.
96155
let range = match change.range {
@@ -125,10 +184,7 @@ impl Document {
125184
let rhs = self.contents.line_to_char(range.end.line as usize) + range.end.character as usize;
126185

127186
// Remove the old slice of text, and insert the new slice of text.
128-
if lhs != rhs {
129-
self.contents.remove(lhs..rhs);
130-
}
131-
187+
self.contents.remove(lhs..rhs);
132188
self.contents.insert(lhs, change.text.as_str());
133189

134190
// We've edited the AST, and updated the document. We can now re-parse.

extensions/positron-r/amalthea/crates/ark/src/lsp/help.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ impl RHtmlHelp {
4242
let contents = RFunction::from(".ps.help.getHtmlHelpContents")
4343
.param("topic", topic)
4444
.param("package", package)
45-
.call()?;
45+
.call();
46+
47+
if let Err(error) = contents {
48+
log::error!("{}", error);
49+
return Ok(None);
50+
};
51+
52+
let contents = contents.unwrap_unchecked();
4653

4754
// check for NULL (implies no help available)
4855
if r_typeof(*contents) == NILSXP {

0 commit comments

Comments
 (0)