helix-mirror/helix-lsp/src/lib.rs

183 lines
5.5 KiB
Rust
Raw Normal View History

2020-10-21 11:42:45 +04:00
mod client;
2020-12-23 10:50:16 +04:00
mod select_all;
mod transport;
pub use jsonrpc_core as jsonrpc;
pub use lsp_types as lsp;
2020-10-21 11:42:45 +04:00
pub use client::Client;
pub use lsp::{Position, Url};
use helix_core::syntax::LanguageConfiguration;
2020-10-21 11:42:45 +04:00
use thiserror::Error;
2020-11-05 10:15:19 +04:00
use std::{collections::HashMap, sync::Arc};
2020-10-21 11:42:45 +04:00
#[derive(Error, Debug)]
pub enum Error {
#[error("protocol error: {0}")]
Rpc(#[from] jsonrpc::Error),
#[error("failed to parse: {0}")]
Parse(#[from] serde_json::Error),
#[error("request timed out")]
Timeout,
#[error(transparent)]
Other(#[from] anyhow::Error),
}
2020-10-21 08:47:20 +04:00
pub mod util {
use super::*;
use helix_core::{Range, Rope, Transaction};
2020-10-21 08:47:20 +04:00
pub fn lsp_pos_to_pos(doc: &Rope, pos: lsp::Position) -> usize {
2020-10-21 08:47:20 +04:00
let line = doc.line_to_char(pos.line as usize);
let line_start = doc.char_to_utf16_cu(line);
doc.utf16_cu_to_char(line_start + pos.character as usize)
2020-10-21 08:47:20 +04:00
}
pub fn pos_to_lsp_pos(doc: &Rope, pos: usize) -> lsp::Position {
let line = doc.char_to_line(pos);
2020-12-25 12:42:50 +04:00
let line_start = doc.char_to_utf16_cu(doc.line_to_char(line));
let col = doc.char_to_utf16_cu(pos) - line_start;
2020-10-21 08:47:20 +04:00
2020-12-01 04:53:17 +04:00
lsp::Position::new(line as u32, col as u32)
}
pub fn range_to_lsp_range(doc: &Rope, range: Range) -> lsp::Range {
let start = pos_to_lsp_pos(doc, range.from());
let end = pos_to_lsp_pos(doc, range.to());
lsp::Range::new(start, end)
}
pub fn generate_transaction_from_edits(doc: &Rope, edits: Vec<lsp::TextEdit>) -> Transaction {
Transaction::change(
doc,
edits.into_iter().map(|edit| {
// simplify "" into None for cleaner changesets
let replacement = if !edit.new_text.is_empty() {
Some(edit.new_text.into())
} else {
None
};
let start = lsp_pos_to_pos(doc, edit.range.start);
let end = lsp_pos_to_pos(doc, edit.range.end);
(start, end, replacement)
}),
)
}
// apply_insert_replace_edit
}
#[derive(Debug, PartialEq, Clone)]
pub enum Notification {
PublishDiagnostics(lsp::PublishDiagnosticsParams),
}
impl Notification {
pub fn parse(method: &str, params: jsonrpc::Params) -> Notification {
use lsp::notification::Notification as _;
match method {
lsp::notification::PublishDiagnostics::METHOD => {
let params: lsp::PublishDiagnosticsParams = params
.parse()
.expect("Failed to parse PublishDiagnostics params");
// TODO: need to loop over diagnostics and distinguish them by URI
Notification::PublishDiagnostics(params)
}
_ => unimplemented!("unhandled notification: {}", method),
}
}
}
pub use jsonrpc::Call;
2020-11-05 10:15:19 +04:00
type LanguageId = String;
2020-12-23 10:50:16 +04:00
use crate::select_all::SelectAll;
use smol::channel::Receiver;
2020-11-05 10:15:19 +04:00
pub struct Registry {
inner: HashMap<LanguageId, Arc<Client>>,
2020-12-23 10:50:16 +04:00
pub incoming: SelectAll<Receiver<Call>>,
2020-11-05 10:15:19 +04:00
}
2021-01-08 11:31:19 +04:00
impl Default for Registry {
fn default() -> Self {
Self::new()
}
}
2020-11-05 10:15:19 +04:00
impl Registry {
2020-12-23 10:50:16 +04:00
pub fn new() -> Self {
2020-11-05 10:15:19 +04:00
Self {
inner: HashMap::new(),
2020-12-23 10:50:16 +04:00
incoming: SelectAll::new(),
2020-11-05 10:15:19 +04:00
}
}
pub fn get(
&mut self,
language_config: &LanguageConfiguration,
ex: &smol::Executor,
) -> Option<Arc<Client>> {
// TODO: propagate the error
if let Some(config) = &language_config.language_server_config {
// avoid borrow issues
let inner = &mut self.inner;
let s_incoming = &self.incoming;
let language_server = inner
.entry(language_config.scope.clone()) // can't use entry with Borrow keys: https://github.com/rust-lang/rfcs/pull/1769
.or_insert_with(|| {
2020-11-05 10:15:19 +04:00
// TODO: lookup defaults for id (name, args)
// initialize a new client
let (mut client, incoming) = Client::start(&ex, &config.command, &config.args);
2020-12-23 10:50:16 +04:00
// TODO: run this async without blocking
smol::block_on(client.initialize()).unwrap();
s_incoming.push(incoming);
2020-12-23 10:50:16 +04:00
2020-11-05 10:15:19 +04:00
Arc::new(client)
})
.clone();
return Some(language_server);
}
None
2020-11-05 10:15:19 +04:00
}
}
// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
// spawn one server per language type, need to spawn one per workspace if server doesn't support
// workspaces
//
// could also be a client per root dir
//
// storing a copy of Option<Arc<RwLock<Client>>> on Document would make the LSP client easily
// accessible during edit/save callbacks
//
// the event loop needs to process all incoming streams, maybe we can just have that be a separate
// task that's continually running and store the state on the client, then use read lock to
// retrieve data during render
// -> PROBLEM: how do you trigger an update on the editor side when data updates?
//
// -> The data updates should pull all events until we run out so we don't frequently re-render
2020-12-23 10:50:16 +04:00
//
//
// v2:
//
// there should be a registry of lsp clients, one per language type (or workspace).
// the clients should lazy init on first access
// the client.initialize() should be called async and we buffer any requests until that completes
// there needs to be a way to process incoming lsp messages from all clients.
// -> notifications need to be dispatched to wherever
// -> requests need to generate a reply and travel back to the same lsp!