A simple CPU rendered GUI IDE experience.
add additional commands
| -rw-r--r-- | src/commands.rs | 155 | ||||
| -rw-r--r-- | src/edi.rs | 96 | ||||
| -rw-r--r-- | src/hov.rs | 2 | ||||
| -rw-r--r-- | src/lsp.rs | 53 | ||||
| -rw-r--r-- | src/rnd.rs | 15 | ||||
| -rw-r--r-- | src/text.rs | 24 |
6 files changed, 249 insertions, 96 deletions
diff --git a/src/commands.rs b/src/commands.rs index c21acb1..c85911b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,13 +1,21 @@ use std::iter::repeat; use std::path::Path; +use std::process::Stdio; +use std::sync::Arc; use Default::default; +use Into::into; +use anyhow::{anyhow, bail}; use dsb::Cell; use dsb::cell::Style; +use lsp_types::*; +use rust_analyzer::lsp::ext::*; use crate::FG; +use crate::edi::{Editor, lsp_m}; +use crate::lsp::PathURI; use crate::menu::{back, charc, filter, next, score}; -use crate::text::{TextArea, col, color_}; +use crate::text::{RopeExt, SortTedits, TextArea, col, color_}; macro_rules! commands { ($(#[doc = $d: literal] $t:tt $identifier: ident: $c:literal),+ $(,)?) => { @@ -44,7 +52,18 @@ commands!( @ RARestart: "ra-restart", /// go to parent module @ RAParent: "parent", - + /// join lines under cursors. + @ RAJoinLines: "join-lines", + /// gets list of runnables + @ RARunnables: "runnables", + /// Open docs for type at cursor + @ RADocs: "open-docs", + /// Rebuilds rust-analyzer proc macros + @ RARebuildProcMacros: "rebuild-proc-macros", + /// Cancels current running rust-analyzer check process + @ RACancelFlycheck: "cancel-flycheck", + /// Opens Cargo.toml file for this workspace + @ RAOpenCargoToml: "open-cargo-toml" ); #[derive(Debug, Default)] @@ -157,3 +176,135 @@ fn r( }); to.extend(b); } + +impl Editor { + pub fn handle_command( + &mut self, + z: Cmd, + w: Arc<winit::window::Window>, + ) -> anyhow::Result<()> { + if !z.needs_lsp() { + return Ok(()); + } + let Some((l, o)) = lsp_m!(self + p) else { + bail!("no lsp"); + }; + + match z { + Cmd::RAMoveIU | Cmd::RAMoveID => { + let r = self + .text + .to_l_position(*self.text.cursor.first()) + .unwrap(); + let mut x = l.request_immediate::<rust_analyzer::lsp::ext::MoveItem>(&MoveItemParams { + direction: if let Cmd::RAMoveIU = z { MoveItemDirection::Up } else { MoveItemDirection::Down }, + text_document: o.tid(), + range: Range { start : r, end : r}, + })?; + + x.sort_tedits(); + for t in x { + self.text.apply_snippet_tedit(&t)?; + } + } + Cmd::RARestart => { + _ = l.request::<ReloadWorkspace>(&())?.0; + } + Cmd::RAParent => { + let Some(GotoDefinitionResponse::Link([ref x])) = + l.request_immediate::<ParentModule>( + &TextDocumentPositionParams { + text_document: o.tid(), + position: self + .text + .to_l_position(*self.text.cursor.first()) + .unwrap(), + }, + )? + else { + self.bar.last_action = "no such parent".into(); + return Ok(()); + }; + self.open_loclink(x, w); + } + Cmd::RAJoinLines => { + let teds = + l.request_immediate::<JoinLines>(&JoinLinesParams { + ranges: self + .text + .cursor + .iter() + .filter_map(|x| { + self.text.to_l_range( + x.sel.map(into).unwrap_or(*x..*x), + ) + }) + .collect(), + text_document: o.tid(), + })?; + self.text + .apply_tedits(&mut { teds }) + .map_err(|_| anyhow!("couldnt apply edits"))?; + } + Cmd::RADocs => { + let u = l.request_immediate::<ExternalDocs>( + &TextDocumentPositionParams { + position: self + .text + .to_l_position(*self.text.cursor.first()) + .unwrap(), + text_document: o.tid(), + }, + )?; + use rust_analyzer::lsp::ext::ExternalDocsResponse::*; + std::process::Command::new("firefox-nightly") + .arg( + match &u { + WithLocal(ExternalDocsPair { + web: Some(x), + .. + }) if let Some("doc.rust-lang.org") = + x.domain() + && let Some(x) = + x.path().strip_prefix("/nightly/") + && option_env!("USER") == Some("os") => + format!("http://127.0.0.1:3242/{x}"), // i have a lighttpd server running + WithLocal(ExternalDocsPair { + local: Some(url), + .. + }) if let Ok(p) = url.to_file_path() + && p.exists() => + url.to_string(), + WithLocal(ExternalDocsPair { + web: Some(url), + .. + }) + | Simple(Some(url)) => url.to_string(), + _ => return Ok(()), + } + .to_string(), + ) + .stdout(Stdio::null()) + .spawn() + .unwrap(); + } + Cmd::RARebuildProcMacros => { + _ = l.request::<RebuildProcMacros>(&())?; + } + Cmd::RACancelFlycheck => l.notify::<CancelFlycheck>(&())?, + Cmd::RAOpenCargoToml => { + let Some(GotoDefinitionResponse::Scalar(x)) = + &l.request_immediate::<OpenCargoToml>( + &OpenCargoTomlParams { text_document: o.tid() }, + )? + else { + bail!("wtf?"); + }; + self.open_loc(x, w); + } + _ => unimplemented!(), + } + + Ok(()) + } +} @@ -15,9 +15,6 @@ use lsp_types::request::*; use lsp_types::*; use regex::Regex; use ropey::Rope; -use rust_analyzer::lsp::ext::{ - MoveItemDirection, MoveItemParams, ParentModule, ReloadWorkspace, -}; use rust_fsm::StateMachine; use serde_derive::{Deserialize, Serialize}; use tokio::sync::oneshot::Sender; @@ -31,7 +28,6 @@ pub mod st; use st::*; use crate::bar::Bar; -use crate::commands::Cmd; use crate::complete::Complete; use crate::hov::{self, Hovr}; use crate::lsp::{ @@ -725,6 +721,8 @@ impl Editor { vo: 0, cells: cells.into(), }, + range: x.range, + // range: x.range.and_then(|x| text.l_range(x)), } .into(), )) @@ -951,72 +949,11 @@ impl Editor { (), )); }, - Some(Do::ProcessCommand(x)) => 'out: { - _ = try bikeshed anyhow::Result<()> { - let z = x.sel(); - if z.needs_lsp() - && let Some((l, o)) = lsp!(self + p) - { - match z { - Cmd::RAMoveIU | Cmd::RAMoveID => { - let r = self - .text - .to_l_position( - *self.text.cursor.first(), - ) - .unwrap(); - let mut x = l.runtime.block_on(l.request::<rust_analyzer::lsp::ext::MoveItem>(&MoveItemParams { - direction: if let Cmd::RAMoveIU = z { MoveItemDirection::Up } else { MoveItemDirection::Down }, - text_document: o.tid(), - range: Range { start : r, end : r}, - })?.0)?; - - x.sort_tedits(); - for t in x { - self.text.apply_snippet_tedit(&t)?; - } - } - Cmd::RARestart => { - _ = l.request::<ReloadWorkspace>(&())?.0; - } - Cmd::RAParent => { - let Some(GotoDefinitionResponse::Link( - [ref x], - )) = l.runtime.block_on( - l.request::<ParentModule>( - &TextDocumentPositionParams { - text_document: o.tid(), - position: self - .text - .to_l_position( - *self - .text - .cursor - .first(), - ) - .unwrap(), - }, - )? - .0, - )? - else { - self.bar.last_action = - "no such parent".into(); - break 'out; - }; - self.open_loclink(x, window.clone()); - } - } - break 'out; - } - - // match x.sel() { - // Cmd::RAMoveID => {} - // Cmd::RAMoveIU => todo!(), - // Cmd::RARestart => todo!(), - // Cmd::RAParent => todo!(), - // } - }; + Some(Do::ProcessCommand(x)) => { + let z = x.sel(); + if let Err(e) = self.handle_command(z, window.clone()) { + self.bar.last_action = format!("{e}"); + } } Some(Do::SymbolsHandleKey) => { if let Some(lsp) = lsp!(self) { @@ -1100,7 +1037,7 @@ impl Editor { let f = x .uri .to_file_path() - .map_err(|()| anyhow::anyhow!("dammit"))? + .map_err(|()| anyhow!("dammit"))? .canonicalize()?; self.state = State::Default; self.requests.complete = CompletionState::None; @@ -1114,7 +1051,7 @@ impl Editor { let p = self .text .l_position(r.start) - .ok_or(anyhow::anyhow!("rah"))?; + .ok_or(anyhow!("rah"))?; if p != 0 { self.text.cursor.just(p, &self.text.rope); } @@ -1902,8 +1839,21 @@ impl Editor { } Ok(()) } + /// this is so dumb + pub fn open_loc( + &mut self, + Location { uri, range }: &Location, + w: Arc<Window>, + ) { + self.open(&uri.to_file_path().unwrap(), w.clone()).unwrap(); - fn open_loclink( + self.text.cursor.just( + self.text.l_position(range.start).unwrap(), + &self.text.rope, + ); + self.text.scroll_to_cursor(); + } + pub fn open_loclink( &mut self, LocationLink { target_uri, target_range, .. }: &LocationLink, w: Arc<Window>, @@ -306,6 +306,8 @@ fn t() { pub struct Hovr { pub(crate) span: Option<[(VisualX, usize); 2]>, pub(crate) item: crate::text::CellBuffer, + #[serde(skip)] + pub(crate) range: Option<lsp_types::Range>, } pub type VisualX = usize; @@ -21,10 +21,10 @@ use lsp_server::{ ErrorCode, Message, Notification as N, Request as LRq, Response as Re, ResponseError, }; -use rust_analyzer::lsp::ext::*; use lsp_types::notification::*; use lsp_types::request::*; use lsp_types::*; +use rust_analyzer::lsp::ext::*; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::sync::oneshot; @@ -64,6 +64,7 @@ pub enum RequestError<X> { Rx(PhantomData<X>), Failure(Re, #[serde(skip)] Option<Backtrace>), Cancelled(Re, DiagnosticServerCancellationData), + Send(Message), } pub type AQErr = RequestError<LSPError>; impl Request for LSPError { @@ -79,6 +80,7 @@ pub trait Anonymize<T> { impl<T, E> Anonymize<T> for Result<T, RequestError<E>> { fn anonymize(self) -> Result<T, RequestError<LSPError>> { self.map_err(|e| match e { + RequestError::Send(x) => RequestError::Send(x), RequestError::Rx(_) => RequestError::Rx(PhantomData), RequestError::Failure(r, b) => RequestError::Failure(r, b), RequestError::Cancelled(r, d) => RequestError::Cancelled(r, d), @@ -97,9 +99,16 @@ impl<X: Request + std::fmt::Debug> std::error::Error for RequestError<X> { None } } +impl<X> From<SendError<Message>> for RequestError<X> { + fn from(x: SendError<Message>) -> Self { + Self::Send(x.into_inner()) + } +} impl<X: Request> Display for RequestError<X> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::Send(x) => + write!(f, "{} failed; couldnt send {x:?}", X::METHOD), Self::Rx(_) => write!(f, "{} failed; couldnt get from thingy", X::METHOD), Self::Failure(x, bt) => write!( @@ -125,6 +134,12 @@ impl Client { pub fn cancel(&self, rid: i32) { _ = self.notify::<Cancel>(&CancelParams { id: rid.into() }); } + pub fn request_immediate<'me, X: Request>( + &'me self, + y: &X::Params, + ) -> Result<X::Result, RequestError<X>> { + self.runtime.block_on(self.request::<X>(y)?.0) + } #[must_use] pub fn request<'me, X: Request>( &'me self, @@ -452,8 +467,12 @@ impl Client { self.request::<lsp_request!("workspace/symbol")>( &lsp_types::WorkspaceSymbolParams { query: f, - search_scope: Some(lsp_types::WorkspaceSymbolSearchScope::Workspace), - search_kind: Some(lsp_types::WorkspaceSymbolSearchKind::AllSymbols), + search_scope: Some( + lsp_types::WorkspaceSymbolSearchScope::Workspace, + ), + search_kind: Some( + lsp_types::WorkspaceSymbolSearchKind::AllSymbols, + ), ..Default::default() }, ) @@ -466,14 +485,12 @@ impl Client { t: &'a mut TextArea, ) { if let Ok([x]) = self.runtime.block_on( - self.request::<MatchingBrace>( - &MatchingBraceParams { - text_document: f.tid(), - positions: vec![ - t.to_l_position(*t.cursor.first()).unwrap(), - ], - }, - ) + self.request::<MatchingBrace>(&MatchingBraceParams { + text_document: f.tid(), + positions: vec![ + t.to_l_position(*t.cursor.first()).unwrap(), + ], + }) .unwrap() .0, ) { @@ -594,12 +611,10 @@ impl Client { let r = self .runtime .block_on( - self.request::<OnEnter>( - &TextDocumentPositionParams { - text_document: f.tid(), - position: t.to_l_position(*c).unwrap(), - }, - ) + self.request::<OnEnter>(&TextDocumentPositionParams { + text_document: f.tid(), + position: t.to_l_position(*c).unwrap(), + }) .unwrap() .0, ) @@ -834,6 +849,7 @@ pub fn run( "hoverActions": true, "workspaceSymbolScopeKindFiltering": true, "onEnter": true, + "localDocs": true, }}), ..default() }, @@ -883,6 +899,9 @@ pub fn run( "autoself": { "enable": true, }, "privateEditable": { "enable": true }, }, + "imports": { + "granularity": "group", + }, }}), trace: None, workspace_folders: Some(vec![workspace]), @@ -21,8 +21,8 @@ use crate::edi::{Editor, lsp_m}; use crate::lsp::Rq; use crate::text::{CoerceOption, RopeExt, col, color_}; use crate::{ - BG, BORDER, CompletionAction, CompletionState, FG, FONT, complete, filter, - lsp, sig, + BG, BORDER, CompletionAction, CompletionState, FG, FONT, complete, + filter, lsp, sig, }; #[implicit_fn::implicit_fn] @@ -134,6 +134,15 @@ pub fn render( x.style.fg = col!("#FFD173"); }); } } + if let Some(crate::hov::Hovr{ range:Some(r),..} ) = &ed.requests.hovering.result { + x.get_range(text.map_to_visual((r.start.character as _, r.start.line as _)), + text.map_to_visual((r.end.character as usize, r.end.line as _))) + .for_each(|x| { + x.style.secondary_color = col!("#73d0ff"); + x.style.flags |= Style::UNDERCURL; + }); + // x.range; + } if let Some((lsp, p)) = lsp_m!(ed + p) && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.diagnostics.get(&uri, &lsp.diagnostics.guard()) { #[derive(Copy, Clone, Debug)] enum EType { @@ -698,7 +707,7 @@ pub fn render( h as _, BORDER, ); - } + } State::Symbols(Rq { result: Some(x), .. }) => 'out: { let ws = ed.workspace.as_deref().unwrap(); let c = x.cells(50, ws); diff --git a/src/text.rs b/src/text.rs index 72ec25b..2f4d30a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -948,7 +948,7 @@ impl TextArea { // arc_swap::Guard<Arc<Box<[SemanticToken]>>>, // &SemanticTokensLegend, // )>; - if leg.is_none() { + if leg.is_none() || self.tokens.is_empty() { self.tree_sit(path, &mut cells); } if let Some(tabstops) = &self.tabstops { @@ -1110,6 +1110,28 @@ impl TextArea { } best } + + pub(crate) fn apply_tedits_adjusting( + &mut self, + teds: &mut [TextEdit], + ) -> Result<(), ()> { + teds.sort_tedits(); + for ted in teds { + self.apply_adjusting(ted)?; + } + Ok(()) + } + + pub(crate) fn apply_tedits( + &mut self, + teds: &mut [TextEdit], + ) -> Result<(), ()> { + teds.sort_tedits(); + for ted in teds { + self.apply(ted)?; + } + Ok(()) + } } pub fn is_word(r: char) -> bool { |