A simple CPU rendered GUI IDE experience.
-rw-r--r--src/commands.rs155
-rw-r--r--src/edi.rs96
-rw-r--r--src/hov.rs2
-rw-r--r--src/lsp.rs53
-rw-r--r--src/rnd.rs15
-rw-r--r--src/text.rs24
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(())
+ }
+}
diff --git a/src/edi.rs b/src/edi.rs
index 2d35504..863643b 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -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>,
diff --git a/src/hov.rs b/src/hov.rs
index 9b74b51..a0f8261 100644
--- a/src/hov.rs
+++ b/src/hov.rs
@@ -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;
diff --git a/src/lsp.rs b/src/lsp.rs
index 8461050..7cfa3b6 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -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]),
diff --git a/src/rnd.rs b/src/rnd.rs
index 7c3138f..e5d3f40 100644
--- a/src/rnd.rs
+++ b/src/rnd.rs
@@ -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 {