A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | src/act.rs | 74 | ||||
| -rw-r--r-- | src/com.rs | 5 | ||||
| -rw-r--r-- | src/lsp.rs | 14 | ||||
| -rw-r--r-- | src/main.rs | 162 | ||||
| -rw-r--r-- | src/text.rs | 12 |
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); +} @@ -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); @@ -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) { |