aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/src/lib.rs2
-rw-r--r--helix-core/src/transaction.rs9
-rw-r--r--helix-lsp/src/client.rs137
-rw-r--r--helix-lsp/src/lib.rs25
-rw-r--r--helix-lsp/src/transport.rs39
-rw-r--r--helix-term/src/application.rs87
-rw-r--r--helix-view/src/keymap.rs3
7 files changed, 225 insertions, 77 deletions
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 8458c36f..ddf1439c 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -27,4 +27,4 @@ pub use diagnostic::Diagnostic;
pub use history::History;
pub use state::State;
-pub use transaction::{Assoc, Change, ChangeSet, Transaction};
+pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index 9bd8c615..f1cb2ca1 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -5,8 +5,9 @@ use std::convert::TryFrom;
/// (from, to, replacement)
pub type Change = (usize, usize, Option<Tendril>);
+// TODO: pub(crate)
#[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) enum Operation {
+pub enum Operation {
/// Move cursor by n characters.
Retain(usize),
/// Delete n characters.
@@ -40,6 +41,12 @@ impl ChangeSet {
}
// TODO: from iter
+ //
+
+ #[doc(hidden)] // used by lsp to convert to LSP changes
+ pub fn changes(&self) -> &[Operation] {
+ &self.changes
+ }
#[must_use]
fn len_after(&self) -> usize {
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index 56413768..3c2c1ce0 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -1,11 +1,11 @@
use crate::{
transport::{Payload, Transport},
- Error, Notification,
+ Call, Error,
};
type Result<T> = core::result::Result<T, Error>;
-use helix_core::{State, Transaction};
+use helix_core::{ChangeSet, Transaction};
use helix_view::Document;
// use std::collections::HashMap;
@@ -27,7 +27,7 @@ pub struct Client {
stderr: BufReader<ChildStderr>,
outgoing: Sender<Payload>,
- pub incoming: Receiver<Notification>,
+ pub incoming: Receiver<Call>,
pub request_counter: u64,
@@ -87,6 +87,7 @@ impl Client {
Ok(params)
}
+ /// Execute a RPC request on the language server.
pub async fn request<R: lsp::request::Request>(
&mut self,
params: R::Params,
@@ -126,6 +127,7 @@ impl Client {
Ok(response)
}
+ /// Send a RPC notification to the language server.
pub async fn notify<R: lsp::notification::Notification>(
&mut self,
params: R::Params,
@@ -149,6 +151,35 @@ impl Client {
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
// -------------------------------------------------------------------------------------------
@@ -163,7 +194,9 @@ impl Client {
// root_uri: Some(lsp_types::Url::parse("file://localhost/")?),
root_uri: None, // set to project root in the future
initialization_options: None,
- capabilities: lsp::ClientCapabilities::default(),
+ capabilities: lsp::ClientCapabilities {
+ ..Default::default()
+ },
trace: None,
workspace_folders: None,
client_info: None,
@@ -203,23 +236,107 @@ impl Client {
.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
pub async fn text_document_did_change(
&mut self,
doc: &Document,
transaction: &Transaction,
) -> 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 {
text_document: lsp::VersionedTextDocumentIdentifier::new(
lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
doc.version,
),
- content_changes: 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?
+ content_changes: changes,
})
.await
}
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index c37222f1..1ee8199f 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -1,13 +1,12 @@
mod client;
mod transport;
-use jsonrpc_core as jsonrpc;
-use lsp_types as lsp;
+pub use jsonrpc_core as jsonrpc;
+pub use lsp_types as lsp;
pub use client::Client;
pub use lsp::{Position, Url};
-use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
@@ -30,19 +29,13 @@ pub mod util {
let line_start = doc.char_to_utf16_cu(line);
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.
-#[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),
+ lsp::Position::new(line as u64, col as u64)
+ }
}
#[derive(Debug, PartialEq, Clone)]
@@ -67,3 +60,5 @@ impl Notification {
}
}
}
+
+pub use jsonrpc::Call;
diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs
index 4ab3d5ec..4c349a13 100644
--- a/helix-lsp/src/transport.rs
+++ b/helix-lsp/src/transport.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use log::debug;
-use crate::{Error, Message, Notification};
+use crate::{Error, Notification};
type Result<T> = core::result::Result<T, Error>;
@@ -24,10 +24,23 @@ pub(crate) enum Payload {
value: jsonrpc::MethodCall,
},
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 {
- incoming: Sender<Notification>, // TODO Notification | Call
+ incoming: Sender<jsonrpc::Call>,
outgoing: Receiver<Payload>,
pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>,
@@ -42,7 +55,7 @@ impl Transport {
ex: &Executor,
reader: BufReader<ChildStdout>,
writer: BufWriter<ChildStdin>,
- ) -> (Receiver<Notification>, Sender<Payload>) {
+ ) -> (Receiver<jsonrpc::Call>, Sender<Payload>) {
let (incoming, rx) = smol::channel::unbounded();
let (tx, outgoing) = smol::channel::unbounded();
@@ -112,6 +125,10 @@ impl Transport {
let json = serde_json::to_string(&value)?;
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(())
}
- pub async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> {
+ async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> {
match msg {
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) => {
- debug!("<- {:?}", call);
- // dispatch
+ self.incoming.send(call).await?;
+ // let notification = Notification::parse(&method, params);
}
};
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 {
jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
debug!("<- {}", result);
@@ -191,6 +202,8 @@ impl Transport {
}
let msg = msg.unwrap();
+ debug!("<- {:?}", msg);
+
self.recv_msg(msg).await.unwrap();
}
}
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index b9594b7e..802dd399 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -433,8 +433,8 @@ impl<'a> Application<'a> {
event = reader.next().fuse() => {
self.handle_terminal_events(event).await
}
- notification = self.lsp.incoming.next().fuse() => {
- self.handle_lsp_notification(notification).await
+ call = self.lsp.incoming.next().fuse() => {
+ self.handle_lsp_message(call).await
}
}
}
@@ -566,43 +566,56 @@ impl<'a> Application<'a> {
};
}
- pub async fn handle_lsp_notification(&mut self, notification: Option<helix_lsp::Notification>) {
- use helix_lsp::Notification;
- match notification {
- Some(Notification::PublishDiagnostics(params)) => {
- let path = Some(params.uri.to_file_path().unwrap());
- let view = self
- .editor
- .views
- .iter_mut()
- .find(|view| view.doc.path == path);
-
- if let Some(view) = view {
- let doc = view.doc.text().slice(..);
- let diagnostics = params
- .diagnostics
- .into_iter()
- .map(|diagnostic| {
- use helix_lsp::util::lsp_pos_to_pos;
- let start = lsp_pos_to_pos(&doc, diagnostic.range.start);
- let end = lsp_pos_to_pos(&doc, diagnostic.range.end);
-
- helix_core::Diagnostic {
- range: (start, end),
- line: diagnostic.range.start.line as usize,
- message: diagnostic.message,
- // severity
- // code
- // source
- }
- })
- .collect();
-
- view.doc.diagnostics = diagnostics;
-
- self.render();
+ pub async fn handle_lsp_message(&mut self, call: Option<helix_lsp::Call>) {
+ use helix_lsp::{Call, Notification};
+ match call {
+ Some(Call::Notification(helix_lsp::jsonrpc::Notification {
+ method, params, ..
+ })) => {
+ let notification = Notification::parse(&method, params);
+ match notification {
+ Notification::PublishDiagnostics(params) => {
+ let path = Some(params.uri.to_file_path().unwrap());
+ let view = self
+ .editor
+ .views
+ .iter_mut()
+ .find(|view| view.doc.path == path);
+
+ if let Some(view) = view {
+ let doc = view.doc.text().slice(..);
+ let diagnostics = params
+ .diagnostics
+ .into_iter()
+ .map(|diagnostic| {
+ use helix_lsp::util::lsp_pos_to_pos;
+ let start = lsp_pos_to_pos(&doc, diagnostic.range.start);
+ let end = lsp_pos_to_pos(&doc, diagnostic.range.end);
+
+ helix_core::Diagnostic {
+ range: (start, end),
+ line: diagnostic.range.start.line as usize,
+ message: diagnostic.message,
+ // severity
+ // code
+ // source
+ }
+ })
+ .collect();
+
+ view.doc.diagnostics = diagnostics;
+
+ self.render();
+ }
+ }
+ _ => unreachable!(),
}
}
+ Some(Call::MethodCall(call)) => {
+ // TODO: need to make Result<Value, Error>
+
+ unimplemented!("{:?}", call)
+ }
_ => unreachable!(),
}
}
diff --git a/helix-view/src/keymap.rs b/helix-view/src/keymap.rs
index aaba34a6..c815911e 100644
--- a/helix-view/src/keymap.rs
+++ b/helix-view/src/keymap.rs
@@ -82,6 +82,9 @@ use std::collections::HashMap;
// = = align?
// + =
// }
+//
+// gd = goto definition
+// gr = goto reference
// }
#[cfg(feature = "term")]