A simple CPU rendered GUI IDE experience.
-rw-r--r--src/com.rs5
-rw-r--r--src/lsp.rs102
-rw-r--r--src/main.rs90
-rw-r--r--src/sni.rs11
-rw-r--r--src/text.rs52
5 files changed, 179 insertions, 81 deletions
diff --git a/src/com.rs b/src/com.rs
index f7ed8be..a8358c4 100644
--- a/src/com.rs
+++ b/src/com.rs
@@ -278,9 +278,10 @@ fn t() {
let (c, r) = dsb::fit(&crate::FONT, ppem, lh, (w, h));
dbg!(dsb::size(&crate::FONT, ppem, lh, (c, r)));
let y = serde_json::from_str(include_str!("../complete_")).unwrap();
- let cells = s(&Complete { r: y, selection: 0, vo: 0 }, c, "");
- dbg!(c, r);
+ let cells =
+ s(&Complete { r: y, selection: 0, vo: 0, start: 0 }, c, "");
dbg!(w, h);
+ dbg!(c, r);
let mut fonts = dsb::Fonts::new(
F::FontRef(*crate::FONT, &[(2003265652, 550.0)]),
diff --git a/src/lsp.rs b/src/lsp.rs
index 367a922..2af09ac 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -1,6 +1,8 @@
+use std::backtrace::Backtrace;
use std::collections::HashMap;
use std::fmt::Display;
-use std::mem::forget;
+use std::marker::PhantomData;
+use std::mem::{MaybeUninit, forget};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::AtomicI32;
@@ -14,7 +16,7 @@ use anyhow::bail;
use crossbeam::channel::{
Receiver, RecvError, SendError, Sender, unbounded,
};
-use log::{debug, error};
+use log::{debug, error, trace};
use lsp_server::{
ErrorCode, Message, Notification as N, Request as LRq, Response as Re,
ResponseError,
@@ -49,26 +51,34 @@ impl Drop for Client {
}
}
#[derive(Debug)]
-pub enum RequestError {
- Rx,
+pub enum RequestError<X> {
+ Rx(PhantomData<X>),
+ Failure(Re, Backtrace),
Cancelled(Re, DiagnosticServerCancellationData),
}
-impl From<oneshot::error::RecvError> for RequestError {
+// impl<X> Debug for RequestError<X> {}
+impl<X> From<oneshot::error::RecvError> for RequestError<X> {
fn from(_: oneshot::error::RecvError) -> Self {
- Self::Rx
+ Self::Rx(PhantomData)
}
}
-impl std::error::Error for RequestError {
+impl<X: Request + std::fmt::Debug> std::error::Error for RequestError<X> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
-impl Display for RequestError {
+impl<X: Request> Display for RequestError<X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Self::Rx => write!(f, "couldnt get from thingy"),
+ Self::Rx(_) =>
+ write!(f, "{} failed; couldnt get from thingy", X::METHOD),
+ Self::Failure(x, bt) => write!(
+ f,
+ "{} failed; returned badge :( {x:?} ({bt:?})",
+ X::METHOD
+ ),
Self::Cancelled(x, y) =>
- write!(f, "server cancelled us. {x:?} {y:?}"),
+ write!(f, "server cancelled {}. {x:?} {y:?}", X::METHOD),
}
}
}
@@ -91,7 +101,8 @@ impl Client {
y: &X::Params,
) -> Result<
(
- impl Future<Output = Result<X::Result, RequestError>> + use<'me, X>,
+ impl Future<Output = Result<X::Result, RequestError<X>>>
+ + use<'me, X>,
i32,
),
SendError<Message>,
@@ -104,7 +115,7 @@ impl Client {
}))?;
let (tx, rx) = oneshot::channel();
if self.initialized.is_some() {
- debug!("sent request {id}'s handler");
+ debug!("sent request {} ({id})'s handler", X::METHOD);
self.send_to.send((id, tx)).expect("oughtnt really fail");
}
Ok((
@@ -117,12 +128,15 @@ impl Client {
forget(g);
if let Some(ResponseError { code, ref mut data, .. }) =
x.error
- && code == ErrorCode::ServerCancelled as i32
{
- let e = serde_json::from_value(
- data.take().unwrap_or_default(),
- );
- Err(RequestError::Cancelled(x, e.expect("lsp??")))
+ if code == ErrorCode::ServerCancelled as i32 {
+ let e = serde_json::from_value(
+ data.take().unwrap_or_default(),
+ );
+ Err(RequestError::Cancelled(x, e.expect("lsp??")))
+ } else {
+ Err(RequestError::Failure(x, Backtrace::capture()))
+ }
} else {
Ok(serde_json::from_value::<X::Result>(
x.result.unwrap_or_default(),
@@ -172,7 +186,12 @@ impl Client {
&self,
x: CompletionItem,
) -> Result<
- impl Future<Output = Result<CompletionItem, RequestError>>,
+ impl Future<
+ Output = Result<
+ CompletionItem,
+ RequestError<ResolveCompletionItem>,
+ >,
+ >,
SendError<Message>,
> {
self.request::<ResolveCompletionItem>(&x).map(|x| x.0)
@@ -184,7 +203,10 @@ impl Client {
(x, y): (usize, usize),
c: CompletionContext,
) -> impl Future<
- Output = Result<Option<CompletionResponse>, RequestError>,
+ Output = Result<
+ Option<CompletionResponse>,
+ RequestError<Completion>,
+ >,
> + use<'me> {
let (rx, _) = self
.request::<Completion>(&CompletionParams {
@@ -204,8 +226,12 @@ impl Client {
&'me self,
f: &Path,
(x, y): (usize, usize),
- ) -> impl Future<Output = Result<Option<SignatureHelp>, RequestError>>
- + use<'me> {
+ ) -> impl Future<
+ Output = Result<
+ Option<SignatureHelp>,
+ RequestError<SignatureHelpRequest>,
+ >,
+ > + use<'me> {
self.request::<SignatureHelpRequest>(&SignatureHelpParams {
context: None,
text_document_position_params: TextDocumentPositionParams {
@@ -332,8 +358,12 @@ impl Client {
&'static self,
f: &Path,
t: &TextArea,
- ) -> impl Future<Output = Result<Vec<InlayHint>, RequestError>> + use<>
- {
+ ) -> impl Future<
+ Output = Result<
+ Vec<InlayHint>,
+ RequestError<lsp_request!("textDocument/inlayHint")>,
+ >,
+ > + use<> {
self.request::<lsp_request!("textDocument/inlayHint")>(&InlayHintParams {
work_done_progress_params: default(),
text_document: f.tid(),
@@ -359,7 +389,12 @@ impl Client {
pub fn rq_semantic_tokens(
&'static self,
- to: &mut Rq<Box<[SemanticToken]>, Box<[SemanticToken]>>,
+ to: &mut Rq<
+ Box<[SemanticToken]>,
+ Box<[SemanticToken]>,
+ (),
+ RequestError<SemanticTokensFullRequest>,
+ >,
f: &Path,
w: Option<Arc<Window>>,
) -> anyhow::Result<()> {
@@ -377,7 +412,8 @@ impl Client {
},
)?;
let x = self.runtime.spawn(async move {
- let y = rx.await?.unwrap();
+ let t = rx.await;
+ let y = t?.unwrap();
debug!("received semantic tokens");
let r = match y {
SemanticTokensResult::Partial(_) =>
@@ -691,16 +727,20 @@ pub fn run(
}
}
Ok(Message::Response(x)) => {
- if let Some(e) =& x.error {
+ if let Some(e) = &x.error {
if e.code == ErrorCode::RequestCanceled as i32 {}
else if e.code == ErrorCode::ServerCancelled as i32 {
if let Some((s, _)) = map.remove(&x.id.i32()) {
log::info!("request {} cancelled", x.id);
_ = s.send(x);
}
- }
- else {
- error!("received error from lsp for response {x:?}");
+ } else {
+ if let Some((s, _)) = map.remove(&x.id.i32()) {
+ _ = s.send(x.clone());
+ trace!("received error from lsp for response {x:?}");
+ } else {
+ error!("received error from lsp for response {x:?}");
+ }
}
}
else if let Some((s, took)) = map.remove(&x.id.i32()) {
@@ -875,11 +915,11 @@ impl<T> OnceOff<T> {
}
#[derive(Debug)]
-pub struct Rq<T, R, D = (), E = RequestError> {
+pub struct Rq<T, R, D = (), E = RequestError<R>> {
pub result: Option<T>,
pub request: Option<(AbortOnDropHandle<Result<R, E>>, D)>,
}
-pub type RqS<T, R: Request, D = ()> = Rq<T, R::Result, D>;
+pub type RqS<T, R: Request, D = ()> = Rq<T, R::Result, D, RequestError<R>>;
impl<T, R, D, E> Default for Rq<T, R, D, E> {
fn default() -> Self {
Self { result: None, request: None }
diff --git a/src/main.rs b/src/main.rs
index 6b7f05b..32c0530 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
// this looks pretty good though
#![feature(tuple_trait, unboxed_closures, fn_traits)]
#![feature(
+ vec_try_remove,
iter_next_chunk,
iter_array_chunks,
array_windows,
@@ -32,6 +33,7 @@
)]
#![allow(incomplete_features, redundant_semicolons)]
use std::borrow::Cow;
+use std::collections::HashMap;
use std::iter::once;
mod act;
use std::num::NonZeroU32;
@@ -71,7 +73,7 @@ use winit::window::Icon;
use crate::bar::Bar;
use crate::hov::Hovr;
-use crate::lsp::{RedrawAfter, RqS};
+use crate::lsp::{RedrawAfter, RequestError, RqS};
use crate::text::{Diff, Mapping, TextArea, col, is_word};
mod bar;
pub mod com;
@@ -82,6 +84,7 @@ mod sni;
mod text;
mod winit_app;
fn main() {
+ let x = 4;
unsafe { std::env::set_var("CARGO_UNSTABLE_RUSTC_UNICODE", "true") };
env_logger::init();
// lsp::x();
@@ -218,7 +221,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
std::thread::Builder::new()
.name("Rust Analyzer".into())
.stack_size(1024 * 1024 * 8)
- .spawn(|| rust_analyzer::bin::run_server(b))
+ .spawn(|| {
+ std::panic::set_hook(Box::new(|info| {
+ println!("RA panic @ {}", info.location().unwrap());
+ }));
+ rust_analyzer::bin::run_server(b)
+ })
.unwrap();
let (c, t2, changed) = lsp::run(
(a.sender, a.receiver),
@@ -257,11 +265,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let mut semantic_tokens = default();
let mut diag =
Rq::<String, Option<String>, (), anyhow::Error>::default();
- let mut inlay: Rq<Vec<InlayHint>, Vec<InlayHint>> = default();
+ let mut inlay: Rq<Vec<InlayHint>, Vec<InlayHint>, (), RequestError<lsp_request!("textDocument/inlayHint")>> = default();
let mut def = Rq::<
LocationLink,
Option<GotoDefinitionResponse>,
(usize, usize),
+ RequestError<lsp_request!("textDocument/definition")>,
>::default();
// let mut complete = None::<(CompletionResponse, (usize, usize))>;
// let mut complete_ = None::<(
@@ -660,7 +669,8 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let mut r = c.len()/columns;
assert_eq!(c.len()%columns, 0);
let (w, h) = dsb::size(&fonts.regular, ppem, ls, (columns, r));
- if position.0 + w >= window.inner_size().width as usize || position.1 + h >= window.inner_size().height as usize {
+ if w >= window.inner_size().width as usize || position.1 + h >= window.inner_size().height as usize {
+ unsafe { dsb::render_owned(c, (columns, c.len() / columns), 18.0, fonts, 1.1, true).save("fail.png") };
return Err(());
}
assert!(w < window.inner_size().width as _ &&h < window.inner_size().height as _);
@@ -690,7 +700,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let mut pass = true;
if let Some((lsp, p)) = lsp!() && let Some(diag) = lsp.diagnostics.get(&Url::from_file_path(p).unwrap(), &lsp.diagnostics.guard()) {
let dawg = diag.iter().filter(|diag| text.l_range(diag.range).is_some_and(|x| x.contains(&text.mapped_index_at(cursor_position)) && (text.vo..text.vo+r).contains(&(diag.range.start.line as _))));
- for diag in dawg {
+ for diag in dawg {
match diag.data.as_ref().unwrap_or_default().get("rendered") {
Some(x) if let Some(x) = x.as_str() => {
let mut t = pattypan::term::Terminal::new((95, (r.saturating_sub(5)) as _), false);
@@ -816,7 +826,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
&mut fonts,
i.as_mut(),
&c, 40, ppem, ls, 0., 0., 0.
- ) else { return ; };
+ ) else { break 'out; };
i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER);
}
}
@@ -1026,6 +1036,9 @@ hovering.request = (DropH::new(handle), cursor_position).into();
match state.consume(Action::M(button)).unwrap() {
Some(Do::MoveCursor) => {
text.cursor = text.mapped_index_at(cursor_position);
+ if let Some((lsp, path)) = lsp!() {
+ sig_help.request(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(path, text.cursor()))));
+ }
text.setc();
}
Some(Do::ExtendSelectionToMouse) => {
@@ -1151,21 +1164,33 @@ hovering.request = (DropH::new(handle), cursor_position).into();
let act = lsp.runtime.block_on(
lsp.request::<CodeActionResolveRequest>(&act).unwrap().0
).unwrap();
- dbg!(&act);
+ let mut f_ = |edits: &[SnippetTextEdit]|{
+ let mut first = false;
+ for SnippetTextEdit { text_edit, insert_text_format ,..}in edits {
+ match insert_text_format {
+ Some(InsertTextFormat::SNIPPET) => {
+ text.apply_snippet(&text_edit).unwrap()
+ },
+ _ => {
+ if first {
+ let (b, e) = text.apply(&text_edit).unwrap();
+ log::error!("dont account for this case yet");
+ } else {
+ text.apply_adjusting(text_edit).unwrap();
+ }
+ }
+ }
+ first = false;
+ }
+ };
match act.edit {
Some(WorkspaceEdit {
document_changes:Some(DocumentChanges::Edits(x)),
..
}) => {
- for TextDocumentEdit { edits, text_document } in x {
- if text_document.uri!= f.tid().uri { continue }
- for SnippetTextEdit { text_edit, insert_text_format ,..}in edits {
- match insert_text_format {
- Some(InsertTextFormat::SNIPPET) =>
- text.apply_snippet(&text_edit).unwrap(),
- _ => text.apply(&text_edit).unwrap()
- }
- }
+ for x in x {
+ if x.text_document.uri!= f.tid().uri { return }
+ f_(&x.edits);
}
}
@@ -1178,14 +1203,9 @@ hovering.request = (DropH::new(handle), cursor_position).into();
DocumentChangeOperation::Edit(TextDocumentEdit {
edits, text_document,..
}) => {
-
- for SnippetTextEdit { text_edit, insert_text_format ,..}in edits {
- match insert_text_format {
- Some(InsertTextFormat::SNIPPET) =>
- text.apply_snippet(&text_edit).unwrap(),
- _ => text.apply(&text_edit).unwrap()
+ if text_document.uri!= f.tid().uri { return }
+ f_(&edits);
}
- }}
x=>log::error!("didnt apply {x:?}"),
};
// if text_document.uri!= f.tid().uri { continue }
@@ -1273,27 +1293,19 @@ hovering.request = (DropH::new(handle), cursor_position).into();
Some(CDo::Finish(x)) => {
let sel = x.sel(&filter(&text));
let sel = lsp.runtime.block_on(lsp.resolve(sel.clone()).unwrap()).unwrap();
- let CompletionItem { text_edit: Some(CompletionTextEdit::Edit(ed)), additional_text_edits, insert_text_format, .. } = sel else { panic!() };
+ let CompletionItem { text_edit: Some(CompletionTextEdit::Edit(ed)), additional_text_edits, insert_text_format, .. } = sel.clone() else { panic!() };
match insert_text_format {
- Some(InsertTextFormat::SNIPPET) =>
- text.apply_snippet(&ed).unwrap(),
+ Some(InsertTextFormat::SNIPPET) =>{
+ text.apply_snippet(&ed).unwrap();
+ },
_ => {
- text.apply(&ed).unwrap();
- text.cursor = text.l_position(ed.range.start).unwrap() + ed.new_text.chars().count();
+ let (s, _) = text.apply(&ed).unwrap();
+ text.cursor = s + ed.new_text.chars().count();
}
}
for additional in additional_text_edits.into_iter().flatten() {
- let p = text.l_position(additional.range.end).unwrap();
- if p < text.cursor {
- if !text.visible(p) {
- // line added behind, not visible
- text.vo += additional.new_text.chars().filter(|x|x.is_newline()).count();
- }
- text.apply(&additional).unwrap();
- text.cursor += additional.new_text.chars().count(); // compensate
- }
+ text.apply_adjusting(&additional).unwrap();
}
-
if hist.record(&text) { change!();}
sig_help = Rq::new(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(o, text.cursor()))));
}
@@ -1571,7 +1583,7 @@ CodeAction(Rq<act::CodeActions, Option<CodeActionResponse>,()> => Rq { result :
K(Key::Named(ArrowUp)) => _ [CASelectPrev],
K(Key::Named(Enter)) => Default [CASelect(act::CodeActions => act)],
},
-CodeAction(Rq<act::CodeActions, Option<CodeActionResponse>,()> => rq) => {
+CodeAction(Rq<act::CodeActions, Option<CodeActionResponse>,(), RequestError<lsp_request!("textDocument/codeAction")>> => rq) => {
K(Key::Named(Escape)) => Default,
C(_) => _,
M(_) => _,
diff --git a/src/sni.rs b/src/sni.rs
index 9605e47..19c1775 100644
--- a/src/sni.rs
+++ b/src/sni.rs
@@ -5,7 +5,7 @@ use helix_core::snippets::parser::SnippetElement;
#[derive(Debug, Clone)]
pub struct Snippet {
pub stops: Vec<(Stop, StopP)>,
- pub last: StopP,
+ pub last: Option<StopP>,
pub index: usize,
}
@@ -45,13 +45,12 @@ impl Snippet {
}
stops.sort_by_key(|x| x.0);
stops.iter_mut().for_each(|x| x.1.manipulate(|x| x + at));
- let last = stops.remove(0);
- assert_eq!(last.0, 0);
- Some((Snippet { last: last.1, stops, index: 0 }, o))
+ let last = stops.try_remove(0);
+ Some((Snippet { last: last.map(|x| x.1), stops, index: 0 }, o))
}
pub fn manipulate(&mut self, f: impl FnMut(usize) -> usize + Clone) {
self.stops.iter_mut().for_each(|x| x.1.manipulate(f.clone()));
- self.last.manipulate(f);
+ self.last.as_mut().map(|x| x.manipulate(f));
}
pub fn next(&mut self) -> Option<StopP> {
self.stops.get(self.index).map(|x| {
@@ -64,7 +63,7 @@ impl Snippet {
.iter()
.skip(self.index)
.map(|x| &x.1)
- .chain([&self.last])
+ .chain(self.last.iter())
.cloned()
}
diff --git a/src/text.rs b/src/text.rs
index 48b1db1..adefe0b 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -460,11 +460,26 @@ impl TextArea {
self.set_ho();
}
- pub fn apply(&mut self, x: &TextEdit) -> Result<(), ()> {
+ pub fn apply(&mut self, x: &TextEdit) -> Result<(usize, usize), ()> {
let begin = self.l_position(x.range.start).ok_or(())?;
let end = self.l_position(x.range.end).ok_or(())?;
self.rope.try_remove(begin..end).map_err(|_| ())?;
self.rope.try_insert(begin, &x.new_text).map_err(|_| ())?;
+ Ok((begin, end))
+ }
+ pub fn apply_adjusting(&mut self, x: &TextEdit) -> Result<(), ()> {
+ let (b, e) = self.apply(&x)?;
+ if e < self.cursor {
+ if !self.visible(e) {
+ // line added behind, not visible
+ self.vo +=
+ x.new_text.chars().filter(|&x| x == '\n').count();
+ }
+ let removed = dbg!(e - b);
+ self.cursor += x.new_text.chars().count();
+ self.cursor -= removed; // compensate
+ // text.cursor += additional.new_text.chars().count(); // compensate
+ }
Ok(())
}
pub fn apply_snippet(&mut self, x: &TextEdit) -> anyhow::Result<()> {
@@ -486,7 +501,9 @@ impl TextArea {
}
None => {
self.tabstops = None;
- begin + x.new_text.chars().count()
+ sni.last
+ .map(|x| x.r().end)
+ .unwrap_or_else(|| begin + x.new_text.chars().count())
}
};
Ok(())
@@ -709,7 +726,7 @@ impl TextArea {
self.cursor = x.r().end;
}
None => {
- self.cursor = x.last.clone().r().end;
+ self.cursor = x.last.clone().unwrap().r().end;
self.tabstops = None;
}
},
@@ -1763,3 +1780,32 @@ impl Mapping<'_> {
*x
}
}
+
+#[test]
+fn apply() {
+ let mut t = TextArea::default();
+ t.insert(
+ r#"fn main() {
+ let x = 4;
+}
+"#,
+ );
+
+ t.apply_snippet(&TextEdit {
+ range: lsp_types::Range {
+ start: Position { line: 0, character: 8 },
+ end: Position { line: 0, character: 9 },
+ },
+ new_text: "$0var_name".into(),
+ })
+ .unwrap();
+ t.apply_adjusting(&TextEdit {
+ range: lsp_types::Range {
+ start: Position { line: 1, character: 4 },
+ end: Position { line: 1, character: 4 },
+ },
+ new_text: "let x = var_name;\n ".to_owned(),
+ })
+ .unwrap();
+ assert_eq!(t.cursor, 8);
+}