A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | src/edi.rs | 1321 | ||||
| -rw-r--r-- | src/edi/st.rs | 118 | ||||
| -rw-r--r-- | src/lsp.rs | 111 | ||||
| -rw-r--r-- | src/main.rs | 1573 | ||||
| -rw-r--r-- | src/rnd.rs | 754 |
5 files changed, 2326 insertions, 1551 deletions
@@ -1,65 +1,1332 @@ -use std::path::PathBuf; +use std::borrow::Cow; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; use std::sync::Arc; -use std::thread::JoinHandle; +use std::time::{Instant, SystemTime}; +use Default::default; +use implicit_fn::implicit_fn; +use lsp_server::{Connection, Request as LRq}; use lsp_types::request::*; use lsp_types::*; +use regex::Regex; +use ropey::Rope; +use rust_fsm::StateMachine; use tokio::sync::oneshot::Sender; +use tokio::task::spawn_blocking; +use tokio_util::task::AbortOnDropHandle as DropH; +use winit::event::{KeyEvent, MouseButton}; +use winit::keyboard::{Key, NamedKey}; +use winit::window::Window; +pub mod st; + +use st::*; use crate::bar::Bar; -use crate::hov::Hovr; -use crate::lsp::{Client, RequestError, Rq, RqS}; -use crate::text::TextArea; -use crate::{ClickHistory, CompletionState, Hist, State}; +use crate::com::Complete; +use crate::hov::{self, Hovr}; +use crate::lsp::{self, Client, PathURI, RedrawAfter, RequestError, Rq}; +use crate::text::{self, CoerceOption, Mapping, TextArea}; +use crate::{ + BoolRequest, CDo, ClickHistory, CompletionAction, CompletionState, + Hist, act, alt, ctrl, filter, shift, sig, sym, trm, +}; #[derive(Default)] pub struct Editor { - text: TextArea, - origin: Option<PathBuf>, - state: State, - bar: Bar, - workspace: Option<PathBuf>, - lsp: Option<( + pub text: TextArea, + pub origin: Option<PathBuf>, + pub state: State, + pub bar: Bar, + pub workspace: Option<PathBuf>, + pub lsp: Option<( &'static Client, - JoinHandle<()>, - Sender<Arc<winit::window::Window>>, + std::thread::JoinHandle<()>, + Option<Sender<Arc<Window>>>, )>, - tree: Option<Vec<PathBuf>>, - hovering: Rq<Hovr, Option<Hovr>, (usize, usize), anyhow::Error>, - document_highlights: Rq< + pub tree: Option<Vec<PathBuf>>, + pub hovering: Rq<Hovr, Option<Hovr>, (usize, usize), anyhow::Error>, + pub document_highlights: Rq< Vec<DocumentHighlight>, Vec<DocumentHighlight>, (), RequestError<DocumentHighlightRequest>, >, - complete: CompletionState, - sig_help: Rq< + pub complete: CompletionState, + pub sig_help: Rq< (SignatureHelp, usize, Option<usize>), Option<SignatureHelp>, (), RequestError<SignatureHelpRequest>, >, // vo, lines - semantic_tokens: Rq< + pub semantic_tokens: Rq< Box<[SemanticToken]>, Box<[SemanticToken]>, (), RequestError<SemanticTokensFullRequest>, >, - diag: Rq<String, Option<String>, (), anyhow::Error>, - inlay: Rq< + pub diag: Rq<String, Option<String>, (), anyhow::Error>, + pub inlay: Rq< Vec<InlayHint>, Vec<InlayHint>, (), RequestError<lsp_request!("textDocument/inlayHint")>, >, - def: Rq< + pub def: Rq< LocationLink, Option<GotoDefinitionResponse>, (usize, usize), RequestError<lsp_request!("textDocument/definition")>, >, - chist: ClickHistory, - hist: Hist, - mtime: Option<std::time::SystemTime>, + pub chist: ClickHistory, + pub hist: Hist, + pub mtime: Option<std::time::SystemTime>, +} +macro_rules! lsp { + ($self:ident) => { + $self.lsp.as_ref().map(|(x, ..)| *x) + }; + ($self:ident + p) => { + $crate::edi::lsp_m!($self).zip($self.origin.as_deref()) + }; +} +pub(crate) use lsp as lsp_m; +macro_rules! inlay { + ($self:ident) => { + lsp!($self + p).map(|(lsp, path)| { + $self + .inlay + .request(lsp.runtime.spawn(lsp.inlay(path, &$self.text))) + }) + }; +} +macro_rules! change { + ($self:ident) => { + lsp!($self + p).map(|(x, origin)| { + x.edit(&origin, $self.text.rope.to_string()).unwrap(); + x.rq_semantic_tokens(&mut $self.semantic_tokens, origin, None) + .unwrap(); + inlay!($self); + }); + }; +} +fn rooter(x: &Path) -> Option<PathBuf> { + for f in std::fs::read_dir(&x).unwrap().filter_map(Result::ok) { + if f.file_name() == "Cargo.toml" { + return Some(f.path().with_file_name("").to_path_buf()); + } + } + x.parent().and_then(rooter) +} + +impl Editor { + pub fn new() -> Self { + let mut me = Self::default(); + + me.origin = std::env::args() + .nth(1) + .and_then(|x| PathBuf::try_from(x).ok()) + .and_then(|x| x.canonicalize().ok()); + + std::env::args().nth(1).map(|x| { + me.text.insert(&std::fs::read_to_string(x).unwrap()).unwrap(); + me.text.cursor = 0; + }); + me.workspace = me + .origin + .as_ref() + .and_then(|x| rooter(&x.parent().unwrap())) + .and_then(|x| x.canonicalize().ok()); + me.tree = me.workspace.as_ref().map(|x| { + walkdir::WalkDir::new(x) + .into_iter() + .flatten() + .filter(|x| { + x.path().extension().is_some_and(|x| x == "rs") + }) + .map(|x| x.path().to_owned()) + .collect::<Vec<_>>() + }); + me.lsp = me.workspace.as_ref().zip(me.origin.clone()).map( + |(workspace, origin)| { + let dh = std::panic::take_hook(); + let main = std::thread::current_id(); + // let mut c = Command::new("rust-analyzer") + // .stdin(Stdio::piped()) + // .stdout(Stdio::piped()) + // .stderr(Stdio::inherit()) + // .spawn() + // .unwrap(); + let (a, b) = Connection::memory(); + std::thread::Builder::new() + .name("Rust Analyzer".into()) + .stack_size(1024 * 1024 * 8) + .spawn(move || { + let ra = std::thread::current_id(); + std::panic::set_hook(Box::new(move |info| { + // iz + if std::thread::current_id() == main { + dh(info); + } else if std::thread::current_id() == ra + || std::thread::current() + .name() + .is_some_and(|x| x.starts_with("RA")) + { + println!( + "RA panic @ {}", + info.location().unwrap() + ); + } + })); + rust_analyzer::bin::run_server(b) + }) + .unwrap(); + let (c, t2, changed) = lsp::run( + (a.sender, a.receiver), + // lsp_server::stdio::stdio_transport( + // BufReader::new(c.stdout.take().unwrap()), + // c.stdin.take().unwrap(), + // ), + WorkspaceFolder { + uri: Url::from_file_path(&workspace).unwrap(), + name: workspace + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(), + }, + ); + c.open(&origin, std::fs::read_to_string(&origin).unwrap()) + .unwrap(); + (&*Box::leak(Box::new(c)), (t2), Some(changed)) + }, + ); + me.hist.last = me.text.clone(); + me.lsp.as_ref().zip(me.origin.as_deref()).map( + |((x, ..), origin)| { + x.rq_semantic_tokens(&mut me.semantic_tokens, origin, None) + .unwrap() + }, + ); + + me.mtime = Self::modify(me.origin.as_deref()); + + me + } + + #[must_use = "please apply this"] + pub fn modify(origin: Option<&Path>) -> Option<SystemTime> { + origin.as_ref().map(|x| x.metadata().unwrap().modified().unwrap()) + } + + // #[must_use] + // pub fn inlay( + // &self, + // ) -> Option< + // JoinHandle<Result<Vec<InlayHint>, RequestError<InlayHintRequest>>>, + // > { + // lsp!(self + p).map(|(lsp, path)| { + // lsp.runtime.spawn(lsp.inlay(path, &self.text)) + // }) + // } + + pub fn save(&mut self) { + let t = self.text.rope.to_string(); + std::fs::write(self.origin.as_ref().unwrap(), &t).unwrap(); + self.bar.last_action = "saved".into(); + lsp!(self + p).map(|(l, o)| { + let v = l.runtime.block_on(l.format(o)).unwrap(); + for v in v.coerce() { + self.text.apply_adjusting(&v); + } + change!(self); + self.hist.push(&self.text); + l.notify::<lsp_notification!("textDocument/didSave")>( + &DidSaveTextDocumentParams { + text_document: o.tid(), + text: Some(t), + }, + ) + .unwrap(); + }); + self.mtime = Self::modify(self.origin.as_deref()); + } + pub fn poll(&mut self) { + let Some((l, ..)) = self.lsp else { return }; + for rq in l.req_rx.try_iter() { + match rq { + LRq { method: "workspace/diagnostic/refresh", .. } => { + // let x = l.pull_diag(o.into(), diag.result.clone()); + // diag.request(l.runtime.spawn(x)); + } + rq => log::debug!("discarding request {rq:?}"), + } + } + let r = &l.runtime; + self.inlay.poll( + |x, p| { + x.ok().or(p.1).inspect(|x| { + self.text.set_inlay(x); + }) + }, + r, + ); + self.document_highlights.poll(|x, _| x.ok(), r); + self.diag.poll(|x, _| x.ok().flatten(), r); + if let CompletionState::Complete(rq) = &mut self.complete { + rq.poll( + |f, (c, _)| { + f.ok().flatten().map(|x| Complete { + r: x, + start: c, + selection: 0, + vo: 0, + }) + }, + r, + ); + }; + + if let State::Symbols(x) = &mut self.state { + x.poll( + |x, (_, p)| { + x.ok().map(|r| { + let tree = + self.tree.as_deref().unwrap().iter().map( + |x| SymbolInformation { + name: x + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + kind: SymbolKind::FILE, + location: Location { + range: lsp_types::Range { + end: default(), + start: default(), + }, + uri: Url::from_file_path(&x) + .unwrap(), + }, + container_name: None, + deprecated: None, + tags: None, + }, + ); + sym::Symbols { + tedit: p.map(|x| x.tedit).unwrap_or_default(), + r: tree.chain(r).collect(), + ..default() // dont care about previous selection + } + }) + }, + &r, + ); + } + if let State::CodeAction(x) = &mut self.state { + x.poll( + |x, _| { + let lems: Vec<CodeAction> = x + .ok()?? + .into_iter() + .map(|x| match x { + CodeActionOrCommand::CodeAction(x) => x, + _ => panic!("alas we dont like these"), + }) + .collect(); + if lems.is_empty() { + self.bar.last_action = + "no code actions available".into(); + None + } else { + Some(act::CodeActions::new(lems)) + } + }, + &r, + ); + } + self.def.poll( + |x, _| { + x.ok().flatten().and_then(|x| match &x { + GotoDefinitionResponse::Link([x, ..]) => + Some(x.clone()), + _ => None, + }) + }, + &r, + ); + self.semantic_tokens.poll(|x, _| x.ok(), &l.runtime); + self.sig_help.poll( + |x, ((), y)| { + x.ok().flatten().map(|x| { + if let Some((old_sig, vo, max)) = y + && &sig::active(&old_sig) == &sig::active(&x) + { + (x, vo, max) + } else { + (x, 0, None) + } + }) + }, + &r, + ); + self.hovering.poll(|x, _| x.ok().flatten(), &r); + } + #[implicit_fn] + pub fn cursor_moved( + &mut self, + cursor_position: (usize, usize), + w: Arc<Window>, + c: usize, + ) { + match self.state.consume(Action::C(cursor_position)).unwrap() { + Some(Do::ExtendSelectionToMouse) => { + *self.state.sel() = self.text.extend_selection_to( + self.text.mapped_index_at(cursor_position), + self.state.sel().clone(), + ); + w.request_redraw(); + } + Some(Do::StartSelection) => { + let x = self.text.mapped_index_at(cursor_position); + self.hist.last.cursor = x; + self.text.cursor = x; + *self.state.sel() = x..x; + } + Some(Do::Hover) + if let Some(hover) = + self.text.visual_index_at(cursor_position) + && let Some((cl, o)) = lsp!(self + p) => + 'out: { + let l = &mut self.hovering.result; + if let Some(Hovr { + span: Some([(_x, _y), (_x2, _)]), + .. + }) = &*l + { + let Some(_y) = _y.checked_sub(self.text.vo) else { + break 'out; + }; + if cursor_position.1 == _y + && (_x..=_x2).contains( + &&(cursor_position.0 + - self.text.line_number_offset() + - 1), + ) + { + break 'out; + } else { + // println!("span no longer below cursor; cancel hover {_x}..{_x2} {}", cursor_position.0 - text.line_number_offset() - 1); + *l = None; + w.request_redraw(); + } + } + let text = self.text.clone(); + let mut rang = None; + let z = match hover { + Mapping::Char(_, _, i) => TextDocumentPositionParams { + position: text.to_l_position(i).unwrap(), + text_document: o.tid(), + }, + Mapping::Fake(mark, relpos, abspos, _) => { + let Some(ref loc) = mark.l[relpos].1 else { + break 'out; + }; + let (x, y) = text.xy(abspos).unwrap(); + let Some(mut begin) = text.reverse_source_map(y) + else { + break 'out; + }; + let start = begin.nth(x - 1).unwrap() + 1; + let left = mark.l[..relpos] + .iter() + .rev() + .take_while(_.1.as_ref() == Some(loc)) + .count(); + let start = start + relpos - left; + let length = mark.l[relpos..] + .iter() + .take_while(_.1.as_ref() == Some(loc)) + .count() + + left; + rang = Some([(start, y), (start + length, y)]); + TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: loc.uri.clone(), + }, + position: loc.range.start, + } + } + }; + if ctrl() { + if self + .def + .request + .as_ref() + .is_none_or(|&(_, x)| x != cursor_position) + { + let handle = cl.runtime.spawn(w.redraw_after(cl.request::<lsp_request!("textDocument/definition")>(&GotoDefinitionParams { + text_document_position_params: z.clone(), + work_done_progress_params: default(), + partial_result_params: default(), + }).unwrap().0)); + self.def.request = + Some((DropH::new(handle), cursor_position)); + } else if self.def.result.as_ref().is_some_and(|em| { + let z = em.origin_selection_range.unwrap(); + (z.start.character..z.end.character).contains( + &((cursor_position.0 + - text.line_number_offset() + - 1) as _), + ) + }) { + self.def.result = None; + } + } else { + self.def.result = None; + } + if let Some((_, c)) = self.hovering.request + && c == cursor_position + { + break 'out; + } + // if !running.insert(hover) {return} + let (rx, _) = cl + .request::<HoverRequest>(&HoverParams { + text_document_position_params: z, + work_done_progress_params: default(), + }) + .unwrap(); + // println!("rq hov of {hover:?} (cur {})", hovering.request.is_some()); + let handle: tokio::task::JoinHandle< + Result<Option<Hovr>, anyhow::Error>, + > = cl.runtime.spawn(w.redraw_after(async move { + let Some(x) = rx.await? else { + return Ok(None::<Hovr>); + }; + let (w, cells) = spawn_blocking(move || { + let x = match &x.contents { + lsp_types::HoverContents::Scalar( + marked_string, + ) => match marked_string { + MarkedString::LanguageString(x) => + Cow::Borrowed(&*x.value), + MarkedString::String(x) => + Cow::Borrowed(&**x), + }, + lsp_types::HoverContents::Array( + marked_strings, + ) => Cow::Owned( + marked_strings + .iter() + .map(|x| match x { + MarkedString::LanguageString( + x, + ) => &*x.value, + MarkedString::String(x) => &*x, + }) + .collect::<String>(), + ), + lsp_types::HoverContents::Markup( + markup_content, + ) => Cow::Borrowed(&*markup_content.value), + }; + let x = hov::p(&x).unwrap(); + let m = hov::l(&x) + .into_iter() + .max() + .map(_ + 2) + .unwrap_or(usize::MAX) + .min(c - 10); + (m, hov::markdown2(m, &x)) + }) + .await + .unwrap(); + let span = rang.or_else(|| { + x.range.and_then(|range| try { + let (startx, starty) = + text.l_pos_to_char(range.start)?; + let (endx, endy) = + text.l_pos_to_char(range.end)?; + let x1 = text + .reverse_source_map(starty)? + .nth(startx)?; + let x2 = text + .reverse_source_map(endy)? + .nth(endx)?; + [ + (x1, range.start.line as _), + (x2, range.start.line as _), + ] + }) + }); + anyhow::Ok(Some( + hov::Hovr { + span, + item: text::CellBuffer { + c: w, + vo: 0, + cells: cells.into(), + }, + } + .into(), + )) + })); + self.hovering.request = + (DropH::new(handle), cursor_position).into(); + // hovering.result = None; + // lsp!().map(|(cl, o)| { + // let window = window.clone(); + // }); + // }); + } + Some(Do::Hover) => { + self.def.result = None; + self.hovering.result = None; + w.request_redraw(); + } + None => {} + x => unreachable!("{x:?}"), + } + } + pub fn click( + &mut self, + bt: MouseButton, + cursor_position: (usize, usize), + w: Arc<Window>, + ) { + let text = &mut self.text; + _ = self.complete.consume(CompletionAction::Click).unwrap(); + match self.state.consume(Action::M(bt)).unwrap() { + Some(Do::MoveCursor) => { + text.cursor = text.mapped_index_at(cursor_position); + if let Some((lsp, path)) = lsp!(self + p) { + self.sig_help.request(lsp.runtime.spawn( + w.redraw_after( + lsp.request_sig_help(path, text.cursor()), + ), + )); + self.document_highlights.request(lsp.runtime.spawn( + w.redraw_after(lsp.document_highlights( + path, + text.to_l_position(text.cursor).unwrap(), + )), + )); + } + self.hist.last.cursor = text.cursor; + self.chist.push(text.cursor()); + text.setc(); + } + Some(Do::NavForward) => { + self.chist.forth().map(|x| { + text.cursor = text.rope.line_to_char(x.1) + x.0; + text.scroll_to_cursor(); + }); + } + Some(Do::NavBack) => { + self.chist.back().map(|x| { + text.cursor = text.rope.line_to_char(x.1) + x.0; + text.scroll_to_cursor(); + }); + } + Some(Do::ExtendSelectionToMouse) => { + *self.state.sel() = text.extend_selection_to( + text.mapped_index_at(cursor_position), + self.state.sel().clone(), + ); + } + Some(Do::StartSelection) => { + let x = text.mapped_index_at(cursor_position); + self.hist.last.cursor = x; + *self.state.sel() = + text.extend_selection_to(x, text.cursor..text.cursor); + } + Some(Do::GoToDefinition) => { + if let Some(LocationLink { + ref target_uri, + target_range, + .. + }) = self.def.result + && let Some(p) = self.origin.as_deref() + { + if target_uri == &p.tid().uri { + text.cursor = + text.l_position(target_range.start).unwrap(); + text.scroll_to_cursor(); + } + } + } + None => {} + _ => unreachable!(), + } + } + pub fn scroll(&mut self, rows: f32) { + let rows = if alt() { rows * 8. } else { rows * 3. }; + let (vo, max) = lower::saturating::math! { if let Some(x)= &mut self.hovering.result && shift() { + let n = x.item.l(); + (&mut x.item.vo, n - 15) + } else if let Some((_, ref mut vo, Some(max))) = self.sig_help.result && shift(){ + (vo, max - 15) + } else { + let n =self. text.l() - 1; (&mut self.text.vo, n) + }}; + if rows < 0.0 { + let rows = rows.ceil().abs() as usize; + *vo = (*vo + rows).min(max); + } else { + let rows = rows.floor() as usize; + *vo = vo.saturating_sub(rows); + } + inlay!(self); + } + pub fn keyboard( + &mut self, + event: KeyEvent, + window: &mut Arc<Window>, + ) -> ControlFlow<()> { + let mut o: Option<Do> = self + .state + .consume(Action::K(event.logical_key.clone())) + .unwrap(); + match o { + Some(Do::Reinsert) => + o = self + .state + .consume(Action::K(event.logical_key.clone())) + .unwrap(), + _ => {} + } + match o { + Some(Do::SpawnTerminal) => { + trm::toggle( + self.workspace + .as_deref() + .unwrap_or(Path::new("/home/os/")), + ); + } + Some(Do::MatchingBrace) => { + if let Some((l, f)) = lsp!(self + p) { + l.matching_brace(f, &mut self.text); + } + } + Some(Do::Symbols) => + if let Some(lsp) = lsp!(self) { + self.state = + State::Symbols(Rq::new(lsp.runtime.spawn( + window.redraw_after(lsp.symbols("".into())), + ))); + }, + Some(Do::SymbolsHandleKey) => { + if let Some(lsp) = lsp!(self) { + let State::Symbols(Rq { result: Some(x), request }) = + &mut self.state + else { + unreachable!() + }; + let ptedit = x.tedit.rope.clone(); + if handle2( + &event.logical_key, + &mut x.tedit, + lsp!(self + p), + ) + .is_some() + || ptedit != x.tedit.rope + { + *request = Some(( + DropH::new(lsp.runtime.spawn( + window.redraw_after( + lsp.symbols(x.tedit.rope.to_string()), + ), + )), + (), + )); + // state = State::Symbols(Rq::new(lsp.runtime.spawn(lsp.symbols("".into())))); + } + } + } + Some(Do::SymbolsSelectNext) => { + let State::Symbols(Rq { result: Some(x), .. }) = + &mut self.state + else { + unreachable!() + }; + x.next(); + } + Some(Do::SymbolsSelectPrev) => { + let State::Symbols(Rq { result: Some(x), .. }) = + &mut self.state + else { + unreachable!() + }; + x.back(); + } + Some(Do::SymbolsSelect) => { + let State::Symbols(Rq { result: Some(x), .. }) = + &mut self.state + else { + unreachable!() + }; + let x = x.sel(); // TODO dedup + let _: anyhow::Result<()> = try bikeshed _ { + let f = x + .location + .uri + .to_file_path() + .map_err(|()| anyhow::anyhow!("dammit"))? + .canonicalize()?; + self.origin = Some(f.clone()); + let r = self.text.r; + self.text = default(); + self.text.r = r; + let new = std::fs::read_to_string(f) + .map_err(anyhow::Error::from)?; + self.text.insert(&new)?; + self.text.cursor = self.text + .l_position(x.location.range.start) + .ok_or(anyhow::anyhow!("dangit"))?; + self.text.scroll_to_cursor_centering(); + self.hist = Hist { + history: vec![], + redo_history: vec![], + last: self.text.clone(), + last_edit: Instant::now(), + changed: false, + }; + self.complete = CompletionState::None; + self.mtime = Self::modify(self.origin.as_deref()); + + lsp!(self + p).map(|(x, origin)| { + ( + self.def, + self.semantic_tokens, + self.inlay, + self.sig_help, + self.complete, + self.hovering, + ) = ( + default(), + default(), + default(), + default(), + default(), + default(), + ); + x.open(&origin, new).unwrap(); + x.rq_semantic_tokens( + &mut self.semantic_tokens, + origin, + Some(window.clone()), + ) + .unwrap(); + }); + self.state = State::Default; + self.bar.last_action = "open".to_string(); + }; + } + Some(Do::CodeAction) => { + if let Some((lsp, f)) = lsp!(self + p) { + let r = lsp.request::<lsp_request!("textDocument/codeAction")>(&CodeActionParams { + text_document: f.tid(), range: self.text.to_l_range(self.text.cursor..self.text.cursor).unwrap(), context: CodeActionContext { + trigger_kind: Some(CodeActionTriggerKind::INVOKED), + // diagnostics: if let Some((lsp, p)) = lsp!() && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.diagnostics.get(&uri, &lsp.diagnostics.guard()) { dbg!(diag.iter().filter(|x| { + // self.text.l_range(x.range).unwrap().contains(&self.text.cursor) + // }).cloned().collect()) } else { vec![] }, + ..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 })); + self.state = State::CodeAction(r2); + } + } + Some(Do::CASelectLeft) => { + let State::CodeAction(Rq { result: Some(c), .. }) = + &mut self.state + else { + panic!() + }; + c.left(); + } + Some(Do::CASelectRight) => 'out: { + let Some((lsp, f)) = lsp!(self + p) else { + unreachable!() + }; + let State::CodeAction(Rq { result: Some(c), .. }) = + &mut self.state + else { + panic!() + }; + let Some(act) = c.right() else { break 'out }; + let act = act.clone(); + self.state = State::Default; + self.hist.last.cursor = self.text.cursor; + self.hist.test_push(&self.text); + let act = lsp + .runtime + .block_on( + lsp.request::<CodeActionResolveRequest>(&act) + .unwrap() + .0, + ) + .unwrap(); + let mut f_ = |edits: &[SnippetTextEdit]| { + // let mut first = false; + for edit in edits { + self.text.apply_snippet_tedit(edit).unwrap(); + } + }; + match act.edit { + Some(WorkspaceEdit { + document_changes: Some(DocumentChanges::Edits(x)), + .. + }) => + for x in x { + if x.text_document.uri != f.tid().uri { + continue; + } + f_(&x.edits); + }, + Some(WorkspaceEdit { + document_changes: + Some(DocumentChanges::Operations(x)), + .. + }) => { + for op in x { + match op { + DocumentChangeOperation::Edit( + TextDocumentEdit { + edits, + text_document, + .. + }, + ) => { + if text_document.uri != f.tid().uri { + continue; + } + f_(&edits); + } + 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 { + // self.text.apply(&x).unwrap(); + // } + } + } + _ => {} + } + change!(self); + self.hist.record(&self.text); + } + Some(Do::CASelectNext) => { + let State::CodeAction(Rq { result: Some(c), .. }) = + &mut self.state + else { + panic!() + }; + c.down(); + } + Some(Do::CASelectPrev) => { + let State::CodeAction(Rq { result: Some(c), .. }) = + &mut self.state + else { + panic!() + }; + c.up(); + } + Some( + Do::Reinsert + | Do::GoToDefinition + | Do::NavBack + | Do::NavForward, + ) => panic!(), + Some(Do::Save) => match &self.origin { + Some(x) => { + self.state.consume(Action::Saved).unwrap(); + self.save(); + } + None => { + self.state.consume(Action::RequireFilename).unwrap(); + } + }, + Some(Do::SaveTo(x)) => { + self.origin = Some(PathBuf::try_from(x).unwrap()); + self.save(); + } + Some(Do::Edit) => { + self.hist.test_push(&self.text); + let cb4 = self.text.cursor; + if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) = + event.logical_key + && let CompletionState::Complete(..) = self.complete + { + } else { + handle2( + &event.logical_key, + &mut self.text, + lsp!(self + p), + ); + } + self.text.scroll_to_cursor(); + inlay!(self); + if cb4 != self.text.cursor + && let CompletionState::Complete(Rq { + result: Some(c), + .. + }) = &self.complete + && ((self.text.cursor < c.start) + || (!super::is_word(self.text.at_()) + && (self.text.at_() != '.' + || self.text.at_() != ':'))) + { + self.complete = CompletionState::None; + } + if self.sig_help.running() + && cb4 != self.text.cursor + && let Some((lsp, path)) = lsp!(self + p) + { + self.sig_help.request(lsp.runtime.spawn( + window.redraw_after( + lsp.request_sig_help(path, self.text.cursor()), + ), + )); + } + if self.hist.record(&self.text) + && let Some((lsp, path)) = lsp!(self + p) + { + change!(self); + } + lsp!(self + p).map(|(lsp, o)| { + let window = window.clone(); + match event.logical_key.as_ref() { + Key::Character(y) + if let Some(x) = &lsp.initialized + && let Some(x) = &x + .capabilities + .signature_help_provider + && let Some(x) = &x.trigger_characters + && x.contains(&y.to_string()) => + { + self.sig_help.request(lsp.runtime.spawn( + window.redraw_after(lsp.request_sig_help( + o, + self.text.cursor(), + )), + )); + } + _ => {} + } + match self + .complete + .consume(CompletionAction::K( + event.logical_key.as_ref(), + )) + .unwrap() + { + Some(CDo::Request(ctx)) => { + let h = DropH::new(lsp.runtime.spawn( + window.redraw_after(lsp.request_complete( + o, + self.text.cursor(), + ctx, + )), + )); + let CompletionState::Complete(Rq { + request: x, + result: c, + }) = &mut self.complete + else { + panic!() + }; + *x = Some(( + h, + c.as_ref() + .map(|x| x.start) + .or(x.as_ref().map(|x| x.1)) + .unwrap_or(self.text.cursor), + )); + } + Some(CDo::SelectNext) => { + let CompletionState::Complete(Rq { + result: Some(c), + .. + }) = &mut self.complete + else { + panic!() + }; + c.next(&filter(&self.text)); + } + Some(CDo::SelectPrevious) => { + let CompletionState::Complete(Rq { + result: Some(c), + .. + }) = &mut self.complete + else { + panic!() + }; + c.back(&filter(&self.text)); + } + Some(CDo::Finish(x)) => { + let sel = x.sel(&filter(&self.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.clone() + else { + panic!() + }; + match insert_text_format { + Some(InsertTextFormat::SNIPPET) => { + self.text.apply_snippet(&ed).unwrap(); + } + _ => { + let (s, _) = + self.text.apply(&ed).unwrap(); + self.text.cursor = + s + ed.new_text.chars().count(); + } + } + for additional in + additional_text_edits.into_iter().flatten() + { + self.text + .apply_adjusting(&additional) + .unwrap(); + } + if self.hist.record(&self.text) { + change!(self); + } + self.sig_help = Rq::new(lsp.runtime.spawn( + window.redraw_after(lsp.request_sig_help( + o, + self.text.cursor(), + )), + )); + } + None => return, + }; + }); + } + Some(Do::Undo) => { + self.hist.test_push(&self.text); + self.hist.undo(&mut self.text); + self.bar.last_action = "undid".to_string(); + change!(self); + } + Some(Do::Redo) => { + self.hist.test_push(&self.text); + self.hist.redo(&mut self.text); + self.bar.last_action = "redid".to_string(); + change!(self); + } + Some(Do::Quit) => return ControlFlow::Break(()), + Some(Do::StartSelection) => { + let Key::Named(y) = event.logical_key else { panic!() }; + *self.state.sel() = self.text.extend_selection( + y, + self.text.cursor..self.text.cursor, + ); + } + Some(Do::UpdateSelection) => { + let Key::Named(y) = event.logical_key else { panic!() }; + *self.state.sel() = self + .text + .extend_selection(y, self.state.sel().clone()); + self.text.scroll_to_cursor(); + inlay!(self); + } + Some(Do::Insert(x, c)) => { + self.hist.push_if_changed(&self.text); + _ = self.text.remove(x.clone()); + self.text.cursor = x.start; + self.text.setc(); + self.text.insert(&c); + self.hist.push_if_changed(&self.text); + change!(self); + } + Some(Do::Delete(x)) => { + self.hist.push_if_changed(&self.text); + self.text.cursor = x.start; + _ = self.text.remove(x); + self.hist.push_if_changed(&self.text); + change!(self); + } + Some(Do::Copy(x)) => { + clipp::copy(self.text.rope.slice(x).to_string()); + } + Some(Do::Cut(x)) => { + self.hist.push_if_changed(&self.text); + clipp::copy(self.text.rope.slice(x.clone()).to_string()); + self.text.rope.remove(x.clone()); + self.text.cursor = x.start; + self.hist.push_if_changed(&self.text); + change!(self); + } + Some(Do::Paste) => { + self.hist.push_if_changed(&self.text); + self.text.insert(&clipp::paste()); + self.hist.push_if_changed(&self.text); + change!(self); + } + Some(Do::OpenFile(x)) => { + let _ = try { + self.origin = Some(PathBuf::from(&x).canonicalize()?); + self.text = TextArea::default(); + let new = std::fs::read_to_string(x)?; + self.text.insert(&new); + self.text.cursor = 0; + self.hist = Hist { + history: vec![], + redo_history: vec![], + last: self.text.clone(), + last_edit: Instant::now(), + changed: false, + }; + self.complete = CompletionState::None; + self.mtime = Self::modify(self.origin.as_deref()); + + lsp!(self + p).map(|(x, origin)| { + ( + self.def, + self.semantic_tokens, + self.inlay, + self.sig_help, + self.complete, + self.hovering, + ) = ( + default(), + default(), + default(), + default(), + default(), + default(), + ); + x.open(&origin, new).unwrap(); + x.rq_semantic_tokens( + &mut self.semantic_tokens, + origin, + Some(window.clone()), + ) + .unwrap(); + }); + self.bar.last_action = "open".to_string(); + }; + } + Some( + Do::MoveCursor | Do::ExtendSelectionToMouse | Do::Hover, + ) => { + unreachable!() + } + Some(Do::StartSearch(x)) => { + let s = Regex::new(&x).unwrap(); + let n = s + .find_iter(&self.text.rope.to_string()) + .enumerate() + .count(); + s.clone() + .find_iter(&self.text.rope.to_string()) + .enumerate() + .find(|(_, x)| x.start() > self.text.cursor) + .map(|(x, m)| { + self.state = State::Search(s, x, n); + self.text.cursor = + self.text.rope.byte_to_char(m.end()); + self.text.scroll_to_cursor_centering(); + inlay!(self); + }) + .unwrap_or_else(|| { + self.bar.last_action = "no matches".into() + }); + } + Some(Do::SearchChanged) => { + let (re, index, _) = self.state.search(); + let s = self.text.rope.to_string(); + let m = re.find_iter(&s).nth(*index).unwrap(); + self.text.cursor = self.text.rope.byte_to_char(m.end()); + self.text.scroll_to_cursor_centering(); + inlay!(self); + } + Some(Do::Boolean(BoolRequest::ReloadFile, true)) => { + self.text.rope = Rope::from_str( + &std::fs::read_to_string( + self.origin.as_ref().unwrap(), + ) + .unwrap(), + ); + self.text.cursor = + self.text.cursor.min(self.text.rope.len_chars()); + self.mtime = Self::modify(self.origin.as_deref()); + self.bar.last_action = "reloaded".into(); + } + Some(Do::Boolean(BoolRequest::ReloadFile, false)) => {} + None => {} + } + ControlFlow::Continue(()) + } } +use NamedKey::*; + +pub fn handle2<'a>( + key: &'a Key, + text: &mut TextArea, + l: Option<(&Client, &Path)>, +) -> Option<&'a str> { + use Key::*; -impl Editor {} + match key { + Named(Space) => text.insert(" ").unwrap(), + Named(Backspace) if ctrl() => text.backspace_word(), + Named(Backspace) => text.backspace(), + Named(Home) if ctrl() => { + text.cursor = 0; + text.vo = 0; + } + Named(End) if ctrl() => { + text.cursor = text.rope.len_chars(); + text.vo = text.l().saturating_sub(text.r); + } + Named(Home) => text.home(), + Named(End) => text.end(), + Named(Tab) => text.tab(), + Named(Delete) => { + text.right(); + text.backspace() + } + Named(ArrowLeft) if ctrl() => text.word_left(), + Named(ArrowRight) if ctrl() => text.word_right(), + Named(ArrowLeft) => text.left(), + Named(ArrowRight) => text.right(), + Named(ArrowUp) => text.up(), + Named(ArrowDown) => text.down(), + Named(PageDown) => text.page_down(), + Named(PageUp) => text.page_up(), + Named(Enter) if let Some((l, p)) = l => l.enter(p, text), + Named(Enter) => text.enter(), + Character(x) => { + text.insert(&x); + return Some(x); + } + _ => {} + }; + None +} + +impl State { + fn sel(&mut self) -> &mut std::ops::Range<usize> { + let State::Selection(x) = self else { panic!() }; + x + } + fn search(&mut self) -> (&mut Regex, &mut usize, &mut usize) { + let State::Search(x, y, z) = self else { panic!() }; + (x, y, z) + } +} diff --git a/src/edi/st.rs b/src/edi/st.rs new file mode 100644 index 0000000..f487a0e --- /dev/null +++ b/src/edi/st.rs @@ -0,0 +1,118 @@ +use std::ops::Range; + +use Default::default; +use NamedKey::*; +use lsp_types::*; +use regex::Regex; +use winit::event::MouseButton; +use winit::keyboard::{Key, NamedKey, SmolStr}; + +use crate::lsp::{RequestError, Rq, RqS}; +use crate::sym::Symbols; +use crate::text::TextArea; +use crate::{ + BoolRequest, CLICKING, InputRequest, act, ctrl, handle, shift, +}; + +impl Default for State { + fn default() -> Self { + Self::Default + } +} +rust_fsm::state_machine! { +#[derive(Debug)] +pub(crate) State => Action => Do + +Dead => K(Key => _) => Dead, +Default => { + K(Key::Character(x) if x == "s" && ctrl()) => Save [Save], + K(Key::Character(x) if x == "q" && ctrl()) => Dead [Quit], + K(Key::Character(x) if x == "v" && ctrl()) => _ [Paste], + K(Key::Character(x) if x == "z" && ctrl()) => _ [Undo], + K(Key::Character(x) if x == "y" && ctrl()) => _ [Redo], + 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 == "l" && ctrl()) => _ [Symbols], + K(Key::Character(x) if x == "." && ctrl()) => _ [CodeAction], + K(Key::Character(x) if x == "0" && ctrl()) => _ [MatchingBrace], + K(Key::Character(x) if x == "`" && ctrl()) => _ [SpawnTerminal], + K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection], + M(MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection], + M(MouseButton::Left if ctrl()) => _ [GoToDefinition], + M(MouseButton::Left) => _ [MoveCursor], + M(MouseButton::Back) => _ [NavBack], + M(MouseButton::Forward) => _ [NavForward], + C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection(0..0) [StartSelection], + Changed => RequestBoolean(BoolRequest => BoolRequest::ReloadFile), + C(_) => _ [Hover], + K(_) => _ [Edit], + M(_) => _, +}, +Symbols(Rq { result: Some(_x), request: None }) => { + K(Key::Named(Tab) if shift()) => _ [SymbolsSelectNext], + K(Key::Named(ArrowDown)) => _ [SymbolsSelectNext], + K(Key::Named(ArrowUp | Tab)) => _ [SymbolsSelectPrev], + K(Key::Named(Enter)) => _ [SymbolsSelect], + K(Key::Named(Escape)) => Default, + K(_) => _ [SymbolsHandleKey], +}, +Symbols(Rq::<Symbols, Vec<SymbolInformation>, (), RequestError<lsp_request!("workspace/symbol")>> => _rq) => { + K(Key::Named(Escape)) => Default, + C(_) => _, + M(_) => _, + K(_) => _, +}, +CodeAction(Rq { result : Some(_x), request }) => { + K(Key::Named(Tab) if shift()) => _ [CASelectPrev], + K(Key::Named(ArrowDown | Tab)) => _ [CASelectNext], + K(Key::Named(ArrowUp)) => _ [CASelectPrev], + K(Key::Named(Enter | ArrowRight)) => _ [CASelectRight], + K(Key::Named(ArrowLeft)) => _ [CASelectLeft], +}, +CodeAction(RqS<act::CodeActions, lsp_request!("textDocument/codeAction")> => 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], +}, // note: it does in fact fall through. this syntax is not an arm, merely shorthand. +Selection(x) => { + C(_ if unsafe { CLICKING }) => _ [ExtendSelectionToMouse], + C(_) => Selection(x), + M(MouseButton => MouseButton::Left) => Default [MoveCursor], + K(Key::Named(Backspace)) => Default [Delete(Range<usize> => x)], + K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut(Range<usize> => x)], + K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy(Range<usize> => x)], + K(Key::Character(y)) => Default [Insert((Range<usize>, SmolStr) => (x, y))], + K(_) => Default [Edit], +}, +Save => { + RequireFilename => Procure((TextArea, InputRequest) => (default(), InputRequest::SaveFile)), + Saved => Default, +}, +Procure((_, _)) => K(Key::Named(Escape)) => Default, +Procure((t, InputRequest::Search)) => K(Key::Named(Enter)) => Default [StartSearch(String => t.rope.to_string())], +Procure((t, InputRequest::SaveFile)) => K(Key::Named(Enter)) => Default [SaveTo(String => t.rope.to_string())], +Procure((t, InputRequest::OpenFile)) => K(Key::Named(Enter)) => Default [OpenFile(String => t.rope.to_string())], +Procure((t, a)) => K(k) => Procure((handle(k, t), a)), +RequestBoolean(t) => { + K(Key::Character(x) if x == "y") => Default [Boolean((BoolRequest, bool) => (t, true))], + K(Key::Character(x) if x == "n") => Default [Boolean((t, false))], + K(Key::Named(Escape)) => Default [Boolean((t, false))], + K(_) => RequestBoolean(t), + C(_) => _, + Changed => _, + M(_) => _, +}, +Search((x, y, m)) => { + M(MouseButton::Left) => Default [MoveCursor], + C(_) => Search((x, y, m)), + K(Key::Named(Enter) if shift()) => Search((x, y.checked_sub(1).unwrap_or(m-1), m)) [SearchChanged], + K(Key::Named(Enter)) => Search((Regex, usize, usize) => (x, (y+ 1) % m, m)) [SearchChanged], + K(_) => Default [Reinsert], +} +} @@ -141,7 +141,11 @@ impl Client { x.result.clone().unwrap_or_default(), ) .unwrap_or_else(|_| { - panic!("lsp failure for {x:?}\ndidnt follow spec for {}\npossibly spec issue", X::METHOD) + panic!( + "lsp failure for {x:?}\ndidnt follow spec \ + for {}\npossibly spec issue", + X::METHOD + ) })) } }, @@ -355,15 +359,28 @@ impl Client { } } } - pub fn document_highlights<'me>(&'me self, f: &Path, cursor: Position) -> impl Future<Output = Result<Vec<DocumentHighlight>, RequestError<DocumentHighlightRequest>>> + use<'me> { + pub fn document_highlights<'me>( + &'me self, + f: &Path, + cursor: Position, + ) -> impl Future< + Output = Result< + Vec<DocumentHighlight>, + RequestError<DocumentHighlightRequest>, + >, + > + use<'me> { let p = DocumentHighlightParams { - text_document_position_params: TextDocumentPositionParams { text_document: f.tid(), position: cursor }, + text_document_position_params: TextDocumentPositionParams { + text_document: f.tid(), + position: cursor, + }, work_done_progress_params: default(), partial_result_params: default(), }; - self.request::<lsp_request!("textDocument/documentHighlight")>(&p).unwrap().0.map(|x| { - x.map(|x| x.unwrap_or_default()) - }) + self.request::<lsp_request!("textDocument/documentHighlight")>(&p) + .unwrap() + .0 + .map(|x| x.map(|x| x.unwrap_or_default())) } pub fn symbols( &'static self, @@ -398,11 +415,21 @@ impl Client { }) }) } - pub fn matching_brace<'a>(&'static self, f: &Path, t: &'a mut TextArea) { - if let Ok(Some([x])) = self.runtime.block_on(self.request::<lsp_request!("experimental/matchingBrace")>(&MatchingBraceParams { - text_document: f.tid(), - positions: vec![t.to_l_position(t.cursor).unwrap()], - }).unwrap().0) { + pub fn matching_brace<'a>( + &'static self, + f: &Path, + t: &'a mut TextArea, + ) { + if let Ok(Some([x])) = self.runtime.block_on( + self.request::<lsp_request!("experimental/matchingBrace")>( + &MatchingBraceParams { + text_document: f.tid(), + positions: vec![t.to_l_position(t.cursor).unwrap()], + }, + ) + .unwrap() + .0, + ) { t.cursor = t.l_position(x).unwrap(); } } @@ -438,12 +465,31 @@ impl Client { // } // } } - pub fn format(&'static self, f: &Path) -> impl Future<Output = Result<<Formatting as Request>::Result, RequestError<Formatting>>> { - self.request::<lsp_request!("textDocument/formatting")>(&DocumentFormattingParams { - text_document: f.tid(), - options: FormattingOptions { tab_size: 4, insert_spaces: false, properties: default(), trim_trailing_whitespace: Some(true), insert_final_newline: Some(true), trim_final_newlines: Some(false), }, - work_done_progress_params: default(), - }).unwrap().0 + pub fn format( + &'static self, + f: &Path, + ) -> impl Future< + Output = Result< + <Formatting as Request>::Result, + RequestError<Formatting>, + >, + > { + self.request::<lsp_request!("textDocument/formatting")>( + &DocumentFormattingParams { + text_document: f.tid(), + options: FormattingOptions { + tab_size: 4, + insert_spaces: false, + properties: default(), + trim_trailing_whitespace: Some(true), + insert_final_newline: Some(true), + trim_final_newlines: Some(false), + }, + work_done_progress_params: default(), + }, + ) + .unwrap() + .0 } pub fn rq_semantic_tokens( &'static self, @@ -488,18 +534,26 @@ impl Client { } pub fn enter<'a>(&self, f: &Path, t: &'a mut TextArea) { - let r = self.runtime.block_on(self.request::<lsp_request!("experimental/onEnter")>(&TextDocumentPositionParams { - text_document: f.tid(), - position: t.to_l_position(t.cursor).unwrap(), - }).unwrap().0).unwrap(); + let r = self + .runtime + .block_on( + self.request::<lsp_request!("experimental/onEnter")>( + &TextDocumentPositionParams { + text_document: f.tid(), + position: t.to_l_position(t.cursor).unwrap(), + }, + ) + .unwrap() + .0, + ) + .unwrap(); match r { None => t.enter(), - Some(r) => { + Some(r) => for f in r { t.apply_snippet_tedit(&f).unwrap(); - } - } - } + }, + } } } pub fn run( @@ -957,7 +1011,10 @@ fn x33() { let y = serde_json::from_str::<SemanticTokensParams>(&y).unwrap(); } #[pin_project::pin_project] -pub struct Map<T, U, F: FnMut(T) -> U, Fu: Future<Output = T>>(#[pin] Fu, F); +pub struct Map<T, U, F: FnMut(T) -> U, Fu: Future<Output = T>>( + #[pin] Fu, + F, +); impl<T, F: FnMut(T) -> U, U, Fu: Future<Output = T>> Future for Map<T, U, F, Fu> { @@ -1009,7 +1066,7 @@ impl<T> OnceOff<T> { } } -impl<R:Request> std::fmt::Debug for RequestError<R> { +impl<R: Request> std::fmt::Debug for RequestError<R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self, f) } diff --git a/src/main.rs b/src/main.rs index 8acf0bd..699d6f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -// this looks pretty good though #![feature(tuple_trait, unboxed_closures, fn_traits)] #![feature( anonymous_lifetime_in_impl_trait, @@ -33,55 +32,39 @@ try_blocks, portable_simd )] -#![allow(incomplete_features, redundant_semicolons)] -use std::borrow::Cow; -use std::iter::once; +#![allow(incomplete_features, irrefutable_let_patterns)] mod act; +mod edi; +mod rnd; mod sym; mod trm; -mod edi; + use std::num::NonZeroU32; -use std::os::fd::AsFd; -use std::path::{Path, PathBuf}; use std::sync::LazyLock; use std::time::Instant; -use crate::text::CoerceOption; + use Default::default; use NamedKey::*; -use atools::prelude::AASAdd; use diff_match_patch_rs::PatchInput; -use diff_match_patch_rs::traits::DType; use dsb::cell::Style; -use dsb::{Cell, F, Fonts}; -use fimg::pixels::Blend; -use fimg::{Image, OverlayAt}; +use dsb::{Cell, F}; +use fimg::Image; use lsp::{PathURI, Rq}; -use lsp_server::{Connection, Request as LRq}; -use lsp_types::request::{ - CodeActionResolveRequest, HoverRequest, SignatureHelpRequest, -}; use lsp_types::*; -use regex::Regex; -use ropey::Rope; use rust_fsm::StateMachine; use swash::{FontRef, Instance}; -use tokio::task::spawn_blocking; -use tokio_util::task::AbortOnDropHandle as DropH; -use url::Url; -use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::event::{ ElementState, Event, Ime, MouseButton, MouseScrollDelta, WindowEvent, }; use winit::event_loop::{ControlFlow, EventLoop}; -use winit::keyboard::{Key, ModifiersState, NamedKey, SmolStr}; +use winit::keyboard::{Key, ModifiersState, NamedKey}; use winit::platform::wayland::WindowAttributesExtWayland; use winit::window::Icon; -use crate::bar::Bar; -use crate::hov::Hovr; -use crate::lsp::{Client, RedrawAfter, RequestError, RqS}; -use crate::sym::Symbols; -use crate::text::{Diff, Mapping, TextArea, col, is_word}; +use crate::edi::Editor; +use crate::edi::st::*; +use crate::lsp::RqS; +use crate::text::{Diff, TextArea, col, is_word}; mod bar; pub mod com; pub mod hov; @@ -95,6 +78,7 @@ fn main() { // let x = HashMap::new(); unsafe { std::env::set_var("CARGO_UNSTABLE_RUSTC_UNICODE", "true") }; env_logger::init(); + // lsp::x(); entry(EventLoop::new().unwrap()) } @@ -106,15 +90,17 @@ struct Hist { pub last: TextArea, pub last_edit: std::time::Instant, pub changed: bool, -}impl Default for Hist{ +} +impl Default for Hist { fn default() -> Self { - Self{ - history: vec![], - redo_history: vec![], - last: TextArea::default(), - last_edit: Instant::now(), - changed: false, - } } + Self { + history: vec![], + redo_history: vec![], + last: TextArea::default(), + last_edit: Instant::now(), + changed: false, + } + } } #[derive(Debug, Default)] struct ClickHistory { @@ -216,11 +202,9 @@ const BORDER: [u8; 3] = col!("#ffffff"); pub(crate) fn entry(event_loop: EventLoop<()>) { let ppem = 20.0; let ls = 20.0; - let mut text = TextArea::default(); - let mut origin = std::env::args() - .nth(1) - .and_then(|x| PathBuf::try_from(x).ok()) - .and_then(|x| x.canonicalize().ok()); + let ed = Editor::new(); + let ed = Box::leak(Box::new(ed)); + let mut fonts = dsb::Fonts::new( F::FontRef(*FONT, &[(2003265652, 550.0)]), F::instance(*FONT, *BFONT), @@ -228,148 +212,12 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { F::instance(*IFONT, *BIFONT), ); let mut cursor_position = (0, 0); - - let mut state = State::Default; - let mut bar = Bar { last_action: String::default() }; let mut i = Image::build(1, 1).fill(BG); let mut cells = vec![]; - std::env::args().nth(1).map(|x| { - text.insert(&std::fs::read_to_string(x).unwrap()); - text.cursor = 0; - }); - fn rooter(x: &Path) -> Option<PathBuf> { - for f in std::fs::read_dir(&x).unwrap().filter_map(Result::ok) { - if f.file_name() == "Cargo.toml" { - return Some(f.path().with_file_name("").to_path_buf()); - } - } - x.parent().and_then(rooter) - } - - let workspace = origin - .as_ref() - .and_then(|x| rooter(&x.parent().unwrap())) - .and_then(|x| x.canonicalize().ok()); - let tree = workspace.as_ref().map(|x| { - walkdir::WalkDir::new(x).into_iter().flatten().filter(|x| x.path().extension().is_some_and(_ == "rs") ).map(|x| { - x.path().to_owned() - }).collect::<Vec<_>>() - }); - let c = workspace.as_ref().zip(origin.clone()).map( - |(workspace, origin)| { - let dh = std::panic::take_hook(); - let main = std::thread::current_id(); - // let mut c = Command::new("rust-analyzer") - // .stdin(Stdio::piped()) - // .stdout(Stdio::piped()) - // .stderr(Stdio::inherit()) - // .spawn() - // .unwrap(); - let (a, b) = Connection::memory(); - std::thread::Builder::new() - .name("Rust Analyzer".into()) - .stack_size(1024 * 1024 * 8) - .spawn(move || { - let ra = std::thread::current_id(); - std::panic::set_hook(Box::new(move |info| { - // iz - if std::thread::current_id() == main { - dh(info); - } else if std::thread::current_id() == ra - || std::thread::current() - .name() - .is_some_and(|x| x.starts_with("RA")) - { - println!( - "RA panic @ {}", - info.location().unwrap() - ); - } - })); - rust_analyzer::bin::run_server(b) - }) - .unwrap(); - let (c, t2, changed) = lsp::run( - (a.sender, a.receiver), - // lsp_server::stdio::stdio_transport( - // BufReader::new(c.stdout.take().unwrap()), - // c.stdin.take().unwrap(), - // ), - WorkspaceFolder { - uri: Url::from_file_path(&workspace).unwrap(), - name: workspace - .file_name() - .unwrap() - .to_string_lossy() - .into_owned(), - }, - ); - c.open(&origin, std::fs::read_to_string(&origin).unwrap()) - .unwrap(); - (&*Box::leak(Box::new(c)), (t2), changed) - }, - ); - let (lsp, _t, mut w) = match c { - Some((a, b, c)) => (Some(a), Some(b), Some(c)), - None => (None, None, None), - }; - macro_rules! lsp { - () => { - lsp.zip(origin.as_deref()) - }; - } - let mut hovering = - Rq::<Hovr, Option<Hovr>, (usize, usize), anyhow::Error>::default(); - let mut document_highlights: Rq<Vec<DocumentHighlight>, _, _, _> = Rq::default(); - let mut complete = CompletionState::None; - let mut sig_help = // vo, lines - RqS::<(SignatureHelp, usize, Option<usize>), SignatureHelpRequest, ()>::default(); - let mut semantic_tokens = default(); - let mut diag = - Rq::<String, Option<String>, (), anyhow::Error>::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::<( - // JoinHandle< - // Result< - // Option<CompletionResponse>, - // tokio::sync::oneshot::error::RecvError, - // >, - // >, - // (usize, usize), - // )>; - // let mut hl_result = None; - let mut chist = ClickHistory::default(); - let mut hist = Hist { - history: vec![], - redo_history: vec![], - last: text.clone(), - last_edit: Instant::now(), - changed: false, + let mut w = match &mut ed.lsp { + Some((.., c)) => c.take(), + None => None, }; - macro_rules! modify { - () => { - origin - .as_ref() - .map(|x| x.metadata().unwrap().modified().unwrap()) - }; - } - - lsp!().map(|(x, origin)| { - x.rq_semantic_tokens(&mut semantic_tokens, origin, None).unwrap() - }); - let mut mtime: Option<std::time::SystemTime> = modify!(); let app = winit_app::WinitAppBuilder::with_init( move |elwt| { @@ -377,8 +225,14 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { x.with_title("gracilaria") .with_decorations(false) .with_name("com.bendn.gracilaria", "") - .with_window_icon(Some(Icon::from_rgba(include_bytes!("../dist/icon-32").to_vec(), 32, 32).unwrap())) - + .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(); @@ -397,43 +251,6 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { ) .with_event_handler( move |(window, _context), surface, event, elwt| { - macro_rules! inlay { - () => { - lsp!().map(|(lsp, path)| inlay.request(lsp.runtime.spawn(window.redraw_after(lsp.inlay(path, &text))))); - }; - } - macro_rules! change { - () => { - lsp!().map(|(x, origin)| { - x.edit(&origin, text.rope.to_string()).unwrap(); - x.rq_semantic_tokens(&mut semantic_tokens, origin, Some(window.clone())).unwrap(); - inlay!(); - }); - }; - } - macro_rules! save { - () => {{ - let t = text.rope.to_string(); - std::fs::write(origin.as_ref().unwrap(), &t).unwrap(); - bar.last_action = "saved".into(); - lsp!().map(|(l, o)| { - let v = l.runtime.block_on(l.format(o)).unwrap(); - for v in v.coerce() { - text.apply_adjusting(&v); - } - change!(); - hist.push(&text); - l.notify::<lsp_notification!("textDocument/didSave")>( - &DidSaveTextDocumentParams { - text_document: o.tid(), - text: Some(t), - }, - ) - .unwrap(); - }); - mtime = modify!(); - }}; - } elwt.set_control_flow(ControlFlow::Wait); let (fw, fh) = dsb::dims(&FONT, ppem); let (c, r) = dsb::fit( @@ -445,83 +262,16 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { window.inner_size().height as _, ), ); - if modify!() != mtime { - mtime = modify!(); - state.consume(Action::Changed).unwrap(); + if let t = Editor::modify(ed.origin.as_deref()) + && t != ed.mtime + { + ed.mtime = t; + ed.state.consume(Action::Changed).unwrap(); window.request_redraw(); } - if let Some((l, o)) = lsp!() { - for rq in l.req_rx.try_iter() { - match rq { - LRq { method: "workspace/diagnostic/refresh", .. } => { - // let x = l.pull_diag(o.into(), diag.result.clone()); - // diag.request(l.runtime.spawn(x)); - }, - rq => - log::debug!("discarding request {rq:?}"), - } - } - let r = &l.runtime; - inlay.poll(|x, p| x.ok().or(p.1).inspect(|x| { - text.set_inlay(x); - }), r); - document_highlights.poll(|x, _| { - x.ok() - }, r); - diag.poll(|x, _|x.ok().flatten(), r); - if let CompletionState::Complete(rq)= &mut complete { - rq.poll(|f, (c,_)| { - f.ok().flatten().map(|x| {Complete {r:x,start:c,selection:0,vo:0,}}) - }, r); - }; - - if let State::Symbols(x) = &mut state { - x.poll(|x, (_, p)| x.ok().map(|r| { - let tree = tree.as_deref().unwrap().iter().map(|x| { - SymbolInformation{ name: x.file_name().unwrap().to_str().unwrap().to_string() - ,kind: SymbolKind::FILE,location: Location { - range: lsp_types::Range{end:Position::default(), start:Position::default()}, - uri: Url::from_file_path(&x).unwrap(), - - }, container_name: None,deprecated: None, - tags: None, } - }); - sym::Symbols { - tedit: p.map(_.tedit).unwrap_or_default(), - r: tree.chain(r).collect(),..default() // dont care about previous selection - } - }), &l.runtime); - } - if let State::CodeAction(x) = &mut state { - x.poll(|x, _| { - let lems: Vec<CodeAction> = x.ok()??.into_iter().map(|x| match x { - CodeActionOrCommand::CodeAction(x) => x, - _ => panic!("alas we dont like these"), - }).collect(); - if lems.is_empty() { - bar.last_action = "no code actions available".into(); - None - } else { - Some(act::CodeActions::new(lems)) - } - },&l.runtime); - } - def.poll(|x, _| - x.ok().flatten().and_then(|x| match &x { - GotoDefinitionResponse::Link([x, ..]) => Some(x.clone()), - _ => None, - }) - , &l.runtime); - semantic_tokens.poll(|x, _| x.ok(), &l.runtime); - sig_help.poll(|x, ((), y)| x.ok().flatten().map(|x| { - if let Some((old_sig, vo, max)) = y && &sig::active(&old_sig) == &sig::active(&x){ - (x, vo, max) - } else { - (x, 0, None) - } - }), &l.runtime); - hovering.poll(|x, _| x.ok().flatten(), &l.runtime); - } match event { + ed.poll(); + + match event { Event::AboutToWait => {} Event::WindowEvent { window_id, @@ -555,462 +305,36 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { ] } } - Event::WindowEvent { event: WindowEvent::Ime(Ime::Preedit(x, y)), .. } => {} - Event::WindowEvent { event: WindowEvent::Ime(Ime::Commit(x)), .. } => { - text.insert(&x); + Event::WindowEvent { + event: WindowEvent::Ime(Ime::Preedit(..)), + .. + } => {} + Event::WindowEvent { + event: WindowEvent::Ime(Ime::Commit(x)), + .. + } => { + ed.text.insert(&x); window.request_redraw(); } Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, } if window_id == window.id() => { - {let (cx, cy) = text.cursor_visual(); - let met = FONT.metrics(&[]); - let fac = ppem / met.units_per_em as f32; - window.set_ime_cursor_area( - PhysicalPosition::new( - ((cx + text.line_number_offset()) as f64 * (fw) as f64).round(), - ((cy.saturating_sub(text.vo)) as f64 * (fh + ls * fac) as f64).floor()) - , PhysicalSize::new(fw, fh) - )}; - let Some(surface) = surface else { - eprintln!( - "RedrawRequested fired before Resumed or \ - after Suspended" - ); - return; - }; - let size = window.inner_size(); - - if size.height != 0 && size.width != 0 { - let now = Instant::now(); - if c*r!=cells.len(){ - return; - } - cells.fill(Cell { - style: Style { fg: BG, secondary_color: BG, bg: BG, flags: 0 }, - letter: None, - }); - let x = match &state { - State::Selection(x) => Some(x.clone()), - _ => None, - }; - text.line_numbers( - (c, r - 1), - [67, 76, 87], - BG, - (&mut cells, (c, r)), - (1, 0), - ); - let t_ox = text.line_number_offset() + 1; - text.c = c - t_ox; - text.r = r - 1; - // let mut text = text.clone(); - // for (_, inlay) in inlay.result.as_ref().into_iter().flatten().chunk_by(|x| x.position.line).into_iter() { - // let mut off = 0; - // for inlay in inlay { - // let label = match &inlay.label { - // InlayHintLabel::String(x) => x.clone(), - // InlayHintLabel::LabelParts(v) => { - // v.iter().map(_.value.clone()).collect::<String>() - // }, - // }; - // text.rope.insert(text.l_position(inlay.position).unwrap() + off, &label); - // off += label.chars().count(); - // } - // } - - text.write_to( - (&mut cells, (c, r)), - (t_ox, 0), - x, - |(_c, _r), text, mut x| { - if let Some(hl) = &document_highlights.result { - for DocumentHighlight { range: r, .. } in hl { - // let s = match kind { - // Some(DocumentHighlightKind::READ) => Style::UNDERLINE, - // Some(DocumentHighlightKind::WRITE) => Style::UNDERLINE, - // _ => Style::UNDERCURL, - // }; - let (x1, y1) = text.map_to_visual((r.start.character as _, r.start.line as _)); - let (x2, y2) = text.map_to_visual((r.end.character as _, r.end.line as _)); - x.get_simple((x1, y1), (x2, y2)).coerce().for_each(|x| { - x.style.bg = col!("#3a4358"); - }); - } - } - if let Some(LocationLink { - origin_selection_range: Some(r), .. - }) = def.result { _ = try { - let (x1, y1) = text.map_to_visual((r.start.character as _, r.start.line as _)); - let (x2, y2) = text.map_to_visual((r.end.character as _, r.end.line as _)); - x.get_simple((x1, y1), (x2, y2))?.iter_mut().for_each(|x| { - x.style.flags |= Style::UNDERLINE; - x.style.fg = col!("#FFD173"); - }); - } } - if let Some((lsp, p)) = lsp!() && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.diagnostics.get(&uri, &lsp.diagnostics.guard()) { - #[derive(Copy, Clone, Debug)] - enum EType { - Hint, Info, Error,Warning,Related(DiagnosticSeverity), - } - let mut occupied = vec![]; - 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, - _ => EType::Info, - }; - once((diag.range, &*diag.message, sev_)).chain(diag.related_information.iter().flatten().filter(|sp| sp.location.uri == uri).map(move |x| { - (x.location.range, &*x.message, EType::Related(sev)) - })) - }).for_each(|(mut r, m, sev)| { - if let EType::Related(x) = sev && x != DiagnosticSeverity::ERROR { - return; - } - let p = r.start.line; - while occupied.contains(&r.start.line) { - r.start.line+=1; - }; - occupied.push(r.start.line); - let f = |cell:&mut Cell| { - cell.style.bg.blend(match sev { - EType::Error => col!("#ff66662c"), - EType::Warning | EType::Hint | EType::Info => col!("#9469242c"), - EType::Related(DiagnosticSeverity::ERROR) => col!("#dfbfff26"), - EType::Related(_) => col!("#ffad6625"), - }); - }; - if r.start == r.end { - x.get(text.map_to_visual((r.start.character as _, p as _))).map(f); - } else { - x.get_range(text.map_to_visual((r.start.character as _, p as _)), - text.map_to_visual((r.end.character as usize, r.end.line as _))) - .for_each(f) - } - let l = r.start.line as usize; - let Some(x_) = text.visual_eol(l).map(_+2) else { - return; - }; - let m = m.lines().next().unwrap_or(m); - x.get_range( - (x_, l), - (x_ + m.chars().count(), l), - ).zip(m.chars()).for_each(|(x, ch)| { - let (bg, fg) = match sev { - EType::Warning => { col!("#ff942f1b", "#fa973a") }, - EType::Error => { col!("#ff942f1b", "#f26462") }, - EType::Related(DiagnosticSeverity::WARNING) => { col!("#dfbfff26", "#DFBFFF") } - _ => return - }; - x.style.bg.blend(bg); - x.style.fg = fg; - x.letter = Some(ch); - }) - }); - } - if let State::Search(re, j, _) = &state { - re.find_iter(&text.rope.to_string()) - .enumerate() - .for_each(|(i, m)| { - for x in x.get_range( - text.map_to_visual(text.xy(text.rope.byte_to_char(m.start())).unwrap()),text.map_to_visual( text.xy(text - .rope - .byte_to_char( - m.end(), - )).unwrap())) - { - x.style.bg = if i == *j { - [105, 83, 128] - } else { - [65, 62, 83] - } - } - }); - } - }, - origin.as_deref(), - semantic_tokens.result.as_deref().zip( - match lsp { - Some(lsp::Client { initialized: Some(lsp_types::InitializeResult { - capabilities: ServerCapabilities { - semantic_tokens_provider: - Some(SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions{ - legend,.. - })),.. - }, .. - }), .. - }) => Some(legend), - _ => None, - }), - ); - - bar.write_to( - BG, - FG, - (&mut cells, (c, r)), - r - 1, - origin - .as_ref() - .map(|x| workspace.as_ref().and_then(|w| x.strip_prefix(w).ok()).unwrap_or(&x).to_str().unwrap()) - .unwrap_or("new buffer"), - &state, - &text, - lsp - ); - unsafe { - dsb::render( - &cells, - (c, r), - ppem, - &mut fonts, - ls, - true, - i.as_mut(),(0,0) - ) - }; - - let place_around = |(_x, _y): (usize, usize), fonts: &mut Fonts,mut i:Image<&mut [u8], 3> ,c: &[Cell],columns:usize, ppem_:f32,ls_:f32, ox:f32, oy: f32, toy: f32| { - let met = FONT.metrics(&[]); - let fac = ppem / met.units_per_em as f32; - let position = ( - (((_x) as f32 * fw).round() + ox) as usize, - (((_y) as f32 * (fh + ls * fac)).round() + oy) as usize, - ); - - 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)); - // std::fs::write("cells", Cell::store(c)); - - if w >= size.width as usize - || (position.1 + h >= size.height as usize && !position.1.checked_sub(h).is_some()) - || position.1 >= size.height as usize - || position.0 >= size.width as usize - { - unsafe { dsb::render_owned(c, (columns, c.len() / columns), ppem, fonts, ls, true).save("fail.png") }; - 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); - let (_, y) = dsb::fit(&fonts.regular, ppem, ls, (window.inner_size().width as _ /* - left */,((window.inner_size().height as usize).saturating_sub( top)) )); /* suspicious saturation */ - r = r.min(y); - - let left = - if position.0 + w as usize > window.inner_size().width as usize { - window.inner_size().width as usize- w as usize - } else { position.0 }; - - let (w, h) = dsb::size(&fonts.regular, ppem, ls, (columns, r)); - unsafe{ dsb::render( - &c, - (columns, 0), - ppem, - fonts, - ls, - true, - i.copy(), - (left as _, top as _) - )}; - 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()) { - 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 { - 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); - for b in x.replace('\n', "\r\n").replace("⸬", ":").replace("/home/os", "").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 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); - }, - _ => {} - } - } - }; - hovering.result.as_ref().filter(|_|pass).map(|x| x.span.clone().map(|[(_x, _y),(_x2, _)]| { - // let [(_x, _y), (_x2, _)] = text.position(sp); - // dbg!(x..=x2, cursor_position.0) - // if !(_x..=_x2).contains(&&(cursor_position.0 .wrapping_sub( text.line_number_offset()+1))) { - // return - // } - - let [_x, _x2] = [_x, _x2].add(text.line_number_offset()+1); - let Some(_y) = _y.checked_sub(text.vo) else { - return; - }; - - // if !(cursor_position.1 == _y && (_x..=_x2).contains(&cursor_position.0)) { - // return; - // } - - let r = x.item.l().min(15); - let c = x.item.displayable(r); - 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); - 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}; - i.r#box((left .saturating_sub(1) as _, top.saturating_sub(1) as _), w as _,h as _, BORDER); - }, - State::Symbols(Rq { result: Some(x),..}) => 'out: { - let ws = workspace.as_deref().unwrap(); - let c = x.cells(50,ws); - // let (_x, _y) = text.cursor_visual(); - let _x = 0; - let _y = r - 1; - let Ok((is_above,left, top, w, mut h)) = place_around((_x, _y), &mut fonts, i.as_mut(), &c, 50, ppem, ls, 0., 0., 0.)else { println!("ra?"); break 'out}; - 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)); - if c.len() == 0 { - complete.consume(CompletionAction::NoResult).unwrap(); None - } else { Some(c) }}, - _ => None, - }; - 'out: {if let Rq{result: Some((ref x, vo, ref mut max)), .. } = sig_help { - let (sig, p) = sig::active(x); - let c = sig::sig((sig, p), 40); - 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 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.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_; - } - Some((is_above_, left, top, w_, h_)) - }); - { - let ppem = 15.0; - let ls = 10.0; - let (fw, _) = dsb::dims(&FONT, ppem); - let cols = (w as f32 / fw).floor() as usize; - sig::doc(sig, cols) .map(|mut cells| { - *max = Some(cells.l()); - cells.vo = vo; - let cells = cells.displayable(cells.l().min(15)); - 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 }) else { - return - }; - i.r#box((left_.saturating_sub(1) as _, top_.saturating_sub(1) as _), w as _,h_ as _, BORDER); - }); - } - } else if let Some(c) = com { - let ppem = 20.0; - let (_x, _y) = text.cursor_visual(); - let _x = _x + text.line_number_offset()+1; - let _y = _y.wrapping_sub(text.vo); - let Ok((_,left, top, w, 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 met = FONT.metrics(&[]); - let fac = ppem / met.units_per_em as f32; - // if x.view_o == Some(x.cells.row) || x.view_o.is_none() { - let (fw, fh) = dsb::dims(&FONT, ppem); - let cursor = - Image::<_, 4>::build(3, (fh).ceil() as u32) - .fill([0xFF, 0xCC, 0x66, 255]); - let mut draw_at = |x: usize, y:usize, w| unsafe { - let x = (x + t_ox).saturating_sub(text.ho)%c; - - if (text.vo..text.vo + r).contains(&y) { - i.as_mut().overlay_at( - w, - (x as f32 * fw).floor() as u32, - ((y - text.vo) as f32 - * (fh + ls * fac)) - .floor() - as u32, - // 4 + ((x - 1) as f32 * sz) as u32, - // (x as f32 * (ppem * 1.25)) as u32 - 20, - ); - } - }; - let (x, y) = text.cursor_visual(); - let image = - Image::<_, 4>::build(2, (fh).ceil() as u32) - .fill([82,82,82, 255]); - for stop in text.tabstops.as_ref().into_iter().flat_map(|x|x.list()) { - let Some((x, y)) = text.xy(stop.clone().r().end) else { continue }; - draw_at(x, y, &image); - } - if matches!( - state, - State::Default | State::Selection(_) - ) - { - draw_at(x, y, &cursor); - } - window.pre_present_notify(); - let buffer = surface.buffer_mut().unwrap(); - let x = unsafe { - std::slice::from_raw_parts_mut( - buffer.as_ptr() as *mut u8, - buffer.len() * 4, - ) - .as_chunks_unchecked_mut::<4>() - }; - fimg::overlay::copy_rgb_bgr_(i.flatten(), x); - dbg!(now.elapsed()); - buffer.present().unwrap(); - } + rnd::render( + ed, + &mut cells, + ppem, + window, + fw, + fh, + ls, + c, + r, + surface, + cursor_position, + &mut fonts, + i.as_mut(), + ); } Event::WindowEvent { @@ -1030,130 +354,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { (position.y / (fh + ls * fac) as f64).floor() as usize, ); - match state - .consume(Action::C(cursor_position)) - .unwrap() - { - Some(Do::ExtendSelectionToMouse) => { - *state.sel() = text.extend_selection_to( - text.mapped_index_at(cursor_position), - state.sel().clone(), - ); - window.request_redraw(); - } - Some(Do::StartSelection) => { - let x = text.mapped_index_at(cursor_position); - hist.last.cursor = x; - text.cursor = x; - *state.sel() = x..x; - } - Some(Do::Hover) if let Some(hover) = text.visual_index_at(cursor_position) && - let Some((cl, o)) = lsp!() => 'out: { - let l = &mut hovering.result; - if let Some(Hovr{ span: Some([(_x, _y), (_x2, _)]),..}) = &*l { - let Some(_y) = _y.checked_sub(text.vo) else { break 'out }; - if cursor_position.1 == _y && (_x..=_x2).contains(&&(cursor_position.0 - text.line_number_offset()-1)) { - break 'out; - } else { - // println!("span no longer below cursor; cancel hover {_x}..{_x2} {}", cursor_position.0 - text.line_number_offset() - 1); - *l = None; - window.request_redraw(); - } - } - let text = text.clone(); - let mut rang = None; - let z = match hover { - Mapping::Char(_, _, i) => { - TextDocumentPositionParams { position: text.to_l_position(i).unwrap(), text_document: o.tid() } - }, - Mapping::Fake(mark, relpos, abspos, _) => { - let Some(ref loc) = mark.l[relpos].1 else { - break 'out; - }; - let (x, y) = text.xy(abspos).unwrap(); - let Some(mut begin) = text.reverse_source_map(y) else { break 'out }; - let start = begin.nth(x - 1).unwrap() + 1; - let left = mark.l[..relpos].iter().rev().take_while(_.1.as_ref() == Some(loc)).count(); - let start = start + relpos - left; - let length = mark.l[relpos..].iter().take_while(_.1.as_ref() == Some(loc)).count() + left; - rang = Some([(start, y), (start + length, y)]); - TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri: loc.uri.clone() }, position: loc.range.start } - } - }; - if ctrl() { - if def.request.as_ref().is_none_or(|&(_, x)| x != cursor_position) { - let handle = cl.runtime.spawn(window.redraw_after(cl.request::<lsp_request!("textDocument/definition")>(&GotoDefinitionParams { - text_document_position_params: z.clone(), - work_done_progress_params: default(), - partial_result_params: default(), - }).unwrap().0)); - def.request = Some((DropH::new(handle), cursor_position)); - } else if def.result.as_ref().is_some_and(|em| { - let z = em.origin_selection_range.unwrap(); - (z.start.character..z.end.character).contains(&((cursor_position.0 - text.line_number_offset()-1) as _)) - }) { - def.result = None; - } - } else { - def.result = None; - } -if let Some((_, c)) = hovering.request && c == cursor_position { - break 'out; -} - // if !running.insert(hover) {return} -let (rx, _) = cl.request::<HoverRequest>(&HoverParams { - text_document_position_params: z, - work_done_progress_params:default() -}).unwrap(); -// println!("rq hov of {hover:?} (cur {})", hovering.request.is_some()); -let handle: tokio::task::JoinHandle<Result<Option<Hovr>, anyhow::Error>> = cl.runtime.spawn(window.redraw_after(async move { - let Some(x) = rx.await? else {return Ok(None::<Hovr>)}; - let (w, cells) = spawn_blocking(move || { - let x = match &x.contents { - lsp_types::HoverContents::Scalar(marked_string) => { - match marked_string{ - MarkedString::LanguageString(x) =>Cow::Borrowed(&*x.value), - MarkedString::String(x) => Cow::Borrowed(&**x), - } - }, - lsp_types::HoverContents::Array(marked_strings) => { - Cow::Owned(marked_strings.iter().map(|x| match x{ - MarkedString::LanguageString(x) => &*x.value, - MarkedString::String(x) => &*x, - }).collect::<String>()) - }, - lsp_types::HoverContents::Markup(markup_content) => { - Cow::Borrowed(&*markup_content.value) - }, - }; - let x = hov::p(&x).unwrap(); - let m = hov::l(&x).into_iter().max().map(_+2).unwrap_or(usize::MAX).min(c-10); - (m, hov::markdown2(m, &x)) - }).await.unwrap(); - let span = rang.or_else(|| x.range.and_then(|range| try { - let (startx, starty) = text.l_pos_to_char(range.start)?; - let (endx, endy) = text.l_pos_to_char(range.end)?; - let x1 = text.reverse_source_map(starty)?.nth(startx)?; - let x2 = text.reverse_source_map(endy)?.nth(endx)?; - [(x1, range.start.line as _), (x2, range.start.line as _)] - })); - anyhow::Ok(Some( hov::Hovr { span, item: text::CellBuffer { c: w, vo: 0, cells: cells.into() }}.into())) -})); -hovering.request = (DropH::new(handle), cursor_position).into(); -// hovering.result = None; -// lsp!().map(|(cl, o)| { -// let window = window.clone(); -// }); -// }); - } - Some(Do::Hover) => { - def.result = None; - hovering.result = None; - window.request_redraw(); - } - None => {} - x => unreachable!("{x:?}"), - } + ed.cursor_moved(cursor_position, window.clone(), c); } Event::WindowEvent { event: @@ -1163,57 +364,7 @@ hovering.request = (DropH::new(handle), cursor_position).into(); if button == MouseButton::Left { unsafe { CLICKING = true }; } - _ = complete.consume(CompletionAction::Click).unwrap(); - 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())))); - document_highlights.request(lsp.runtime.spawn(window.redraw_after(lsp.document_highlights(path, text.to_l_position(text.cursor).unwrap())))); - } - hist.last.cursor = text.cursor; - chist.push(text.cursor()); - text.setc(); - } - Some(Do::NavForward) => { - chist.forth().map(|x| { - text.cursor = text.rope.line_to_char(x.1) + x.0; - text.scroll_to_cursor(); - }); - } - Some(Do::NavBack) => { - chist.back().map(|x| { - text.cursor = text.rope.line_to_char(x.1) + x.0; - text.scroll_to_cursor(); - }); - } - Some(Do::ExtendSelectionToMouse) => { - *state.sel() = text.extend_selection_to( - text.mapped_index_at(cursor_position), - state.sel().clone(), - ); - } - Some(Do::StartSelection) => { - let x = text.mapped_index_at(cursor_position); - hist.last.cursor = x; - *state.sel() = text.extend_selection_to( - x, - text.cursor..text.cursor, - ); - } - Some(Do::GoToDefinition) => { - if let Some(LocationLink { - ref target_uri, - target_range, .. }) = def.result && let Some((l, p)) = lsp!() { - if target_uri == &p.tid().uri { - text.cursor = text.l_position(target_range.start).unwrap(); - text.scroll_to_cursor(); - } - } - } - None => {} - _ => unreachable!(), - } + ed.click(button, cursor_position, window.clone()); window.request_redraw(); } Event::WindowEvent { @@ -1233,24 +384,8 @@ hovering.request = (DropH::new(handle), cursor_position).into(); phase: _, }, } => { - let rows = if alt() { rows * 8. } else { rows * 3. }; - let (vo, max) = lower::saturating::math! { if let Some(x)= &mut hovering.result && shift() { - let n = x.item.l(); - (&mut x.item.vo, n - 15) - } else if let Some((_, ref mut vo, Some(max))) = sig_help.result && shift(){ - (vo, max - 15) - } else { - let n = text.l() - 1; (&mut text.vo, n) - }}; - if rows < 0.0 { - let rows = rows.ceil().abs() as usize; - *vo = (*vo + rows).min(max); - } else { - let rows = rows.floor() as usize; - *vo = vo.saturating_sub(rows); - } + ed.scroll(rows); window.request_redraw(); - inlay!(); } Event::WindowEvent { event: WindowEvent::ModifiersChanged(modifiers), @@ -1279,400 +414,8 @@ hovering.request = (DropH::new(handle), cursor_position).into(); ) { return; } - let mut o: Option<Do> = state - .consume(Action::K(event.logical_key.clone())) - .unwrap(); - match o { - Some(Do::Reinsert) => - o = state - .consume(Action::K( - event.logical_key.clone(), - )) - .unwrap(), - _ => {} - } - match o { - Some(Do::SpawnTerminal) => { - trm::toggle(workspace.as_deref().unwrap_or(Path::new("/home/os/"))); - } - Some(Do::MatchingBrace) => { - if let Some((l, f)) = lsp!() { - l.matching_brace(f, &mut text); - } - } - Some(Do::Symbols) => { - if let Some(lsp) = lsp { - state = State::Symbols(Rq::new(lsp.runtime.spawn(window.redraw_after(lsp.symbols("".into()))))); - } - } - Some(Do::SymbolsHandleKey) => { - if let Some(lsp) = lsp { - let State::Symbols(Rq { result :Some(x), request}) = &mut state else {unreachable!()}; - let ptedit = x.tedit.rope.clone(); - if handle2(&event.logical_key, &mut x.tedit, lsp!()).is_some() || ptedit != x.tedit.rope { - *request = Some((DropH::new(lsp.runtime.spawn(window.redraw_after(lsp.symbols(x.tedit.rope.to_string())))), ())); - // state = State::Symbols(Rq::new(lsp.runtime.spawn(lsp.symbols("".into())))); - } - } - } - Some(Do::SymbolsSelectNext) => { - let State::Symbols(Rq { result :Some(x), ..}) = &mut state else {unreachable!()}; - x.next(); - }, - Some(Do::SymbolsSelectPrev) => { - let State::Symbols(Rq { result :Some(x), ..}) = &mut state else {unreachable!()}; - x.back(); - }, - Some(Do::SymbolsSelect) - => { let State::Symbols(Rq { result :Some(x), ..}) = &mut state else {unreachable!()}; - let x = x.sel(); // TODO dedup - let _: anyhow::Result<()> = try { - let f = x.location.uri.to_file_path().map_err(|()| anyhow::anyhow!("dammit"))?.canonicalize().map_err(anyhow::Error::from)?; - origin = Some(f.clone()); - let r = text.r; - text = default(); - text.r = r; - let new = std::fs::read_to_string(f).map_err(anyhow::Error::from)?; - text.insert(&new); - text.cursor = text.l_position(x.location.range.start).ok_or(anyhow::anyhow!("dangit"))?; - text.scroll_to_cursor_centering(); - hist = Hist { - history: vec![], - redo_history: vec![], - last: text.clone(), - last_edit: Instant::now(), - changed: false, - }; - complete = CompletionState::None; - mtime = modify!(); - - lsp!().map(|(x, origin)| { - (def, semantic_tokens, inlay, sig_help, complete, hovering) = (default(), default(), default(), default(), default(), default()); - x.open(&origin,new).unwrap(); - x.rq_semantic_tokens(&mut semantic_tokens, origin, Some(window.clone())).unwrap(); - }); - state = State::Default; - bar.last_action = "open".to_string(); - }; - - }, - 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.cursor..text.cursor).unwrap(), context: CodeActionContext { - trigger_kind: Some(CodeActionTriggerKind::INVOKED), - // diagnostics: if let Some((lsp, p)) = lsp!() && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.diagnostics.get(&uri, &lsp.diagnostics.guard()) { dbg!(diag.iter().filter(|x| { - - // text.l_range(x.range).unwrap().contains(&text.cursor) - // }).cloned().collect()) } else { vec![] }, - ..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::CASelectLeft) => { - let State::CodeAction(Rq{ result: Some(c), .. }) = &mut state else { panic!()}; - c.left(); - } - Some(Do::CASelectRight) =>'out: { - let Some((lsp,f)) = lsp!() else {unreachable!()}; - let State::CodeAction(Rq{ result: Some(c), .. }) = &mut state else { panic!()}; - let Some(act) = c.right() else { break 'out }; - let act = act.clone(); - state = State::Default; - hist.last.cursor = text.cursor; - hist.test_push(&text); - let act = lsp.runtime.block_on( - lsp.request::<CodeActionResolveRequest>(&act).unwrap().0 - ).unwrap(); - let mut f_ = |edits: &[SnippetTextEdit]|{ - // let mut first = false; - for edit in edits { - text.apply_snippet_tedit(edit).unwrap(); - } - }; - match act.edit { - Some(WorkspaceEdit { - document_changes:Some(DocumentChanges::Edits(x)), - .. - }) => { - for x in x { - if x.text_document.uri!= f.tid().uri { return } - f_(&x.edits); - } - - } - Some(WorkspaceEdit { - document_changes:Some(DocumentChanges::Operations(x)), - .. - }) => { - for op in x { - match op { - DocumentChangeOperation::Edit(TextDocumentEdit { - edits, text_document,.. - }) => { - if text_document.uri!= f.tid().uri { return } - f_(&edits); - } - 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::CASelectNext) => { - let State::CodeAction(Rq{ result: Some(c), .. }) = &mut state else { panic!()}; - c.down(); - } - Some(Do::CASelectPrev) => { - let State::CodeAction(Rq{ result: Some(c), .. }) = &mut state else { panic!()}; - c.up(); - } - Some(Do::Reinsert | Do::GoToDefinition | Do::NavBack | Do::NavForward) => panic!(), - Some(Do::Save) => match &origin { - Some(x) => { - state.consume(Action::Saved).unwrap(); - save!(); - } - None => { - state - .consume(Action::RequireFilename) - .unwrap(); - } - }, - Some(Do::SaveTo(x)) => { - origin = Some(PathBuf::try_from(x).unwrap()); - save!(); - } - Some(Do::Edit) => { - hist.test_push(&text); - let cb4 = text.cursor; - if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) = event.logical_key && let CompletionState::Complete(..) = complete{ - } else { - handle2(&event.logical_key, &mut text, lsp!()); - } - text.scroll_to_cursor(); - inlay!(); - if cb4 != text.cursor && let CompletionState::Complete(Rq{ result: Some(c),.. })= &mut complete - && ((text.cursor < c.start) || (!is_word(text.at_())&& (text.at_() != '.' || text.at_() != ':')) ) { - complete = CompletionState::None; - } - if sig_help.running() && cb4 != text.cursor && let Some((lsp, path)) = lsp!() { - sig_help.request(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(path, text.cursor())))); - } - if hist.record(&text) && let Some((lsp, path)) = lsp!() { - change!(); - } - lsp!().map(|(lsp, o)|{ - let window = window.clone(); - match event.logical_key.as_ref() { - Key::Character(y) - if let Some(x) = &lsp.initialized - && let Some(x) = &x.capabilities.signature_help_provider - && let Some(x) = &x.trigger_characters && x.contains(&y.to_string()) => { - sig_help.request(lsp.runtime.spawn(window.redraw_after(lsp.request_sig_help(o, text.cursor())))); - }, - _ => {} - } - match complete.consume(CompletionAction::K(event.logical_key.as_ref())).unwrap(){ - Some(CDo::Request(ctx)) => { - let h = DropH::new(lsp.runtime.spawn( - window.redraw_after(lsp.request_complete(o, text.cursor(), ctx)) - )); - let CompletionState::Complete(Rq{ request : x, result: c, }) = &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))); - } - Some(CDo::SelectNext) => { - let CompletionState::Complete(Rq{ result: Some(c), .. }) = &mut complete else { panic!()}; - c.next(&filter(&text)); - } - Some(CDo::SelectPrevious) => { - let CompletionState::Complete(Rq{ result: Some(c), .. }) = &mut complete else { panic!()}; - c.back(&filter(&text)); - } - 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.clone() else { panic!() }; - match insert_text_format { - Some(InsertTextFormat::SNIPPET) =>{ - text.apply_snippet(&ed).unwrap(); - }, - _ => { - let (s, _) = text.apply(&ed).unwrap(); - text.cursor = s + ed.new_text.chars().count(); - } - } - for additional in additional_text_edits.into_iter().flatten() { - 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())))); - } - None => {return}, - }; - }); - - } - Some(Do::Undo) => { - hist.test_push(&text); - hist.undo(&mut text); - bar.last_action = "undid".to_string(); - change!(); - } - Some(Do::Redo) => { - hist.test_push(&text); - hist.redo(&mut text); - bar.last_action = "redid".to_string(); - change!(); - } - Some(Do::Quit) => elwt.exit(), - Some(Do::StartSelection) => { - let Key::Named(y) = event.logical_key else { - panic!() - }; - *state.sel() = text.extend_selection( - y, - text.cursor..text.cursor, - ); - } - Some(Do::UpdateSelection) => { - let Key::Named(y) = event.logical_key else { - panic!() - }; - *state.sel() = text - .extend_selection(y, state.sel().clone()); - text.scroll_to_cursor(); - inlay!(); - } - Some(Do::Insert(x, c)) => { - hist.push_if_changed(&text); - _ = text.remove(x.clone()); - text.cursor = x.start; - text.setc(); - text.insert(&c); - hist.push_if_changed(&text); - change!(); - } - Some(Do::Delete(x)) => { - hist.push_if_changed(&text); - text.cursor = x.start; - _ = text.remove(x); - hist.push_if_changed(&text); - change!(); - } - Some(Do::Copy(x)) => { - clipp::copy(text.rope.slice(x).to_string()); - } - Some(Do::Cut(x)) => { - hist.push_if_changed(&text); - clipp::copy( - text.rope.slice(x.clone()).to_string(), - ); - text.rope.remove(x.clone()); - text.cursor = x.start; - hist.push_if_changed(&text); - change!(); - } - Some(Do::Paste) => { - hist.push_if_changed(&text); - text.insert(&clipp::paste()); - hist.push_if_changed(&text); - change!(); - } - Some(Do::OpenFile(x)) => { let _ = try { - origin = Some(PathBuf::from(&x).canonicalize()?); - text = TextArea::default(); - let new = std::fs::read_to_string(x)?; - text.insert(&new); - text.cursor = 0; - hist = Hist { - history: vec![], - redo_history: vec![], - last: text.clone(), - last_edit: Instant::now(), - changed: false, - }; - complete = CompletionState::None; - mtime = modify!(); - - lsp!().map(|(x, origin)| { - (def, semantic_tokens, inlay, sig_help, complete, hovering) = (default(), default(), default(), default(), default(), default()); - x.open(&origin,new).unwrap(); - x.rq_semantic_tokens(&mut semantic_tokens, origin, Some(window.clone())).unwrap(); - }); - bar.last_action = "open".to_string(); - }; - } - Some( - Do::MoveCursor | Do::ExtendSelectionToMouse | Do::Hover, - ) => { - unreachable!() - } - Some(Do::StartSearch(x)) => { - let s = Regex::new(&x).unwrap(); - let n = s - .find_iter(&text.rope.to_string()) - .enumerate() - .count(); - s.clone() - .find_iter(&text.rope.to_string()) - .enumerate() - .find(|(_, x)| x.start() > text.cursor) - .map(|(x, m)| { - state = State::Search(s, x, n); - text.cursor = - text.rope.byte_to_char(m.end()); - text.scroll_to_cursor_centering(); - inlay!(); - }) - .unwrap_or_else(|| { - bar.last_action = "no matches".into() - }); - } - Some(Do::SearchChanged) => { - let (re, index, _) = state.search(); - let s = text.rope.to_string(); - let m = re.find_iter(&s).nth(*index).unwrap(); - text.cursor = text.rope.byte_to_char(m.end()); - text.scroll_to_cursor_centering(); - inlay!(); - } - Some(Do::Boolean( - BoolRequest::ReloadFile, - true, - )) => { - text.rope = Rope::from_str( - &std::fs::read_to_string( - origin.as_ref().unwrap(), - ) - .unwrap(), - ); - text.cursor = - text.cursor.min(text.rope.len_chars()); - mtime = modify!(); - bar.last_action = "reloaded".into(); - } - Some(Do::Boolean( - BoolRequest::ReloadFile, - false, - )) => {} - None => {} + if ed.keyboard(event, window).is_break() { + elwt.exit(); } window.request_redraw(); } @@ -1683,48 +426,8 @@ hovering.request = (DropH::new(handle), cursor_position).into(); winit_app::run_app(event_loop, app); } -fn handle2<'a>(key: &'a Key, text: &mut TextArea, l: Option<(&Client, &Path)>) -> Option<&'a str> { - use Key::*; - - match key { - Named(Space) => text.insert(" ").unwrap(), - Named(Backspace) if ctrl() => text.backspace_word(), - Named(Backspace) => text.backspace(), - Named(Home) if ctrl() => { - text.cursor = 0; - text.vo = 0; - } - Named(End) if ctrl() => { - text.cursor = text.rope.len_chars(); - text.vo = text.l().saturating_sub(text.r); - } - Named(Home) => text.home(), - Named(End) => text.end(), - Named(Tab) => text.tab(), - Named(Delete) => { - text.right(); - text.backspace() - } - Named(ArrowLeft) if ctrl() => text.word_left(), - Named(ArrowRight) if ctrl() => text.word_right(), - Named(ArrowLeft) => text.left(), - Named(ArrowRight) => text.right(), - Named(ArrowUp) => text.up(), - Named(ArrowDown) => text.down(), - Named(PageDown) => text.page_down(), - Named(PageUp) => text.page_up(), - Named(Enter) if let Some((l, p)) = l => l.enter(p, text), - Named(Enter) => text.enter(), - Character(x) => { - text.insert(&x); - return Some(x); - } - _ => {} - }; - None -} -fn handle(key: Key, mut text: TextArea,) -> TextArea { - handle2(&key, &mut text,None); +fn handle(key: Key, mut text: TextArea) -> TextArea { + edi::handle2(&key, &mut text, None); text } pub static FONT: LazyLock<FontRef<'static>> = LazyLock::new(|| { @@ -1754,121 +457,6 @@ fn alt() -> bool { fn ctrl() -> bool { unsafe { MODIFIERS }.control_key() } -impl State { - fn sel(&mut self) -> &mut Range<usize> { - let State::Selection(x) = self else { panic!() }; - x - } - fn search(&mut self) -> (&mut Regex, &mut usize, &mut usize) { - let State::Search(x, y, z) = self else { panic!() }; - (x, y, z) - } -} - -use std::ops::Range; -impl Default for State { - fn default() -> Self { - Self::Default - } -} -rust_fsm::state_machine! { -#[derive(Debug)] -pub(crate) State => Action => Do - -Dead => K(Key => _) => Dead, -Default => { - K(Key::Character(x) if x == "s" && ctrl()) => Save [Save], - K(Key::Character(x) if x == "q" && ctrl()) => Dead [Quit], - K(Key::Character(x) if x == "v" && ctrl()) => _ [Paste], - K(Key::Character(x) if x == "z" && ctrl()) => _ [Undo], - K(Key::Character(x) if x == "y" && ctrl()) => _ [Redo], - 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 == "l" && ctrl()) => _ [Symbols], - K(Key::Character(x) if x == "." && ctrl()) => _ [CodeAction], - K(Key::Character(x) if x == "0" && ctrl()) => _ [MatchingBrace], - K(Key::Character(x) if x == "`" && ctrl()) => _ [SpawnTerminal], - K(Key::Named(ArrowUp | ArrowLeft | ArrowDown | ArrowRight | Home | End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection], - M(MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection], - M(MouseButton::Left if ctrl()) => _ [GoToDefinition], - M(MouseButton::Left) => _ [MoveCursor], - M(MouseButton::Back) => _ [NavBack], - M(MouseButton::Forward) => _ [NavForward], - C(((usize, usize)) => .. if unsafe { CLICKING }) => Selection(0..0) [StartSelection], - Changed => RequestBoolean(BoolRequest => BoolRequest::ReloadFile), - C(_) => _ [Hover], - K(_) => _ [Edit], - M(_) => _, -}, -Symbols(Rq { result: Some(_x), request: None }) => { - K(Key::Named(Tab) if shift()) => _ [SymbolsSelectNext], - K(Key::Named(ArrowDown)) => _ [SymbolsSelectNext], - K(Key::Named(ArrowUp | Tab)) => _ [SymbolsSelectPrev], - K(Key::Named(Enter)) => _ [SymbolsSelect], - K(Key::Named(Escape)) => Default, - K(_) => _ [SymbolsHandleKey], -}, -Symbols(Rq::<Symbols, Vec<SymbolInformation>, (), RequestError<lsp_request!("workspace/symbol")>> => _rq) => { - K(Key::Named(Escape)) => Default, - C(_) => _, - M(_) => _, - K(_) => _, -}, -CodeAction(Rq { result : Some(_x), request }) => { - K(Key::Named(Tab) if shift()) => _ [CASelectPrev], - K(Key::Named(ArrowDown | Tab)) => _ [CASelectNext], - K(Key::Named(ArrowUp)) => _ [CASelectPrev], - K(Key::Named(Enter | ArrowRight)) => _ [CASelectRight], - K(Key::Named(ArrowLeft)) => _ [CASelectLeft], -}, -CodeAction(RqS<act::CodeActions, lsp_request!("textDocument/codeAction")> => 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], -}, // note: it does in fact fall through. this syntax is not an arm, merely shorthand. -Selection(x) => { - C(_ if unsafe { CLICKING }) => _ [ExtendSelectionToMouse], - C(_) => Selection(x), - M(MouseButton => MouseButton::Left) => Default [MoveCursor], - K(Key::Named(Backspace)) => Default [Delete(Range<usize> => x)], - K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut(Range<usize> => x)], - K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy(Range<usize> => x)], - K(Key::Character(y)) => Default [Insert((Range<usize>, SmolStr) => (x, y))], - K(_) => Default [Edit], -}, -Save => { - RequireFilename => Procure((TextArea, InputRequest) => (default(), InputRequest::SaveFile)), - Saved => Default, -}, -Procure((_, _)) => K(Key::Named(Escape)) => Default, -Procure((t, InputRequest::Search)) => K(Key::Named(Enter)) => Default [StartSearch(String => t.rope.to_string())], -Procure((t, InputRequest::SaveFile)) => K(Key::Named(Enter)) => Default [SaveTo(String => t.rope.to_string())], -Procure((t, InputRequest::OpenFile)) => K(Key::Named(Enter)) => Default [OpenFile(String => t.rope.to_string())], -Procure((t, a)) => K(k) => Procure((handle(k, t), a)), -RequestBoolean(t) => { - K(Key::Character(x) if x == "y") => Default [Boolean((BoolRequest, bool) => (t, true))], - K(Key::Character(x) if x == "n") => Default [Boolean((t, false))], - K(Key::Named(Escape)) => Default [Boolean((t, false))], - K(_) => RequestBoolean(t), - C(_) => _, - Changed => _, - M(_) => _, -}, -Search((x, y, m)) => { - M(MouseButton::Left) => Default [MoveCursor], - C(_) => Search((x, y, m)), - K(Key::Named(Enter) if shift()) => Search((x, y.checked_sub(1).unwrap_or(m-1), m)) [SearchChanged], - K(Key::Named(Enter)) => Search((Regex, usize, usize) => (x, (y+ 1) % m, m)) [SearchChanged], - K(_) => Default [Reinsert], -} -} - #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum InputRequest { SaveFile, @@ -1977,12 +565,3 @@ fn filter(text: &TextArea) -> String { .collect::<String>() } } -fn frunctinator( - parameter1: usize, - _parameter2: u8, - _paramter4: u16, -) -> usize { - lower::saturating::math! { parameter1 }; - - 0 -} diff --git a/src/rnd.rs b/src/rnd.rs new file mode 100644 index 0000000..5f8354b --- /dev/null +++ b/src/rnd.rs @@ -0,0 +1,754 @@ +use std::iter::once; +use std::os::fd::AsFd; +use std::sync::{Arc, LazyLock}; +use std::time::Instant; + +use atools::prelude::*; +use dsb::{Cell, Fonts}; +use dsb::cell::Style; +use fimg::{Image, OverlayAt}; +use fimg::pixels::Blend; +use lsp_types::*; +use rust_fsm::StateMachine; +use softbuffer::Surface; +use swash::{FontRef, Instance}; +use url::Url; +use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::window::Window; + +use crate::edi::st::State; +use crate::edi::{Editor, lsp_m}; +use crate::lsp::Rq; +use crate::text::{CoerceOption, col}; +use crate::{BG, BORDER, CompletionAction, CompletionState, FG, com, filter, lsp, sig}; + +#[implicit_fn::implicit_fn] +pub fn render( + ed: &mut Editor, + cells: &mut [Cell], + ppem: f32, + window: &mut Arc<Window>, + fw: f32, + fh: f32, + ls: f32, + c: usize, + r: usize, + surface: Option<&mut Surface<Arc<Window>, Arc<Window>>>, + cursor_position: (usize, usize), + fonts: &mut dsb::Fonts, + mut i: Image<&mut [u8], 3>, +) { + let text = &mut ed.text; + let (cx, cy) = text.cursor_visual(); + let met = super::FONT.metrics(&[]); + let fac = ppem / met.units_per_em as f32; + window.set_ime_cursor_area( + PhysicalPosition::new( + ((cx + text.line_number_offset()) as f64 * (fw) as f64) + .round(), + ((cy.saturating_sub(text.vo)) as f64 * (fh + ls * fac) as f64) + .floor(), + ), + PhysicalSize::new(fw, fh), + ); + let Some(surface) = surface else { + eprintln!( + "RedrawRequested fired before Resumed or after Suspended" + ); + return; + }; + let size = window.inner_size(); + + if size.height != 0 && size.width != 0 { + let now = Instant::now(); + if c * r != cells.len() { + return; + } + cells.fill(Cell { + style: Style { fg: BG, secondary_color: BG, bg: BG, flags: 0 }, + letter: None, + }); + let x = match &ed.state { + State::Selection(x) => Some(x.clone()), + _ => None, + }; + text.line_numbers( + (c, r - 1), + [67, 76, 87], + BG, + (cells, (c, r)), + (1, 0), + ); + let t_ox = text.line_number_offset() + 1; + text.c = c - t_ox; + text.r = r - 1; + // let mut text = text.clone(); + // for (_, inlay) in inlay.result.as_ref().into_iter().flatten().chunk_by(|x| x.position.line).into_iter() { + // let mut off = 0; + // for inlay in inlay { + // let label = match &inlay.label { + // InlayHintLabel::String(x) => x.clone(), + // InlayHintLabel::LabelParts(v) => { + // v.iter().map(_.value.clone()).collect::<String>() + // }, + // }; + // text.rope.insert(text.l_position(inlay.position).unwrap() + off, &label); + // off += label.chars().count(); + // } + // } + + text.write_to( + (cells, (c, r)), + (t_ox, 0), + x, + |(_c, _r), text, mut x| { + if let Some(hl) = &ed.document_highlights.result { + for DocumentHighlight { range: r, .. } in hl { + // let s = match kind { + // Some(DocumentHighlightKind::READ) => Style::UNDERLINE, + // Some(DocumentHighlightKind::WRITE) => Style::UNDERLINE, + // _ => Style::UNDERCURL, + // }; + let (x1, y1) = text.map_to_visual((r.start.character as _, r.start.line as _)); + let (x2, y2) = text.map_to_visual((r.end.character as _, r.end.line as _)); + x.get_simple((x1, y1), (x2, y2)).coerce().for_each(|x| { + x.style.bg = col!("#3a4358"); + }); + } + } + if let Some(LocationLink { + origin_selection_range: Some(r), .. + }) = ed.def.result { _ = try { + let (x1, y1) = text.map_to_visual((r.start.character as _, r.start.line as _)); + let (x2, y2) = text.map_to_visual((r.end.character as _, r.end.line as _)); + x.get_simple((x1, y1), (x2, y2))?.iter_mut().for_each(|x| { + x.style.flags |= Style::UNDERLINE; + x.style.fg = col!("#FFD173"); + }); + } } + 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 { + Hint, Info, Error,Warning,Related(DiagnosticSeverity), + } + let mut occupied = vec![]; + 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, + _ => EType::Info, + }; + once((diag.range, &*diag.message, sev_)).chain(diag.related_information.iter().flatten().filter(|sp| sp.location.uri == uri).map(move |x| { + (x.location.range, &*x.message, EType::Related(sev)) + })) + }).for_each(|(mut r, m, sev)| { + if let EType::Related(x) = sev && x != DiagnosticSeverity::ERROR { + return; + } + let p = r.start.line; + while occupied.contains(&r.start.line) { + r.start.line+=1; + }; + occupied.push(r.start.line); + let f = |cell:&mut Cell| { + cell.style.bg.blend(match sev { + EType::Error => col!("#ff66662c"), + EType::Warning | EType::Hint | EType::Info => col!("#9469242c"), + EType::Related(DiagnosticSeverity::ERROR) => col!("#dfbfff26"), + EType::Related(_) => col!("#ffad6625"), + }); + }; + if r.start == r.end { + x.get(text.map_to_visual((r.start.character as _, p as _))).map(f); + } else { + x.get_range(text.map_to_visual((r.start.character as _, p as _)), + text.map_to_visual((r.end.character as usize, r.end.line as _))) + .for_each(f) + } + let l = r.start.line as usize; + let Some(x_) = text.visual_eol(l).map(_+2) else { + return; + }; + let m = m.lines().next().unwrap_or(m); + x.get_range( + (x_, l), + (x_ + m.chars().count(), l), + ).zip(m.chars()).for_each(|(x, ch)| { + let (bg, fg) = match sev { + EType::Warning => { col!("#ff942f1b", "#fa973a") }, + EType::Error => { col!("#ff942f1b", "#f26462") }, + EType::Related(DiagnosticSeverity::WARNING) => { col!("#dfbfff26", "#DFBFFF") } + _ => return + }; + x.style.bg.blend(bg); + x.style.fg = fg; + x.letter = Some(ch); + }) + }); + } + if let State::Search(re, j, _) = &ed.state { + re.find_iter(&text.rope.to_string()) + .enumerate() + .for_each(|(i, m)| { + for x in x.get_range( + text.map_to_visual(text.xy(text.rope.byte_to_char(m.start())).unwrap()),text.map_to_visual( text.xy(text + .rope + .byte_to_char( + m.end(), + )).unwrap())) + { + x.style.bg = if i == *j { + [105, 83, 128] + } else { + [65, 62, 83] + } + } + }); + } + }, + ed.origin.as_deref(), + ed.semantic_tokens.result.as_deref().zip( + match lsp_m!(ed) { + Some(lsp::Client { initialized: Some(lsp_types::InitializeResult { + capabilities: ServerCapabilities { + semantic_tokens_provider: + Some(SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions{ + legend,.. + })),.. + }, .. + }), .. + }) => Some(legend), + _ => None, + }), + ); + + ed.bar.write_to( + BG, + FG, + (cells, (c, r)), + r - 1, + ed.origin + .as_ref() + .map(|x| { + ed.workspace + .as_ref() + .and_then(|w| x.strip_prefix(w).ok()) + .unwrap_or(&x) + .to_str() + .unwrap() + }) + .unwrap_or("new buffer"), + &ed.state, + &text, + lsp_m!(ed), + ); + unsafe { + dsb::render( + &cells, + (c, r), + ppem, + fonts, + ls, + true, + i.copy(), + (0, 0), + ) + }; + + let mut place_around = |(_x, _y): (usize, usize), + i: Image<&mut [u8], 3>, + c: &[Cell], + columns: usize, + ppem_: f32, + ls_: f32, + ox: f32, + oy: f32, + toy: f32| { + let met = super::FONT.metrics(&[]); + let fac = ppem / met.units_per_em as f32; + let position = ( + (((_x) as f32 * fw).round() + ox) as usize, + (((_y) as f32 * (fh + ls * fac)).round() + oy) as usize, + ); + + 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)); + // std::fs::write("cells", Cell::store(c)); + + if w >= size.width as usize + || (position.1 + h >= size.height as usize + && !position.1.checked_sub(h).is_some()) + || position.1 >= size.height as usize + || position.0 >= size.width as usize + { + unsafe { + dsb::render_owned( + c, + (columns, c.len() / columns), + ppem, + fonts, + ls, + true, + ) + .save("fail.png") + }; + 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, + ); + let (_, y) = dsb::fit( + &fonts.regular, + ppem, + ls, + ( + window.inner_size().width as _, /* - left */ + ((window.inner_size().height as usize) + .saturating_sub(top)), + ), + ); /* suspicious saturation */ + r = r.min(y); + + let left = if position.0 + w as usize + > window.inner_size().width as usize + { + window.inner_size().width as usize - w as usize + } else { + position.0 + }; + + let (w, h) = dsb::size(&fonts.regular, ppem, ls, (columns, r)); + unsafe { + dsb::render( + &c, + (columns, 0), + ppem, + fonts, + ls, + true, + i, + (left as _, top as _), + ) + }; + Ok((is_above, left, top, w, h)) + }; + let mut pass = true; + if let Some((lsp, p)) = lsp_m!(ed + p) + && 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 { + 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, + ); + for b in x + .replace('\n', "\r\n") + .replace("⸬", ":") + .replace("/home/os", "") + .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 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, + ) + }, + i.copy(), + &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, + ); + } + _ => {} + } + } + }; + ed.hovering.result.as_ref().filter(|_| pass).map(|x| { + x.span.clone().map(|[(_x, _y), (_x2, _)]| { + // let [(_x, _y), (_x2, _)] = text.position(sp); + // dbg!(x..=x2, cursor_position.0) + // if !(_x..=_x2).contains(&&(cursor_position.0 .wrapping_sub( text.line_number_offset()+1))) { + // return + // } + + let [_x, _x2] = + [_x, _x2].add(text.line_number_offset() + 1); + let Some(_y) = _y.checked_sub(text.vo) else { + return; + }; + + // if !(cursor_position.1 == _y && (_x..=_x2).contains(&cursor_position.0)) { + // return; + // } + + let r = x.item.l().min(15); + let c = x.item.displayable(r); + let Ok((_, left, top, w, h)) = place_around( + (_x, _y), + i.copy(), + 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 &ed.state { + State::CodeAction(Rq { result: Some(x), .. }) => 'out: { + let m = x.maxc(); + let c = x.write(m); + 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), + i.copy(), + &c, + m, + ppem, + ls, + 0., + 0., + 0., + ) else { + println!("ra?"); + break 'out; + }; + i.r#box( + ( + left.saturating_sub(1) as _, + top.saturating_sub(1) as _, + ), + w as _, + h as _, + BORDER, + ); + } + State::Symbols(Rq { result: Some(x), .. }) => 'out: { + let ws = ed.workspace.as_deref().unwrap(); + let c = x.cells(50, ws); + // let (_x, _y) = text.cursor_visual(); + let _x = 0; + let _y = r - 1; + let Ok((is_above, left, top, w, mut h)) = place_around( + (_x, _y), + i.copy(), + &c, + 50, + ppem, + ls, + 0., + 0., + 0., + ) else { + println!("ra?"); + break 'out; + }; + i.r#box( + ( + left.saturating_sub(1) as _, + top.saturating_sub(1) as _, + ), + w as _, + h as _, + BORDER, + ); + } + _ => {} + } + let com = match ed.complete { + CompletionState::Complete(Rq { + result: Some(ref x), .. + }) => { + let c = com::s(x, 40, &filter(&text)); + if c.len() == 0 { + ed.complete + .consume(CompletionAction::NoResult) + .unwrap(); + None + } else { + Some(c) + } + } + _ => None, + }; + 'out: { + if let Rq { result: Some((ref x, vo, ref mut max)), .. } = + ed.sig_help + { + let (sig, p) = sig::active(x); + let c = sig::sig((sig, p), 40); + 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 Ok((is_above, left, top, w, mut h)) = place_around( + (_x, _y), + i.copy(), + &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.and_then(|c| { + let Ok((is_above_, left, top, w_, h_)) = place_around( + (_x, _y), + i.copy(), + &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_; + } + Some((is_above_, left, top, w_, h_)) + }); + { + let ppem = 15.0; + let ls = 10.0; + let (fw, _) = dsb::dims(&FONT, ppem); + let cols = (w as f32 / fw).floor() as usize; + sig::doc(sig, cols).map(|mut cells| { + *max = Some(cells.l()); + cells.vo = vo; + let cells = cells.displayable(cells.l().min(15)); + let Ok((_, left_, top_, _w_, h_)) = place_around( + (_x, _y), + i.copy(), + 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 + }, + ) else { + return; + }; + i.r#box( + ( + left_.saturating_sub(1) as _, + top_.saturating_sub(1) as _, + ), + w as _, + h_ as _, + BORDER, + ); + }); + } + } else if let Some(c) = com { + let ppem = 20.0; + let (_x, _y) = text.cursor_visual(); + let _x = _x + text.line_number_offset() + 1; + let _y = _y.wrapping_sub(text.vo); + let Ok((_, left, top, w, h)) = place_around( + (_x, _y), + i.copy(), + &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 met = FONT.metrics(&[]); + let fac = ppem / met.units_per_em as f32; + // if x.view_o == Some(x.cells.row) || x.view_o.is_none() { + let (fw, fh) = dsb::dims(&FONT, ppem); + let cursor = Image::<_, 4>::build(3, (fh).ceil() as u32) + .fill([0xFF, 0xCC, 0x66, 255]); + let mut draw_at = |x: usize, y: usize, w| unsafe { + let x = (x + t_ox).saturating_sub(text.ho) % c; + + if (text.vo..text.vo + r).contains(&y) { + i.overlay_at( + w, + (x as f32 * fw).floor() as u32, + ((y - text.vo) as f32 * (fh + ls * fac)).floor() + as u32, + // 4 + ((x - 1) as f32 * sz) as u32, + // (x as f32 * (ppem * 1.25)) as u32 - 20, + ); + } + }; + let (x, y) = text.cursor_visual(); + let image = Image::<_, 4>::build(2, (fh).ceil() as u32) + .fill([82, 82, 82, 255]); + for stop in + text.tabstops.as_ref().into_iter().flat_map(|x| x.list()) + { + let Some((x, y)) = text.xy(stop.clone().r().end) else { + continue; + }; + draw_at(x, y, &image); + } + if matches!(ed.state, State::Default | State::Selection(_)) { + draw_at(x, y, &cursor); + } + window.pre_present_notify(); + let buffer = surface.buffer_mut().unwrap(); + let x = unsafe { + std::slice::from_raw_parts_mut( + buffer.as_ptr() as *mut u8, + buffer.len() * 4, + ) + .as_chunks_unchecked_mut::<4>() + }; + fimg::overlay::copy_rgb_bgr_(i.flatten(), x); + dbg!(now.elapsed()); + buffer.present().unwrap(); + } +} |