lsp: Work on syncing the state with the language server.

This commit is contained in:
Blaž Hrastnik 2020-12-25 17:20:09 +09:00
parent cd16df19c1
commit 2ab069bb3f
8 changed files with 92 additions and 35 deletions

3
Cargo.lock generated
View File

@ -553,7 +553,6 @@ dependencies = [
"futures-util", "futures-util",
"glob", "glob",
"helix-core", "helix-core",
"helix-view",
"jsonrpc-core", "jsonrpc-core",
"log", "log",
"lsp-types", "lsp-types",
@ -604,7 +603,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossterm", "crossterm",
"futures-util",
"helix-core", "helix-core",
"helix-lsp",
"once_cell", "once_cell",
"smol", "smol",
"tui", "tui",

View File

@ -8,7 +8,6 @@ edition = "2018"
[dependencies] [dependencies]
helix-core = { path = "../helix-core" } helix-core = { path = "../helix-core" }
helix-view = { path = "../helix-view" }
once_cell = "1.4" once_cell = "1.4"

View File

@ -5,8 +5,7 @@
type Result<T> = core::result::Result<T, Error>; type Result<T> = core::result::Result<T, Error>;
use helix_core::{ChangeSet, Transaction}; use helix_core::{ChangeSet, Rope, Transaction};
use helix_view::Document;
// use std::collections::HashMap; // use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
@ -23,10 +22,6 @@
Executor, Executor,
}; };
fn text_document_identifier(doc: &Document) -> lsp::TextDocumentIdentifier {
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(doc.path().unwrap()).unwrap())
}
pub struct Client { pub struct Client {
_process: Child, _process: Child,
stderr: BufReader<ChildStderr>, stderr: BufReader<ChildStderr>,
@ -232,13 +227,18 @@ pub async fn exit(&self) -> Result<()> {
// Text document // Text document
// ------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------
pub async fn text_document_did_open(&self, doc: &Document) -> Result<()> { pub async fn text_document_did_open(
&self,
uri: lsp::Url,
version: i32,
doc: &Rope,
) -> Result<()> {
self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams { self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem { text_document: lsp::TextDocumentItem {
uri: lsp::Url::from_file_path(doc.path().unwrap()).unwrap(), uri,
language_id: "rust".to_string(), // TODO: hardcoded for now language_id: "rust".to_string(), // TODO: hardcoded for now
version: doc.version, version,
text: String::from(doc.text()), text: String::from(doc),
}, },
}) })
.await .await
@ -290,6 +290,21 @@ fn to_changes(changeset: &ChangeSet) -> Vec<lsp::TextDocumentContentChangeEvent>
}; };
} }
Insert(s) => { Insert(s) => {
// TODO:
// thread 'main' panicked at 'Attempt to index past end of slice: char index 1211, slice char length 0', /home/speed/.cargo/registry/src/github.com-1ecc6299db9ec823/ropey-1.2.0/src/slice.rs:301:9
// stack backtrace:
// 0: rust_begin_unwind
// at /rustc/b32e6e6ac8921035177256ab6806e6ab0d4b9b94/library/std/src/panicking.rs:493:5
// 1: std::panicking::begin_panic_fmt
// at /rustc/b32e6e6ac8921035177256ab6806e6ab0d4b9b94/library/std/src/panicking.rs:435:5
// 2: ropey::slice::RopeSlice::char_to_line
// at /home/speed/.cargo/registry/src/github.com-1ecc6299db9ec823/ropey-1.2.0/src/slice.rs:301:9
// 3: helix_lsp::util::pos_to_lsp_pos
// at /home/speed/src/helix/helix-lsp/src/lib.rs:39:20
// 4: helix_lsp::client::Client::to_changes
// at /home/speed/src/helix/helix-lsp/src/client.rs:293:33
// 5: helix_lsp::client::Client::text_document_did_change::{{closure}}
// at /home/speed/src/helix/helix-lsp/src/client.rs:338:55
let start = pos_to_lsp_pos(&old_text, old_pos); let start = pos_to_lsp_pos(&old_text, old_pos);
// insert // insert
@ -309,8 +324,8 @@ fn to_changes(changeset: &ChangeSet) -> Vec<lsp::TextDocumentContentChangeEvent>
// 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(
&self, &self,
doc: &Document, text_document: lsp::VersionedTextDocumentIdentifier,
transaction: &Transaction, changes: &ChangeSet,
) -> Result<()> { ) -> Result<()> {
// figure out what kind of sync the server supports // figure out what kind of sync the server supports
@ -335,16 +350,12 @@ pub async fn text_document_did_change(
text: "".to_string(), text: "".to_string(),
}] // TODO: probably need old_state here too? }] // TODO: probably need old_state here too?
} }
lsp::TextDocumentSyncKind::Incremental => Self::to_changes(transaction.changes()), lsp::TextDocumentSyncKind::Incremental => Self::to_changes(changes),
lsp::TextDocumentSyncKind::None => return Ok(()), 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,
// TODO: doc.into() Url
lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
doc.version,
),
content_changes: changes, content_changes: changes,
}) })
.await .await
@ -352,9 +363,12 @@ pub async fn text_document_did_change(
// TODO: impl into() TextDocumentIdentifier / VersionedTextDocumentIdentifier for Document. // TODO: impl into() TextDocumentIdentifier / VersionedTextDocumentIdentifier for Document.
pub async fn text_document_did_close(&self, doc: &Document) -> Result<()> { pub async fn text_document_did_close(
&self,
text_document: lsp::TextDocumentIdentifier,
) -> Result<()> {
self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams { self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams {
text_document: text_document_identifier(doc), text_document,
}) })
.await .await
} }
@ -365,16 +379,17 @@ pub async fn text_document_did_save(&self) -> anyhow::Result<()> {
unimplemented!() unimplemented!()
} }
pub async fn completion(&self, doc: &Document) -> anyhow::Result<Vec<lsp::CompletionItem>> { pub async fn completion(
&self,
text_document: lsp::TextDocumentIdentifier,
position: lsp::Position,
) -> anyhow::Result<Vec<lsp::CompletionItem>> {
// TODO: figure out what should happen when you complete with multiple cursors // TODO: figure out what should happen when you complete with multiple cursors
let params = lsp::CompletionParams { let params = lsp::CompletionParams {
text_document_position: lsp::TextDocumentPositionParams { text_document_position: lsp::TextDocumentPositionParams {
text_document: text_document_identifier(doc), text_document,
position: crate::util::pos_to_lsp_pos( position,
&doc.text().slice(..),
doc.selection().cursor(),
),
}, },
// TODO: support these tokens by async receiving and updating the choice list // TODO: support these tokens by async receiving and updating the choice list
work_done_progress_params: lsp::WorkDoneProgressParams { work_done_progress_params: lsp::WorkDoneProgressParams {

View File

@ -44,16 +44,23 @@ pub fn new(mut args: Args, executor: &'static smol::Executor<'static>) -> Result
let mut editor = Editor::new(); let mut editor = Editor::new();
let size = terminal.size()?; let size = terminal.size()?;
let language_servers = helix_lsp::Registry::new();
let language_server = language_servers.get("rust", &executor).unwrap();
if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() { if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
editor.open(file, (size.width, size.height))?; editor.open(file, (size.width, size.height))?;
// TODO: do this everywhere
editor
.view_mut()
.unwrap()
.doc
.set_language_server(Some(language_server.clone()));
} }
let mut compositor = Compositor::new(); let mut compositor = Compositor::new();
compositor.push(Box::new(ui::EditorView::new())); compositor.push(Box::new(ui::EditorView::new()));
let language_servers = helix_lsp::Registry::new();
let language_server = language_servers.get("rust", &executor).unwrap();
let mut app = Self { let mut app = Self {
editor, editor,
terminal, terminal,
@ -90,8 +97,9 @@ fn render(&mut self) {
pub async fn event_loop(&mut self) { pub async fn event_loop(&mut self) {
let mut reader = EventStream::new(); let mut reader = EventStream::new();
let doc = &self.editor.view().unwrap().doc;
self.language_server self.language_server
.text_document_did_open(&self.editor.view().unwrap().doc) .text_document_did_open(doc.url().unwrap(), doc.version, doc.text())
.await .await
.unwrap(); .unwrap();

View File

@ -841,9 +841,13 @@ pub fn completion(cx: &mut Context) {
use std::time::Duration; use std::time::Duration;
// TODO: blocking here is not ideal // TODO: blocking here is not ideal
let pos = helix_lsp::util::pos_to_lsp_pos(
&cx.view.doc.text().slice(..),
cx.view.doc.selection().cursor(),
);
let res = smol::block_on( let res = smol::block_on(
language_server language_server
.completion(&cx.view.doc) .completion(cx.view.doc.identifier(), pos)
.timeout(Duration::from_secs(2)), .timeout(Duration::from_secs(2)),
) )
.expect("completion failed!") .expect("completion failed!")

View File

@ -41,7 +41,8 @@ pub fn render_view(
self.render_buffer(view, area, surface, theme); self.render_buffer(view, area, surface, theme);
// clear with background color // clear with background color
surface.set_style(viewport, theme.get("ui.background")); // TODO: this seems to prevent setting style later
// surface.set_style(viewport, theme.get("ui.background"));
let area = Rect::new(0, viewport.height - 2, viewport.width, 1); let area = Rect::new(0, viewport.height - 2, viewport.width, 1);
self.render_statusline(view, area, surface, theme); self.render_statusline(view, area, surface, theme);

View File

@ -13,6 +13,7 @@ default = ["term"]
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
helix-core = { path = "../helix-core" } helix-core = { path = "../helix-core" }
helix-lsp = { path = "../helix-lsp"}
# Conversion traits # Conversion traits
# tui = { version = "0.12", default-features = false, features = ["crossterm"], optional = true} # tui = { version = "0.12", default-features = false, features = ["crossterm"], optional = true}
@ -22,3 +23,4 @@ once_cell = "1.4"
url = "2" url = "2"
smol = "1" smol = "1"
futures-util = "0.3"

View File

@ -1,6 +1,7 @@
use anyhow::Error; use anyhow::Error;
use std::future::Future; use std::future::Future;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc;
use helix_core::{ use helix_core::{
syntax::LOADER, ChangeSet, Diagnostic, History, Position, Range, Rope, RopeSlice, Selection, syntax::LOADER, ChangeSet, Diagnostic, History, Position, Range, Rope, RopeSlice, Selection,
@ -35,6 +36,7 @@ pub struct Document {
pub version: i32, // should be usize? pub version: i32, // should be usize?
pub diagnostics: Vec<Diagnostic>, pub diagnostics: Vec<Diagnostic>,
pub language_server: Option<Arc<helix_lsp::Client>>,
} }
/// Like std::mem::replace() except it allows the replacement value to be mapped from the /// Like std::mem::replace() except it allows the replacement value to be mapped from the
@ -53,6 +55,8 @@ fn take_with<T, F>(mut_ref: &mut T, closure: F)
} }
} }
use futures_util::TryFutureExt;
use helix_lsp::lsp;
use url::Url; use url::Url;
impl Document { impl Document {
@ -72,6 +76,7 @@ fn new(state: State) -> Self {
diagnostics: Vec::new(), diagnostics: Vec::new(),
version: 0, version: 0,
history: History::default(), history: History::default(),
language_server: None,
} }
} }
@ -134,7 +139,7 @@ pub fn save(&self) -> impl Future<Output = Result<(), anyhow::Error>> {
// TODO: flush? // TODO: flush?
Ok(()) Ok(())
} // and_then(// lsp.send_text_saved_notification()) } // and_then notify save
} }
pub fn set_language(&mut self, scope: &str, scopes: &[String]) { pub fn set_language(&mut self, scope: &str, scopes: &[String]) {
@ -148,6 +153,10 @@ pub fn set_language(&mut self, scope: &str, scopes: &[String]) {
}; };
} }
pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
self.language_server = language_server;
}
pub fn set_selection(&mut self, selection: Selection) { pub fn set_selection(&mut self, selection: Selection) {
// TODO: use a transaction? // TODO: use a transaction?
self.state.selection = selection; self.state.selection = selection;
@ -181,6 +190,14 @@ pub fn apply(&mut self, transaction: &Transaction) -> bool {
} }
// TODO: map state.diagnostics over changes::map_pos too // TODO: map state.diagnostics over changes::map_pos too
// emit lsp notification
if let Some(language_server) = &self.language_server {
let notify = language_server
.text_document_did_change(self.versioned_identifier(), transaction.changes());
smol::block_on(notify).expect("failed to emit textDocument/didChange");
}
} }
success success
} }
@ -263,4 +280,14 @@ pub fn relative_path(&self) -> Option<&Path> {
// } // }
// TODO: transact(Fn) ? // TODO: transact(Fn) ?
// -- LSP methods
pub fn identifier(&self) -> lsp::TextDocumentIdentifier {
lsp::TextDocumentIdentifier::new(self.url().unwrap())
}
pub fn versioned_identifier(&self) -> lsp::VersionedTextDocumentIdentifier {
lsp::VersionedTextDocumentIdentifier::new(self.url().unwrap(), self.version)
}
} }