A simple CPU rendered GUI IDE experience.
bendn 4 months ago
parent 1bc0507 · commit bc3f6b1
-rw-r--r--src/com.rs88
-rw-r--r--src/lsp.rs29
-rw-r--r--src/main.rs157
-rw-r--r--src/text.rs17
4 files changed, 160 insertions, 131 deletions
diff --git a/src/com.rs b/src/com.rs
index f2c47ca..cf30f9f 100644
--- a/src/com.rs
+++ b/src/com.rs
@@ -1,3 +1,4 @@
+use std::collections::HashSet;
use std::mem::MaybeUninit;
use std::sync::LazyLock;
@@ -8,48 +9,61 @@ use fimg::Image;
use itertools::Itertools;
use lsp_types::*;
+use crate::text::color;
use crate::{Complete, FG};
-const BG: [u8; 3] = crate::text::color(*b"1c212b");
-const T_BG: [u8; 3] = crate::text::color(*b"11141a");
-pub fn s(x: &Complete, c: usize, filter: &str) -> Vec<Cell> {
+pub fn s(completion: &Complete, c: usize, filter: &str) -> Vec<Cell> {
let mut out = vec![];
- let x = &x.r;
+ let x = &completion.r;
let y = match x {
CompletionResponse::Array(x) => x,
CompletionResponse::List(x) => &x.items,
};
+ #[thread_local]
static mut MATCHER: LazyLock<nucleo::Matcher> =
LazyLock::new(|| nucleo::Matcher::new(nucleo::Config::DEFAULT));
+
let p = nucleo::pattern::Pattern::parse(
filter,
- nucleo::pattern::CaseMatching::Ignore,
+ nucleo::pattern::CaseMatching::Smart,
nucleo::pattern::Normalization::Smart,
);
-
+ dbg!(filter);
let mut i = y
.iter()
.filter(|y| {
- y.filter_text
- .as_deref()
- .unwrap_or(&y.label)
- .starts_with(filter)
+ filter.is_empty()
+ || y.filter_text
+ .as_deref()
+ .unwrap_or(&y.label)
+ .chars()
+ .collect::<HashSet<_>>()
+ .intersection(&filter.chars().collect())
+ .count()
+ > 0
})
.map(|y| {
- let mut to = vec![];
+ let mut utf32 = vec![];
+
+ let hay = y.filter_text.as_deref().unwrap_or(&y.label);
+ let mut indices = vec![];
let score = p
- .score(
- nucleo::Utf32Str::new(
- y.filter_text.as_deref().unwrap_or(&y.label),
- &mut to,
- ),
+ .indices(
+ nucleo::Utf32Str::new(hay, &mut utf32),
unsafe { &mut *MATCHER },
+ &mut indices,
)
.unwrap_or(0);
- (score, y)
+ indices.sort_unstable();
+ indices.dedup();
+
+ (score, y, indices)
})
.sorted_by_key(|x| x.0)
- .take(13);
+ .rev()
+ .zip(0..)
+ .skip(completion.vo)
+ .take(N);
// let Some((s, x)) = i.next() else {
// return vec![];
@@ -93,15 +107,29 @@ pub fn s(x: &Complete, c: usize, filter: &str) -> Vec<Cell> {
// // .starts_with(filter)
// // })
// .take(13)
- i.for_each(|(_, x)| r(x, c, &mut out));
+ i.for_each(|((_, x, indices), i)| {
+ r(x, c, i == completion.selection, &indices, &mut out)
+ });
+
out
}
fn charc(c: &str) -> usize {
c.chars().count()
}
#[implicit_fn::implicit_fn]
-fn r(x: &CompletionItem, c: usize, to: &mut Vec<Cell>) {
- let mut b = vec![D; c];
+fn r(
+ x: &CompletionItem,
+ c: usize,
+ selected: bool,
+ indices: &[u32],
+ to: &mut Vec<Cell>,
+) {
+ let bg = if selected { color(*b"262d3b") } else { color(*b"1c212b") };
+ const T_BG: [u8; 3] = color(*b"11141a");
+
+ let ds: Style = Style { bg: bg, color: FG, flags: 0 };
+ let d: Cell = Cell { letter: None, style: ds };
+ let mut b = vec![d; c];
let ty = match x.kind {
Some(CompletionItemKind::TEXT) => " ",
Some(
@@ -149,18 +177,22 @@ fn r(x: &CompletionItem, c: usize, to: &mut Vec<Cell>) {
i.iter_mut()
.rev()
.zip(details.map(|x| {
- Style { bg: BG, color: [154, 155, 154], ..default() }
- .basic(x)
+ Style { bg, color: [154, 155, 154], ..default() }.basic(x)
}))
.for_each(|(a, b)| *a = b);
}
i.iter_mut()
- .zip(x.label.chars().map(|x| DS.basic(x)))
- .for_each(|(a, b)| *a = b);
+ .zip(x.label.chars().map(|x| ds.basic(x)))
+ .zip(0..)
+ .for_each(|((a, b), i)| {
+ *a = b;
+ if indices.contains(&i) {
+ a.style |= (Style::BOLD, color(*b"ffcc66"));
+ }
+ });
to.extend(b);
}
-const DS: Style = Style { bg: BG, color: FG, flags: 0 };
-const D: Cell = Cell { letter: None, style: DS };
+pub const N: usize = 13;
#[test]
fn t() {
let ppem = 20.0;
@@ -170,7 +202,7 @@ fn t() {
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, scroll: 0 }, c, "");
+ s(&Complete { r: y, start: 0, selection: 0, vo: 0 }, c, "");
dbg!(c, r);
dbg!(w, h);
diff --git a/src/lsp.rs b/src/lsp.rs
index 4815f2e..1942819 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::path::Path;
+use std::sync::Arc;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::Ordering::Relaxed;
use std::task::Poll;
@@ -8,7 +9,7 @@ use std::time::Instant;
use Default::default;
use anyhow::Error;
-use arc_swap::ArcSwap;
+use arc_swap::{ArcSwap, ArcSwapOption};
use crossbeam::channel::{
Receiver, RecvError, SendError, Sender, unbounded,
};
@@ -27,6 +28,7 @@ use lsp_types::*;
use parking_lot::Mutex;
use serde_json::json;
use tokio::sync::oneshot;
+use winit::window::Window;
pub struct Client {
pub runtime: tokio::runtime::Runtime,
@@ -35,7 +37,6 @@ pub struct Client {
pub initialized: Option<InitializeResult>,
// pub pending: HashMap<i32, oneshot::Sender<Re>>,
pub send_to: Sender<(i32, oneshot::Sender<Re>)>,
- pub ch_tx: Sender<()>,
pub progress:
&'static papaya::HashMap<ProgressToken, Option<WorkDoneProgress>>,
pub not_rx: Receiver<N>,
@@ -162,7 +163,11 @@ impl Client {
rx
}
- pub fn rq_semantic_tokens(&self, f: &Path) -> anyhow::Result<()> {
+ pub fn rq_semantic_tokens(
+ &self,
+ f: &Path,
+ w: Option<Arc<Window>>,
+ ) -> anyhow::Result<()> {
debug!("requested semantic tokens");
let Some(b"rs") = f.extension().map(|x| x.as_encoded_bytes())
else {
@@ -186,7 +191,6 @@ impl Client {
},
)?;
let d = self.semantic_tokens.0;
- let ch = self.ch_tx.clone();
let x = self.runtime.spawn(async move {
let y = rx.await?.unwrap();
debug!("received semantic tokens");
@@ -197,7 +201,7 @@ impl Client {
SemanticTokensResult::Tokens(x) =>
d.store(x.data.into_boxed_slice().into()),
};
- ch.send(())?;
+ w.map(|x| x.request_redraw());
anyhow::Ok(())
});
*p = Some((x, id));
@@ -212,15 +216,19 @@ pub fn run(
lsp_server::IoThreads,
),
workspace: WorkspaceFolder,
-) -> (Client, lsp_server::IoThreads, JoinHandle<()>, Receiver<()>) {
+) -> (
+ Client,
+ lsp_server::IoThreads,
+ JoinHandle<()>,
+ oneshot::Sender<Arc<Window>>,
+) {
let now = Instant::now();
let (req_tx, req_rx) = unbounded();
let (not_tx, not_rx) = unbounded();
let (_req_tx, _req_rx) = unbounded();
- let (ch_tx, ch_rx) = unbounded();
+ let (window_tx, window_rx) = oneshot::channel::<Arc<Window>>();
let mut c: Client = Client {
tx,
- ch_tx: ch_tx.clone(),
progress: Box::leak(Box::new(papaya::HashMap::new())),
runtime: tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
@@ -431,6 +439,7 @@ CompletionItemKind::TYPE_PARAMETER]
log::info!("lsp took {:?} to initialize", now.elapsed());
let h = spawn(move || {
let mut map = HashMap::new();
+ let w = window_rx.blocking_recv().unwrap();
loop {
crossbeam::select! {
recv(req_rx) -> x => match x {
@@ -477,7 +486,7 @@ CompletionItemKind::TYPE_PARAMETER]
Ok(Message::Notification(x @ N { method: "$/progress", .. })) => {
let ProgressParams {token,value:ProgressParamsValue::WorkDone(x) } = x.load::<Progress>().unwrap();
progress.update(token, move |_| Some(x.clone()), &progress.guard());
- _ = ch_tx.send(());
+ w.request_redraw();
}
Ok(Message::Notification(notification)) => {
debug!("rx {notification:?}");
@@ -490,7 +499,7 @@ CompletionItemKind::TYPE_PARAMETER]
}
}
});
- (c, iot, h, ch_rx)
+ (c, iot, h, window_tx)
}
// trait RecvEepy<T>: Sized {
diff --git a/src/main.rs b/src/main.rs
index 89e1d90..b84dda5 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(
+ thread_local,
result_option_map_or_default,
iter_intersperse,
stmt_expr_attributes,
@@ -35,6 +36,7 @@ use std::time::Instant;
use Default::default;
use NamedKey::*;
+use array_chunks::ExtensionTrait;
use atools::prelude::AASAdd;
use crossbeam::channel::RecvError;
use diff_match_patch_rs::PatchInput;
@@ -65,7 +67,7 @@ use winit::window::{Icon, Window};
use crate::bar::Bar;
use crate::hov::Hovr;
-use crate::text::{Diff, TextArea};
+use crate::text::{Diff, TextArea, is_word};
mod bar;
pub mod com;
pub mod hov;
@@ -221,10 +223,10 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
);
c.open(&origin, std::fs::read_to_string(&origin).unwrap())
.unwrap();
- (c, (t, t2), changed)
+ (&*Box::leak(Box::new(c)), (t, t2), changed)
},
);
- let (lsp, t, ch) = match c {
+ let (lsp, t, mut w) = match c {
Some((a, b, c)) => (Some(a), Some(b), Some(c)),
None => (None, None, None),
};
@@ -261,15 +263,8 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
.map(|x| x.metadata().unwrap().modified().unwrap())
};
}
- macro_rules! change {
- () => {
- lsp!().map(|(x, origin)| {
- x.edit(&origin, text.rope.to_string()).unwrap();
- x.rq_semantic_tokens(origin).unwrap();
- });
- };
- }
- lsp!().map(|(x, origin)| x.rq_semantic_tokens(origin).unwrap());
+
+ lsp!().map(|(x, origin)| x.rq_semantic_tokens(origin, None).unwrap());
let mut mtime = modify!();
macro_rules! save {
() => {{
@@ -282,7 +277,6 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
mtime = modify!();
}};
}
- static PUT: OnceLock<Arc<Window>> = OnceLock::new();
let app = winit_app::WinitAppBuilder::with_init(
move |elwt| {
let window = winit_app::make_window(elwt, |x| {
@@ -290,9 +284,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
.with_name("com.bendn.gracilaria", "")
.with_window_icon(Some(Icon::from_rgba(include_bytes!("../dist/icon-32").to_vec(), 32, 32).unwrap()))
});
+ if let Some(x) = w.take() {
+ x.send(window.clone()).unwrap();
+ }
+
window.set_ime_allowed(true);
window.set_ime_purpose(winit::window::ImePurpose::Terminal);
- PUT.set(window.clone()).unwrap();
let context =
softbuffer::Context::new(window.clone()).unwrap();
@@ -304,6 +301,14 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
)
.with_event_handler(
move |(window, _context), surface, event, elwt| {
+ macro_rules! change {
+ () => {
+ lsp!().map(|(x, origin)| {
+ x.edit(&origin, text.rope.to_string()).unwrap();
+ x.rq_semantic_tokens(origin, Some(window.clone())).unwrap();
+ });
+ };
+ }
elwt.set_control_flow(ControlFlow::Wait);
let (fw, fh) = dsb::dims(&FONT, ppem);
let (c, r) = dsb::fit(
@@ -321,16 +326,15 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
window.request_redraw();
}
if let CompletionState::Complete(o, x)= &mut complete &&
- x.as_ref().is_some_and(|(x, _)|x.is_finished()) && let Some((task, c)) = x.take() && let Some(ref l) = lsp{
+ x.as_ref().is_some_and(|(x, _)|x.is_finished()) &&
+ let Some((task, c)) = x.take() && let Some(ref l) = lsp{
// if text.cursor() ==* c_ {
- println!("bl0ck on");
-
*o = l.runtime.block_on(task).ok().and_then(Result::ok).flatten().map(|x| Complete {
-r:x,start:0,selection:0,scroll:0,
+r:x,start:c,selection:0,vo:0,
} );
if let Some(x) = o {
- std::fs::write("complete_", serde_json::to_string_pretty(&x.r).unwrap()).unwrap();
- println!("resolved")
+ // std::fs::write("complete_", serde_json::to_string_pretty(&x.r).unwrap()).unwrap();
+ // println!("resolved")
}
// println!("{complete:#?}");
// } else {
@@ -456,7 +460,7 @@ r:x,start:0,selection:0,scroll:0,
.unwrap_or("new buffer"),
&state,
&text,
- lsp.as_ref()
+ lsp
);
unsafe {
dsb::render(
@@ -723,10 +727,7 @@ RUNNING.remove(&hover,&RUNNING.guard());
if button == MouseButton::Left {
unsafe { CLICKING = true };
}
- match complete.consume(CompletionAction::Click).unwrap() {
- Some(CDo::Abort(x))=>{ x.abort() },
- _ => {},
- }
+ _ = complete.consume(CompletionAction::Click).unwrap();
match state.consume(Action::M(button)).unwrap() {
Some(Do::MoveCursor) => {
text.cursor = text.index_at(cursor_position);
@@ -832,56 +833,40 @@ RUNNING.remove(&hover,&RUNNING.guard());
Some(Do::Edit) => {
hist.test_push(&text);
let cb4 = text.cursor;
- let r = handle2(&event.logical_key, &mut text);
+ if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) = event.logical_key && let CompletionState::Complete(..) = complete{
+ } else {
+ handle2(&event.logical_key, &mut text);
+ }
+
text.scroll_to_cursor();
+ if cb4 != text.cursor && let CompletionState::Complete(Some(c), t)= &mut complete
+ && ((text.cursor < c.start) || (!is_word(text.at_())&& (text.at_() != '.' || text.at_() != ':')) ) {
+ if let Some((x, _)) = t.take() {
+ x.abort();
+ }
+ complete = CompletionState::None;
+ }
if hist.record(&text) {
change!();
}
-if let Some(r) = r {
lsp!().map(|(lsp, o)|{
let window = window.clone();
- let ctx = match complete.consume(CompletionAction::TypeCharacter).unwrap() {
- Some(CDo::Request) => {
- CompletionContext {
- trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character:Some(r.to_string()) }
-
- // println!("make rq");
- // let x = lsp.request_complete(o, text.cursor(), );
- // let h = lsp.runtime.spawn(async move {
- // let r = x.await;
- // window.request_redraw();
- // r
- // });
- // complete = CompletionState::Complete(None, Some((h,cb4)));
- // dbg!(&complete);
- }
- Some(CDo::Update) => {
- // CompletionContext {
- // trigger_kind: CompletionTriggerKind
- CompletionContext {
- trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, trigger_character:None }
- // complete = CompletionState::Complete(None, Some(lsp.runtime.spawn(async move {
- // let r = x.await;
- // window.request_redraw();
- // r
- // })));
+ match complete.consume(CompletionAction::K(event.logical_key.as_ref())).unwrap(){
+ Some(CDo::Request(ctx)) => {
+ let x = lsp.request_complete(o, text.cursor(), ctx);
+ let h = lsp.runtime.spawn(async move {
+ x.await.inspect(|_| window.request_redraw())
+ });
+ let CompletionState::Complete(c, x) = &mut complete else { panic!()};
+ *x = Some((h,c.as_ref().map(|x|x.start).or(x.as_ref().map(|x|x.1)).unwrap_or(text.cursor)));
}
None => {return},
_ => panic!(),
};
- println!("make rq");
- let x = lsp.request_complete(o, text.cursor(), ctx);
- let h = lsp.runtime.spawn(async move {
- let r = x.await;
- window.request_redraw();
- r
- });
- complete = CompletionState::Complete(None, Some((h,text.cursor)));
- dbg!(&complete);
-
+
});
-}
+
}
Some(Do::Undo) => {
hist.test_push(&text);
@@ -1018,16 +1003,6 @@ if let Some(r) = r {
};
},
);
- ch.map(|ch| {
- thread::Builder::new().name("redrawer".into()).spawn(move || {
- for () in ch {
- PUT.get().map(|x| {
- x.request_redraw();
- println!("rq redraw");
- });
- }
- })
- });
winit_app::run_app(event_loop, app);
}
@@ -1241,26 +1216,33 @@ impl<T> M<T> for Option<T> {
rust_fsm::state_machine! {
#[derive(Debug)]
- pub(crate) CompletionState => CompletionAction => CDo
+ pub(crate) CompletionState => CompletionAction<'i> => CDo
None => Click => None,
- None => TypeCharacter => Complete(
+ None => K(Key<&'i str> => Key::Character(k @ ("." | ":"))) => Complete(
(Option<Complete>, Option<(JoinHandle<
Result<
Option<CompletionResponse>,
tokio::sync::oneshot::error::RecvError,
>,
>, usize)>) => (None,None)
- ) [Request],
+ ) [Request(CompletionContext => CompletionContext {trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character:Some(k.to_string()) })],
+ None => K(Key::Named(NamedKey::Space) if ctrl()) => Complete((None, None)) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })],
+ None => K(Key::Character(x) if x.chars().next().is_some_and(is_word)) => Complete((None,None)) [Request(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character:None })],
+ None => K(_) => _,
+
+ // when
+ Complete((_x,_y)) => K(Key::Named(NamedKey::Tab) if shift()) => _ [SelectPrevious],
+ Complete((_x,_y)) => K(Key::Named(NamedKey::Tab)) => _ [SelectNext],
+
+ // exit cases
Complete((_x, None)) => Click => None,
- Complete((_x, Some((y, _)))) => Click => None [Abort(JoinHandle<
- Result<
- Option<CompletionResponse>,
- tokio::sync::oneshot::error::RecvError,
- >,
- > => y)],
- Complete((_x, _y)) => TypeCharacter => _ [Update],
- Complete((Some(x), task)) => Enter => None [Finish(Complete => {
- if let Some((task, _)) = task { task.abort() }; x
+ Complete((_x, Some((y, _)))) => Click => None [Abort(((),) => y.abort())],
+ Complete((_x, Some((y, _)))) => K(Key::Character(x) if !x.chars().all(is_word)) => None [Abort(y.abort())],
+ Complete((_x, None)) => K(Key::Character(x) if !x.chars().all(is_word)) => None,
+
+ Complete((_x, _y)) => K(_) => _ [Request(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, trigger_character:None })],
+ Complete((Some(x), task)) => K(Key::Named(NamedKey::Enter)) => None [Finish(Complete => {
+ task.map(|(task, _)| task.abort()); x
})]
}
impl Default for CompletionState {
@@ -1268,9 +1250,10 @@ impl Default for CompletionState {
Self::None
}
}
-#[derive(Debug)] struct Complete {
+#[derive(Debug)]
+struct Complete {
r: CompletionResponse,
start: usize,
selection: usize,
- scroll: usize,
+ vo: usize,
}
diff --git a/src/text.rs b/src/text.rs
index 55eaf0d..13edb6e 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -393,7 +393,11 @@ impl TextArea {
self.set_ho();
}
- pub fn at(&self) -> char {
+ pub fn at_(&self) -> char {
+ self.rope.get_char(self.cursor - 1).unwrap_or('\n')
+ }
+ /// ??
+ pub fn at_plus_one(&self) -> char {
self.rope.get_char(self.cursor).unwrap_or('\n')
}
#[implicit_fn]
@@ -405,8 +409,8 @@ impl TextArea {
.take_while(_.is_whitespace())
.count();
- self.cursor += if is_word(self.at()).not()
- && !self.at().is_whitespace()
+ self.cursor += if is_word(self.at_plus_one()).not()
+ && !self.at_plus_one().is_whitespace()
&& !is_word(self.rope.char(self.cursor + 1))
{
self.rope
@@ -532,8 +536,9 @@ impl TextArea {
self.set_ho();
}
pub fn backspace_word(&mut self) {
- _ = self.rope.try_remove(self.word_left_p()..self.cursor);
- self.cursor = self.word_left_p();
+ let c = self.word_left_p();
+ _ = self.rope.try_remove(c..self.cursor);
+ self.cursor = c;
self.setc();
self.set_ho();
}
@@ -931,7 +936,7 @@ impl TextArea {
}
}
-fn is_word(r: char) -> bool {
+pub fn is_word(r: char) -> bool {
matches!(r, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')
}
pub static LOADER: LazyLock<Loader> = LazyLock::new(|| {