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) {
log::error!(
"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) = lsp!(self) 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();
if let Some(x) = act.edit
&& let Err(e) = self.apply_wsedit(x)
{
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!()
};
use ttools::OptionOfMutRefToTuple;
*x = Some((
h,
c.as_ref()
.map(|x| x.start)
.or(x.on::<1>().copied())
.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(())
}
}