Reroute LSP notification events into the main app event loop.

pull/5/head
Blaž Hrastnik 4 years ago
parent 64b5b23315
commit f9bfba4d96

1
Cargo.lock generated

@ -472,6 +472,7 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"crossterm", "crossterm",
"futures-util",
"helix-core", "helix-core",
"helix-lsp", "helix-lsp",
"helix-view", "helix-view",

@ -0,0 +1 @@
pub struct Diagnostic {}

@ -1,4 +1,5 @@
#![allow(unused)] #![allow(unused)]
mod diagnostic;
pub mod graphemes; pub mod graphemes;
mod history; mod history;
pub mod indent; pub mod indent;
@ -22,6 +23,7 @@ pub use selection::Range;
pub use selection::Selection; pub use selection::Selection;
pub use syntax::Syntax; pub use syntax::Syntax;
pub use diagnostic::Diagnostic;
pub use history::History; pub use history::History;
pub use state::State; pub use state::State;

@ -1,6 +1,6 @@
use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
use crate::syntax::LOADER; use crate::syntax::LOADER;
use crate::{ChangeSet, Position, Range, Rope, RopeSlice, Selection, Syntax}; use crate::{ChangeSet, Diagnostic, Position, Range, Rope, RopeSlice, Selection, Syntax};
use anyhow::Error; use anyhow::Error;
use std::path::PathBuf; use std::path::PathBuf;
@ -28,6 +28,8 @@ pub struct State {
/// Pending changes since last history commit. /// Pending changes since last history commit.
pub changes: ChangeSet, pub changes: ChangeSet,
pub old_state: Option<(Rope, Selection)>, pub old_state: Option<(Rope, Selection)>,
pub diagnostics: Vec<Diagnostic>,
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
@ -58,12 +60,13 @@ impl State {
syntax: None, syntax: None,
changes, changes,
old_state, old_state,
diagnostics: Vec::new(),
} }
} }
// TODO: passing scopes here is awkward // TODO: passing scopes here is awkward
pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> { pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
use std::{env, fs::File, io::BufReader, path::PathBuf}; use std::{env, fs::File, io::BufReader};
let _current_dir = env::current_dir()?; let _current_dir = env::current_dir()?;
let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?; let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
@ -81,7 +84,8 @@ impl State {
state.syntax = Some(syntax); state.syntax = Some(syntax);
}; };
state.path = Some(path); // canonicalize path to absolute value
state.path = Some(std::fs::canonicalize(path)?);
Ok(state) Ok(state)
} }

@ -2,7 +2,7 @@ mod transport;
use transport::{Payload, Transport}; use transport::{Payload, Transport};
use std::collections::HashMap; // use std::collections::HashMap;
use jsonrpc_core as jsonrpc; use jsonrpc_core as jsonrpc;
use lsp_types as lsp; use lsp_types as lsp;
@ -32,10 +32,12 @@ enum Message {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
enum Notification {} pub enum Notification {
PublishDiagnostics(lsp::PublishDiagnosticsParams),
}
impl Notification { impl Notification {
pub fn parse(method: &str, params: jsonrpc::Params) { pub fn parse(method: &str, params: jsonrpc::Params) -> Notification {
use lsp::notification::Notification as _; use lsp::notification::Notification as _;
match method { match method {
@ -44,11 +46,10 @@ impl Notification {
.parse() .parse()
.expect("Failed to parse PublishDiagnostics params"); .expect("Failed to parse PublishDiagnostics params");
println!("{:?}", params);
// TODO: need to loop over diagnostics and distinguish them by URI // TODO: need to loop over diagnostics and distinguish them by URI
Notification::PublishDiagnostics(params)
} }
_ => println!("unhandled notification: {}", method), _ => unimplemented!("unhandled notification: {}", method),
} }
} }
} }
@ -58,13 +59,13 @@ pub struct Client {
stderr: BufReader<ChildStderr>, stderr: BufReader<ChildStderr>,
outgoing: Sender<Payload>, outgoing: Sender<Payload>,
incoming: Receiver<Message>, pub incoming: Receiver<Notification>,
pub request_counter: u64, pub request_counter: u64,
capabilities: Option<lsp::ServerCapabilities>, capabilities: Option<lsp::ServerCapabilities>,
// TODO: handle PublishDiagnostics Version // TODO: handle PublishDiagnostics Version
diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>, // diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
} }
impl Client { impl Client {
@ -95,7 +96,7 @@ impl Client {
request_counter: 0, request_counter: 0,
capabilities: None, capabilities: None,
diagnostics: HashMap::new(), // diagnostics: HashMap::new(),
} }
} }
@ -226,10 +227,7 @@ impl Client {
) -> anyhow::Result<()> { ) -> anyhow::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( uri: lsp::Url::from_file_path(state.path().unwrap()).unwrap(),
std::fs::canonicalize(state.path.as_ref().unwrap()).unwrap(),
)
.unwrap(),
language_id: "rust".to_string(), // TODO: hardcoded for now language_id: "rust".to_string(), // TODO: hardcoded for now
version: 0, version: 0,
text: String::from(&state.doc), text: String::from(&state.doc),
@ -243,11 +241,12 @@ impl Client {
&mut self, &mut self,
state: &helix_core::State, state: &helix_core::State,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams { self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new( text_document: lsp::VersionedTextDocumentIdentifier::new(
lsp::Url::from_file_path(state.path.as_ref().unwrap()).unwrap(), lsp::Url::from_file_path(state.path().unwrap()).unwrap(),
0, // TODO: version
), ),
text: None, // TODO? content_changes: vec![], // TODO:
}) })
.await .await
} }

@ -24,7 +24,7 @@ pub(crate) enum Payload {
} }
pub(crate) struct Transport { pub(crate) struct Transport {
incoming: Sender<Message>, incoming: Sender<Notification>, // TODO Notification | Call
outgoing: Receiver<Payload>, outgoing: Receiver<Payload>,
pending_requests: HashMap<jsonrpc::Id, Sender<anyhow::Result<Value>>>, pending_requests: HashMap<jsonrpc::Id, Sender<anyhow::Result<Value>>>,
@ -39,7 +39,7 @@ impl Transport {
ex: &Executor, ex: &Executor,
reader: BufReader<ChildStdout>, reader: BufReader<ChildStdout>,
writer: BufWriter<ChildStdin>, writer: BufWriter<ChildStdin>,
) -> (Receiver<Message>, Sender<Payload>) { ) -> (Receiver<Notification>, 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();
@ -111,7 +111,7 @@ impl Transport {
} }
pub async fn send(&mut self, request: String) -> anyhow::Result<()> { pub async fn send(&mut self, request: String) -> anyhow::Result<()> {
println!("-> {}", request); // println!("-> {}", request);
// send the headers // send the headers
self.writer self.writer
@ -132,11 +132,11 @@ impl Transport {
Message::Notification(jsonrpc::Notification { method, params, .. }) => { Message::Notification(jsonrpc::Notification { method, params, .. }) => {
let notification = Notification::parse(&method, params); let notification = Notification::parse(&method, params);
println!("<- {} {:?}", method, notification); // println!("<- {} {:?}", method, notification);
// dispatch self.incoming.send(notification).await?;
} }
Message::Call(call) => { Message::Call(call) => {
println!("<- {:?}", call); // println!("<- {:?}", call);
// dispatch // dispatch
} }
_ => unreachable!(), _ => unreachable!(),
@ -147,7 +147,7 @@ impl Transport {
pub async fn recv_response(&mut self, output: jsonrpc::Output) -> anyhow::Result<()> { pub 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, .. }) => {
println!("<- {}", result); // println!("<- {}", result);
let tx = self let tx = self
.pending_requests .pending_requests

@ -24,3 +24,5 @@ num_cpus = "1.13"
tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] } tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] }
crossterm = { version = "0.18", features = ["event-stream"] } crossterm = { version = "0.18", features = ["event-stream"] }
clap = { version = "3.0.0-beta.2 ", default-features = false, features = ["std", "cargo"] } clap = { version = "3.0.0-beta.2 ", default-features = false, features = ["std", "cargo"] }
futures-util = "0.3"

@ -1,6 +1,11 @@
use clap::ArgMatches as Args; use clap::ArgMatches as Args;
use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State}; use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State};
use helix_view::{commands, keymap, prompt::Prompt, Editor, Theme, View}; use helix_view::{
commands,
keymap::{self, Keymaps},
prompt::Prompt,
Editor, Theme, View,
};
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -31,14 +36,16 @@ const OFFSET: u16 = 6; // 5 linenr + 1 gutter
type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>; type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
static EX: smol::Executor = smol::Executor::new();
const BASE_WIDTH: u16 = 30; const BASE_WIDTH: u16 = 30;
pub struct Application { pub struct Application<'a> {
editor: Editor, editor: Editor,
prompt: Option<Prompt>, prompt: Option<Prompt>,
terminal: Renderer, terminal: Renderer,
keymap: Keymaps,
executor: &'a smol::Executor<'a>,
lsp: helix_lsp::Client,
} }
struct Renderer { struct Renderer {
@ -235,7 +242,7 @@ impl Renderer {
.set_string(1, self.size.1 - 2, mode, self.text_color); .set_string(1, self.size.1 - 2, mode, self.text_color);
} }
pub fn render_prompt(&mut self, view: &View, prompt: &Prompt) { pub fn render_prompt(&mut self, view: &View, prompt: &Prompt, theme: &Theme) {
// completion // completion
if !prompt.completion.is_empty() { if !prompt.completion.is_empty() {
// TODO: find out better way of clearing individual lines of the screen // TODO: find out better way of clearing individual lines of the screen
@ -254,7 +261,7 @@ impl Renderer {
} }
self.surface.set_style( self.surface.set_style(
Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height), Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height),
view.theme.get("ui.statusline"), theme.get("ui.statusline"),
); );
for (i, command) in prompt.completion.iter().enumerate() { for (i, command) in prompt.completion.iter().enumerate() {
let color = if prompt.completion_selection_index.is_some() let color = if prompt.completion_selection_index.is_some()
@ -330,8 +337,8 @@ impl Renderer {
} }
} }
impl Application { impl<'a> Application<'a> {
pub fn new(mut args: Args) -> Result<Self, Error> { pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
let terminal = Renderer::new()?; let terminal = Renderer::new()?;
let mut editor = Editor::new(); let mut editor = Editor::new();
@ -339,11 +346,18 @@ impl Application {
editor.open(file, terminal.size)?; editor.open(file, terminal.size)?;
} }
let lsp = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
let mut app = Self { let mut app = Self {
editor, editor,
terminal, terminal,
// TODO; move to state // TODO; move to state
prompt: None, prompt: None,
//
keymap: keymap::default(),
executor,
lsp,
}; };
Ok(app) Ok(app)
@ -361,7 +375,7 @@ impl Application {
if prompt.should_close { if prompt.should_close {
self.prompt = None; self.prompt = None;
} else { } else {
self.terminal.render_prompt(view, prompt); self.terminal.render_prompt(view, prompt, theme_ref);
} }
} }
} }
@ -375,7 +389,13 @@ impl Application {
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 keymap = keymap::default();
// initialize lsp
let res = self.lsp.initialize().await;
let res = self
.lsp
.text_document_did_open(&self.editor.view().unwrap().state)
.await;
self.render(); self.render();
@ -384,126 +404,149 @@ impl Application {
break; break;
} }
// Handle key events use futures_util::{select, FutureExt};
match reader.next().await { select! {
Some(Ok(Event::Resize(width, height))) => { event = reader.next().fuse() => {
self.terminal.resize(width, height); self.handle_terminal_events(event).await
}
notification = self.lsp.incoming.next().fuse() => {
self.handle_lsp_notification(notification).await
}
}
}
}
// TODO: simplistic ensure cursor in view for now pub async fn handle_terminal_events(
// TODO: loop over views &mut self,
if let Some(view) = self.editor.view_mut() { event: Option<Result<Event, crossterm::ErrorKind>>,
view.size = self.terminal.size; ) {
view.ensure_cursor_in_view() // Handle key events
}; match event {
Some(Ok(Event::Resize(width, height))) => {
self.terminal.resize(width, height);
// TODO: simplistic ensure cursor in view for now
// TODO: loop over views
if let Some(view) = self.editor.view_mut() {
view.size = self.terminal.size;
view.ensure_cursor_in_view()
};
self.render();
}
Some(Ok(Event::Key(event))) => {
// if there's a prompt, it takes priority
if let Some(prompt) = &mut self.prompt {
self.prompt
.as_mut()
.unwrap()
.handle_input(event, &mut self.editor);
self.render(); self.render();
} } else if let Some(view) = self.editor.view_mut() {
Some(Ok(Event::Key(event))) => { let keys = vec![event];
// if there's a prompt, it takes priority // TODO: sequences (`gg`)
if let Some(prompt) = &mut self.prompt { // TODO: handle count other than 1
self.prompt match view.state.mode() {
.as_mut() Mode::Insert => {
.unwrap() if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
.handle_input(event, &mut self.editor); command(view, 1);
} else if let KeyEvent {
self.render(); code: KeyCode::Char(c),
} else if let Some(view) = self.editor.view_mut() { ..
let keys = vec![event]; } = event
// TODO: sequences (`gg`) {
// TODO: handle count other than 1 commands::insert::insert_char(view, c);
match view.state.mode() {
Mode::Insert => {
if let Some(command) = keymap[&Mode::Insert].get(&keys) {
command(view, 1);
} else if let KeyEvent {
code: KeyCode::Char(c),
..
} = event
{
commands::insert::insert_char(view, c);
}
view.ensure_cursor_in_view();
} }
Mode::Normal => { view.ensure_cursor_in_view();
if let &[KeyEvent { }
code: KeyCode::Char(':'), Mode::Normal => {
.. if let &[KeyEvent {
}] = keys.as_slice() code: KeyCode::Char(':'),
{ ..
let prompt = Prompt::new( }] = keys.as_slice()
":".to_owned(), {
|_input: &str| { let prompt = Prompt::new(
// TODO: i need this duplicate list right now to avoid borrow checker issues ":".to_owned(),
let command_list = vec![ |_input: &str| {
String::from("q"), // TODO: i need this duplicate list right now to avoid borrow checker issues
String::from("aaa"), let command_list = vec![
String::from("bbb"), String::from("q"),
String::from("ccc"), String::from("aaa"),
String::from("ddd"), String::from("bbb"),
String::from("eee"), String::from("ccc"),
String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"), String::from("ddd"),
String::from("q"), String::from("eee"),
String::from("aaa"), String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"),
String::from("bbb"), String::from("q"),
String::from("ccc"), String::from("aaa"),
String::from("ddd"), String::from("bbb"),
String::from("eee"), String::from("ccc"),
String::from("q"), String::from("ddd"),
String::from("aaa"), String::from("eee"),
String::from("bbb"), String::from("q"),
String::from("ccc"), String::from("aaa"),
String::from("ddd"), String::from("bbb"),
String::from("eee"), String::from("ccc"),
String::from("q"), String::from("ddd"),
String::from("aaa"), String::from("eee"),
String::from("bbb"), String::from("q"),
String::from("ccc"), String::from("aaa"),
String::from("ddd"), String::from("bbb"),
String::from("eee"), String::from("ccc"),
String::from("q"), String::from("ddd"),
String::from("aaa"), String::from("eee"),
String::from("bbb"), String::from("q"),
String::from("ccc"), String::from("aaa"),
String::from("ddd"), String::from("bbb"),
String::from("eee"), String::from("ccc"),
]; String::from("ddd"),
command_list String::from("eee"),
.into_iter() ];
.filter(|command| command.contains(_input)) command_list
.collect() .into_iter()
}, // completion .filter(|command| command.contains(_input))
|editor: &mut Editor, input: &str| match input { .collect()
"q" => editor.should_close = true, }, // completion
_ => (), |editor: &mut Editor, input: &str| match input {
}, "q" => editor.should_close = true,
); _ => (),
},
self.prompt = Some(prompt); );
// HAXX: special casing for command mode self.prompt = Some(prompt);
} else if let Some(command) = keymap[&Mode::Normal].get(&keys) {
command(view, 1); // HAXX: special casing for command mode
} else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
// TODO: simplistic ensure cursor in view for now command(view, 1);
view.ensure_cursor_in_view();
} // TODO: simplistic ensure cursor in view for now
view.ensure_cursor_in_view();
} }
mode => { }
if let Some(command) = keymap[&mode].get(&keys) { mode => {
command(view, 1); if let Some(command) = self.keymap[&mode].get(&keys) {
command(view, 1);
// TODO: simplistic ensure cursor in view for now // TODO: simplistic ensure cursor in view for now
view.ensure_cursor_in_view(); view.ensure_cursor_in_view();
}
} }
} }
self.render();
} }
self.render();
} }
Some(Ok(Event::Mouse(_))) => (), // unhandled
Some(Err(x)) => panic!(x),
None => break,
} }
Some(Ok(Event::Mouse(_))) => (), // unhandled
Some(Err(x)) => panic!(x),
None => panic!(),
};
}
pub async fn handle_lsp_notification(&mut self, notification: Option<helix_lsp::Notification>) {
use helix_lsp::Notification;
match notification {
Some(Notification::PublishDiagnostics(params)) => unimplemented!("{:?}", params),
_ => unreachable!(),
} }
} }

@ -26,15 +26,15 @@ fn main() -> Result<(), Error> {
std::thread::spawn(move || smol::block_on(EX.run(smol::future::pending::<()>()))); std::thread::spawn(move || smol::block_on(EX.run(smol::future::pending::<()>())));
} }
let mut lsp = helix_lsp::Client::start(&EX, "rust-analyzer", &[]); // let mut lsp = helix_lsp::Client::start(&EX, "rust-analyzer", &[]);
smol::block_on(async { smol::block_on(async {
let res = lsp.initialize().await; // let res = lsp.initialize().await;
let state = helix_core::State::load("test.rs".into(), &[]).unwrap(); // let state = helix_core::State::load("test.rs".into(), &[]).unwrap();
let res = lsp.text_document_did_open(&state).await; // let res = lsp.text_document_did_open(&state).await;
loop {} // loop {}
// Application::new(args).unwrap().run().await; Application::new(args, &EX).unwrap().run().await;
}); });
Ok(()) Ok(())

@ -87,8 +87,8 @@ use std::collections::HashMap;
pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers}; pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
// TODO: could be trie based // TODO: could be trie based
type Keymap = HashMap<Vec<Key>, Command>; pub type Keymap = HashMap<Vec<Key>, Command>;
type Keymaps = HashMap<state::Mode, Keymap>; pub type Keymaps = HashMap<state::Mode, Keymap>;
macro_rules! key { macro_rules! key {
($ch:expr) => { ($ch:expr) => {

@ -18,7 +18,7 @@ pub struct View {
pub first_line: usize, pub first_line: usize,
pub size: (u16, u16), pub size: (u16, u16),
// TODO: Doc<> fields // TODO: Doc fields
pub history: History, pub history: History,
} }

Loading…
Cancel
Save