From 2f06af7ce72c32c8ba179a82e28a7e1094f092a9 Mon Sep 17 00:00:00 2001 From: Dongdong Zhou Date: Sat, 21 Nov 2020 17:09:44 +0000 Subject: [PATCH] updates to proxy --- core/src/buffer.rs | 6 +++ core/src/lsp.rs | 15 +------- core/src/proxy.rs | 12 ++++++ proxy/Cargo.toml | 2 +- proxy/src/buffer.rs | 88 +++++++++++++++++++++++++++++++++++++++++++ proxy/src/dispatch.rs | 24 ++++++++++-- proxy/src/lsp.rs | 77 +++++++++++++++++++++++++++++++++++++ 7 files changed, 205 insertions(+), 19 deletions(-) diff --git a/core/src/buffer.rs b/core/src/buffer.rs index 4bf8dec5..de5aa90f 100644 --- a/core/src/buffer.rs +++ b/core/src/buffer.rs @@ -536,6 +536,12 @@ fn apply_delta( self.rev, ); state.lsp.lock().update(&self, &content_change, self.rev); + state + .proxy + .lock() + .as_ref() + .unwrap() + .update(self.id, delta, self.rev); let logical_start_line = self.rope.line_of_offset(iv.start); let new_logical_end_line = self.rope.line_of_offset(iv.start + newlen) + 1; diff --git a/core/src/lsp.rs b/core/src/lsp.rs index 9f54775b..b9147f0a 100644 --- a/core/src/lsp.rs +++ b/core/src/lsp.rs @@ -23,20 +23,7 @@ }; use xi_rope::RopeDelta; -use lsp_types::{ - ClientCapabilities, CodeActionCapability, CodeActionContext, CodeActionKind, - CodeActionKindLiteralSupport, CodeActionLiteralSupport, CodeActionParams, - CodeActionResponse, CompletionCapability, CompletionItemCapability, - CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams, - DidSaveTextDocumentParams, DocumentFormattingParams, DocumentSymbolParams, - DocumentSymbolResponse, FormattingOptions, GotoDefinitionParams, - InitializeParams, InitializeResult, PartialResultParams, Position, - PublishDiagnosticsParams, Range, SemanticTokensParams, ServerCapabilities, - TextDocumentClientCapabilities, TextDocumentContentChangeEvent, - TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, - TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, TraceOption, Url, - VersionedTextDocumentIdentifier, WorkDoneProgressParams, -}; +use lsp_types::*; use serde_json::{json, to_value, Value}; use crate::buffer::{Buffer, BufferId}; diff --git a/core/src/proxy.rs b/core/src/proxy.rs index 22aeb312..2222f33f 100644 --- a/core/src/proxy.rs +++ b/core/src/proxy.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_json::json; use serde_json::Value; +use xi_rope::RopeDelta; use xi_rpc::Callback; use xi_rpc::Handler; use xi_rpc::RpcLoop; @@ -50,6 +51,17 @@ pub fn new_buffer(&self, buffer_id: BufferId, path: PathBuf) -> Result { return Ok(resp.content); } + pub fn update(&self, buffer_id: BufferId, delta: &RopeDelta, rev: u64) { + self.peer.send_rpc_notification( + "update", + &json!({ + "buffer_id": buffer_id, + "delta": delta, + "rev": rev, + }), + ) + } + pub fn get_completion( &self, request_id: usize, diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index 1e0e357c..201ccc01 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] lapce-rpc = { path = "../rpc" } xi-rpc = { path = "../../xi-editor/rust/rpc/" } -xi-rope = { path = "../../xi-editor/rust/rope/" } +xi-rope = { path = "../../xi-editor/rust/rope/", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } lsp-types = { version = "0.82.0", features = ["proposed"] } parking_lot = { version = "0.11.0", features = ["deadlock_detection"] } diff --git a/proxy/src/buffer.rs b/proxy/src/buffer.rs index e28e0abe..7b2645a1 100644 --- a/proxy/src/buffer.rs +++ b/proxy/src/buffer.rs @@ -3,6 +3,7 @@ use std::io::Read; use std::path::PathBuf; +use lsp_types::*; use serde::{Deserialize, Deserializer, Serialize}; use xi_rope::{ interval::IntervalBounds, rope::Rope, Cursor, Delta, DeltaBuilder, Interval, @@ -17,6 +18,7 @@ pub struct Buffer { pub id: BufferId, pub rope: Rope, pub path: PathBuf, + pub rev: u64, } impl Buffer { @@ -32,12 +34,56 @@ pub fn new(id: BufferId, path: PathBuf) -> Buffer { rope, path, language_id, + rev: 0, } } + pub fn update( + &mut self, + delta: &RopeDelta, + rev: u64, + ) -> Option { + if self.rev + 1 != rev { + return None; + } + self.rev += 1; + let content_change = get_document_content_changes(delta, self); + self.rope = delta.apply(&self.rope); + let content_change = match content_change { + Some(content_change) => content_change, + None => TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: self.get_document(), + }, + }; + Some(content_change) + } + pub fn get_document(&self) -> String { self.rope.to_string() } + + pub fn offset_of_line(&self, offset: usize) -> usize { + self.rope.offset_of_line(offset) + } + + pub fn line_of_offset(&self, offset: usize) -> usize { + self.rope.line_of_offset(offset) + } + + pub fn offset_to_line_col(&self, offset: usize) -> (usize, usize) { + let line = self.line_of_offset(offset); + (line, offset - self.offset_of_line(line)) + } + + pub fn offset_to_position(&self, offset: usize) -> Position { + let (line, col) = self.offset_to_line_col(offset); + Position { + line: line as u64, + character: col as u64, + } + } } fn load_file(path: &PathBuf) -> Result { @@ -54,3 +100,45 @@ fn language_id_from_path(path: &PathBuf) -> Option<&str> { _ => return None, }) } + +fn get_document_content_changes( + delta: &RopeDelta, + buffer: &Buffer, +) -> Option { + let (interval, _) = delta.summary(); + let (start, end) = interval.start_end(); + + // TODO: Handle more trivial cases like typing when there's a selection or transpose + if let Some(node) = delta.as_simple_insert() { + let text = String::from(node); + + let (start, end) = interval.start_end(); + let text_document_content_change_event = TextDocumentContentChangeEvent { + range: Some(Range { + start: buffer.offset_to_position(start), + end: buffer.offset_to_position(end), + }), + range_length: Some((end - start) as u64), + text, + }; + + return Some(text_document_content_change_event); + } + // Or a simple delete + else if delta.is_simple_delete() { + let mut end_position = buffer.offset_to_position(end); + + let text_document_content_change_event = TextDocumentContentChangeEvent { + range: Some(Range { + start: buffer.offset_to_position(start), + end: end_position, + }), + range_length: Some((end - start) as u64), + text: String::new(), + }; + + return Some(text_document_content_change_event); + } + + None +} diff --git a/proxy/src/dispatch.rs b/proxy/src/dispatch.rs index 301e8643..a4e1c97f 100644 --- a/proxy/src/dispatch.rs +++ b/proxy/src/dispatch.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use std::thread; use std::{collections::HashMap, io}; +use xi_rope::{RopeDelta, RopeInfo}; use xi_rpc::RpcPeer; use xi_rpc::{Handler, RpcCtx}; @@ -32,7 +33,14 @@ pub struct Dispatcher { #[serde(rename_all = "snake_case")] #[serde(tag = "method", content = "params")] pub enum Notification { - Initialize { workspace: PathBuf }, + Initialize { + workspace: PathBuf, + }, + Update { + buffer_id: BufferId, + delta: RopeDelta, + rev: u64, + }, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -73,7 +81,6 @@ pub fn new(sender: Sender) -> Dispatcher { pub fn mainloop(&self, receiver: Receiver) -> Result<()> { for msg in receiver { - eprintln!("receive msg {}", msg); let rpc: RpcObject = msg.into(); if rpc.is_response() { } else { @@ -119,6 +126,17 @@ fn handle_notification(&self, rpc: Notification) { Notification::Initialize { workspace } => { *self.workspace.lock() = workspace; } + Notification::Update { + buffer_id, + delta, + rev, + } => { + let mut buffers = self.buffers.lock(); + let buffer = buffers.get_mut(&buffer_id).unwrap(); + if let Some(content_change) = buffer.update(&delta, rev) { + self.lsp.lock().update(buffer, &content_change, buffer.rev); + } + } } } @@ -129,9 +147,7 @@ fn handle_request(&self, id: RequestId, rpc: Request) { let content = buffer.rope.to_string(); self.buffers.lock().insert(buffer_id, buffer); let resp = NewBufferResponse { content }; - eprintln!("proxy receive new buffer"); self.sender.send(json!({ - "jsonrpc": "2.0", "id": id, "result": resp, })); diff --git a/proxy/src/lsp.rs b/proxy/src/lsp.rs index 2fed4767..5e68a6e6 100644 --- a/proxy/src/lsp.rs +++ b/proxy/src/lsp.rs @@ -121,6 +121,17 @@ pub fn get_completion( }); } } + + pub fn update( + &self, + buffer: &Buffer, + content_change: &TextDocumentContentChangeEvent, + rev: u64, + ) { + if let Some(client) = self.clients.get(&buffer.language_id) { + client.update(buffer, content_change, rev); + } + } } impl LspClient { @@ -481,6 +492,52 @@ pub fn get_completion( // } }) } + + pub fn send_did_change( + &self, + buffer: &Buffer, + changes: Vec, + version: u64, + ) { + let uri = self.get_uri(buffer); + let text_document_did_change_params = DidChangeTextDocumentParams { + text_document: VersionedTextDocumentIdentifier { + uri, + version: Some(version as i64), + }, + content_changes: changes, + }; + + let params = Params::from( + serde_json::to_value(text_document_did_change_params).unwrap(), + ); + self.send_notification("textDocument/didChange", params); + } + + pub fn get_sync_kind(&self) -> Option { + let state = self.state.lock(); + let text_document_sync = state + .server_capabilities + .as_ref() + .and_then(|c| c.text_document_sync.as_ref())?; + match &text_document_sync { + &TextDocumentSyncCapability::Kind(kind) => Some(kind.clone()), + &TextDocumentSyncCapability::Options(options) => options.clone().change, + } + } + + pub fn update( + &self, + buffer: &Buffer, + content_change: &TextDocumentContentChangeEvent, + rev: u64, + ) { + let sync_kind = self.get_sync_kind().unwrap_or(TextDocumentSyncKind::Full); + let changes = get_change_for_sync_kind(sync_kind, buffer, content_change); + if let Some(changes) = changes { + self.send_did_change(buffer, changes, rev); + } + } } pub enum LspHeader { @@ -550,3 +607,23 @@ pub fn read_message(reader: &mut T) -> Result { let body = String::from_utf8(body_buffer)?; Ok(body) } + +pub fn get_change_for_sync_kind( + sync_kind: TextDocumentSyncKind, + buffer: &Buffer, + content_change: &TextDocumentContentChangeEvent, +) -> Option> { + match sync_kind { + TextDocumentSyncKind::None => None, + TextDocumentSyncKind::Full => { + let text_document_content_change_event = + TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: buffer.get_document(), + }; + Some(vec![text_document_content_change_event]) + } + TextDocumentSyncKind::Incremental => Some(vec![content_change.clone()]), + } +}