diff --git a/Cargo.lock b/Cargo.lock index c4beee76a..2e329fd1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,6 +472,7 @@ dependencies = [ "anyhow", "clap", "crossterm", + "futures-util", "helix-core", "helix-lsp", "helix-view", diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs new file mode 100644 index 000000000..aee648aa4 --- /dev/null +++ b/helix-core/src/diagnostic.rs @@ -0,0 +1 @@ +pub struct Diagnostic {} diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 62d23a109..8458c36f7 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -1,4 +1,5 @@ #![allow(unused)] +mod diagnostic; pub mod graphemes; mod history; pub mod indent; @@ -22,6 +23,7 @@ pub use selection::Range; pub use selection::Selection; pub use syntax::Syntax; +pub use diagnostic::Diagnostic; pub use history::History; pub use state::State; diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 35e20aef5..0f94f696f 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -1,6 +1,6 @@ use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; 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 std::path::PathBuf; @@ -28,6 +28,8 @@ pub struct State { /// Pending changes since last history commit. pub changes: ChangeSet, pub old_state: Option<(Rope, Selection)>, + + pub diagnostics: Vec, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -58,12 +60,13 @@ impl State { syntax: None, changes, old_state, + diagnostics: Vec::new(), } } // TODO: passing scopes here is awkward pub fn load(path: PathBuf, scopes: &[String]) -> Result { - use std::{env, fs::File, io::BufReader, path::PathBuf}; + use std::{env, fs::File, io::BufReader}; let _current_dir = env::current_dir()?; let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?; @@ -81,7 +84,8 @@ impl State { state.syntax = Some(syntax); }; - state.path = Some(path); + // canonicalize path to absolute value + state.path = Some(std::fs::canonicalize(path)?); Ok(state) } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 41b3fdb2d..3598a5943 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -2,7 +2,7 @@ mod transport; use transport::{Payload, Transport}; -use std::collections::HashMap; +// use std::collections::HashMap; use jsonrpc_core as jsonrpc; use lsp_types as lsp; @@ -32,10 +32,12 @@ enum Message { } #[derive(Debug, PartialEq, Clone)] -enum Notification {} +pub enum Notification { + PublishDiagnostics(lsp::PublishDiagnosticsParams), +} impl Notification { - pub fn parse(method: &str, params: jsonrpc::Params) { + pub fn parse(method: &str, params: jsonrpc::Params) -> Notification { use lsp::notification::Notification as _; match method { @@ -44,11 +46,10 @@ impl Notification { .parse() .expect("Failed to parse PublishDiagnostics params"); - println!("{:?}", params); - // 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, outgoing: Sender, - incoming: Receiver, + pub incoming: Receiver, pub request_counter: u64, capabilities: Option, // TODO: handle PublishDiagnostics Version - diagnostics: HashMap>, + // diagnostics: HashMap>, } impl Client { @@ -95,7 +96,7 @@ impl Client { request_counter: 0, capabilities: None, - diagnostics: HashMap::new(), + // diagnostics: HashMap::new(), } } @@ -226,10 +227,7 @@ impl Client { ) -> anyhow::Result<()> { self.notify::(lsp::DidOpenTextDocumentParams { text_document: lsp::TextDocumentItem { - uri: lsp::Url::from_file_path( - std::fs::canonicalize(state.path.as_ref().unwrap()).unwrap(), - ) - .unwrap(), + uri: lsp::Url::from_file_path(state.path().unwrap()).unwrap(), language_id: "rust".to_string(), // TODO: hardcoded for now version: 0, text: String::from(&state.doc), @@ -243,11 +241,12 @@ impl Client { &mut self, state: &helix_core::State, ) -> anyhow::Result<()> { - self.notify::(lsp::DidSaveTextDocumentParams { - text_document: lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path(state.path.as_ref().unwrap()).unwrap(), + self.notify::(lsp::DidChangeTextDocumentParams { + text_document: lsp::VersionedTextDocumentIdentifier::new( + lsp::Url::from_file_path(state.path().unwrap()).unwrap(), + 0, // TODO: version ), - text: None, // TODO? + content_changes: vec![], // TODO: }) .await } diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs index c44ffa918..8915a9251 100644 --- a/helix-lsp/src/transport.rs +++ b/helix-lsp/src/transport.rs @@ -24,7 +24,7 @@ pub(crate) enum Payload { } pub(crate) struct Transport { - incoming: Sender, + incoming: Sender, // TODO Notification | Call outgoing: Receiver, pending_requests: HashMap>>, @@ -39,7 +39,7 @@ impl Transport { ex: &Executor, reader: BufReader, writer: BufWriter, - ) -> (Receiver, Sender) { + ) -> (Receiver, Sender) { let (incoming, rx) = 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<()> { - println!("-> {}", request); + // println!("-> {}", request); // send the headers self.writer @@ -132,11 +132,11 @@ impl Transport { Message::Notification(jsonrpc::Notification { method, params, .. }) => { let notification = Notification::parse(&method, params); - println!("<- {} {:?}", method, notification); - // dispatch + // println!("<- {} {:?}", method, notification); + self.incoming.send(notification).await?; } Message::Call(call) => { - println!("<- {:?}", call); + // println!("<- {:?}", call); // dispatch } _ => unreachable!(), @@ -147,7 +147,7 @@ impl Transport { pub async fn recv_response(&mut self, output: jsonrpc::Output) -> anyhow::Result<()> { match output { jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => { - println!("<- {}", result); + // println!("<- {}", result); let tx = self .pending_requests diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index aa91a095d..db1edee97 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -24,3 +24,5 @@ num_cpus = "1.13" tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] } crossterm = { version = "0.18", features = ["event-stream"] } clap = { version = "3.0.0-beta.2 ", default-features = false, features = ["std", "cargo"] } + +futures-util = "0.3" diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index d65e7e2e3..3d5b3459c 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,6 +1,11 @@ use clap::ArgMatches as Args; 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::{ borrow::Cow, @@ -31,14 +36,16 @@ const OFFSET: u16 = 6; // 5 linenr + 1 gutter type Terminal = tui::Terminal>; -static EX: smol::Executor = smol::Executor::new(); - const BASE_WIDTH: u16 = 30; -pub struct Application { +pub struct Application<'a> { editor: Editor, prompt: Option, terminal: Renderer, + + keymap: Keymaps, + executor: &'a smol::Executor<'a>, + lsp: helix_lsp::Client, } struct Renderer { @@ -235,7 +242,7 @@ impl Renderer { .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 if !prompt.completion.is_empty() { // TODO: find out better way of clearing individual lines of the screen @@ -254,7 +261,7 @@ impl Renderer { } self.surface.set_style( 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() { let color = if prompt.completion_selection_index.is_some() @@ -330,8 +337,8 @@ impl Renderer { } } -impl Application { - pub fn new(mut args: Args) -> Result { +impl<'a> Application<'a> { + pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result { let terminal = Renderer::new()?; let mut editor = Editor::new(); @@ -339,11 +346,18 @@ impl Application { editor.open(file, terminal.size)?; } + let lsp = helix_lsp::Client::start(&executor, "rust-analyzer", &[]); + let mut app = Self { editor, terminal, // TODO; move to state prompt: None, + + // + keymap: keymap::default(), + executor, + lsp, }; Ok(app) @@ -361,7 +375,7 @@ impl Application { if prompt.should_close { self.prompt = None; } 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) { 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(); @@ -384,126 +404,149 @@ impl Application { break; } - // Handle key events - match reader.next().await { - Some(Ok(Event::Resize(width, height))) => { - self.terminal.resize(width, height); + use futures_util::{select, FutureExt}; + select! { + event = reader.next().fuse() => { + 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 - // TODO: loop over views - if let Some(view) = self.editor.view_mut() { - view.size = self.terminal.size; - view.ensure_cursor_in_view() - }; + pub async fn handle_terminal_events( + &mut self, + event: Option>, + ) { + // 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(); - } - 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(); - } else if let Some(view) = self.editor.view_mut() { - let keys = vec![event]; - // TODO: sequences (`gg`) - // TODO: handle count other than 1 - 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(); + } else if let Some(view) = self.editor.view_mut() { + let keys = vec![event]; + // TODO: sequences (`gg`) + // TODO: handle count other than 1 + match view.state.mode() { + Mode::Insert => { + if let Some(command) = self.keymap[&Mode::Insert].get(&keys) { + command(view, 1); + } else if let KeyEvent { + code: KeyCode::Char(c), + .. + } = event + { + commands::insert::insert_char(view, c); } - Mode::Normal => { - if let &[KeyEvent { - code: KeyCode::Char(':'), - .. - }] = keys.as_slice() - { - let prompt = Prompt::new( - ":".to_owned(), - |_input: &str| { - // TODO: i need this duplicate list right now to avoid borrow checker issues - let command_list = vec![ - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - ]; - command_list - .into_iter() - .filter(|command| command.contains(_input)) - .collect() - }, // completion - |editor: &mut Editor, input: &str| match input { - "q" => editor.should_close = true, - _ => (), - }, - ); - - self.prompt = Some(prompt); - - // HAXX: special casing for command mode - } else if let Some(command) = keymap[&Mode::Normal].get(&keys) { - command(view, 1); - - // TODO: simplistic ensure cursor in view for now - view.ensure_cursor_in_view(); - } + view.ensure_cursor_in_view(); + } + Mode::Normal => { + if let &[KeyEvent { + code: KeyCode::Char(':'), + .. + }] = keys.as_slice() + { + let prompt = Prompt::new( + ":".to_owned(), + |_input: &str| { + // TODO: i need this duplicate list right now to avoid borrow checker issues + let command_list = vec![ + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + ]; + command_list + .into_iter() + .filter(|command| command.contains(_input)) + .collect() + }, // completion + |editor: &mut Editor, input: &str| match input { + "q" => editor.should_close = true, + _ => (), + }, + ); + + self.prompt = Some(prompt); + + // HAXX: special casing for command mode + } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) { + command(view, 1); + + // TODO: simplistic ensure cursor in view for now + view.ensure_cursor_in_view(); } - mode => { - if let Some(command) = keymap[&mode].get(&keys) { - command(view, 1); + } + mode => { + if let Some(command) = self.keymap[&mode].get(&keys) { + command(view, 1); - // TODO: simplistic ensure cursor in view for now - view.ensure_cursor_in_view(); - } + // TODO: simplistic ensure cursor in view for now + 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) { + use helix_lsp::Notification; + match notification { + Some(Notification::PublishDiagnostics(params)) => unimplemented!("{:?}", params), + _ => unreachable!(), } } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 07f1ffffa..de3a01755 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -26,15 +26,15 @@ fn main() -> Result<(), Error> { 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 { - let res = lsp.initialize().await; - let state = helix_core::State::load("test.rs".into(), &[]).unwrap(); - let res = lsp.text_document_did_open(&state).await; - loop {} + // let res = lsp.initialize().await; + // let state = helix_core::State::load("test.rs".into(), &[]).unwrap(); + // let res = lsp.text_document_did_open(&state).await; + // loop {} - // Application::new(args).unwrap().run().await; + Application::new(args, &EX).unwrap().run().await; }); Ok(()) diff --git a/helix-view/src/keymap.rs b/helix-view/src/keymap.rs index 69e6cabbf..82bdbe21f 100644 --- a/helix-view/src/keymap.rs +++ b/helix-view/src/keymap.rs @@ -87,8 +87,8 @@ use std::collections::HashMap; pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers}; // TODO: could be trie based -type Keymap = HashMap, Command>; -type Keymaps = HashMap; +pub type Keymap = HashMap, Command>; +pub type Keymaps = HashMap; macro_rules! key { ($ch:expr) => { diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index d2a7d5562..817714c8e 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -18,7 +18,7 @@ pub struct View { pub first_line: usize, pub size: (u16, u16), - // TODO: Doc<> fields + // TODO: Doc fields pub history: History, }