A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | src/commands.rs | 4 | ||||
| -rw-r--r-- | src/edi.rs | 1503 | ||||
| -rw-r--r-- | src/edi/input_handlers.rs | 59 | ||||
| -rw-r--r-- | src/edi/input_handlers/click.rs | 86 | ||||
| -rw-r--r-- | src/edi/input_handlers/cursor.rs | 245 | ||||
| -rw-r--r-- | src/edi/input_handlers/keyboard.rs | 888 | ||||
| -rw-r--r-- | src/edi/lsp_impl.rs | 220 | ||||
| -rw-r--r-- | src/rnd.rs | 12 | ||||
| -rw-r--r-- | src/text.rs | 19 |
9 files changed, 1539 insertions, 1497 deletions
diff --git a/src/commands.rs b/src/commands.rs index fc37f45..64a382c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -12,7 +12,7 @@ use rootcause::{bail, report}; use rust_analyzer::lsp::ext::*; use crate::FG; -use crate::edi::{Editor, lsp_m}; +use crate::edi::{Editor, lsp}; use crate::gotolist::{At, GoToList}; use crate::lsp::{PathURI, Rq, tdpp}; use crate::menu::charc; @@ -273,7 +273,7 @@ impl Editor { z: Cmd, w: Arc<dyn winit::window::Window>, ) -> rootcause::Result<()> { - let Some((l, o)) = lsp_m!(self + p) else { + let Some((l, o)) = lsp!(self + p) else { bail!("no lsp"); }; match z { @@ -1,142 +1,52 @@ -use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Debug; use std::mem::take; -use std::ops::ControlFlow; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::SystemTime; use Default::default; use bind::Bind; -use implicit_fn::implicit_fn; -use lsp_server::{Connection, Request as LRq, ResponseError}; +use lsp_server::Connection; use lsp_types::request::*; use lsp_types::*; use regex::Regex; use rootcause::report; use ropey::Rope; -use rust_analyzer::lsp::ext::OnTypeFormatting; -use rust_fsm::StateMachine; use serde_derive::{Deserialize, Serialize}; 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::keyboard::NamedKey; use winit::window::Window; +mod input_handlers; +pub use input_handlers::handle2; + +mod lsp_impl; pub mod st; mod wsedit; +pub use lsp_impl::Requests; use st::*; use crate::bar::Bar; use crate::commands::Cmds; -use crate::complete::Complete; use crate::error::WDebug; use crate::gotolist::{At, GoTo}; use crate::hov::{self, Hovr}; use crate::lsp::{ - self, Anonymize, Client, Map_, PathURI, RequestError, Rq, tdpp, + Anonymize, Client, Map_, PathURI, RequestError, Rq, tdpp, }; use crate::menu::generic::MenuData; use crate::meta::META; -use crate::runnables::Runnables; use crate::sym::{Symbols, SymbolsList, SymbolsType}; use crate::text::cursor::{Ronge, ceach}; use crate::text::hist::{ClickHistory, Hist}; use crate::text::{self, Mapping, RopeExt, SortTedits, TextArea}; use crate::{ - BoolRequest, CDo, CompletionAction, CompletionState, act, alt, ctrl, - filter, hash, shift, sig, sym, trm, + BoolRequest, CDo, CompletionAction, CompletionState, alt, ctrl, + filter, hash, shift, sym, trm, }; -#[allow(dead_code)] -pub fn serialize_tokens<S: serde::Serializer>( - s: &Rq< - Box<[SemanticToken]>, - Box<[SemanticToken]>, - (), - RequestError<SemanticTokensFullRequest>, - >, - ser: S, -) -> Result<S::Ok, S::Error> { - SemanticToken::serialize_tokens_opt( - &s.result.clone().map(|x| x.to_vec()), - ser, - ) -} -#[allow(dead_code)] -pub fn deserialize_tokens<'de, D: serde::Deserializer<'de>>( - ser: D, -) -> Result< - Rq< - Box<[SemanticToken]>, - Box<[SemanticToken]>, - (), - RequestError<SemanticTokensFullRequest>, - >, - D::Error, -> { - SemanticToken::deserialize_tokens_opt(ser) - .map(|x| Rq { result: x.map(Into::into), request: None }) -} -#[derive(Default, Debug, Serialize, Deserialize)] -pub struct Requests { - pub hovering: - Rq<Hovr, Option<Hovr>, (usize, usize), RequestError<HoverRequest>>, - pub document_highlights: Rq< - Vec<DocumentHighlight>, - Vec<DocumentHighlight>, - (), - RequestError<DocumentHighlightRequest>, - >, - pub complete: CompletionState, - pub sig_help: Rq< - (SignatureHelp, usize, Option<usize>), - Option<SignatureHelp>, - (), - RequestError<SignatureHelpRequest>, - >, // vo, lines - // #[serde(serialize_with = "serialize_tokens")] - // #[serde(deserialize_with = "deserialize_tokens")] - #[serde(skip)] - pub semantic_tokens: Rq< - Box<[SemanticToken]>, - Box<[SemanticToken]>, - (), - RequestError<SemanticTokensFullRequest>, - >, - pub diag: Rq< - String, - Option<String>, - (), - RequestError<DocumentDiagnosticRequest>, - >, - #[serde(skip)] - pub inlay: Rq< - Vec<InlayHint>, - Vec<InlayHint>, - (), - RequestError<lsp_request!("textDocument/inlayHint")>, - >, - pub def: Rq< - LocationLink, - Option<GotoDefinitionResponse>, - (usize, usize), - RequestError<lsp_request!("textDocument/definition")>, - >, - #[serde(skip)] - pub document_symbols: Rq< - Option<Vec<DocumentSymbol>>, - Option<DocumentSymbolResponse>, - (), - RequestError<lsp_request!("textDocument/documentSymbol")>, - >, - #[serde(skip)] - pub git_diff: Rq<imara_diff::Diff, imara_diff::Diff, (), ()>, -} impl Debug for Editor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Editor") @@ -185,13 +95,13 @@ macro_rules! lsp { $self.lsp.as_ref().map(|(x, ..)| *x) }; ($self:ident + p) => { - $crate::edi::lsp_m!($self).zip($self.origin.as_deref()) + $crate::edi::lsp!($self).zip($self.origin.as_deref()) }; } -pub(crate) use lsp as lsp_m; +pub(crate) use lsp; macro_rules! inlay { ($self:ident) => { - $crate::edi::lsp_m!($self + p).map(|(lsp, path)| { + $crate::edi::lsp!($self + p).map(|(lsp, path)| { $self .requests .inlay @@ -208,12 +118,12 @@ macro_rules! change { change!(@$self, Some($w)) }; (just $self:ident) => { - lsp_m!($self + p).map(|(x, origin)| { + lsp!($self + p).map(|(x, origin)| { x.edit(&origin, $self.text.rope.to_string()).unwrap(); }) }; (@$self:ident, $w:expr) => { - lsp_m!($self + p).map(|(x, origin)| { + lsp!($self + p).map(|(x, origin)| { x.edit(&origin, $self.text.rope.to_string()).unwrap(); x.rq_semantic_tokens( &mut $self.requests.semantic_tokens, @@ -336,7 +246,7 @@ impl Editor { rust_analyzer::bin::run_server(b) }) .unwrap(); - let (c, t2, changed) = lsp::run( + let (c, t2, changed) = crate::lsp::run( (a.sender, a.receiver), // lsp_server::stdio::stdio_transport( // BufReader::new(c.stdout.take().unwrap()), @@ -442,469 +352,6 @@ impl Editor { std::fs::write(self.origin.as_ref().unwrap(), &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:?}"), - } - } - self.requests.inlay.poll(|x, p| { - x.ok().or(p.1).inspect(|x| { - self.text.set_inlay(x); - }) - }); - self.requests.document_highlights.poll(|x, _| x.ok()); - self.requests.diag.poll(|x, _| x.ok().flatten()); - if let CompletionState::Complete(rq) = &mut self.requests.complete - { - rq.poll(|f, (c, _)| { - f.ok().flatten().map(|x| Complete { - r: x, - start: c, - selection: 0, - vo: 0, - }) - }); - }; - match &mut self.state { - State::Symbols(x) => { - x.poll(|x, (_, p)| { - let Some(p) = p else { unreachable!() }; - x.ok().flatten().map(|r| sym::Symbols { - data: (r, p.data.1, p.data.2, p.data.3, p.data.4), - selection: 0, - vo: 0, - ..p - }) - }); - } - State::CodeAction(x) => { - if 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 { - self.bar.last_action = - format!("{} code actions", lems.len()); - Some(act::CodeActions::new(lems)) - } - }) && x.result.is_none() - { - self.state = State::Default; - } - } - State::Runnables(x) => { - x.poll(|x, ((), old)| { - Some(Runnables { - data: x.ok()?, - ..old.unwrap_or_default() - }) - }); - } - State::GoToL(z) => match &mut z.data.1 { - Some(crate::gotolist::O::References(y)) => { - y.poll(|x, _| { - x.ok().flatten().map(|x| { - z.data.0 = x.iter().map(GoTo::from).collect() - }) - }); - } - Some(crate::gotolist::O::Impl(y)) => { - y.poll(|x, _| { - x.ok().map(|x| { - x.and_then(|x| try { - z.data.0 = match x { - GotoDefinitionResponse::Scalar( - location, - ) => vec![GoTo::from( - location, - )], - GotoDefinitionResponse::Array( - locations, - ) => locations - .into_iter() - .map(GoTo::from) - .collect(), - GotoDefinitionResponse::Link( - location_links, - ) => location_links - .into_iter() - .map(|LocationLink {target_uri, target_range, .. }| { - GoTo::from( - Location { - uri: target_uri, - range: target_range, - } - ) - }) - .collect(), - }; - }); - }) - }); - } - _ => {} - }, - _ => {} - } - self.requests.def.poll(|x, _| { - x.ok().flatten().and_then(|x| match &x { - GotoDefinitionResponse::Link([x, ..]) => Some(x.clone()), - _ => None, - }) - }); - self.requests - .semantic_tokens - .poll(|x, _| x.ok().inspect(|x| self.text.set_toks(&x))); - self.requests.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) - } - }) - }); - self.requests.hovering.poll(|x, _| x.ok().flatten()); - self.requests.git_diff.poll(|x, _| x.ok()); - self.requests.document_symbols.poll(|x, _| { - x.ok().flatten().map(|x| match x { - DocumentSymbolResponse::Flat(_) => None, - DocumentSymbolResponse::Nested(x) => Some(x), - }) - }); - } - #[implicit_fn] - pub fn cursor_moved( - &mut self, - cursor_position: (usize, usize), - w: Arc<dyn Window>, - c: usize, - ) { - match self.state.consume(Action::C(cursor_position)).unwrap() { - Some(Do::ExtendSelectionToMouse) => { - let p = self.text.mapped_index_at(cursor_position); - self.text - .cursor - .first_mut() - .extend_selection_to(p, &self.text.rope); - w.request_redraw(); - } - Some(Do::StartSelection) => { - let x = self.text.mapped_index_at(cursor_position); - self.text.cursor.first_mut().position = x; - self.text.cursor.first_mut().sel = Some((x..x).into()); - self.hist.lc = self.text.cursor.clone(); - } - 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.requests.hovering.result; - if let Some(Hovr { - span: Some([(_x, _y), (_x2, _)]), .. - }) = &*l - && let Some(_y) = _y.checked_sub(self.text.vo) - && let Some(_x) = _x.checked_sub(self.text.ho) - && let Some(_x2) = _x2.checked_sub(self.text.ho) - && 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.data[relpos as usize].1 - else { - break 'out; - }; - let (x, y) = text.xy(abspos as _).unwrap(); - let Some(mut begin) = text.reverse_source_map(y) - else { - break 'out; - }; - let start = - begin.nth(x.saturating_sub(1)).unwrap() + 1; - let left = mark.data[..relpos as usize] - .iter() - .rev() - .take_while(_.1.as_ref() == Some(loc)) - .count(); - let start = start + relpos as usize - left; - let length = mark.data[relpos as usize..] - .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 - .requests - .def - .request - .as_ref() - .is_none_or(|&(_, x)| x != cursor_position) - { - let handle = - cl.runtime.spawn( - 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.requests.def.request = - Some((DropH::new(handle), cursor_position)); - } else if self - .requests - .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.requests.def.result = None; - } - } else { - self.requests.def.result = None; - } - if let Some((_, c)) = self.requests.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 {})", requests.hovering.request.is_some()); - let handle: tokio::task::JoinHandle< - Result<Option<Hovr>, _>, - > = cl.runtime.spawn(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 _), - ] - }) - }); - Ok(Some( - hov::Hovr { - span, - item: text::CellBuffer { - c: w, - vo: 0, - cells: cells.into(), - }, - range: x.range, - // range: x.range.and_then(|x| text.l_range(x)), - } - .into(), - )) - }); - self.requests.hovering.request = - (DropH::new(handle), cursor_position).into(); - // requests.hovering.result = None; - // lsp!().map(|(cl, o)| { - // let window = window.clone(); - // }); - // }); - } - Some(Do::Hover) => { - self.requests.def.result = None; - self.requests.hovering.result = None; - w.request_redraw(); - } - None => {} - x => unreachable!("{x:?}"), - } - } - pub fn click( - &mut self, - bt: MouseButton, - cursor_position: (usize, usize), - w: Arc<dyn Window>, - ) { - let text = &mut self.text; - _ = self - .requests - .complete - .consume(CompletionAction::Click) - .unwrap(); - match self.state.consume(Action::M(bt)).unwrap() { - Some(Do::MoveCursor) => { - text.cursor.just( - text.mapped_index_at(cursor_position), - &text.rope, - ); - if let Some((lsp, path)) = lsp!(self + p) { - if self.requests.sig_help.result.is_some() { - self.requests.sig_help.request(lsp.runtime.spawn( - lsp.request_sig_help( - path, - text.primary_cursor(), - ), - )); - } - self.requests.document_highlights.request( - lsp.runtime.spawn( - lsp.document_highlights( - path, - text.to_l_position( - text.cursor.first().position, - ) - .unwrap(), - ), - ), - ); - } - self.hist.lc = text.cursor.clone(); - self.chist.push(text.primary_cursor()); - text.cursor.first().setc(&text.rope); - } - Some(Do::NavForward) => self.nav_forward(), - Some(Do::NavBack) => self.nav_back(), - Some(Do::ExtendSelectionToMouse) => { - let p = text.mapped_index_at(cursor_position); - text.cursor.first_mut().extend_selection_to(p, &text.rope); - } - Some(Do::StartSelection) => { - let p = text.mapped_index_at(cursor_position); - - let x = *text.cursor.first(); - text.cursor.first_mut().sel = Some((x..x).into()); - text.cursor.first_mut().extend_selection_to(p, &text.rope); - self.hist.lc = text.cursor.clone(); - } - Some(Do::GoToDefinition) => { - if let Some(x) = self.requests.def.result.clone() - && let Err(e) = self.go(&x, w.clone()) - { - log::error!("gtd: {e}"); - } - } - Some(Do::InsertCursorAtMouse) => { - text.cursor.add( - text.mapped_index_at(cursor_position), - &text.rope, - ); - self.hist.lc = text.cursor.clone(); - self.chist.push(text.primary_cursor()); - text.cursor.first().setc(&text.rope); - } - None => {} - _ => unreachable!(), - } - } pub fn nav_back(&mut self) { self.chist.back().map(|x| { @@ -943,872 +390,7 @@ impl Editor { } inlay!(self); } - pub fn keyboard( - &mut self, - event: KeyEvent, - window: &mut Arc<dyn 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::Escape) => { - take(&mut self.requests.complete); - take(&mut self.requests.sig_help); - self.text.cursor.alone(); - } - Some(Do::Comment(p)) => { - ceach!(self.text.cursor, |cursor| { - Some( - if let Some(x) = cursor.sel - && matches!(p, State::Selection) - { - self.text.comment(x.into()); - } else { - self.text - .comment(cursor.position..cursor.position); - }, - ) - }); - self.text.cursor.clear_selections(); - change!(self, window.clone()); - } - Some(Do::SpawnTerminal) => { - if let Err(e) = trm::toggle( - self.workspace - .as_deref() - .unwrap_or(Path::new("/home/os/")), - ) { - log::error!("opening terminal failed {e}"); - } - } - Some(Do::MatchingBrace) => { - if let Some((l, f)) = lsp!(self + p) { - l.matching_brace(f, &mut self.text); - } - } - Some(Do::DeleteBracketPair) => { - if let Some((l, f)) = lsp!(self + p) { - if let Ok(x) = l.matching_brace_at( - f, - self.text.cursor.positions(&self.text.rope), - ) { - use itertools::Itertools; - for p in - // self.text.cursor.iter() - x - .iter() - .flatten() - .flat_map(|(a, b)| { - [a, b].map(|c| { - self.text - .rope - .l_position(*c) - .unwrap() - }) - }) - .sorted() - .rev() - { - self.text.remove(p..p + 1).unwrap(); - } - } - } - } - Some(Do::Symbols) => - if let Some((lsp, o)) = lsp!(self + p) { - let mut q = Rq::new( - lsp.runtime.spawn( - lsp.workspace_symbols("".into()) - .map(|x| x.anonymize()) - .map(|x| { - x.map(|x| { - x.map(SymbolsList::Workspace) - }) - }), - ), - ); - q.result = Some(Symbols::new( - self.tree.as_deref().unwrap(), - self.text.bookmarks.clone(), - o.into(), - )); - self.state = State::Symbols(q); - }, - Some(Do::SwitchType) => - if let Some((lsp, p)) = lsp!(self + p) { - let State::Symbols(Rq { result: Some(x), request }) = - &mut self.state - else { - unreachable!() - }; - x.data.3 = sym::SymbolsType::Document; - let p = p.to_owned(); - take(&mut x.data.0); - *request = Some(( - DropH::new(lsp.runtime.spawn(async move { - lsp.document_symbols(&p) - .await - .anonymize() - .map(|x| x.map(SymbolsList::Document)) - })), - (), - )); - }, - Some(Do::ProcessCommand(mut x, z)) => - match Cmds::complete_or_accept(&z) { - crate::menu::generic::CorA::Complete => { - x.tedit.rope = - Rope::from_str(&format!("{} ", z.name())); - x.tedit.cursor.end(&x.tedit.rope); - self.state = State::Command(x); - } - crate::menu::generic::CorA::Accept => { - if let Err(e) = - self.handle_command(z, window.clone()) - { - self.bar.last_action = format!("{e}"); - } - } - }, - Some(Do::CmdTyped) => { - let State::Command(x) = &self.state else { - unreachable!() - }; - if let Some(Ok(crate::commands::Cmd::GoTo(Some(x)))) = - x.sel() - { - self.text.scroll_to_ln_centering(x as _); - } - } - 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 - { - if x.data.3 == SymbolsType::Workspace { - *request = Some(( - DropH::new( - lsp.runtime.spawn( - lsp.workspace_symbols( - x.tedit.rope.to_string(), - ) - .map(|x| { - x.anonymize().map(|x| { - x.map( - SymbolsList::Workspace, - ) - }) - }), - ), - ), - (), - )); - } else { - x.selection = 0; - x.vo = 0; - } - // 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(); - if let Some(Ok(x)) = x.sel() - && Some(&*x.at.path) == self.origin.as_deref() - { - match x.at { - sym::GoTo { path: _, at: At::R(x) } => { - let x = self.text.l_range(x).unwrap(); - self.text.vo = self.text.char_to_line(x.start); - } - sym::GoTo { path: _, at: At::P(x) } => - self.text.vo = self.text.char_to_line(x), - } - } - } - Some(Do::SymbolsSelectPrev) => { - let State::Symbols(Rq { result: Some(x), .. }) = - &mut self.state - else { - unreachable!() - }; - x.back(); - if let Some(Ok(x)) = x.sel() - && Some(&*x.at.path) == self.origin.as_deref() - { - match x.at.at { - At::R(x) => { - let x = self.text.l_range(x).unwrap(); - self.text.vo = self.text.char_to_line(x.start); - } - At::P(x) => - self.text.vo = self.text.char_to_line(x), - } - } - } - Some(Do::SymbolsSelect(x)) => - if let Some(Ok(x)) = x.sel() - && let Err(e) = self.go(x.at, window.clone()) - { - log::error!("alas! {e}"); - }, - Some(Do::RenameSymbol(to)) => { - if let Some((lsp, f)) = lsp!(self + p) { - let x = lsp - .request_immediate::<lsp_request!("textDocument/rename")>( - &RenameParams { - text_document_position: - TextDocumentPositionParams { - text_document: f.tid(), - position: self - .text - .to_l_position( - self.text - .cursor - .first() - .position, - ) - .unwrap(), - }, - new_name: to, - work_done_progress_params: default(), - }, - ); - - match x { - Ok(Some(x)) => - if let Err(e) = - self.apply_wsedit(x, &f.to_owned()) - { - println!( - "couldnt apply one or more wsedits: \ - {e}" - ); - }, - Err(RequestError::Failure( - lsp_server::Response { - result: None, - error: - Some(ResponseError { - code: -32602, - message, - data: None, - }), - .. - }, - .., - )) => self.bar.last_action = message, - _ => {} - } - } - } - 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.first().position..self.text.cursor.first().position, - ) - .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.requests.diagnostics.get(&uri, &lsp.requests.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(); - - self.state = - State::CodeAction(Rq::new(lsp.runtime.spawn(r.0))); - } - } - 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.lc = self.text.cursor.clone(); - self.hist.test_push(&mut self.text); - let act = lsp - .request_immediate::<CodeActionResolveRequest>(&act) - .unwrap(); - let f = f.to_owned(); - if let Some(x) = act.edit - && let Err(e) = self.apply_wsedit(x, &f) - { - log::error!("{e}"); - } - } - 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::NavBack) => self.nav_back(), - Some(Do::NavForward) => self.nav_forward(), - Some( - Do::Reinsert - | Do::GoToDefinition - | Do::MoveCursor - | Do::ExtendSelectionToMouse - | Do::Hover - | Do::InsertCursorAtMouse, - ) => panic!(), - Some(Do::Save) => match &self.origin { - Some(_) => { - 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.text.cursor.clear_selections(); - self.hist.test_push(&mut self.text); - let cb4 = self.text.cursor.first(); - if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) = - event.logical_key - && let CompletionState::Complete(..) = - self.requests.complete - { - } else { - if let Some(x) = handle2( - &event.logical_key, - &mut self.text, - lsp!(self + p), - ) && let Some((l, p)) = lsp!(self + p) - && let Some( - InitializeResult { - capabilities: - ServerCapabilities { - document_on_type_formatting_provider: - Some(DocumentOnTypeFormattingOptions { - first_trigger_character, - more_trigger_character: Some(t), - }), - .. - }, - .. - }, - .., - ) = &l.initialized - && (first_trigger_character == first_trigger_character - || t.iter().any(|y| y == x)) - && self.text.cursor.inner.len() == 1 - && change!(just self).is_some() - && let Ok(Some(mut x)) = l - .request_immediate::<OnTypeFormatting>( - &DocumentOnTypeFormattingParams { - text_document_position: - TextDocumentPositionParams { - text_document: p.tid(), - position: self - .text - .to_l_position( - *self.text.cursor.first(), - ) - .unwrap(), - }, - ch: x.into(), - options: FormattingOptions { - tab_size: 4, - ..default() - }, - }, - ) - { - x.sort_tedits(); - for x in x { - self.text.apply_snippet_tedit(&x).unwrap(); - } - } - }; - self.text.scroll_to_cursor(); - if cb4 != self.text.cursor.first() - && let CompletionState::Complete(Rq { - result: Some(c), - .. - }) = &self.requests.complete - && let at = - self.text.cursor.first().at_(&self.text.rope) - && ((self.text.cursor.first() < c.start) - || (!super::is_word(at) - && (at != '.' || at != ':'))) - { - self.requests.complete = CompletionState::None; - } - if self.requests.sig_help.running() - && cb4 != self.text.cursor.first() - && let Some((lsp, path)) = lsp!(self + p) - { - self.requests.sig_help.request( - lsp.runtime.spawn( - lsp.request_sig_help( - path, - self.text - .cursor - .first() - .cursor(&self.text.rope), - ), - ), - ); - } - if self.hist.record(&self.text) { - change!(self, window.clone()); - } - lsp!(self + p).map(|(lsp, o)| { - 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.requests.sig_help.request( - lsp.runtime.spawn( - lsp.request_sig_help( - o, - self.text - .cursor - .first() - .cursor(&self.text.rope), - ), - ), - ); - } - _ => {} - } - match self - .requests - .complete - .consume(CompletionAction::K( - event.logical_key.as_ref(), - )) - .unwrap() - { - Some(CDo::Request(ctx)) => { - let h = DropH::new( - lsp.runtime.spawn( - lsp.request_complete( - o, - self.text - .cursor - .first() - .cursor(&self.text.rope), - ctx, - ), - ), - ); - let CompletionState::Complete(Rq { - request: x, - result: c, - }) = &mut self.requests.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.first()), - )); - } - Some(CDo::SelectNext) => { - let CompletionState::Complete(Rq { - result: Some(c), - .. - }) = &mut self.requests.complete - else { - panic!() - }; - c.next(&filter(&self.text)); - } - Some(CDo::SelectPrevious) => { - let CompletionState::Complete(Rq { - result: Some(c), - .. - }) = &mut self.requests.complete - else { - panic!() - }; - c.back(&filter(&self.text)); - } - Some(CDo::Finish(x)) => { - let sel = x.sel(&filter(&self.text)); - let sel = lsp.resolve(sel.clone()).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(); - } - _ => { - self.text.apply(&ed).unwrap(); - // self.text - // .cursor - // .first_mut() - // .position = - // s + ed.new_text.chars().count(); - } - } - if let Some(mut additional_tedits) = - additional_text_edits - { - additional_tedits.sort_tedits(); - for additional in additional_tedits { - self.text - .apply_adjusting(&additional) - .unwrap(); - } - } - if self.hist.record(&self.text) { - change!(self, window.clone()); - } - self.requests.sig_help = Rq::new( - lsp.runtime.spawn( - lsp.request_sig_help( - o, - self.text - .cursor - .first() - .cursor(&self.text.rope), - ), - ), - ); - } - None => return, - }; - }); - } - Some(Do::Undo) => { - self.hist.test_push(&mut self.text); - self.hist.undo(&mut self.text).unwrap(); - self.bar.last_action = "undid".to_string(); - change!(self, window.clone()); - } - Some(Do::Redo) => { - self.hist.test_push(&mut self.text); - self.hist.redo(&mut self.text).unwrap(); - self.bar.last_action = "redid".to_string(); - change!(self, window.clone()); - } - Some(Do::Quit) => return ControlFlow::Break(()), - Some(Do::SetCursor(x)) => { - self.text.cursor.each(|c| { - let Some(r) = c.sel else { return }; - match x { - LR::Left => c.position = r.start, - LR::Right => c.position = r.end, - } - }); - self.text.cursor.clear_selections(); - } - Some(Do::StartSelection) => { - let Key::Named(y) = event.logical_key else { panic!() }; - // let mut z = vec![]; - self.text.cursor.each(|x| { - x.sel = Some(Ronge::from(**x..**x)); - x.extend_selection( - y, - // **x..**x, - &self.text.rope, - &mut self.text.vo, - self.text.r, - ); - }); - // *self.state.sel() = z; - } - Some(Do::UpdateSelection) => { - let Key::Named(y) = event.logical_key else { panic!() }; - self.text.cursor.each(|x| { - x.extend_selection( - y, - // dbg!(r.clone()), - &self.text.rope, - &mut self.text.vo, - self.text.r, - ); - }); - - self.text.scroll_to_cursor(); - inlay!(self); - } - Some(Do::Insert(c)) => { - // self.text.cursor.inner.clear(); - self.hist.push_if_changed(&mut self.text); - ceach!(self.text.cursor, |cursor| { - let Some(r) = cursor.sel else { return }; - _ = self.text.remove(r.into()); - // self.text.cursor.first().setc(&self.text.rope); - }); - self.text.insert(&c); - self.text.cursor.clear_selections(); - self.hist.push_if_changed(&mut self.text); - change!(self, window.clone()); - } - Some(Do::Delete) => { - self.hist.push_if_changed(&mut self.text); - ceach!(self.text.cursor, |cursor| { - let Some(r) = cursor.sel else { return }; - _ = self.text.remove(r.into()); - }); - self.text.cursor.clear_selections(); - self.hist.push_if_changed(&mut self.text); - change!(self, window.clone()); - } - - Some(Do::Copy) => { - self.hist.push_if_changed(&mut self.text); - unsafe { take(&mut META) }; - let mut clip = String::new(); - self.text.cursor.each_ref(|x| { - if let Some(x) = x.sel { - unsafe { - META.count += 1; - META.splits.push(clip.len()); - } - clip.extend(self.text.rope.slice(x).chars()); - } - }); - unsafe { - META.splits.push(clip.len()); - META.hash = hash(&clip) - }; - clipp::copy(clip); - self.text.cursor.clear_selections(); - self.hist.push_if_changed(&mut self.text); - change!(self, window.clone()); - } - Some(Do::Cut) => { - self.hist.push_if_changed(&mut self.text); - unsafe { take(&mut META) }; - let mut clip = String::new(); - self.text.cursor.each_ref(|x| { - if let Some(x) = x.sel { - unsafe { - META.count += 1; - META.splits.push(clip.len()); - } - clip.extend(self.text.rope.slice(x).chars()); - } - }); - unsafe { - META.splits.push(clip.len()); - META.hash = hash(&clip) - }; - clipp::copy(clip); - ceach!(self.text.cursor, |x| { - if let Some(x) = x.sel { - self.text.remove(x.into()).unwrap(); - } - }); - self.text.cursor.clear_selections(); - self.hist.push_if_changed(&mut self.text); - change!(self, window.clone()); - } - Some(Do::PasteOver) => { - self.hist.push_if_changed(&mut self.text); - ceach!(self.text.cursor, |cursor| { - let Some(r) = cursor.sel else { return }; - _ = self.text.remove(r.into()); - }); - self.text.cursor.clear_selections(); - self.paste(); - // self.hist.push_if_changed(&mut self.text); - } - Some(Do::Paste) => self.paste(), - Some(Do::OpenFile(x)) => { - _ = self.open(Path::new(&x), window.clone()); - } - 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.first()) - .map(|(x, m)| { - self.state = State::Search(s, x, n); - self.text.cursor.just( - self.text.rope.byte_to_char(m.end()), - &self.text.rope, - ); - 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.just( - self.text.rope.byte_to_char(m.end()), - &self.text.rope, - ); - self.text.scroll_to_cursor_centering(); - inlay!(self); - } - Some(Do::Boolean(BoolRequest::ReloadFile, true)) => { - self.hist.push_if_changed(&mut self.text); - self.text.rope = Rope::from_str( - &std::fs::read_to_string( - self.origin.as_ref().unwrap(), - ) - .unwrap(), - ); - - self.text.cursor.first_mut().position = self - .text - .cursor - .first() - .position - .min(self.text.rope.len_chars()); - self.mtime = Self::modify(self.origin.as_deref()); - self.bar.last_action = "reloaded".into(); - self.hist.push(&mut self.text) - } - Some(Do::Boolean(BoolRequest::ReloadFile, false)) => {} - Some(Do::InsertCursor(dir)) => { - let (x, y) = match dir { - Direction::Above => self.text.cursor.min(), - Direction::Below => self.text.cursor.max(), - } - .cursor(&self.text.rope); - let y = match dir { - Direction::Above => y - 1, - Direction::Below => y + 1, - }; - let position = self.text.line_to_char(y); - self.text.cursor.add(position + x, &self.text.rope); - } - Some(Do::Run(x)) => - if let Some((l, ws)) = - lsp!(self).zip(self.workspace.as_deref()) - { - l.runtime - .block_on(crate::runnables::run(x, ws)) - .unwrap(); - }, - Some(Do::GoToImplementations) => { - let State::GoToL(x) = &mut self.state else { - unreachable!() - }; - if let Some(l) = lsp!(self) { - x.data.1 = Some(crate::gotolist::O::Impl(Rq::new( - l.runtime.spawn( - l.go_to_implementations(tdpp!(self)).unwrap(), - ), - ))); - } - } - Some(Do::GTLSelect(x)) => - if let Some(Ok(g)) = x.sel() - && let Err(e) = self.go(g, window.clone()) - { - eprintln!("go-to-list select fail: {e}"); - }, - Some(Do::GT) => { - let State::GoToL(x) = &mut self.state else { - unreachable!() - }; - if let Some(Ok(GoTo { path: p, at: At::R(r) })) = x.sel() - && Some(&*p) == self.origin.as_deref() - { - // let x = self.text.l_range(r).unwrap(); - self.text.scroll_to_ln_centering(r.start.line as _); - // self.text.vo = self.text.char_to_line(x.start); - } - } - None => {} - } - ControlFlow::Continue(()) - } pub fn paste(&mut self) { self.hist.push_if_changed(&mut self.text); let r = clipp::paste(); @@ -2012,59 +594,6 @@ impl Editor { } use NamedKey::*; -pub fn handle2<'a>( - key: &'a Key, - text: &mut TextArea, - l: Option<(&Client, &Path)>, -) -> Option<&'a str> { - use Key::*; - - match key { - Character(" ") => text.insert(" "), - Named(Backspace) if ctrl() => text.backspace_word(), - Named(Backspace) => text.backspace(), - Named(Home) if ctrl() => { - text.cursor.just(0, &text.rope); - text.vo = 0; - } - Named(End) if ctrl() => { - text.cursor.just(text.rope.len_chars(), &text.rope); - text.vo = text.l().saturating_sub(text.r); - } - Character("e") if alt() => text.end(), - Character("q") if alt() => text.home(), - Named(Home) => text.home(), - Named(End) => text.end(), - Named(Tab) => text.tab(), - Named(Delete) => { - text.right(); - text.backspace() - } - Character("a") if alt() && ctrl() => text.word_left(), - Character("d") if alt() && ctrl() => text.word_right(), - Character("a") if alt() => text.left(), - Character("w") if alt() => text.up(), - Character("s") if alt() => text.down(), - Character("d") if alt() => text.right(), - 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).unwrap(), - Named(Enter) => text.enter(), - Character(x) => { - text.insert(&x); - return Some(x); - } - _ => {} - }; - None -} - impl State { fn search(&mut self) -> (&mut Regex, &mut usize, &mut usize) { let State::Search(x, y, z) = self else { panic!() }; diff --git a/src/edi/input_handlers.rs b/src/edi/input_handlers.rs new file mode 100644 index 0000000..ac4ca4c --- /dev/null +++ b/src/edi/input_handlers.rs @@ -0,0 +1,59 @@ +use std::path::Path; + +use winit::keyboard::Key; +mod click; +mod cursor; +mod keyboard; +use crate::edi::*; +pub fn handle2<'a>( + key: &'a Key, + text: &mut TextArea, + l: Option<(&Client, &Path)>, +) -> Option<&'a str> { + use Key::*; + + match key { + Character(" ") => text.insert(" "), + Named(Backspace) if ctrl() => text.backspace_word(), + Named(Backspace) => text.backspace(), + Named(Home) if ctrl() => { + text.cursor.just(0, &text.rope); + text.vo = 0; + } + Named(End) if ctrl() => { + text.cursor.just(text.rope.len_chars(), &text.rope); + text.vo = text.l().saturating_sub(text.r); + } + Character("e") if alt() => text.end(), + Character("q") if alt() => text.home(), + Named(Home) => text.home(), + Named(End) => text.end(), + Named(Tab) => text.tab(), + Named(Delete) => { + text.right(); + text.backspace() + } + Character("a") if alt() && ctrl() => text.word_left(), + Character("d") if alt() && ctrl() => text.word_right(), + Character("a") if alt() => text.left(), + Character("w") if alt() => text.up(), + Character("s") if alt() => text.down(), + Character("d") if alt() => text.right(), + 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).unwrap(), + Named(Enter) => text.enter(), + Character(x) => { + text.insert(&x); + return Some(x); + } + _ => {} + }; + None +} diff --git a/src/edi/input_handlers/click.rs b/src/edi/input_handlers/click.rs new file mode 100644 index 0000000..0998d46 --- /dev/null +++ b/src/edi/input_handlers/click.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; + +use rust_fsm::StateMachine; +use winit::event::MouseButton; +use winit::window::Window; + +use crate::edi::*; +impl Editor { + pub fn click( + &mut self, + bt: MouseButton, + cursor_position: (usize, usize), + w: Arc<dyn Window>, + ) { + let text = &mut self.text; + _ = self + .requests + .complete + .consume(CompletionAction::Click) + .unwrap(); + match self.state.consume(Action::M(bt)).unwrap() { + Some(Do::MoveCursor) => { + text.cursor.just( + text.mapped_index_at(cursor_position), + &text.rope, + ); + if let Some((lsp, path)) = lsp!(self + p) { + if self.requests.sig_help.result.is_some() { + self.requests.sig_help.request(lsp.runtime.spawn( + lsp.request_sig_help( + path, + text.primary_cursor(), + ), + )); + } + self.requests.document_highlights.request( + lsp.runtime.spawn( + lsp.document_highlights( + path, + text.to_l_position( + text.cursor.first().position, + ) + .unwrap(), + ), + ), + ); + } + self.hist.lc = text.cursor.clone(); + self.chist.push(text.primary_cursor()); + text.cursor.first().setc(&text.rope); + } + Some(Do::NavForward) => self.nav_forward(), + Some(Do::NavBack) => self.nav_back(), + Some(Do::ExtendSelectionToMouse) => { + let p = text.mapped_index_at(cursor_position); + text.cursor.first_mut().extend_selection_to(p, &text.rope); + } + Some(Do::StartSelection) => { + let p = text.mapped_index_at(cursor_position); + + let x = *text.cursor.first(); + text.cursor.first_mut().sel = Some((x..x).into()); + text.cursor.first_mut().extend_selection_to(p, &text.rope); + self.hist.lc = text.cursor.clone(); + } + Some(Do::GoToDefinition) => { + if let Some(x) = self.requests.def.result.clone() + && let Err(e) = self.go(&x, w.clone()) + { + log::error!("gtd: {e}"); + } + } + Some(Do::InsertCursorAtMouse) => { + text.cursor.add( + text.mapped_index_at(cursor_position), + &text.rope, + ); + self.hist.lc = text.cursor.clone(); + self.chist.push(text.primary_cursor()); + text.cursor.first().setc(&text.rope); + } + None => {} + _ => unreachable!(), + } + } +} diff --git a/src/edi/input_handlers/cursor.rs b/src/edi/input_handlers/cursor.rs new file mode 100644 index 0000000..bb283af --- /dev/null +++ b/src/edi/input_handlers/cursor.rs @@ -0,0 +1,245 @@ +use std::borrow::Cow; +use std::sync::Arc; + +use Default::default; +use implicit_fn::implicit_fn; +use lsp_types::request::*; +use lsp_types::*; +use rust_fsm::StateMachine; +use tokio::task::spawn_blocking; +use tokio_util::task::AbortOnDropHandle as DropH; +use winit::window::Window; + +use crate::edi::*; +impl Editor { + #[implicit_fn] + pub fn cursor_moved( + &mut self, + cursor_position: (usize, usize), + w: Arc<dyn Window>, + c: usize, + ) { + match self.state.consume(Action::C(cursor_position)).unwrap() { + Some(Do::ExtendSelectionToMouse) => { + let p = self.text.mapped_index_at(cursor_position); + self.text + .cursor + .first_mut() + .extend_selection_to(p, &self.text.rope); + w.request_redraw(); + } + Some(Do::StartSelection) => { + let x = self.text.mapped_index_at(cursor_position); + self.text.cursor.first_mut().position = x; + self.text.cursor.first_mut().sel = Some((x..x).into()); + self.hist.lc = self.text.cursor.clone(); + } + Some(Do::Hover) + if let Some(hover) = self + .text + .visual_index_at(cursor_position) + .map(Mapping::own) + && let Some(_) = lsp!(self) => + 'out: { + let l = &mut self.requests.hovering.result; + if let Some(Hovr { + span: Some([(_x, _y), (_x2, _)]), .. + }) = &*l + && let Some(_y) = _y.checked_sub(self.text.vo) + && let Some(_x) = _x.checked_sub(self.text.ho) + && let Some(_x2) = _x2.checked_sub(self.text.ho) + && 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(); + } + self.rq_hover(hover, cursor_position, c); + } + Some(Do::Hover) => { + self.requests.def.result = None; + self.requests.hovering.result = None; + w.request_redraw(); + } + None => {} + x => unreachable!("{x:?}"), + } + } + #[implicit_fn] + pub fn rq_hover( + &mut self, + hover: Mapping<'_>, + cursor_position: (usize, usize), + c: usize, + ) { + let (lsp, o) = lsp!(self + p).unwrap(); + 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.data[relpos as usize].1 else { + return; + }; + let (x, y) = text.xy(abspos as _).unwrap(); + let Some(mut begin) = text.reverse_source_map(y) else { + return; + }; + let start = begin.nth(x.saturating_sub(1)).unwrap() + 1; + let left = mark.data[..relpos as usize] + .iter() + .rev() + .take_while(_.1.as_ref() == Some(loc)) + .count(); + let start = start + relpos as usize - left; + let length = mark.data[relpos as usize..] + .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 + .requests + .def + .request + .as_ref() + .is_none_or(|&(_, x)| x != cursor_position) + { + let handle = lsp.runtime.spawn( + lsp.request::<lsp_request!("textDocument/definition")>( + &GotoDefinitionParams { + text_document_position_params: z.clone(), + work_done_progress_params: default(), + partial_result_params: default(), + }, + ) + .unwrap() + .0, + ); + self.requests.def.request = + Some((DropH::new(handle), cursor_position)); + } else if self.requests.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.requests.def.result = None; + } + } else { + self.requests.def.result = None; + } + if let Some((_, c)) = self.requests.hovering.request + && c == cursor_position + { + return; + } + // if !running.insert(hover) {return} + let (rx, _) = lsp + .request::<HoverRequest>(&HoverParams { + text_document_position_params: z, + work_done_progress_params: default(), + }) + .unwrap(); + // println!("rq hov of {hover:?} (cur {})", requests.hovering.request.is_some()); + let handle: tokio::task::JoinHandle<Result<Option<Hovr>, _>> = + lsp.runtime.spawn(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 _), + ] + }) + }); + Ok(Some( + hov::Hovr { + span, + item: text::CellBuffer { + c: w, + vo: 0, + cells: cells.into(), + }, + range: x.range, + // range: x.range.and_then(|x| text.l_range(x)), + } + .into(), + )) + }); + self.requests.hovering.request = + (DropH::new(handle), cursor_position).into(); + // requests.hovering.result = None; + // lsp!().map(|(cl, o)| { + // let window = window.clone(); + // }); + // }); + } +} diff --git a/src/edi/input_handlers/keyboard.rs b/src/edi/input_handlers/keyboard.rs new file mode 100644 index 0000000..eb624ac --- /dev/null +++ b/src/edi/input_handlers/keyboard.rs @@ -0,0 +1,888 @@ +use std::mem::take; +use std::ops::ControlFlow; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use Default::default; +use lsp_server::ResponseError; +use lsp_types::request::*; +use lsp_types::*; +use regex::Regex; +use ropey::Rope; +use rust_analyzer::lsp::ext::OnTypeFormatting; +use rust_fsm::StateMachine; +use tokio_util::task::AbortOnDropHandle as DropH; +use winit::event::KeyEvent; +use winit::keyboard::Key; +use winit::window::Window; + +use crate::edi::*; + +impl Editor { + pub fn keyboard( + &mut self, + event: KeyEvent, + window: &mut Arc<dyn 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::Escape) => { + take(&mut self.requests.complete); + take(&mut self.requests.sig_help); + self.text.cursor.alone(); + } + Some(Do::Comment(p)) => { + ceach!(self.text.cursor, |cursor| { + Some( + if let Some(x) = cursor.sel + && matches!(p, State::Selection) + { + self.text.comment(x.into()); + } else { + self.text + .comment(cursor.position..cursor.position); + }, + ) + }); + self.text.cursor.clear_selections(); + change!(self, window.clone()); + } + Some(Do::SpawnTerminal) => { + if let Err(e) = trm::toggle( + self.workspace + .as_deref() + .unwrap_or(Path::new("/home/os/")), + ) { + log::error!("opening terminal failed {e}"); + } + } + Some(Do::MatchingBrace) => { + if let Some((l, f)) = lsp!(self + p) { + l.matching_brace(f, &mut self.text); + } + } + Some(Do::DeleteBracketPair) => { + if let Some((l, f)) = lsp!(self + p) { + if let Ok(x) = l.matching_brace_at( + f, + self.text.cursor.positions(&self.text.rope), + ) { + use itertools::Itertools; + for p in + // self.text.cursor.iter() + x + .iter() + .flatten() + .flat_map(|(a, b)| { + [a, b].map(|c| { + self.text + .rope + .l_position(*c) + .unwrap() + }) + }) + .sorted() + .rev() + { + self.text.remove(p..p + 1).unwrap(); + } + } + } + } + Some(Do::Symbols) => + if let Some((lsp, o)) = lsp!(self + p) { + let mut q = Rq::new( + lsp.runtime.spawn( + lsp.workspace_symbols("".into()) + .map(|x| x.anonymize()) + .map(|x| { + x.map(|x| { + x.map(SymbolsList::Workspace) + }) + }), + ), + ); + q.result = Some(Symbols::new( + self.tree.as_deref().unwrap(), + self.text.bookmarks.clone(), + o.into(), + )); + self.state = State::Symbols(q); + }, + Some(Do::SwitchType) => + if let Some((lsp, p)) = lsp!(self + p) { + let State::Symbols(Rq { result: Some(x), request }) = + &mut self.state + else { + unreachable!() + }; + x.data.3 = sym::SymbolsType::Document; + let p = p.to_owned(); + take(&mut x.data.0); + *request = Some(( + DropH::new(lsp.runtime.spawn(async move { + lsp.document_symbols(&p) + .await + .anonymize() + .map(|x| x.map(SymbolsList::Document)) + })), + (), + )); + }, + Some(Do::ProcessCommand(mut x, z)) => + match Cmds::complete_or_accept(&z) { + crate::menu::generic::CorA::Complete => { + x.tedit.rope = + Rope::from_str(&format!("{} ", z.name())); + x.tedit.cursor.end(&x.tedit.rope); + self.state = State::Command(x); + } + crate::menu::generic::CorA::Accept => { + if let Err(e) = + self.handle_command(z, window.clone()) + { + self.bar.last_action = format!("{e}"); + } + } + }, + Some(Do::CmdTyped) => { + let State::Command(x) = &self.state else { + unreachable!() + }; + if let Some(Ok(crate::commands::Cmd::GoTo(Some(x)))) = + x.sel() + { + self.text.scroll_to_ln_centering(x as _); + } + } + 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 + { + if x.data.3 == SymbolsType::Workspace { + *request = Some(( + DropH::new( + lsp.runtime.spawn( + lsp.workspace_symbols( + x.tedit.rope.to_string(), + ) + .map(|x| { + x.anonymize().map(|x| { + x.map( + SymbolsList::Workspace, + ) + }) + }), + ), + ), + (), + )); + } else { + x.selection = 0; + x.vo = 0; + } + // 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(); + if let Some(Ok(x)) = x.sel() + && Some(&*x.at.path) == self.origin.as_deref() + { + match x.at { + sym::GoTo { path: _, at: At::R(x) } => { + let x = self.text.l_range(x).unwrap(); + self.text.vo = self.text.char_to_line(x.start); + } + sym::GoTo { path: _, at: At::P(x) } => + self.text.vo = self.text.char_to_line(x), + } + } + } + Some(Do::SymbolsSelectPrev) => { + let State::Symbols(Rq { result: Some(x), .. }) = + &mut self.state + else { + unreachable!() + }; + x.back(); + if let Some(Ok(x)) = x.sel() + && Some(&*x.at.path) == self.origin.as_deref() + { + match x.at.at { + At::R(x) => { + let x = self.text.l_range(x).unwrap(); + self.text.vo = self.text.char_to_line(x.start); + } + At::P(x) => + self.text.vo = self.text.char_to_line(x), + } + } + } + Some(Do::SymbolsSelect(x)) => + if let Some(Ok(x)) = x.sel() + && let Err(e) = self.go(x.at, window.clone()) + { + log::error!("alas! {e}"); + }, + Some(Do::RenameSymbol(to)) => { + if let Some((lsp, f)) = lsp!(self + p) { + let x = lsp + .request_immediate::<lsp_request!("textDocument/rename")>( + &RenameParams { + text_document_position: + TextDocumentPositionParams { + text_document: f.tid(), + position: self + .text + .to_l_position( + self.text + .cursor + .first() + .position, + ) + .unwrap(), + }, + new_name: to, + work_done_progress_params: default(), + }, + ); + + match x { + Ok(Some(x)) => + if let Err(e) = + self.apply_wsedit(x, &f.to_owned()) + { + println!( + "couldnt apply one or more wsedits: \ + {e}" + ); + }, + Err(RequestError::Failure( + lsp_server::Response { + result: None, + error: + Some(ResponseError { + code: -32602, + message, + data: None, + }), + .. + }, + .., + )) => self.bar.last_action = message, + _ => {} + } + } + } + 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.first().position..self.text.cursor.first().position, + ) + .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.requests.diagnostics.get(&uri, &lsp.requests.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(); + + self.state = + State::CodeAction(Rq::new(lsp.runtime.spawn(r.0))); + } + } + 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.lc = self.text.cursor.clone(); + self.hist.test_push(&mut self.text); + let act = lsp + .request_immediate::<CodeActionResolveRequest>(&act) + .unwrap(); + let f = f.to_owned(); + if let Some(x) = act.edit + && let Err(e) = self.apply_wsedit(x, &f) + { + log::error!("{e}"); + } + } + 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::NavBack) => self.nav_back(), + Some(Do::NavForward) => self.nav_forward(), + Some( + Do::Reinsert + | Do::GoToDefinition + | Do::MoveCursor + | Do::ExtendSelectionToMouse + | Do::Hover + | Do::InsertCursorAtMouse, + ) => panic!(), + Some(Do::Save) => match &self.origin { + Some(_) => { + 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.text.cursor.clear_selections(); + self.hist.test_push(&mut self.text); + let cb4 = self.text.cursor.first(); + if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) = + event.logical_key + && let CompletionState::Complete(..) = + self.requests.complete + { + } else { + if let Some(x) = handle2( + &event.logical_key, + &mut self.text, + lsp!(self + p), + ) && let Some((l, p)) = lsp!(self + p) + && let Some( + InitializeResult { + capabilities: + ServerCapabilities { + document_on_type_formatting_provider: + Some(DocumentOnTypeFormattingOptions { + first_trigger_character, + more_trigger_character: Some(t), + }), + .. + }, + .. + }, + .., + ) = &l.initialized + && (first_trigger_character == first_trigger_character + || t.iter().any(|y| y == x)) + && self.text.cursor.inner.len() == 1 + && change!(just self).is_some() + && let Ok(Some(mut x)) = l + .request_immediate::<OnTypeFormatting>( + &DocumentOnTypeFormattingParams { + text_document_position: + TextDocumentPositionParams { + text_document: p.tid(), + position: self + .text + .to_l_position( + *self.text.cursor.first(), + ) + .unwrap(), + }, + ch: x.into(), + options: FormattingOptions { + tab_size: 4, + ..default() + }, + }, + ) + { + x.sort_tedits(); + for x in x { + self.text.apply_snippet_tedit(&x).unwrap(); + } + } + }; + self.text.scroll_to_cursor(); + if cb4 != self.text.cursor.first() + && let CompletionState::Complete(Rq { + result: Some(c), + .. + }) = &self.requests.complete + && let at = + self.text.cursor.first().at_(&self.text.rope) + && ((self.text.cursor.first() < c.start) + || (!crate::is_word(at) + && (at != '.' || at != ':'))) + { + self.requests.complete = CompletionState::None; + } + if self.requests.sig_help.running() + && cb4 != self.text.cursor.first() + && let Some((lsp, path)) = lsp!(self + p) + { + self.requests.sig_help.request( + lsp.runtime.spawn( + lsp.request_sig_help( + path, + self.text + .cursor + .first() + .cursor(&self.text.rope), + ), + ), + ); + } + if self.hist.record(&self.text) { + change!(self, window.clone()); + } + lsp!(self + p).map(|(lsp, o)| { + 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.requests.sig_help.request( + lsp.runtime.spawn( + lsp.request_sig_help( + o, + self.text + .cursor + .first() + .cursor(&self.text.rope), + ), + ), + ); + } + _ => {} + } + match self + .requests + .complete + .consume(CompletionAction::K( + event.logical_key.as_ref(), + )) + .unwrap() + { + Some(CDo::Request(ctx)) => { + let h = DropH::new( + lsp.runtime.spawn( + lsp.request_complete( + o, + self.text + .cursor + .first() + .cursor(&self.text.rope), + ctx, + ), + ), + ); + let CompletionState::Complete(Rq { + request: x, + result: c, + }) = &mut self.requests.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.first()), + )); + } + Some(CDo::SelectNext) => { + let CompletionState::Complete(Rq { + result: Some(c), + .. + }) = &mut self.requests.complete + else { + panic!() + }; + c.next(&filter(&self.text)); + } + Some(CDo::SelectPrevious) => { + let CompletionState::Complete(Rq { + result: Some(c), + .. + }) = &mut self.requests.complete + else { + panic!() + }; + c.back(&filter(&self.text)); + } + Some(CDo::Finish(x)) => { + let sel = x.sel(&filter(&self.text)); + let sel = lsp.resolve(sel.clone()).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(); + } + _ => { + self.text.apply(&ed).unwrap(); + // self.text + // .cursor + // .first_mut() + // .position = + // s + ed.new_text.chars().count(); + } + } + if let Some(mut additional_tedits) = + additional_text_edits + { + additional_tedits.sort_tedits(); + for additional in additional_tedits { + self.text + .apply_adjusting(&additional) + .unwrap(); + } + } + if self.hist.record(&self.text) { + change!(self, window.clone()); + } + self.requests.sig_help = Rq::new( + lsp.runtime.spawn( + lsp.request_sig_help( + o, + self.text + .cursor + .first() + .cursor(&self.text.rope), + ), + ), + ); + } + None => return, + }; + }); + } + Some(Do::Undo) => { + self.hist.test_push(&mut self.text); + self.hist.undo(&mut self.text).unwrap(); + self.bar.last_action = "undid".to_string(); + change!(self, window.clone()); + } + Some(Do::Redo) => { + self.hist.test_push(&mut self.text); + self.hist.redo(&mut self.text).unwrap(); + self.bar.last_action = "redid".to_string(); + change!(self, window.clone()); + } + Some(Do::Quit) => return ControlFlow::Break(()), + Some(Do::SetCursor(x)) => { + self.text.cursor.each(|c| { + let Some(r) = c.sel else { return }; + match x { + LR::Left => c.position = r.start, + LR::Right => c.position = r.end, + } + }); + self.text.cursor.clear_selections(); + } + Some(Do::StartSelection) => { + let Key::Named(y) = event.logical_key else { panic!() }; + // let mut z = vec![]; + self.text.cursor.each(|x| { + x.sel = Some(Ronge::from(**x..**x)); + x.extend_selection( + y, + // **x..**x, + &self.text.rope, + &mut self.text.vo, + self.text.r, + ); + }); + // *self.state.sel() = z; + } + Some(Do::UpdateSelection) => { + let Key::Named(y) = event.logical_key else { panic!() }; + self.text.cursor.each(|x| { + x.extend_selection( + y, + // dbg!(r.clone()), + &self.text.rope, + &mut self.text.vo, + self.text.r, + ); + }); + + self.text.scroll_to_cursor(); + inlay!(self); + } + Some(Do::Insert(c)) => { + // self.text.cursor.inner.clear(); + self.hist.push_if_changed(&mut self.text); + ceach!(self.text.cursor, |cursor| { + let Some(r) = cursor.sel else { return }; + _ = self.text.remove(r.into()); + // self.text.cursor.first().setc(&self.text.rope); + }); + self.text.insert(&c); + self.text.cursor.clear_selections(); + self.hist.push_if_changed(&mut self.text); + change!(self, window.clone()); + } + Some(Do::Delete) => { + self.hist.push_if_changed(&mut self.text); + ceach!(self.text.cursor, |cursor| { + let Some(r) = cursor.sel else { return }; + _ = self.text.remove(r.into()); + }); + self.text.cursor.clear_selections(); + self.hist.push_if_changed(&mut self.text); + change!(self, window.clone()); + } + + Some(Do::Copy) => { + self.hist.push_if_changed(&mut self.text); + unsafe { take(&mut META) }; + let mut clip = String::new(); + self.text.cursor.each_ref(|x| { + if let Some(x) = x.sel { + unsafe { + META.count += 1; + META.splits.push(clip.len()); + } + clip.extend(self.text.rope.slice(x).chars()); + } + }); + unsafe { + META.splits.push(clip.len()); + META.hash = hash(&clip) + }; + clipp::copy(clip); + self.text.cursor.clear_selections(); + self.hist.push_if_changed(&mut self.text); + change!(self, window.clone()); + } + Some(Do::Cut) => { + self.hist.push_if_changed(&mut self.text); + unsafe { take(&mut META) }; + let mut clip = String::new(); + self.text.cursor.each_ref(|x| { + if let Some(x) = x.sel { + unsafe { + META.count += 1; + META.splits.push(clip.len()); + } + clip.extend(self.text.rope.slice(x).chars()); + } + }); + unsafe { + META.splits.push(clip.len()); + META.hash = hash(&clip) + }; + clipp::copy(clip); + ceach!(self.text.cursor, |x| { + if let Some(x) = x.sel { + self.text.remove(x.into()).unwrap(); + } + }); + self.text.cursor.clear_selections(); + self.hist.push_if_changed(&mut self.text); + change!(self, window.clone()); + } + Some(Do::PasteOver) => { + self.hist.push_if_changed(&mut self.text); + ceach!(self.text.cursor, |cursor| { + let Some(r) = cursor.sel else { return }; + _ = self.text.remove(r.into()); + }); + self.text.cursor.clear_selections(); + self.paste(); + // self.hist.push_if_changed(&mut self.text); + } + Some(Do::Paste) => self.paste(), + Some(Do::OpenFile(x)) => { + _ = self.open(Path::new(&x), window.clone()); + } + 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.first()) + .map(|(x, m)| { + self.state = State::Search(s, x, n); + self.text.cursor.just( + self.text.rope.byte_to_char(m.end()), + &self.text.rope, + ); + 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.just( + self.text.rope.byte_to_char(m.end()), + &self.text.rope, + ); + self.text.scroll_to_cursor_centering(); + inlay!(self); + } + Some(Do::Boolean(BoolRequest::ReloadFile, true)) => { + self.hist.push_if_changed(&mut self.text); + self.text.rope = Rope::from_str( + &std::fs::read_to_string( + self.origin.as_ref().unwrap(), + ) + .unwrap(), + ); + + self.text.cursor.first_mut().position = self + .text + .cursor + .first() + .position + .min(self.text.rope.len_chars()); + self.mtime = Self::modify(self.origin.as_deref()); + self.bar.last_action = "reloaded".into(); + self.hist.push(&mut self.text) + } + Some(Do::Boolean(BoolRequest::ReloadFile, false)) => {} + Some(Do::InsertCursor(dir)) => { + let (x, y) = match dir { + Direction::Above => self.text.cursor.min(), + Direction::Below => self.text.cursor.max(), + } + .cursor(&self.text.rope); + let y = match dir { + Direction::Above => y - 1, + Direction::Below => y + 1, + }; + let position = self.text.line_to_char(y); + self.text.cursor.add(position + x, &self.text.rope); + } + Some(Do::Run(x)) => + if let Some((l, ws)) = + lsp!(self).zip(self.workspace.as_deref()) + { + l.runtime + .block_on(crate::runnables::run(x, ws)) + .unwrap(); + }, + Some(Do::GoToImplementations) => { + let State::GoToL(x) = &mut self.state else { + unreachable!() + }; + if let Some(l) = lsp!(self) { + x.data.1 = Some(crate::gotolist::O::Impl(Rq::new( + l.runtime.spawn( + l.go_to_implementations(tdpp!(self)).unwrap(), + ), + ))); + } + } + Some(Do::GTLSelect(x)) => + if let Some(Ok(g)) = x.sel() + && let Err(e) = self.go(g, window.clone()) + { + eprintln!("go-to-list select fail: {e}"); + }, + Some(Do::GT) => { + let State::GoToL(x) = &mut self.state else { + unreachable!() + }; + if let Some(Ok(GoTo { path: p, at: At::R(r) })) = x.sel() + && Some(&*p) == self.origin.as_deref() + { + // let x = self.text.l_range(r).unwrap(); + self.text.scroll_to_ln_centering(r.start.line as _); + // self.text.vo = self.text.char_to_line(x.start); + } + } + None => {} + } + ControlFlow::Continue(()) + } +} diff --git a/src/edi/lsp_impl.rs b/src/edi/lsp_impl.rs new file mode 100644 index 0000000..c28670e --- /dev/null +++ b/src/edi/lsp_impl.rs @@ -0,0 +1,220 @@ +use lsp_server::Request as LRq; +use lsp_types::request::*; +use lsp_types::*; +use serde::{Deserialize, Serialize}; +use ttools::{Tupl, With}; + +use crate::complete::Complete; +use crate::edi::st::*; +use crate::hov::Hovr; +use crate::lsp::{RequestError, Rq}; +use crate::runnables::Runnables; +use crate::sym::GoTo; +use crate::{CompletionState, act, sig, sym}; + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct Requests { + pub hovering: + Rq<Hovr, Option<Hovr>, (usize, usize), RequestError<HoverRequest>>, + pub document_highlights: Rq< + Vec<DocumentHighlight>, + Vec<DocumentHighlight>, + (), + RequestError<DocumentHighlightRequest>, + >, + pub complete: CompletionState, + pub sig_help: Rq< + (SignatureHelp, usize, Option<usize>), + Option<SignatureHelp>, + (), + RequestError<SignatureHelpRequest>, + >, // vo, lines + // #[serde(serialize_with = "serialize_tokens")] + // #[serde(deserialize_with = "deserialize_tokens")] + #[serde(skip)] + pub semantic_tokens: Rq< + Box<[SemanticToken]>, + Box<[SemanticToken]>, + (), + RequestError<SemanticTokensFullRequest>, + >, + pub diag: Rq< + String, + Option<String>, + (), + RequestError<DocumentDiagnosticRequest>, + >, + #[serde(skip)] + pub inlay: Rq< + Vec<InlayHint>, + Vec<InlayHint>, + (), + RequestError<lsp_request!("textDocument/inlayHint")>, + >, + pub def: Rq< + LocationLink, + Option<GotoDefinitionResponse>, + (usize, usize), + RequestError<lsp_request!("textDocument/definition")>, + >, + #[serde(skip)] + pub document_symbols: Rq< + Option<Vec<DocumentSymbol>>, + Option<DocumentSymbolResponse>, + (), + RequestError<lsp_request!("textDocument/documentSymbol")>, + >, + #[serde(skip)] + pub git_diff: Rq<imara_diff::Diff, imara_diff::Diff, (), ()>, +} +impl crate::edi::Editor { + 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:?}"), + } + } + self.requests.inlay.poll(|x, p| { + x.ok().or(p.1).inspect(|x| { + self.text.set_inlay(x); + }) + }); + self.requests.document_highlights.poll(|x, _| x.ok()); + self.requests.diag.poll(|x, _| x.ok().flatten()); + if let CompletionState::Complete(rq) = &mut self.requests.complete + { + rq.poll(|f, (c, _)| { + f.ok().flatten().map(|x| Complete { + r: x, + start: c, + selection: 0, + vo: 0, + }) + }); + }; + match &mut self.state { + State::Symbols(x) => { + x.poll(|x, (_, p)| { + let Some(p) = p else { unreachable!() }; + + x.ok().flatten().map(|r| sym::Symbols { + data: r.with(p.data.drop::<1>()), + selection: 0, + vo: 0, + ..p + }) + }); + } + State::CodeAction(x) => { + if 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 { + self.bar.last_action = + format!("{} code actions", lems.len()); + Some(act::CodeActions::new(lems)) + } + }) && x.result.is_none() + { + self.state = State::Default; + } + } + State::Runnables(x) => { + x.poll(|x, ((), old)| { + Some(Runnables { + data: x.ok()?, + ..old.unwrap_or_default() + }) + }); + } + State::GoToL(z) => match &mut z.data.1 { + Some(crate::gotolist::O::References(y)) => { + y.poll(|x, _| { + x.ok().flatten().map(|x| { + z.data.0 = x.iter().map(GoTo::from).collect() + }) + }); + } + Some(crate::gotolist::O::Impl(y)) => { + y.poll(|x, _| { + x.ok().map(|x| { + x.and_then(|x| try { + z.data.0 = match x { + GotoDefinitionResponse::Scalar( + location, + ) => vec![GoTo::from( + location, + )], + GotoDefinitionResponse::Array( + locations, + ) => locations + .into_iter() + .map(GoTo::from) + .collect(), + GotoDefinitionResponse::Link( + location_links, + ) => location_links + .into_iter() + .map(|LocationLink {target_uri, target_range, .. }| { + GoTo::from( + Location { + uri: target_uri, + range: target_range, + } + ) + }) + .collect(), + }; + }); + }) + }); + } + _ => {} + }, + _ => {} + } + self.requests.def.poll(|x, _| { + x.ok().flatten().and_then(|x| match &x { + GotoDefinitionResponse::Link([x, ..]) => Some(x.clone()), + _ => None, + }) + }); + self.requests + .semantic_tokens + .poll(|x, _| x.ok().inspect(|x| self.text.set_toks(&x))); + self.requests.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) + } + }) + }); + self.requests.hovering.poll(|x, _| x.ok().flatten()); + self.requests.git_diff.poll(|x, _| x.ok()); + self.requests.document_symbols.poll(|x, _| { + x.ok().flatten().map(|x| match x { + DocumentSymbolResponse::Flat(_) => None, + DocumentSymbolResponse::Nested(x) => Some(x), + }) + }); + } +} @@ -17,7 +17,7 @@ use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::window::{ImeRequestData, Window}; use crate::edi::st::State; -use crate::edi::{Editor, lsp_m}; +use crate::edi::{Editor, lsp}; use crate::gotolist::{At, GoTo}; use crate::lsp::Rq; use crate::sym::{UsedSI}; @@ -183,7 +183,7 @@ pub fn render( }); // x.range; } - 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()) { + if let Some((lsp, p)) = lsp!(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), @@ -266,7 +266,7 @@ pub fn render( } }, ed.origin.as_deref(), - match lsp_m!(ed) { + match lsp!(ed) { Some(lsp::Client { initialized: Some(lsp_types::InitializeResult { capabilities: ServerCapabilities { semantic_tokens_provider: @@ -298,7 +298,7 @@ pub fn render( .unwrap_or("new buffer"), &ed.state, &text, - lsp_m!(ed), + lsp!(ed), ); unsafe { dsb::render( @@ -491,7 +491,7 @@ pub fn render( // chain([Cell::default()], x.to_string().chars().map(Cell::basic)).chain([Cell::default()]).collect(); for (x, rel, mut cell) in text - .colored_lines(r, lsp_m!(ed).and_then(|x| x.legend())) + .colored_lines(r, lsp!(ed).and_then(|x| x.legend())) { if rel == 0 { let rem = cells.len() % c; @@ -544,7 +544,7 @@ pub fn render( // dbg !(x); } let mut pass = true; - if let Some((lsp, p)) = lsp_m!(ed + p) + if let Some((lsp, p)) = lsp!(ed + p) && let Some(diag) = lsp.diagnostics.get( &Url::from_file_path(p).unwrap(), &lsp.diagnostics.guard(), diff --git a/src/text.rs b/src/text.rs index 2e1de2b..786d850 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cmp::{Reverse, min}; use std::collections::BTreeSet; use std::fmt::Debug; @@ -315,7 +316,10 @@ impl TextArea { { for (i, (c, _)) in x.data.iter().enumerate() { yield Mapping::Fake( - x, i as u32, x.position, *c, + Cow::Borrowed(x), + i as u32, + x.position, + *c, ); } } @@ -1622,7 +1626,7 @@ pub(crate) use col; #[derive(Debug, PartialEq, Clone)] pub enum Mapping<'a> { Fake( - &'a Marking<Box<[(char, Option<Location>)]>>, + Cow<'a, Marking<Box<[(char, Option<Location>)]>>>, /// Label relative u32, /// True position @@ -1632,6 +1636,17 @@ pub enum Mapping<'a> { Char(char, usize /* line rel */, usize /* true position */), } impl Mapping<'_> { + pub fn own(self) -> Mapping<'static> { + match self { + Self::Fake(mark, a, b, c) => Mapping::Fake( + Cow::Owned(mark.clone().into_owned()), + a, + b, + c, + ), + Self::Char(x, y, z) => Mapping::Char(x, y, z), + } + } fn c(&self) -> char { let (Mapping::Char(x, ..) | Mapping::Fake(.., x)) = self; *x |