on master help sign in
Reroute LSP notification events into the main app event loop.
commitf9bfba4d96f80eb41beb91702558f6f165a0e70f authorBlaž Hrastnik <blaz@mxxn.io> dateTue 20 Oct 2020, 13:58 (6 years ago) parent64b5b23
11 files changed+207−155
Cargo.lock+1 helix-core/src/diagnostic.rs+1 helix-core/src/lib.rs+2 helix-core/src/state.rs+7−3 helix-lsp/src/lib.rs+16−17 helix-lsp/src/transport.rs+7−7 helix-term/Cargo.toml+2 helix-term/src/application.rs+162−119 helix-term/src/main.rs+6−6 helix-view/src/keymap.rs+2−2 helix-view/src/view.rs+1−1
Cargo.lock+1
@@ -472,6 +472,7 @@ dependencies = [
472472 "anyhow",
473473 "clap",
474474 "crossterm",
475+ "futures-util",
475476 "helix-core",
476477 "helix-lsp",
477478 "helix-view",
helix-core/src/diagnostic.rsnew file+1
@@ -0,0 +1 @@
1+pub struct Diagnostic {}
helix-core/src/lib.rs+2
@@ -1,4 +1,5 @@
11#![allow(unused)]
2+mod diagnostic;
23pub mod graphemes;
34mod history;
45pub mod indent;
@@ -22,6 +23,7 @@ pub use selection::Range;
2223pub use selection::Selection;
2324pub use syntax::Syntax;
2425
26+pub use diagnostic::Diagnostic;
2527pub use history::History;
2628pub use state::State;
2729
helix-core/src/state.rs+7−3
@@ -1,6 +1,6 @@
11use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
22use crate::syntax::LOADER;
3−use crate::{ChangeSet, Position, Range, Rope, RopeSlice, Selection, Syntax};
3+use crate::{ChangeSet, Diagnostic, Position, Range, Rope, RopeSlice, Selection, Syntax};
44use anyhow::Error;
55
66use std::path::PathBuf;
@@ -28,6 +28,8 @@ pub struct State {
2828 /// Pending changes since last history commit.
2929 pub changes: ChangeSet,
3030 pub old_state: Option<(Rope, Selection)>,
31+
32+ pub diagnostics: Vec<Diagnostic>,
3133}
3234
3335#[derive(Copy, Clone, PartialEq, Eq)]
@@ -58,12 +60,13 @@ impl State {
5860 syntax: None,
5961 changes,
6062 old_state,
63+ diagnostics: Vec::new(),
6164 }
6265 }
6366
6467 // TODO: passing scopes here is awkward
6568 pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
66− use std::{env, fs::File, io::BufReader, path::PathBuf};
69+ use std::{env, fs::File, io::BufReader};
6770 let _current_dir = env::current_dir()?;
6871
6972 let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
@@ -81,7 +84,8 @@ impl State {
8184 state.syntax = Some(syntax);
8285 };
8386
84− state.path = Some(path);
87+ // canonicalize path to absolute value
88+ state.path = Some(std::fs::canonicalize(path)?);
8589
8690 Ok(state)
8791 }
helix-lsp/src/lib.rs+16−17
@@ -2,7 +2,7 @@ mod transport;
22
33use transport::{Payload, Transport};
44
5−use std::collections::HashMap;
5+// use std::collections::HashMap;
66
77use jsonrpc_core as jsonrpc;
88use lsp_types as lsp;
@@ -32,10 +32,12 @@ enum Message {
3232}
3333
3434#[derive(Debug, PartialEq, Clone)]
35−enum Notification {}
35+pub enum Notification {
36+ PublishDiagnostics(lsp::PublishDiagnosticsParams),
37+}
3638
3739impl Notification {
38− pub fn parse(method: &str, params: jsonrpc::Params) {
40+ pub fn parse(method: &str, params: jsonrpc::Params) -> Notification {
3941 use lsp::notification::Notification as _;
4042
4143 match method {
@@ -44,11 +46,10 @@ impl Notification {
4446 .parse()
4547 .expect("Failed to parse PublishDiagnostics params");
4648
47− println!("{:?}", params);
48−
4949 // TODO: need to loop over diagnostics and distinguish them by URI
50+ Notification::PublishDiagnostics(params)
5051 }
51− _ => println!("unhandled notification: {}", method),
52+ _ => unimplemented!("unhandled notification: {}", method),
5253 }
5354 }
5455}
@@ -58,13 +59,13 @@ pub struct Client {
5859 stderr: BufReader<ChildStderr>,
5960
6061 outgoing: Sender<Payload>,
61− incoming: Receiver<Message>,
62+ pub incoming: Receiver<Notification>,
6263
6364 pub request_counter: u64,
6465
6566 capabilities: Option<lsp::ServerCapabilities>,
6667 // TODO: handle PublishDiagnostics Version
67− diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
68+ // diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
6869}
6970
7071impl Client {
@@ -95,7 +96,7 @@ impl Client {
9596 request_counter: 0,
9697
9798 capabilities: None,
98− diagnostics: HashMap::new(),
99+ // diagnostics: HashMap::new(),
99100 }
100101 }
101102
@@ -226,10 +227,7 @@ impl Client {
226227 ) -> anyhow::Result<()> {
227228 self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
228229 text_document: lsp::TextDocumentItem {
229− uri: lsp::Url::from_file_path(
230− std::fs::canonicalize(state.path.as_ref().unwrap()).unwrap(),
231− )
232− .unwrap(),
230+ uri: lsp::Url::from_file_path(state.path().unwrap()).unwrap(),
233231 language_id: "rust".to_string(), // TODO: hardcoded for now
234232 version: 0,
235233 text: String::from(&state.doc),
@@ -243,11 +241,12 @@ impl Client {
243241 &mut self,
244242 state: &helix_core::State,
245243 ) -> anyhow::Result<()> {
246− self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
247− text_document: lsp::TextDocumentIdentifier::new(
248− lsp::Url::from_file_path(state.path.as_ref().unwrap()).unwrap(),
244+ self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
245+ text_document: lsp::VersionedTextDocumentIdentifier::new(
246+ lsp::Url::from_file_path(state.path().unwrap()).unwrap(),
247+ 0, // TODO: version
249248 ),
250− text: None, // TODO?
249+ content_changes: vec![], // TODO:
251250 })
252251 .await
253252 }
helix-lsp/src/transport.rs+7−7
@@ -24,7 +24,7 @@ pub(crate) enum Payload {
2424}
2525
2626pub(crate) struct Transport {
27− incoming: Sender<Message>,
27+ incoming: Sender<Notification>, // TODO Notification | Call
2828 outgoing: Receiver<Payload>,
2929
3030 pending_requests: HashMap<jsonrpc::Id, Sender<anyhow::Result<Value>>>,
@@ -39,7 +39,7 @@ impl Transport {
3939 ex: &Executor,
4040 reader: BufReader<ChildStdout>,
4141 writer: BufWriter<ChildStdin>,
42− ) -> (Receiver<Message>, Sender<Payload>) {
42+ ) -> (Receiver<Notification>, Sender<Payload>) {
4343 let (incoming, rx) = smol::channel::unbounded();
4444 let (tx, outgoing) = smol::channel::unbounded();
4545
@@ -111,7 +111,7 @@ impl Transport {
111111 }
112112
113113 pub async fn send(&mut self, request: String) -> anyhow::Result<()> {
114− println!("-> {}", request);
114+ // println!("-> {}", request);
115115
116116 // send the headers
117117 self.writer
@@ -132,11 +132,11 @@ impl Transport {
132132 Message::Notification(jsonrpc::Notification { method, params, .. }) => {
133133 let notification = Notification::parse(&method, params);
134134
135− println!("<- {} {:?}", method, notification);
136− // dispatch
135+ // println!("<- {} {:?}", method, notification);
136+ self.incoming.send(notification).await?;
137137 }
138138 Message::Call(call) => {
139− println!("<- {:?}", call);
139+ // println!("<- {:?}", call);
140140 // dispatch
141141 }
142142 _ => unreachable!(),
@@ -147,7 +147,7 @@ impl Transport {
147147 pub async fn recv_response(&mut self, output: jsonrpc::Output) -> anyhow::Result<()> {
148148 match output {
149149 jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
150− println!("<- {}", result);
150+ // println!("<- {}", result);
151151
152152 let tx = self
153153 .pending_requests
helix-term/Cargo.toml+2
@@ -24,3 +24,5 @@ num_cpus = "1.13"
2424tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] }
2525crossterm = { version = "0.18", features = ["event-stream"] }
2626clap = { version = "3.0.0-beta.2 ", default-features = false, features = ["std", "cargo"] }
27+
28+futures-util = "0.3"
helix-term/src/application.rs+162−119
@@ -1,6 +1,11 @@
11use clap::ArgMatches as Args;
22use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State};
3−use helix_view::{commands, keymap, prompt::Prompt, Editor, Theme, View};
3+use helix_view::{
4+ commands,
5+ keymap::{self, Keymaps},
6+ prompt::Prompt,
7+ Editor, Theme, View,
8+};
49
510use std::{
611 borrow::Cow,
@@ -31,14 +36,16 @@ const OFFSET: u16 = 6; // 5 linenr + 1 gutter
3136
3237type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
3338
34−static EX: smol::Executor = smol::Executor::new();
35−
3639const BASE_WIDTH: u16 = 30;
3740
38−pub struct Application {
41+pub struct Application<'a> {
3942 editor: Editor,
4043 prompt: Option<Prompt>,
4144 terminal: Renderer,
45+
46+ keymap: Keymaps,
47+ executor: &'a smol::Executor<'a>,
48+ lsp: helix_lsp::Client,
4249}
4350
4451struct Renderer {
@@ -235,7 +242,7 @@ impl Renderer {
235242 .set_string(1, self.size.1 - 2, mode, self.text_color);
236243 }
237244
238− pub fn render_prompt(&mut self, view: &View, prompt: &Prompt) {
245+ pub fn render_prompt(&mut self, view: &View, prompt: &Prompt, theme: &Theme) {
239246 // completion
240247 if !prompt.completion.is_empty() {
241248 // TODO: find out better way of clearing individual lines of the screen
@@ -254,7 +261,7 @@ impl Renderer {
254261 }
255262 self.surface.set_style(
256263 Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height),
257− view.theme.get("ui.statusline"),
264+ theme.get("ui.statusline"),
258265 );
259266 for (i, command) in prompt.completion.iter().enumerate() {
260267 let color = if prompt.completion_selection_index.is_some()
@@ -330,8 +337,8 @@ impl Renderer {
330337 }
331338}
332339
333−impl Application {
334− pub fn new(mut args: Args) -> Result<Self, Error> {
340+impl<'a> Application<'a> {
341+ pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
335342 let terminal = Renderer::new()?;
336343 let mut editor = Editor::new();
337344
@@ -339,11 +346,18 @@ impl Application {
339346 editor.open(file, terminal.size)?;
340347 }
341348
349+ let lsp = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
350+
342351 let mut app = Self {
343352 editor,
344353 terminal,
345354 // TODO; move to state
346355 prompt: None,
356+
357+ //
358+ keymap: keymap::default(),
359+ executor,
360+ lsp,
347361 };
348362
349363 Ok(app)
@@ -361,7 +375,7 @@ impl Application {
361375 if prompt.should_close {
362376 self.prompt = None;
363377 } else {
364− self.terminal.render_prompt(view, prompt);
378+ self.terminal.render_prompt(view, prompt, theme_ref);
365379 }
366380 }
367381 }
@@ -375,7 +389,13 @@ impl Application {
375389
376390 pub async fn event_loop(&mut self) {
377391 let mut reader = EventStream::new();
378− let keymap = keymap::default();
392+
393+ // initialize lsp
394+ let res = self.lsp.initialize().await;
395+ let res = self
396+ .lsp
397+ .text_document_did_open(&self.editor.view().unwrap().state)
398+ .await;
379399
380400 self.render();
381401
@@ -384,126 +404,149 @@ impl Application {
384404 break;
385405 }
386406
387− // Handle key events
388− match reader.next().await {
389− Some(Ok(Event::Resize(width, height))) => {
390− self.terminal.resize(width, height);
407+ use futures_util::{select, FutureExt};
408+ select! {
409+ event = reader.next().fuse() => {
410+ self.handle_terminal_events(event).await
411+ }
412+ notification = self.lsp.incoming.next().fuse() => {
413+ self.handle_lsp_notification(notification).await
414+ }
415+ }
416+ }
417+ }
391418
392− // TODO: simplistic ensure cursor in view for now
393− // TODO: loop over views
394− if let Some(view) = self.editor.view_mut() {
395− view.size = self.terminal.size;
396− view.ensure_cursor_in_view()
397− };
419+ pub async fn handle_terminal_events(
420+ &mut self,
421+ event: Option<Result<Event, crossterm::ErrorKind>>,
422+ ) {
423+ // Handle key events
424+ match event {
425+ Some(Ok(Event::Resize(width, height))) => {
426+ self.terminal.resize(width, height);
427+
428+ // TODO: simplistic ensure cursor in view for now
429+ // TODO: loop over views
430+ if let Some(view) = self.editor.view_mut() {
431+ view.size = self.terminal.size;
432+ view.ensure_cursor_in_view()
433+ };
434+
435+ self.render();
436+ }
437+ Some(Ok(Event::Key(event))) => {
438+ // if there's a prompt, it takes priority
439+ if let Some(prompt) = &mut self.prompt {
440+ self.prompt
441+ .as_mut()
442+ .unwrap()
443+ .handle_input(event, &mut self.editor);
398444
399445 self.render();
400− }
401− Some(Ok(Event::Key(event))) => {
402− // if there's a prompt, it takes priority
403− if let Some(prompt) = &mut self.prompt {
404− self.prompt
405− .as_mut()
406− .unwrap()
407− .handle_input(event, &mut self.editor);
408−
409− self.render();
410− } else if let Some(view) = self.editor.view_mut() {
411− let keys = vec![event];
412− // TODO: sequences (`gg`)
413− // TODO: handle count other than 1
414− match view.state.mode() {
415− Mode::Insert => {
416− if let Some(command) = keymap[&Mode::Insert].get(&keys) {
417− command(view, 1);
418− } else if let KeyEvent {
419− code: KeyCode::Char(c),
420− ..
421− } = event
422− {
423− commands::insert::insert_char(view, c);
424− }
425− view.ensure_cursor_in_view();
446+ } else if let Some(view) = self.editor.view_mut() {
447+ let keys = vec![event];
448+ // TODO: sequences (`gg`)
449+ // TODO: handle count other than 1
450+ match view.state.mode() {
451+ Mode::Insert => {
452+ if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
453+ command(view, 1);
454+ } else if let KeyEvent {
455+ code: KeyCode::Char(c),
456+ ..
457+ } = event
458+ {
459+ commands::insert::insert_char(view, c);
426460 }
427− Mode::Normal => {
428− if let &[KeyEvent {
429− code: KeyCode::Char(':'),
430− ..
431− }] = keys.as_slice()
432− {
433− let prompt = Prompt::new(
434− ":".to_owned(),
435− |_input: &str| {
436− // TODO: i need this duplicate list right now to avoid borrow checker issues
437− let command_list = vec![
438− String::from("q"),
439− String::from("aaa"),
440− String::from("bbb"),
441− String::from("ccc"),
442− String::from("ddd"),
443− String::from("eee"),
444− String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"),
445− String::from("q"),
446− String::from("aaa"),
447− String::from("bbb"),
448− String::from("ccc"),
449− String::from("ddd"),
450− String::from("eee"),
451− String::from("q"),
452− String::from("aaa"),
453− String::from("bbb"),
454− String::from("ccc"),
455− String::from("ddd"),
456− String::from("eee"),
457− String::from("q"),
458− String::from("aaa"),
459− String::from("bbb"),
460− String::from("ccc"),
461− String::from("ddd"),
462− String::from("eee"),
463− String::from("q"),
464− String::from("aaa"),
465− String::from("bbb"),
466− String::from("ccc"),
467− String::from("ddd"),
468− String::from("eee"),
469− ];
470− command_list
471− .into_iter()
472− .filter(|command| command.contains(_input))
473− .collect()
474− }, // completion
475− |editor: &mut Editor, input: &str| match input {
476− "q" => editor.should_close = true,
477− _ => (),
478− },
479− );
480−
481− self.prompt = Some(prompt);
482−
483− // HAXX: special casing for command mode
484− } else if let Some(command) = keymap[&Mode::Normal].get(&keys) {
485− command(view, 1);
486−
487− // TODO: simplistic ensure cursor in view for now
488− view.ensure_cursor_in_view();
489− }
461+ view.ensure_cursor_in_view();
462+ }
463+ Mode::Normal => {
464+ if let &[KeyEvent {
465+ code: KeyCode::Char(':'),
466+ ..
467+ }] = keys.as_slice()
468+ {
469+ let prompt = Prompt::new(
470+ ":".to_owned(),
471+ |_input: &str| {
472+ // TODO: i need this duplicate list right now to avoid borrow checker issues
473+ let command_list = vec![
474+ String::from("q"),
475+ String::from("aaa"),
476+ String::from("bbb"),
477+ String::from("ccc"),
478+ String::from("ddd"),
479+ String::from("eee"),
480+ String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"),
481+ String::from("q"),
482+ String::from("aaa"),
483+ String::from("bbb"),
484+ String::from("ccc"),
485+ String::from("ddd"),
486+ String::from("eee"),
487+ String::from("q"),
488+ String::from("aaa"),
489+ String::from("bbb"),
490+ String::from("ccc"),
491+ String::from("ddd"),
492+ String::from("eee"),
493+ String::from("q"),
494+ String::from("aaa"),
495+ String::from("bbb"),
496+ String::from("ccc"),
497+ String::from("ddd"),
498+ String::from("eee"),
499+ String::from("q"),
500+ String::from("aaa"),
501+ String::from("bbb"),
502+ String::from("ccc"),
503+ String::from("ddd"),
504+ String::from("eee"),
505+ ];
506+ command_list
507+ .into_iter()
508+ .filter(|command| command.contains(_input))
509+ .collect()
510+ }, // completion
511+ |editor: &mut Editor, input: &str| match input {
512+ "q" => editor.should_close = true,
513+ _ => (),
514+ },
515+ );
516+
517+ self.prompt = Some(prompt);
518+
519+ // HAXX: special casing for command mode
520+ } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
521+ command(view, 1);
522+
523+ // TODO: simplistic ensure cursor in view for now
524+ view.ensure_cursor_in_view();
490525 }
491− mode => {
492− if let Some(command) = keymap[&mode].get(&keys) {
493− command(view, 1);
526+ }
527+ mode => {
528+ if let Some(command) = self.keymap[&mode].get(&keys) {
529+ command(view, 1);
494530
495− // TODO: simplistic ensure cursor in view for now
496− view.ensure_cursor_in_view();
497− }
531+ // TODO: simplistic ensure cursor in view for now
532+ view.ensure_cursor_in_view();
498533 }
499534 }
500− self.render();
501535 }
536+ self.render();
502537 }
503− Some(Ok(Event::Mouse(_))) => (), // unhandled
504− Some(Err(x)) => panic!(x),
505− None => break,
506538 }
539+ Some(Ok(Event::Mouse(_))) => (), // unhandled
540+ Some(Err(x)) => panic!(x),
541+ None => panic!(),
542+ };
543+ }
544+
545+ pub async fn handle_lsp_notification(&mut self, notification: Option<helix_lsp::Notification>) {
546+ use helix_lsp::Notification;
547+ match notification {
548+ Some(Notification::PublishDiagnostics(params)) => unimplemented!("{:?}", params),
549+ _ => unreachable!(),
507550 }
508551 }
509552
helix-term/src/main.rs+6−6
@@ -26,15 +26,15 @@ fn main() -> Result<(), Error> {
2626 std::thread::spawn(move || smol::block_on(EX.run(smol::future::pending::<()>())));
2727 }
2828
29− let mut lsp = helix_lsp::Client::start(&EX, "rust-analyzer", &[]);
29+ // let mut lsp = helix_lsp::Client::start(&EX, "rust-analyzer", &[]);
3030
3131 smol::block_on(async {
32− let res = lsp.initialize().await;
33− let state = helix_core::State::load("test.rs".into(), &[]).unwrap();
34− let res = lsp.text_document_did_open(&state).await;
35− loop {}
32+ // let res = lsp.initialize().await;
33+ // let state = helix_core::State::load("test.rs".into(), &[]).unwrap();
34+ // let res = lsp.text_document_did_open(&state).await;
35+ // loop {}
3636
37− // Application::new(args).unwrap().run().await;
37+ Application::new(args, &EX).unwrap().run().await;
3838 });
3939
4040 Ok(())
helix-view/src/keymap.rs+2−2
@@ -87,8 +87,8 @@ use std::collections::HashMap;
8787pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
8888
8989// TODO: could be trie based
90−type Keymap = HashMap<Vec<Key>, Command>;
91−type Keymaps = HashMap<state::Mode, Keymap>;
90+pub type Keymap = HashMap<Vec<Key>, Command>;
91+pub type Keymaps = HashMap<state::Mode, Keymap>;
9292
9393macro_rules! key {
9494 ($ch:expr) => {
helix-view/src/view.rs+1−1
@@ -18,7 +18,7 @@ pub struct View {
1818 pub first_line: usize,
1919 pub size: (u16, u16),
2020
21− // TODO: Doc<> fields
21+ // TODO: Doc fields
2222 pub history: History,
2323}
2424