mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-25 02:46:17 +04:00
Reroute LSP notification events into the main app event loop.
This commit is contained in:
parent
64b5b23315
commit
f9bfba4d96
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -472,6 +472,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"futures-util",
|
||||
"helix-core",
|
||||
"helix-lsp",
|
||||
"helix-view",
|
||||
|
1
helix-core/src/diagnostic.rs
Normal file
1
helix-core/src/diagnostic.rs
Normal file
@ -0,0 +1 @@
|
||||
pub struct Diagnostic {}
|
@ -1,4 +1,5 @@
|
||||
#![allow(unused)]
|
||||
mod diagnostic;
|
||||
pub mod graphemes;
|
||||
mod history;
|
||||
pub mod indent;
|
||||
@ -22,6 +23,7 @@
|
||||
pub use selection::Selection;
|
||||
pub use syntax::Syntax;
|
||||
|
||||
pub use diagnostic::Diagnostic;
|
||||
pub use history::History;
|
||||
pub use state::State;
|
||||
|
||||
|
@ -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<Diagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
@ -58,12 +60,13 @@ pub fn new(doc: Rope) -> Self {
|
||||
syntax: None,
|
||||
changes,
|
||||
old_state,
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: passing scopes here is awkward
|
||||
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 doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
|
||||
@ -81,7 +84,8 @@ pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
|
||||
state.syntax = Some(syntax);
|
||||
};
|
||||
|
||||
state.path = Some(path);
|
||||
// canonicalize path to absolute value
|
||||
state.path = Some(std::fs::canonicalize(path)?);
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
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 @@ pub fn parse(method: &str, params: jsonrpc::Params) {
|
||||
.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<ChildStderr>,
|
||||
|
||||
outgoing: Sender<Payload>,
|
||||
incoming: Receiver<Message>,
|
||||
pub incoming: Receiver<Notification>,
|
||||
|
||||
pub request_counter: u64,
|
||||
|
||||
capabilities: Option<lsp::ServerCapabilities>,
|
||||
// TODO: handle PublishDiagnostics Version
|
||||
diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
|
||||
// diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@ -95,7 +96,7 @@ pub fn start(ex: &Executor, cmd: &str, args: &[String]) -> Self {
|
||||
request_counter: 0,
|
||||
|
||||
capabilities: None,
|
||||
diagnostics: HashMap::new(),
|
||||
// diagnostics: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,10 +227,7 @@ pub async fn text_document_did_open(
|
||||
) -> anyhow::Result<()> {
|
||||
self.notify::<lsp::notification::DidOpenTextDocument>(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 @@ pub async fn text_document_did_change(
|
||||
&mut self,
|
||||
state: &helix_core::State,
|
||||
) -> anyhow::Result<()> {
|
||||
self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(
|
||||
lsp::Url::from_file_path(state.path.as_ref().unwrap()).unwrap(),
|
||||
self.notify::<lsp::notification::DidChangeTextDocument>(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
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ pub(crate) enum Payload {
|
||||
}
|
||||
|
||||
pub(crate) struct Transport {
|
||||
incoming: Sender<Message>,
|
||||
incoming: Sender<Notification>, // TODO Notification | Call
|
||||
outgoing: Receiver<Payload>,
|
||||
|
||||
pending_requests: HashMap<jsonrpc::Id, Sender<anyhow::Result<Value>>>,
|
||||
@ -39,7 +39,7 @@ pub fn start(
|
||||
ex: &Executor,
|
||||
reader: BufReader<ChildStdout>,
|
||||
writer: BufWriter<ChildStdin>,
|
||||
) -> (Receiver<Message>, Sender<Payload>) {
|
||||
) -> (Receiver<Notification>, Sender<Payload>) {
|
||||
let (incoming, rx) = smol::channel::unbounded();
|
||||
let (tx, outgoing) = smol::channel::unbounded();
|
||||
|
||||
@ -111,7 +111,7 @@ pub async fn send_payload(&mut self, payload: Payload) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, request: String) -> anyhow::Result<()> {
|
||||
println!("-> {}", request);
|
||||
// println!("-> {}", request);
|
||||
|
||||
// send the headers
|
||||
self.writer
|
||||
@ -132,11 +132,11 @@ pub async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> {
|
||||
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 @@ pub async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> {
|
||||
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
|
||||
|
@ -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"
|
||||
|
@ -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 @@
|
||||
|
||||
type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
|
||||
|
||||
static EX: smol::Executor = smol::Executor::new();
|
||||
|
||||
const BASE_WIDTH: u16 = 30;
|
||||
|
||||
pub struct Application {
|
||||
pub struct Application<'a> {
|
||||
editor: Editor,
|
||||
prompt: Option<Prompt>,
|
||||
terminal: Renderer,
|
||||
|
||||
keymap: Keymaps,
|
||||
executor: &'a smol::Executor<'a>,
|
||||
lsp: helix_lsp::Client,
|
||||
}
|
||||
|
||||
struct Renderer {
|
||||
@ -235,7 +242,7 @@ pub fn render_statusline(&mut self, view: &View, theme: &Theme) {
|
||||
.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 @@ pub fn render_prompt(&mut self, view: &View, prompt: &Prompt) {
|
||||
}
|
||||
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 @@ pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport:
|
||||
}
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub fn new(mut args: Args) -> Result<Self, Error> {
|
||||
impl<'a> Application<'a> {
|
||||
pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
|
||||
let terminal = Renderer::new()?;
|
||||
let mut editor = Editor::new();
|
||||
|
||||
@ -339,11 +346,18 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
|
||||
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 @@ fn render(&mut self) {
|
||||
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 @@ fn render(&mut self) {
|
||||
|
||||
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,8 +404,24 @@ pub async fn event_loop(&mut self) {
|
||||
break;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_terminal_events(
|
||||
&mut self,
|
||||
event: Option<Result<Event, crossterm::ErrorKind>>,
|
||||
) {
|
||||
// Handle key events
|
||||
match reader.next().await {
|
||||
match event {
|
||||
Some(Ok(Event::Resize(width, height))) => {
|
||||
self.terminal.resize(width, height);
|
||||
|
||||
@ -413,7 +449,7 @@ pub async fn event_loop(&mut self) {
|
||||
// TODO: handle count other than 1
|
||||
match view.state.mode() {
|
||||
Mode::Insert => {
|
||||
if let Some(command) = keymap[&Mode::Insert].get(&keys) {
|
||||
if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
|
||||
command(view, 1);
|
||||
} else if let KeyEvent {
|
||||
code: KeyCode::Char(c),
|
||||
@ -481,7 +517,7 @@ pub async fn event_loop(&mut self) {
|
||||
self.prompt = Some(prompt);
|
||||
|
||||
// HAXX: special casing for command mode
|
||||
} else if let Some(command) = keymap[&Mode::Normal].get(&keys) {
|
||||
} else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
|
||||
command(view, 1);
|
||||
|
||||
// TODO: simplistic ensure cursor in view for now
|
||||
@ -489,7 +525,7 @@ pub async fn event_loop(&mut self) {
|
||||
}
|
||||
}
|
||||
mode => {
|
||||
if let Some(command) = keymap[&mode].get(&keys) {
|
||||
if let Some(command) = self.keymap[&mode].get(&keys) {
|
||||
command(view, 1);
|
||||
|
||||
// TODO: simplistic ensure cursor in view for now
|
||||
@ -502,8 +538,15 @@ pub async fn event_loop(&mut self) {
|
||||
}
|
||||
Some(Ok(Event::Mouse(_))) => (), // unhandled
|
||||
Some(Err(x)) => panic!(x),
|
||||
None => break,
|
||||
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::<()>())));
|
||||
}
|
||||
|
||||
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(())
|
||||
|
@ -87,8 +87,8 @@
|
||||
pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
|
||||
|
||||
// TODO: could be trie based
|
||||
type Keymap = HashMap<Vec<Key>, Command>;
|
||||
type Keymaps = HashMap<state::Mode, Keymap>;
|
||||
pub type Keymap = HashMap<Vec<Key>, Command>;
|
||||
pub type Keymaps = HashMap<state::Mode, Keymap>;
|
||||
|
||||
macro_rules! key {
|
||||
($ch:expr) => {
|
||||
|
@ -18,7 +18,7 @@ pub struct View {
|
||||
pub first_line: usize,
|
||||
pub size: (u16, u16),
|
||||
|
||||
// TODO: Doc<> fields
|
||||
// TODO: Doc fields
|
||||
pub history: History,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user