Text change generation, RPC call handling.

This commit is contained in:
Blaž Hrastnik 2020-10-23 18:48:03 +09:00
parent af1924404a
commit cc6bdf8f66
7 changed files with 221 additions and 73 deletions

View file

@ -27,4 +27,4 @@ pub use diagnostic::Diagnostic;
pub use history::History; pub use history::History;
pub use state::State; pub use state::State;
pub use transaction::{Assoc, Change, ChangeSet, Transaction}; pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};

View file

@ -5,8 +5,9 @@ use std::convert::TryFrom;
/// (from, to, replacement) /// (from, to, replacement)
pub type Change = (usize, usize, Option<Tendril>); pub type Change = (usize, usize, Option<Tendril>);
// TODO: pub(crate)
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Operation { pub enum Operation {
/// Move cursor by n characters. /// Move cursor by n characters.
Retain(usize), Retain(usize),
/// Delete n characters. /// Delete n characters.
@ -40,6 +41,12 @@ impl ChangeSet {
} }
// TODO: from iter // TODO: from iter
//
#[doc(hidden)] // used by lsp to convert to LSP changes
pub fn changes(&self) -> &[Operation] {
&self.changes
}
#[must_use] #[must_use]
fn len_after(&self) -> usize { fn len_after(&self) -> usize {

View file

@ -1,11 +1,11 @@
use crate::{ use crate::{
transport::{Payload, Transport}, transport::{Payload, Transport},
Error, Notification, Call, Error,
}; };
type Result<T> = core::result::Result<T, Error>; type Result<T> = core::result::Result<T, Error>;
use helix_core::{State, Transaction}; use helix_core::{ChangeSet, Transaction};
use helix_view::Document; use helix_view::Document;
// use std::collections::HashMap; // use std::collections::HashMap;
@ -27,7 +27,7 @@ pub struct Client {
stderr: BufReader<ChildStderr>, stderr: BufReader<ChildStderr>,
outgoing: Sender<Payload>, outgoing: Sender<Payload>,
pub incoming: Receiver<Notification>, pub incoming: Receiver<Call>,
pub request_counter: u64, pub request_counter: u64,
@ -87,6 +87,7 @@ impl Client {
Ok(params) Ok(params)
} }
/// Execute a RPC request on the language server.
pub async fn request<R: lsp::request::Request>( pub async fn request<R: lsp::request::Request>(
&mut self, &mut self,
params: R::Params, params: R::Params,
@ -126,6 +127,7 @@ impl Client {
Ok(response) Ok(response)
} }
/// Send a RPC notification to the language server.
pub async fn notify<R: lsp::notification::Notification>( pub async fn notify<R: lsp::notification::Notification>(
&mut self, &mut self,
params: R::Params, params: R::Params,
@ -149,6 +151,35 @@ impl Client {
Ok(()) Ok(())
} }
/// Reply to a language server RPC call.
pub async fn reply(
&mut self,
id: jsonrpc::Id,
result: core::result::Result<Value, jsonrpc::Error>,
) -> Result<()> {
use jsonrpc::{Failure, Output, Success, Version};
let output = match result {
Ok(result) => Output::Success(Success {
jsonrpc: Some(Version::V2),
id,
result,
}),
Err(error) => Output::Failure(Failure {
jsonrpc: Some(Version::V2),
id,
error,
}),
};
self.outgoing
.send(Payload::Response(output))
.await
.map_err(|e| Error::Other(e.into()))?;
Ok(())
}
// ------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------
// General messages // General messages
// ------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------
@ -163,7 +194,9 @@ impl Client {
// root_uri: Some(lsp_types::Url::parse("file://localhost/")?), // root_uri: Some(lsp_types::Url::parse("file://localhost/")?),
root_uri: None, // set to project root in the future root_uri: None, // set to project root in the future
initialization_options: None, initialization_options: None,
capabilities: lsp::ClientCapabilities::default(), capabilities: lsp::ClientCapabilities {
..Default::default()
},
trace: None, trace: None,
workspace_folders: None, workspace_folders: None,
client_info: None, client_info: None,
@ -203,23 +236,107 @@ impl Client {
.await .await
} }
fn to_changes(changeset: &ChangeSet) -> Vec<lsp::TextDocumentContentChangeEvent> {
let mut iter = changeset.changes().iter().peekable();
let mut old_pos = 0;
let mut changes = Vec::new();
use crate::util::pos_to_lsp_pos;
use helix_core::Operation::*;
// TEMP
let rope = helix_core::Rope::from("");
let old_text = rope.slice(..);
while let Some(change) = iter.next() {
let len = match change {
Delete(i) | Retain(i) => *i,
Insert(_) => 0,
};
let old_end = old_pos + len;
match change {
Retain(_) => {}
Delete(_) => {
let start = pos_to_lsp_pos(&old_text, old_pos);
let end = pos_to_lsp_pos(&old_text, old_end);
// a subsequent ins means a replace, consume it
if let Some(Insert(s)) = iter.peek() {
iter.next();
// replacement
changes.push(lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(start, end)),
text: s.into(),
range_length: None,
});
} else {
// deletion
changes.push(lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(start, end)),
text: "".to_string(),
range_length: None,
});
};
}
Insert(s) => {
let start = pos_to_lsp_pos(&old_text, old_pos);
// insert
changes.push(lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(start, start)),
text: s.into(),
range_length: None,
});
}
}
old_pos = old_end;
}
changes
}
// TODO: trigger any time history.commit_revision happens // TODO: trigger any time history.commit_revision happens
pub async fn text_document_did_change( pub async fn text_document_did_change(
&mut self, &mut self,
doc: &Document, doc: &Document,
transaction: &Transaction, transaction: &Transaction,
) -> Result<()> { ) -> Result<()> {
// figure out what kind of sync the server supports
let capabilities = self.capabilities.as_ref().unwrap(); // TODO: needs post init
let sync_capabilities = match capabilities.text_document_sync {
Some(lsp::TextDocumentSyncCapability::Kind(kind)) => kind,
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
change: Some(kind),
..
})) => kind,
// None | SyncOptions { changes: None }
_ => return Ok(()),
};
let changes = match sync_capabilities {
lsp::TextDocumentSyncKind::Full => {
vec![lsp::TextDocumentContentChangeEvent {
// range = None -> whole document
range: None, //Some(Range)
range_length: None, // u64 apparently deprecated
text: "".to_string(),
}] // TODO: probably need old_state here too?
}
lsp::TextDocumentSyncKind::Incremental => Self::to_changes(transaction.changes()),
lsp::TextDocumentSyncKind::None => return Ok(()),
};
self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams { self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
text_document: lsp::VersionedTextDocumentIdentifier::new( text_document: lsp::VersionedTextDocumentIdentifier::new(
lsp::Url::from_file_path(doc.path().unwrap()).unwrap(), lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
doc.version, doc.version,
), ),
content_changes: vec![lsp::TextDocumentContentChangeEvent { content_changes: changes,
// range = None -> whole document
range: None, //Some(Range)
range_length: None, // u64 apparently deprecated
text: "".to_string(),
}], // TODO: probably need old_state here too?
}) })
.await .await
} }

View file

@ -1,13 +1,12 @@
mod client; mod client;
mod transport; mod transport;
use jsonrpc_core as jsonrpc; pub use jsonrpc_core as jsonrpc;
use lsp_types as lsp; pub use lsp_types as lsp;
pub use client::Client; pub use client::Client;
pub use lsp::{Position, Url}; pub use lsp::{Position, Url};
use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -30,19 +29,13 @@ pub mod util {
let line_start = doc.char_to_utf16_cu(line); let line_start = doc.char_to_utf16_cu(line);
doc.utf16_cu_to_char(pos.character as usize + line_start) doc.utf16_cu_to_char(pos.character as usize + line_start)
} }
} pub fn pos_to_lsp_pos(doc: &helix_core::RopeSlice, pos: usize) -> lsp::Position {
let line = doc.char_to_line(pos);
let line_start = doc.char_to_utf16_cu(line);
let col = doc.char_to_utf16_cu(pos) - line_start;
/// A type representing all possible values sent from the server to the client. lsp::Position::new(line as u64, col as u64)
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] }
#[serde(deny_unknown_fields)]
#[serde(untagged)]
enum Message {
/// A regular JSON-RPC request output (single response).
Output(jsonrpc::Output),
/// A notification.
Notification(jsonrpc::Notification),
/// A JSON-RPC request
Call(jsonrpc::Call),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -67,3 +60,5 @@ impl Notification {
} }
} }
} }
pub use jsonrpc::Call;

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use log::debug; use log::debug;
use crate::{Error, Message, Notification}; use crate::{Error, Notification};
type Result<T> = core::result::Result<T, Error>; type Result<T> = core::result::Result<T, Error>;
@ -24,10 +24,23 @@ pub(crate) enum Payload {
value: jsonrpc::MethodCall, value: jsonrpc::MethodCall,
}, },
Notification(jsonrpc::Notification), Notification(jsonrpc::Notification),
Response(jsonrpc::Output),
}
use serde::{Deserialize, Serialize};
/// A type representing all possible values sent from the server to the client.
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
enum Message {
/// A regular JSON-RPC request output (single response).
Output(jsonrpc::Output),
/// A JSON-RPC request or notification.
Call(jsonrpc::Call),
} }
pub(crate) struct Transport { pub(crate) struct Transport {
incoming: Sender<Notification>, // TODO Notification | Call incoming: Sender<jsonrpc::Call>,
outgoing: Receiver<Payload>, outgoing: Receiver<Payload>,
pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>, pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>,
@ -42,7 +55,7 @@ impl Transport {
ex: &Executor, ex: &Executor,
reader: BufReader<ChildStdout>, reader: BufReader<ChildStdout>,
writer: BufWriter<ChildStdin>, writer: BufWriter<ChildStdin>,
) -> (Receiver<Notification>, Sender<Payload>) { ) -> (Receiver<jsonrpc::Call>, Sender<Payload>) {
let (incoming, rx) = smol::channel::unbounded(); let (incoming, rx) = smol::channel::unbounded();
let (tx, outgoing) = smol::channel::unbounded(); let (tx, outgoing) = smol::channel::unbounded();
@ -112,6 +125,10 @@ impl Transport {
let json = serde_json::to_string(&value)?; let json = serde_json::to_string(&value)?;
self.send(json).await self.send(json).await
} }
Payload::Response(error) => {
let json = serde_json::to_string(&error)?;
self.send(json).await
}
} }
} }
@ -131,24 +148,18 @@ impl Transport {
Ok(()) Ok(())
} }
pub async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> { async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> {
match msg { match msg {
Message::Output(output) => self.recv_response(output).await?, Message::Output(output) => self.recv_response(output).await?,
Message::Notification(jsonrpc::Notification { method, params, .. }) => {
let notification = Notification::parse(&method, params);
debug!("<- {} {:?}", method, notification);
self.incoming.send(notification).await?;
}
Message::Call(call) => { Message::Call(call) => {
debug!("<- {:?}", call); self.incoming.send(call).await?;
// dispatch // let notification = Notification::parse(&method, params);
} }
}; };
Ok(()) Ok(())
} }
pub async fn recv_response(&mut self, output: jsonrpc::Output) -> anyhow::Result<()> { async fn recv_response(&mut self, output: jsonrpc::Output) -> anyhow::Result<()> {
match output { match output {
jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => { jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
debug!("<- {}", result); debug!("<- {}", result);
@ -191,6 +202,8 @@ impl Transport {
} }
let msg = msg.unwrap(); let msg = msg.unwrap();
debug!("<- {:?}", msg);
self.recv_msg(msg).await.unwrap(); self.recv_msg(msg).await.unwrap();
} }
} }

View file

@ -433,8 +433,8 @@ impl<'a> Application<'a> {
event = reader.next().fuse() => { event = reader.next().fuse() => {
self.handle_terminal_events(event).await self.handle_terminal_events(event).await
} }
notification = self.lsp.incoming.next().fuse() => { call = self.lsp.incoming.next().fuse() => {
self.handle_lsp_notification(notification).await self.handle_lsp_message(call).await
} }
} }
} }
@ -566,10 +566,15 @@ impl<'a> Application<'a> {
}; };
} }
pub async fn handle_lsp_notification(&mut self, notification: Option<helix_lsp::Notification>) { pub async fn handle_lsp_message(&mut self, call: Option<helix_lsp::Call>) {
use helix_lsp::Notification; use helix_lsp::{Call, Notification};
match call {
Some(Call::Notification(helix_lsp::jsonrpc::Notification {
method, params, ..
})) => {
let notification = Notification::parse(&method, params);
match notification { match notification {
Some(Notification::PublishDiagnostics(params)) => { Notification::PublishDiagnostics(params) => {
let path = Some(params.uri.to_file_path().unwrap()); let path = Some(params.uri.to_file_path().unwrap());
let view = self let view = self
.editor .editor
@ -606,6 +611,14 @@ impl<'a> Application<'a> {
_ => unreachable!(), _ => unreachable!(),
} }
} }
Some(Call::MethodCall(call)) => {
// TODO: need to make Result<Value, Error>
unimplemented!("{:?}", call)
}
_ => unreachable!(),
}
}
pub async fn run(&mut self) -> Result<(), Error> { pub async fn run(&mut self) -> Result<(), Error> {
enable_raw_mode()?; enable_raw_mode()?;

View file

@ -82,6 +82,9 @@ use std::collections::HashMap;
// = = align? // = = align?
// + = // + =
// } // }
//
// gd = goto definition
// gr = goto reference
// } // }
#[cfg(feature = "term")] #[cfg(feature = "term")]