A simple CPU rendered GUI IDE experience.
semblance of code actions
bendn 3 months ago
parent b1ba633 · commit 52a9cdf
-rw-r--r--src/act.rs74
-rw-r--r--src/com.rs5
-rw-r--r--src/lsp.rs14
-rw-r--r--src/main.rs162
-rw-r--r--src/text.rs12
5 files changed, 241 insertions, 26 deletions
diff --git a/src/act.rs b/src/act.rs
new file mode 100644
index 0000000..e6b216b
--- /dev/null
+++ b/src/act.rs
@@ -0,0 +1,74 @@
+use dsb::Cell;
+use dsb::cell::Style;
+use lsp_types::CodeAction;
+
+#[derive(Debug, Clone)]
+pub struct CodeActions {
+ pub inner: Vec<CodeAction>,
+ pub selection: usize,
+ pub vo: usize,
+}
+use crate::FG;
+use crate::text::col;
+
+const N: usize = 13;
+impl CodeActions {
+ pub fn next(&mut self) {
+ let n = self.inner.len();
+ self.selection += 1;
+ if self.selection == n {
+ self.vo = 0;
+ self.selection = 0;
+ }
+ if self.selection >= self.vo + 13 {
+ self.vo += 1;
+ }
+ }
+
+ pub fn sel(&self) -> &CodeAction {
+ &self.inner[self.selection]
+ }
+
+ #[lower::apply(saturating)]
+ pub fn back(&mut self) {
+ let n = self.inner.len();
+ if self.selection == 0 {
+ self.vo = n - N;
+ self.selection = n - 1;
+ } else {
+ self.selection -= 1;
+ if self.selection < self.vo {
+ self.vo -= 1;
+ }
+ }
+ }
+ pub fn maxc(&self) -> usize {
+ self.inner
+ .iter()
+ .map(|x| x.title.chars().count())
+ .max()
+ .unwrap_or(0)
+ }
+ pub fn write(&self, c: usize) -> Vec<Cell> {
+ let mut into = vec![];
+ for (el, i) in self.inner.iter().zip(0..) {
+ write(el, c, self.selection == i, &mut into);
+ }
+ into
+ }
+}
+fn write(x: &CodeAction, c: usize, selected: bool, to: &mut Vec<Cell>) {
+ let bg = if selected { col!("#262d3b") } else { col!("#1c212b") };
+
+ let mut into = vec![
+ Cell {
+ style: Style { bg, color: FG, flags: 0 },
+ ..Default::default()
+ };
+ c
+ ];
+ into.iter_mut()
+ .zip(x.title.chars())
+ .for_each(|(a, b)| a.letter = Some(b));
+ to.extend(into);
+}
diff --git a/src/com.rs b/src/com.rs
index 0d8545f..f7ed8be 100644
--- a/src/com.rs
+++ b/src/com.rs
@@ -9,7 +9,7 @@ use itertools::Itertools;
use lsp_types::*;
use crate::FG;
-use crate::text::{col, color, color_, set_a};
+use crate::text::{col, color_, set_a};
#[derive(Debug)]
pub struct Complete {
@@ -278,8 +278,7 @@ 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, start: 0, selection: 0, vo: 0 }, c, "");
+ let cells = s(&Complete { r: y, selection: 0, vo: 0 }, c, "");
dbg!(c, r);
dbg!(w, h);
diff --git a/src/lsp.rs b/src/lsp.rs
index 2c5d328..367a922 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -429,6 +429,12 @@ pub fn run(
workspace: Some(WorkspaceClientCapabilities {
diagnostic: Some(DiagnosticWorkspaceClientCapabilities { refresh_support: Some(true) }),
inlay_hint: Some(InlayHintWorkspaceClientCapabilities { refresh_support: Some(true) }),
+ workspace_edit: Some(
+ WorkspaceEditClientCapabilities { document_changes: Some(true),
+ resource_operations: Some(vec![ResourceOperationKind::Create, ResourceOperationKind::Rename, ResourceOperationKind::Delete]),
+ failure_handling: Some(FailureHandlingKind::Abort), normalizes_line_endings: Some(false),
+ change_annotation_support: Some(ChangeAnnotationWorkspaceEditClientCapabilities { groups_on_label: Some(false) }) },
+ ),
..default()
}),
text_document: Some(TextDocumentClientCapabilities {
@@ -439,9 +445,16 @@ pub fn run(
code_action: Some(
CodeActionClientCapabilities {
data_support: Some(true),
+ resolve_support: Some(CodeActionCapabilityResolveSupport { properties: vec!["edit".to_string()] }),
+ code_action_literal_support: Some(CodeActionLiteralSupport { code_action_kind: CodeActionKindLiteralSupport { value_set: [
+ "", "Empty", "QuickFix", "Refactor", "RefactorExtract", "RefactorInline", "RefactorRewrite", "Source", "SourceOrganizeImports", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports"
+ ].map(String::from).into()} }),
..default()
}
),
+ rename: Some(RenameClientCapabilities { prepare_support: Some(true),
+ prepare_support_default_behavior: Some(PrepareSupportDefaultBehavior::IDENTIFIER), honors_change_annotations: Some(false),
+ ..default() }),
hover: Some(HoverClientCapabilities {
dynamic_registration: None,
content_format: Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]),
@@ -571,6 +584,7 @@ pub fn run(
..default()
}),
experimental: Some(json! {{
+ "snippetTextEdit": true,
"colorDiagnosticOutput": true,
"codeActionGroup": true,
"serverStatusNotification": true,
diff --git a/src/main.rs b/src/main.rs
index afec43d..17897d3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -51,7 +51,7 @@ use fimg::pixels::Blend;
use fimg::{Image, OverlayAt};
use lsp::{PathURI, Rq};
use lsp_server::{Connection, Request as LRq};
-use lsp_types::request::{HoverRequest, SignatureHelpRequest};
+use lsp_types::request::{CodeActionResolveRequest, HoverRequest, SignatureHelpRequest};
use lsp_types::*;
use regex::Regex;
use ropey::Rope;
@@ -136,7 +136,7 @@ impl Hist {
x.apply(t, false);
self.last = t.clone();
});
- }
+ }
pub fn redo(&mut self, t: &mut TextArea) {
self.redo_().map(|x| {
x.apply(t, true);
@@ -162,7 +162,7 @@ impl Hist {
new.rope != self.last.rope
}
}
-
+
static mut MODIFIERS: ModifiersState = ModifiersState::empty();
static mut CLICKING: bool = false;
@@ -388,6 +388,14 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
f.ok().flatten().map(|x| {Complete {r:x,start:c,selection:0,vo:0,}})
}, &l.runtime);
};
+ if let State::CodeAction(x) = &mut state {
+ x.poll(|x, _| {
+ Some(act::CodeActions {selection:0, vo:0, inner: x.ok()??.into_iter().map(|x| match x {
+ CodeActionOrCommand::CodeAction(x) => x,
+ _ => panic!("alas we dont like these"),
+ }).collect(),})
+ },&l.runtime);
+ }
def.poll(|x, _|
x.ok().flatten().and_then(|x| match &x {
GotoDefinitionResponse::Link([x, ..]) => Some(x.clone()),
@@ -500,6 +508,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
// off += label.chars().count();
// }
// }
+
text.write_to(
(&mut cells, (c, r)),
(t_ox, 0),
@@ -524,6 +533,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
diag.iter().flat_map(|diag| {
let sev = diag.severity.unwrap_or(DiagnosticSeverity::ERROR);
let sev_ = match sev {
+
DiagnosticSeverity::ERROR => EType::Error,
DiagnosticSeverity::WARNING => EType::Warning,
DiagnosticSeverity::HINT => EType::Hint,
@@ -645,12 +655,15 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
(((_x) as f32 * fw).round() + ox) as usize,
(((_y) as f32 * (fh + ls * fac)).round() + oy) as usize,
);
- assert!(position.0 < 8000 && position.1 < 8000, "{position:?} {_x} {_y}");
+
let ppem = ppem_;
let ls = ls_;
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 {
+ return Err(());
+ }
assert!(w < window.inner_size().width as _ &&h < window.inner_size().height as _);
let is_above = position.1.checked_sub(h).is_some();
let top = position.1.checked_sub(h).unwrap_or(((((_y + 1) as f32) * (fh + ls * fac)).round() + toy) as usize);
@@ -673,7 +686,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
i.copy(),
(left as _, top as _)
)};
- (is_above, left, top, w, h)
+ Ok((is_above, left, top, w, h))
};
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()) {
@@ -681,21 +694,21 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
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((90, (r.saturating_sub(5)) as _), false);
+ let mut t = pattypan::term::Terminal::new((95, (r.saturating_sub(5)) as _), false);
for b in x.replace('\n', "\r\n").bytes(){ t.rx(b,std::fs::File::open("/dev/null").unwrap().as_fd()); }
let y_lim = t.cells.rows().position(|x| x.iter().all(_.letter.is_none())).unwrap_or(20);
let c =t.cells.c() as usize;
let Some(x_lim) = t.cells.rows().map(_.iter().rev().take_while(_.letter.is_none()).count()).map(|x|
c -x).max() else { continue };
let n = t.cells.rows().take(y_lim).flat_map(|x| &x[..x_lim]).copied().collect::<Vec<_>>();
- let (_,left, top, w, h) = place_around(
+ let Ok((_,left, top, w, h)) = place_around(
{ let (x, y) = text.map_to_visual((diag.range.start.character as _, diag.range.start.line as usize));
(x + text.line_number_offset() + 1, y - text.vo) },
&mut fonts,
i.as_mut(),
&n, x_lim,
17.0, 0., 0., 0., 0.
- );
+ ) else { continue };
pass=false;
i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER);
},
@@ -721,15 +734,31 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let r = x.item.l().min(15);
let c = x.item.displayable(r);
- let (_,left, top, w, h) = place_around(
+ let Ok((_,left, top, w, h)) = place_around(
(_x, _y),
&mut fonts,
i.as_mut(),
c, x.item.c,
18.0, 10.0, 0., 0., 0.
- );
+ ) else { return };
i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER);
}));
+ match &state {
+ State::CodeAction(Rq{ result :Some(x), ..}) => 'out: {
+ let m = x.maxc();
+ let c = x.write(m);
+ dbg!(&c);
+ let (_x, _y) = text.cursor_visual();
+ let _x = _x + text.line_number_offset()+1;
+ let Some(_y) = _y.checked_sub(text.vo) else {
+ println!("rah");
+ break 'out };
+ let Ok((is_above,left, top, w, mut h)) = place_around((_x, _y), &mut fonts, i.as_mut(), &c, m, ppem, ls, 0., 0., 0.)else { println!("ra?"); break 'out};
+ dbg!(c);
+ i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER);
+ },
+ _ =>{},
+ }
let com = match complete {
CompletionState::Complete(Rq{ result: Some(ref x,),..}) => {
let c = com::s(x, 40,&filter(&text));
@@ -744,22 +773,22 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let (_x, _y) = text.cursor_visual();
let _x = _x + text.line_number_offset()+1;
let Some(_y) = _y.checked_sub(text.vo) else { break 'out };
- let (is_above,left, top, w, mut h) = place_around((_x, _y), &mut fonts, i.as_mut(), &c, 40, ppem, ls, 0., 0., 0.);
+ let Ok((is_above,left, top, w, mut h)) = place_around((_x, _y), &mut fonts, i.as_mut(), &c, 40, ppem, ls, 0., 0., 0.) else { break 'out };
i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER);
- let com = com.map(|c| {
- let (is_above_,left, top, w_, h_) = place_around(
+ let com = com.and_then(|c| {
+ let Ok((is_above_,left, top, w_, h_)) = place_around(
(_x, _y),
&mut fonts,
i.as_mut(),
&c, 40, ppem, ls, 0., -(h as f32), if is_above { 0.0 } else { h as f32 }
- );
+ ) else { return None};
i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w_ as _,h_ as _, BORDER);
if is_above { // completion below, we need to push the docs, if any, below only below us, if the sig help is still above.
h = h_;
} else {
h+=h_;
}
- (is_above_, left, top, w_, h_)
+ Some((is_above_, left, top, w_, h_))
});
{
let ppem = 15.0;
@@ -770,9 +799,11 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
*max = Some(cells.l());
cells.vo = vo;
let cells = cells.displayable(cells.l().min(15));
- let (_,left_, top_, _w_, h_) = place_around((_x, _y),
+ let Ok((_,left_, top_, _w_, h_)) = place_around((_x, _y),
&mut fonts, i.as_mut(), cells, cols, ppem, ls,
- 0., -(h as f32), if is_above { com.filter(|x| !x.0).map(|(_is, _l, _t, _w, h)| h).unwrap_or_default() as f32 } else { h as f32 });
+ 0., -(h as f32), if is_above { com.filter(|x| !x.0).map(|(_is, _l, _t, _w, h)| h).unwrap_or_default() as f32 } else { h as f32 }) else {
+ return
+ };
i.r#box((left_.saturating_sub(1) as _, top_.saturating_sub(1) as _), w as _,h_ as _, BORDER);
});
}
@@ -781,12 +812,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let (_x, _y) = text.cursor_visual();
let _x = _x + text.line_number_offset()+1;
let _y = _y.wrapping_sub(text.vo);
- let (_,left, top, w, h) = place_around(
+ let Ok((_,left, top, w, h)) = place_around(
(_x, _y),
&mut fonts,
i.as_mut(),
&c, 40, ppem, ls, 0., 0., 0.
- );
+ ) else { return ; };
i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER);
}
}
@@ -1099,6 +1130,85 @@ hovering.request = (DropH::new(handle), cursor_position).into();
_ => {}
}
match o {
+ Some(Do::CodeAction) => {
+ if let Some((lsp, f)) = lsp!() {
+ let r = lsp.request::<lsp_request!("textDocument/codeAction")>(&CodeActionParams {
+ text_document: f.tid(), range: text.to_l_range(text.beginning_of_line(text.cursor).unwrap()
+ ..text.eol(text.cursor)).unwrap(), context: CodeActionContext { trigger_kind: Some(CodeActionTriggerKind::INVOKED), ..default() }, work_done_progress_params: default(), partial_result_params: default() }).unwrap();
+ let mut r2 = Rq::default();
+
+ r2.request(lsp.runtime.spawn(
+ async {r.0.await}
+
+ ));
+ state = State::CodeAction(
+ r2
+ );
+ }
+ }
+ Some(Do::CASelect(act)) if let Some((lsp,f)) = lsp!() => {
+ hist.test_push(&text);
+ let act = act.sel();
+ let act = lsp.runtime.block_on(
+ lsp.request::<CodeActionResolveRequest>(&act).unwrap().0
+ ).unwrap();
+ dbg!(&act);
+ 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()
+ }
+ }
+ }
+
+ }
+ Some(WorkspaceEdit {
+ document_changes:Some(DocumentChanges::Operations(x)),
+ ..
+ }) => {
+ for op in x {
+ match op {
+ 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()
+ }
+ }}
+ x=>log::error!("didnt apply {x:?}"),
+ };
+ // if text_document.uri!= f.tid().uri { continue }
+ // for lsp_types::OneOf::Left(x)| lsp_types::OneOf::Right(AnnotatedTextEdit { text_edit: x, .. }) in edits {
+ // text.apply(&x).unwrap();
+ // }
+ }
+ },
+ _ =>{},
+ }
+ change!();
+ hist.record(&text);
+ }
+ Some(Do::CASelect(_)) => {}
+ Some(Do::CASelectNext) => {
+ let State::CodeAction(Rq{ result: Some(c), .. }) = &mut state else { panic!()};
+ c.next();
+ }
+ Some(Do::CASelectPrev) => {
+ let State::CodeAction(Rq{ result: Some(c), .. }) = &mut state else { panic!()};
+ c.back();
+ }
Some(Do::Reinsert | Do::GoToDefinition) => panic!(),
Some(Do::Save) => match &origin {
Some(x) => {
@@ -1445,6 +1555,7 @@ Default => {
K(Key::Character(x) if x == "f" && ctrl()) => Procure((default(), InputRequest::Search)),
K(Key::Character(x) if x == "o" && ctrl()) => Procure((default(), InputRequest::OpenFile)),
K(Key::Character(x) if x == "c" && ctrl()) => _,
+ K(Key::Character(x) if x == "." && ctrl()) => _ [CodeAction],
K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton => MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
M(MouseButton => MouseButton::Left if ctrl()) => _ [GoToDefinition],
@@ -1455,6 +1566,18 @@ Default => {
K(_) => _ [Edit],
M(_) => _,
},
+CodeAction(Rq<act::CodeActions, Option<CodeActionResponse>,()> => Rq { result : Some(act), request: None, }) => {
+ K(Key::Named(Tab) if shift()) => _ [CASelectPrev],
+ K(Key::Named(ArrowDown | Tab)) => _ [CASelectNext],
+ K(Key::Named(ArrowUp)) => _ [CASelectPrev],
+ K(Key::Named(Enter)) => Default [CASelect(act::CodeActions => act)],
+},
+CodeAction(Rq<act::CodeActions, Option<CodeActionResponse>,()> => rq) => {
+ K(Key::Named(Escape)) => Default,
+ C(_) => _,
+ M(_) => _,
+ K(_) => _,
+},
Selection(x if shift()) => {
K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End)) => Selection(x) [UpdateSelection],
M(MouseButton => MouseButton::Left) => Selection(x) [ExtendSelectionToMouse],
@@ -1577,6 +1700,7 @@ rust_fsm::state_machine! {
Complete(_) => NoResult => None,
Complete(_) => K(Key::Named(Escape)) => None,
Complete(_) => K(Key::Character(x) if !x.chars().all(is_word)) => None,
+ Complete(Rq { result: None, request: _y }) => K(Key::Named(NamedKey::ArrowUp | NamedKey::ArrowUp)) => None,
Complete(Rq { result: Some(x), .. }) => K(Key::Named(NamedKey::Enter)) => None [Finish(Complete => x)],
diff --git a/src/text.rs b/src/text.rs
index a70c8c1..87f7964 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -480,7 +480,7 @@ impl TextArea {
}
None => {
self.tabstops = None;
- end
+ begin + x.new_text.chars().count()
}
};
Ok(())
@@ -503,6 +503,10 @@ impl TextArea {
pub fn x(&self, c: usize) -> usize {
self.xy(c).unwrap().0
}
+ pub fn beginning_of_line(&self, c: usize) -> Option<usize> {
+ self.rope.try_line_to_char(self.y(c)).ok()
+ }
+
// input: char, output: utf8
pub fn x_bytes(&self, c: usize) -> Option<usize> {
let y = self.rope.try_char_to_line(c).ok()?;
@@ -528,8 +532,8 @@ impl TextArea {
}
pub fn setc(&mut self) {
- self.column = self.cursor
- - self.rope.line_to_char(self.rope.char_to_line(self.cursor));
+ self.column =
+ self.cursor - self.beginning_of_line(self.cursor).unwrap();
}
pub fn page_down(&mut self) {
@@ -591,7 +595,7 @@ impl TextArea {
}
pub fn set_ho(&mut self) {
- let x = self.cursor().0;
+ let x = self.cursor_visual().0;
if x < self.ho + 4 {
self.ho = x.saturating_sub(4);
} else if x + 4 > (self.ho + self.c) {