aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-04-30 20:00:30 +0100
committerGitHub <[email protected]>2020-04-30 20:00:30 +0100
commit23c889694e4d983dfda4956ae083cf9387316669 (patch)
treeebc1b799be1c56be0d11c29d6d4f86586bc9310b
parent745bd45ddb2f8b6165ab7eacfd482d8530cab05a (diff)
parent1a2d4e2921b5d2bef41adfec56112fd4f6499274 (diff)
Merge #4153
4153: Add support for incremental text synchronization r=matklad a=lnicola Fixes #3762. This still needs a `ra_vfs` PR, but I want to know I'm on the right track. I tested the change and it didn't crash horribly, but YMMV. Co-authored-by: LaurenČ›iu Nicola <[email protected]>
-rw-r--r--Cargo.lock4
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/caps.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs130
4 files changed, 125 insertions, 13 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e933598fb..522ecf2ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1193,9 +1193,9 @@ dependencies = [
1193 1193
1194[[package]] 1194[[package]]
1195name = "ra_vfs" 1195name = "ra_vfs"
1196version = "0.5.3" 1196version = "0.6.0"
1197source = "registry+https://github.com/rust-lang/crates.io-index" 1197source = "registry+https://github.com/rust-lang/crates.io-index"
1198checksum = "58a265769d5e5655345a9fcbd870a1a7c3658558c0d8efaed79e0669358f46b8" 1198checksum = "fcaa5615f420134aea7667253db101d03a5c5f300eac607872dc2a36407b2ac9"
1199dependencies = [ 1199dependencies = [
1200 "crossbeam-channel", 1200 "crossbeam-channel",
1201 "jod-thread", 1201 "jod-thread",
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 514d6d1a9..0459807fc 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -39,7 +39,7 @@ ra_prof = { path = "../ra_prof" }
39ra_project_model = { path = "../ra_project_model" } 39ra_project_model = { path = "../ra_project_model" }
40ra_syntax = { path = "../ra_syntax" } 40ra_syntax = { path = "../ra_syntax" }
41ra_text_edit = { path = "../ra_text_edit" } 41ra_text_edit = { path = "../ra_text_edit" }
42ra_vfs = "0.5.2" 42ra_vfs = "0.6.0"
43 43
44# This should only be used in CLI 44# This should only be used in CLI
45ra_db = { path = "../ra_db" } 45ra_db = { path = "../ra_db" }
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 45b60768a..e22ab8402 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities {
16 ServerCapabilities { 16 ServerCapabilities {
17 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { 17 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
18 open_close: Some(true), 18 open_close: Some(true),
19 change: Some(TextDocumentSyncKind::Full), 19 change: Some(TextDocumentSyncKind::Incremental),
20 will_save: None, 20 will_save: None,
21 will_save_wait_until: None, 21 will_save_wait_until: None,
22 save: Some(SaveOptions::default()), 22 save: Some(SaveOptions::default()),
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index f3aef3f0f..0a0e616c9 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -6,9 +6,12 @@ mod subscriptions;
6pub(crate) mod pending_requests; 6pub(crate) mod pending_requests;
7 7
8use std::{ 8use std::{
9 borrow::Cow,
9 env, 10 env,
10 error::Error, 11 error::Error,
11 fmt, panic, 12 fmt,
13 ops::Range,
14 panic,
12 path::PathBuf, 15 path::PathBuf,
13 sync::Arc, 16 sync::Arc,
14 time::{Duration, Instant}, 17 time::{Duration, Instant},
@@ -18,11 +21,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use itertools::Itertools; 21use itertools::Itertools;
19use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 22use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
20use lsp_types::{ 23use lsp_types::{
21 NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, 24 DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
22 WorkDoneProgressEnd, WorkDoneProgressReport, 25 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
26 WorkDoneProgressReport,
23}; 27};
24use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; 28use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask};
25use ra_ide::{Canceled, FileId, LibraryData, SourceRootId}; 29use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId};
26use ra_prof::profile; 30use ra_prof::profile;
27use ra_project_model::{PackageRoot, ProjectWorkspace}; 31use ra_project_model::{PackageRoot, ProjectWorkspace};
28use ra_vfs::{VfsFile, VfsTask, Watch}; 32use ra_vfs::{VfsFile, VfsTask, Watch};
@@ -33,6 +37,7 @@ use threadpool::ThreadPool;
33 37
34use crate::{ 38use crate::{
35 config::{Config, FilesWatcher}, 39 config::{Config, FilesWatcher},
40 conv::{ConvWith, TryConvWith},
36 diagnostics::DiagnosticTask, 41 diagnostics::DiagnosticTask,
37 main_loop::{ 42 main_loop::{
38 pending_requests::{PendingRequest, PendingRequests}, 43 pending_requests::{PendingRequest, PendingRequests},
@@ -579,12 +584,16 @@ fn on_notification(
579 Err(not) => not, 584 Err(not) => not,
580 }; 585 };
581 let not = match notification_cast::<req::DidChangeTextDocument>(not) { 586 let not = match notification_cast::<req::DidChangeTextDocument>(not) {
582 Ok(mut params) => { 587 Ok(params) => {
583 let uri = params.text_document.uri; 588 let DidChangeTextDocumentParams { text_document, content_changes } = params;
589 let world = state.snapshot();
590 let file_id = text_document.try_conv_with(&world)?;
591 let line_index = world.analysis().file_line_index(file_id)?;
592 let uri = text_document.uri;
584 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; 593 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
585 let text = 594 state.vfs.write().change_file_overlay(&path, |old_text| {
586 params.content_changes.pop().ok_or_else(|| "empty changes".to_string())?.text; 595 apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes);
587 state.vfs.write().change_file_overlay(path.as_path(), text); 596 });
588 return Ok(()); 597 return Ok(());
589 } 598 }
590 Err(not) => not, 599 Err(not) => not,
@@ -653,6 +662,48 @@ fn on_notification(
653 Ok(()) 662 Ok(())
654} 663}
655 664
665fn apply_document_changes(
666 old_text: &mut String,
667 mut line_index: Cow<'_, LineIndex>,
668 content_changes: Vec<TextDocumentContentChangeEvent>,
669) {
670 // The changes we got must be applied sequentially, but can cross lines so we
671 // have to keep our line index updated.
672 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
673 // remember the last valid line in the index and only rebuild it if needed.
674 enum IndexValid {
675 All,
676 UpToLine(u64),
677 }
678
679 impl IndexValid {
680 fn covers(&self, line: u64) -> bool {
681 match *self {
682 IndexValid::UpToLine(to) => to >= line,
683 _ => true,
684 }
685 }
686 }
687
688 let mut index_valid = IndexValid::All;
689 for change in content_changes {
690 match change.range {
691 Some(range) => {
692 if !index_valid.covers(range.start.line) {
693 line_index = Cow::Owned(LineIndex::new(&old_text));
694 }
695 index_valid = IndexValid::UpToLine(range.start.line);
696 let range = range.conv_with(&line_index);
697 old_text.replace_range(Range::<usize>::from(range), &change.text);
698 }
699 None => {
700 *old_text = change.text;
701 index_valid = IndexValid::UpToLine(0);
702 }
703 }
704 }
705}
706
656fn on_check_task( 707fn on_check_task(
657 task: CheckTask, 708 task: CheckTask,
658 world_state: &mut WorldState, 709 world_state: &mut WorldState,
@@ -958,3 +1009,64 @@ where
958{ 1009{
959 Request::new(id, R::METHOD.to_string(), params) 1010 Request::new(id, R::METHOD.to_string(), params)
960} 1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use std::borrow::Cow;
1016
1017 use lsp_types::{Position, Range, TextDocumentContentChangeEvent};
1018 use ra_ide::LineIndex;
1019
1020 #[test]
1021 fn apply_document_changes() {
1022 fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) {
1023 let line_index = Cow::Owned(LineIndex::new(&text));
1024 super::apply_document_changes(text, line_index, changes);
1025 }
1026
1027 macro_rules! c {
1028 [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
1029 vec![$(TextDocumentContentChangeEvent {
1030 range: Some(Range {
1031 start: Position { line: $sl, character: $sc },
1032 end: Position { line: $el, character: $ec },
1033 }),
1034 range_length: None,
1035 text: String::from($text),
1036 }),+]
1037 };
1038 }
1039
1040 let mut text = String::new();
1041 run(&mut text, vec![]);
1042 assert_eq!(text, "");
1043 run(
1044 &mut text,
1045 vec![TextDocumentContentChangeEvent {
1046 range: None,
1047 range_length: None,
1048 text: String::from("the"),
1049 }],
1050 );
1051 assert_eq!(text, "the");
1052 run(&mut text, c![0, 3; 0, 3 => " quick"]);
1053 assert_eq!(text, "the quick");
1054 run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
1055 assert_eq!(text, "quick foxes");
1056 run(&mut text, c![0, 11; 0, 11 => "\ndream"]);
1057 assert_eq!(text, "quick foxes\ndream");
1058 run(&mut text, c![1, 0; 1, 0 => "have "]);
1059 assert_eq!(text, "quick foxes\nhave dream");
1060 run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]);
1061 assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
1062 run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
1063 assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
1064 run(
1065 &mut text,
1066 c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
1067 );
1068 assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
1069 run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
1070 assert_eq!(text, "the quick \nthey have quiet dreams\n");
1071 }
1072}