From 1b162f596000bd774f22bb1e9390bd1c7d0f80db Mon Sep 17 00:00:00 2001 From: Redhawk18 Date: Sat, 25 May 2024 16:38:23 -0400 Subject: [PATCH] LSP init --- Cargo.toml | 16 +++-- README.md | 16 ++--- ROADMAP.md | 13 ++-- gui/Cargo.toml | 2 +- gui/src/lib.rs | 12 ++-- lsp/Cargo.toml | 16 +++++ lsp/src/client.rs | 156 ++++++++++++++++++++++++++++++++++++++++ lsp/src/lib.rs | 2 + lsp/src/subscription.rs | 7 ++ src/main.rs | 2 +- 10 files changed, 216 insertions(+), 26 deletions(-) create mode 100644 lsp/Cargo.toml create mode 100644 lsp/src/client.rs create mode 100644 lsp/src/lib.rs create mode 100644 lsp/src/subscription.rs diff --git a/Cargo.toml b/Cargo.toml index 68465b3..2b53c56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "blaze" +name = "kuiper" description = "A blazing fast Integrated Development Environment, meant to give power back to developers." version.workspace = true edition.workspace = true @@ -9,25 +9,30 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -blaze_gui.workspace = true +kuiper_gui.workspace = true +kuiper_lsp.workspace = true pretty_env_logger.workspace = true [workspace] -members = [ "gui" ] +members = [ "gui" , "lsp"] [workspace.package] version = "0.1.0" edition = "2021" authors = ["Redhawk18"] -license = "MIT" +license = "MPL-2.0" [workspace.dependencies] -blaze_gui = { path = "gui" } +kuiper_gui = { path = "gui" } +kuiper_lsp = { path = "lsp" } +async-lsp = { version = "0.2", features = ["tokio"] } dark-light = "1.0" iced = { version = "0.12", features = ["advanced", "tokio"] } iced_aw = { version = "0.8", features = ["icons", "menu","tab_bar"], default-features = false } +jsonrpc = "0.17.0" log = "0.4" +lsp-types = "*" pattern_code = "0.1" pretty_env_logger = "0.5" rfd = { version = "0.13", features = ["xdg-portal", "tokio"], default-features = false} @@ -35,5 +40,6 @@ slotmap = "1.0" snafu = "0.8" tokio = { version = "1", features = ["fs"] } + [patch.crates-io] iced_aw = { git = 'https://github.com/iced-rs/iced_aw.git', rev = '99c12f1' } diff --git a/README.md b/README.md index 5c11633..a880029 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@
-# Blaze +# Kuiper -[![Builds](https://img.shields.io/github/actions/workflow/status/Redhawk18/code-editor/build.yml)](https://github.com/Redhawk18/code-editor/actions/workflows/build.yml) -[![License](https://img.shields.io/github/license/Redhawk18/code-editor)](https://github.com/Redhawk18/code-editor/blob/main/LICENSE) +[![Builds](https://img.shields.io/github/actions/workflow/status/Redhawk18/kuiper/build.yml)](https://github.com/Redhawk18/kuiper/actions/workflows/build.yml) +[![License](https://img.shields.io/github/license/Redhawk18/kuiper)](https://github.com/Redhawk18/kuiper/blob/main/LICENSE) A blazing fast [Integrated Development Environment](https://en.wikipedia.org/wiki/Integrated_development_environment), meant to give power back to developers. @@ -21,13 +21,13 @@ Currently to install 1. Clone the repository ``` -git clone git@github.com:Redhawk18/blaze.git +git clone git@github.com:Redhawk18/kuiper.git ``` 2. Compile and install the program ``` -cargo install --path blaze +cargo install --path kuiper ``` 3. Add given path to your `$PATH` @@ -40,20 +40,20 @@ If youre operating system needs additional packages please create a pull request OpenSuse ``` - sudo zypper install atkmm-devel gdk-pixbuf-devel gdk-pixbuf-xlib-devel glib2-devel gtk3-devel harfbuzz-devel pkg-config rustup + sudo zypper install atkmm-devel pkg-config rustup ``` 1. Clone the repository ``` -git clone git@github.com:Redhawk18/blaze.git +git clone git@github.com:Redhawk18/kuiper.git ``` 2. Go into the repository ``` -cd blaze +cd kuiper ``` 3. Compiling diff --git a/ROADMAP.md b/ROADMAP.md index f49b3fe..c048e7e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,18 +2,21 @@ To add some order to this project these objectives need to be completed. -[x] basic gui features (open file, save, quit) +[ x ] basic gui features (open file, save, quit) -[x] gui theme +[ x ] gui theme -[x] pane grid +[ x ] pane grid -[x] file type recognition +[ x ] file type recognition -[x] code icons +[ x ] code icons ### Explore: system shell in gui A shell widget within iced seems the most simple out of the first few widgets and it is very helpful to users. Revisiting this idea, `iced_term` is what should be used. ### Program: custom text editor widget We require highlighting and tooltips when the LSP gives us warnings or errors. Also look into inline hints with in the text editor. + +### Program: A LSP client +My personal understanding is next to none current, so this will be a giant undertaking. diff --git a/gui/Cargo.toml b/gui/Cargo.toml index 943b16d..af87e8e 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "blaze_gui" +name = "kuiper_gui" version.workspace = true edition.workspace = true authors.workspace = true diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 1ecb9ca..4267877 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -19,10 +19,10 @@ use slotmap::{DefaultKey, SlotMap}; use std::path::PathBuf; pub fn start_gui() -> iced::Result { - Blaze::run(Settings::default()) + Kuiper::run(Settings::default()) } -pub(crate) struct Blaze { +pub(crate) struct Kuiper { data: SlotMap, panes: Panes, } @@ -73,7 +73,7 @@ pub(crate) enum Message { TextEditorUpdate(Action), } -impl Blaze { +impl Kuiper { pub(crate) fn get_panestate(&self) -> &PaneState { self.panes.data.get(self.panes.active).unwrap() } @@ -121,7 +121,7 @@ impl PaneState { } } -impl Application for Blaze { +impl Application for Kuiper { type Executor = executor::Default; type Message = Message; type Theme = Theme; @@ -129,7 +129,7 @@ impl Application for Blaze { fn new(_flags: Self::Flags) -> (Self, Command) { ( - Blaze { + Kuiper { data: SlotMap::default(), panes: Panes::default(), }, @@ -141,7 +141,7 @@ impl Application for Blaze { } fn title(&self) -> String { - String::from("Blaze") + String::from("Kuiper") } fn update(&mut self, message: Message) -> Command { diff --git a/lsp/Cargo.toml b/lsp/Cargo.toml new file mode 100644 index 0000000..71dd809 --- /dev/null +++ b/lsp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "kuiper_lsp" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-lsp.workspace = true +iced.workspace = true +log.workspace = true +lsp-types.workspace = true +snafu.workspace = true +tokio.workspace = true diff --git a/lsp/src/client.rs b/lsp/src/client.rs new file mode 100644 index 0000000..833c26e --- /dev/null +++ b/lsp/src/client.rs @@ -0,0 +1,156 @@ +use async_lsp::{ + concurrency::ConcurrencyLayer, panic::CatchUnwindLayer, router::Router, tracing::TracingLayer, + LanguageClient, ResponseError, +}; +use log::{info, trace, Level}; +use lsp_types::{ + notification::{Progress, PublishDiagnostics, ShowMessage}, + ClientCapabilities, HoverContents, HoverParams, InitializeParams, MarkupContent, + NumberOrString, Position, ProgressParamsValue, TextDocumentIdentifier, + TextDocumentPositionParams, Url, WindowClientCapabilities, WorkDoneProgress, + WorkDoneProgressParams, +}; +use std::{ops::ControlFlow, path::Path, process::Stdio}; +use tokio::sync::oneshot; + +const TEST_ROOT: &str = "."; + +struct ClientState { + indexed_tx: Option>, +} + +struct Stop; + +impl LanguageClient for ClientState { + type Error = ResponseError; + type NotifyResult = ControlFlow>; +} + +pub async fn start_lsp() { + let root_dir = Path::new(TEST_ROOT) + .canonicalize() + .expect("test root should be valid"); + + let (indexed_tx, indexed_rx) = oneshot::channel(); + + let (mainloop, mut server) = async_lsp::MainLoop::new_client(|_server| { + let mut router = Router::new(ClientState { + indexed_tx: Some(indexed_tx), + }); + router + .notification::(|this, prog| { + trace!("{:#?} {:#?}", prog.token, prog.value); + if matches!(prog.token, NumberOrString::String(s) if s == "rustAnalyzer/Indexing") + && matches!( + prog.value, + ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)) + ) + { + // Sometimes rust-analyzer auto-index multiple times? + if let Some(tx) = this.indexed_tx.take() { + let _: Result<_, _> = tx.send(()); + } + } + ControlFlow::Continue(()) + }) + .notification::(|_, _| ControlFlow::Continue(())) + .notification::(|_, params| { + trace!("Message {:?}: {}", params.typ, params.message); + ControlFlow::Continue(()) + }) + .event(|_, _: Stop| ControlFlow::Break(Ok(()))); + + ServiceBuilder::new() + .layer(TracingLayer::default()) + .layer(CatchUnwindLayer::default()) + .layer(ConcurrencyLayer::default()) + .service(router) + }); + + tracing_subscriber::fmt() + .with_max_level(Level::Info) + .with_ansi(false) + .with_writer(std::io::stdout) + .init(); + + let child = async_process::Command::new("rust-analyzer") + .current_dir(&root_dir) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn() + .expect("Failed run rust-analyzer"); + let stdout = child.stdout.unwrap(); + let stdin = child.stdin.unwrap(); + + let mainloop_fut = tokio::spawn(async move { + mainloop.run_buffered(stdout, stdin).await.unwrap(); + }); + + // Initialize. + let root_uri = Url::from_file_path(&root_dir).unwrap(); + let init_ret = server + .initialize(InitializeParams { + root_uri: Some(root_uri), + capabilities: ClientCapabilities { + window: Some(WindowClientCapabilities { + work_done_progress: Some(true), + ..WindowClientCapabilities::default() + }), + ..ClientCapabilities::default() + }, + ..InitializeParams::default() + }) + .await + .unwrap(); + info!("Initialized: {init_ret:?}"); + server.initialized(InitializedParams {}).unwrap(); + + // Synchronize documents. + let file_uri = Url::from_file_path(root_dir.join("src/lib.rs")).unwrap(); + let text = "fn func() { let var = 1; }"; + server + .did_open(DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: file_uri.clone(), + language_id: "rust".into(), + version: 0, + text: text.into(), + }, + }) + .unwrap(); + + // Wait until indexed. + indexed_rx.await.unwrap(); + + // Query. + let var_pos = text.find("var").unwrap(); + let hover = server + .hover(HoverParams { + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: file_uri }, + position: Position::new(0, var_pos as _), + }, + work_done_progress_params: WorkDoneProgressParams::default(), + }) + .await + .unwrap() + .unwrap(); + info!("Hover result: {hover:?}"); + assert!( + matches!( + hover.contents, + HoverContents::Markup(MarkupContent { value, .. }) + if value.contains("let var: i32") + ), + "should show the type of `var`", + ); + + // Shutdown. + server.shutdown(()).await.unwrap(); + server.exit(()).unwrap(); + + server.emit(Stop).unwrap(); + mainloop_fut.await.unwrap(); +} diff --git a/lsp/src/lib.rs b/lsp/src/lib.rs new file mode 100644 index 0000000..844ac67 --- /dev/null +++ b/lsp/src/lib.rs @@ -0,0 +1,2 @@ +mod client; +mod subscription; diff --git a/lsp/src/subscription.rs b/lsp/src/subscription.rs new file mode 100644 index 0000000..d784eaf --- /dev/null +++ b/lsp/src/subscription.rs @@ -0,0 +1,7 @@ +use iced::{subscription, Subscription}; + +pub enum Event {} + +pub fn connect() -> Subscription { + todo!() +} diff --git a/src/main.rs b/src/main.rs index b88030f..c91fff8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use blaze_gui::start_gui; +use kuiper_gui::start_gui; fn main() { pretty_env_logger::init();