A simple CPU rendered GUI IDE experience.
Diffstat (limited to 'src/edi/input_handlers/keyboard.rs')
| -rw-r--r-- | src/edi/input_handlers/keyboard.rs | 888 |
1 files changed, 888 insertions, 0 deletions
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(()) + } +} |