A simple CPU rendered GUI IDE experience.
Diffstat (limited to 'src/edi.rs')
-rw-r--r--src/edi.rs1321
1 files changed, 1294 insertions, 27 deletions
diff --git a/src/edi.rs b/src/edi.rs
index d825409..74f7973 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -1,65 +1,1332 @@
-use std::path::PathBuf;
+use std::borrow::Cow;
+use std::ops::ControlFlow;
+use std::path::{Path, PathBuf};
use std::sync::Arc;
-use std::thread::JoinHandle;
+use std::time::{Instant, SystemTime};
+use Default::default;
+use implicit_fn::implicit_fn;
+use lsp_server::{Connection, Request as LRq};
use lsp_types::request::*;
use lsp_types::*;
+use regex::Regex;
+use ropey::Rope;
+use rust_fsm::StateMachine;
use tokio::sync::oneshot::Sender;
+use tokio::task::spawn_blocking;
+use tokio_util::task::AbortOnDropHandle as DropH;
+use winit::event::{KeyEvent, MouseButton};
+use winit::keyboard::{Key, NamedKey};
+use winit::window::Window;
+pub mod st;
+
+use st::*;
use crate::bar::Bar;
-use crate::hov::Hovr;
-use crate::lsp::{Client, RequestError, Rq, RqS};
-use crate::text::TextArea;
-use crate::{ClickHistory, CompletionState, Hist, State};
+use crate::com::Complete;
+use crate::hov::{self, Hovr};
+use crate::lsp::{self, Client, PathURI, RedrawAfter, RequestError, Rq};
+use crate::text::{self, CoerceOption, Mapping, TextArea};
+use crate::{
+ BoolRequest, CDo, ClickHistory, CompletionAction, CompletionState,
+ Hist, act, alt, ctrl, filter, shift, sig, sym, trm,
+};
#[derive(Default)]
pub struct Editor {
- text: TextArea,
- origin: Option<PathBuf>,
- state: State,
- bar: Bar,
- workspace: Option<PathBuf>,
- lsp: Option<(
+ pub text: TextArea,
+ pub origin: Option<PathBuf>,
+ pub state: State,
+ pub bar: Bar,
+ pub workspace: Option<PathBuf>,
+ pub lsp: Option<(
&'static Client,
- JoinHandle<()>,
- Sender<Arc<winit::window::Window>>,
+ std::thread::JoinHandle<()>,
+ Option<Sender<Arc<Window>>>,
)>,
- tree: Option<Vec<PathBuf>>,
- hovering: Rq<Hovr, Option<Hovr>, (usize, usize), anyhow::Error>,
- document_highlights: Rq<
+ pub tree: Option<Vec<PathBuf>>,
+ pub hovering: Rq<Hovr, Option<Hovr>, (usize, usize), anyhow::Error>,
+ pub document_highlights: Rq<
Vec<DocumentHighlight>,
Vec<DocumentHighlight>,
(),
RequestError<DocumentHighlightRequest>,
>,
- complete: CompletionState,
- sig_help: Rq<
+ pub complete: CompletionState,
+ pub sig_help: Rq<
(SignatureHelp, usize, Option<usize>),
Option<SignatureHelp>,
(),
RequestError<SignatureHelpRequest>,
>, // vo, lines
- semantic_tokens: Rq<
+ pub semantic_tokens: Rq<
Box<[SemanticToken]>,
Box<[SemanticToken]>,
(),
RequestError<SemanticTokensFullRequest>,
>,
- diag: Rq<String, Option<String>, (), anyhow::Error>,
- inlay: Rq<
+ pub diag: Rq<String, Option<String>, (), anyhow::Error>,
+ pub inlay: Rq<
Vec<InlayHint>,
Vec<InlayHint>,
(),
RequestError<lsp_request!("textDocument/inlayHint")>,
>,
- def: Rq<
+ pub def: Rq<
LocationLink,
Option<GotoDefinitionResponse>,
(usize, usize),
RequestError<lsp_request!("textDocument/definition")>,
>,
- chist: ClickHistory,
- hist: Hist,
- mtime: Option<std::time::SystemTime>,
+ pub chist: ClickHistory,
+ pub hist: Hist,
+ pub mtime: Option<std::time::SystemTime>,
+}
+macro_rules! lsp {
+ ($self:ident) => {
+ $self.lsp.as_ref().map(|(x, ..)| *x)
+ };
+ ($self:ident + p) => {
+ $crate::edi::lsp_m!($self).zip($self.origin.as_deref())
+ };
+}
+pub(crate) use lsp as lsp_m;
+macro_rules! inlay {
+ ($self:ident) => {
+ lsp!($self + p).map(|(lsp, path)| {
+ $self
+ .inlay
+ .request(lsp.runtime.spawn(lsp.inlay(path, &$self.text)))
+ })
+ };
+}
+macro_rules! change {
+ ($self:ident) => {
+ lsp!($self + p).map(|(x, origin)| {
+ x.edit(&origin, $self.text.rope.to_string()).unwrap();
+ x.rq_semantic_tokens(&mut $self.semantic_tokens, origin, None)
+ .unwrap();
+ inlay!($self);
+ });
+ };
+}
+fn rooter(x: &Path) -> Option<PathBuf> {
+ for f in std::fs::read_dir(&x).unwrap().filter_map(Result::ok) {
+ if f.file_name() == "Cargo.toml" {
+ return Some(f.path().with_file_name("").to_path_buf());
+ }
+ }
+ x.parent().and_then(rooter)
+}
+
+impl Editor {
+ pub fn new() -> Self {
+ let mut me = Self::default();
+
+ me.origin = std::env::args()
+ .nth(1)
+ .and_then(|x| PathBuf::try_from(x).ok())
+ .and_then(|x| x.canonicalize().ok());
+
+ std::env::args().nth(1).map(|x| {
+ me.text.insert(&std::fs::read_to_string(x).unwrap()).unwrap();
+ me.text.cursor = 0;
+ });
+ me.workspace = me
+ .origin
+ .as_ref()
+ .and_then(|x| rooter(&x.parent().unwrap()))
+ .and_then(|x| x.canonicalize().ok());
+ me.tree = me.workspace.as_ref().map(|x| {
+ walkdir::WalkDir::new(x)
+ .into_iter()
+ .flatten()
+ .filter(|x| {
+ x.path().extension().is_some_and(|x| x == "rs")
+ })
+ .map(|x| x.path().to_owned())
+ .collect::<Vec<_>>()
+ });
+ me.lsp = me.workspace.as_ref().zip(me.origin.clone()).map(
+ |(workspace, origin)| {
+ let dh = std::panic::take_hook();
+ let main = std::thread::current_id();
+ // let mut c = Command::new("rust-analyzer")
+ // .stdin(Stdio::piped())
+ // .stdout(Stdio::piped())
+ // .stderr(Stdio::inherit())
+ // .spawn()
+ // .unwrap();
+ let (a, b) = Connection::memory();
+ std::thread::Builder::new()
+ .name("Rust Analyzer".into())
+ .stack_size(1024 * 1024 * 8)
+ .spawn(move || {
+ let ra = std::thread::current_id();
+ std::panic::set_hook(Box::new(move |info| {
+ // iz
+ if std::thread::current_id() == main {
+ dh(info);
+ } else if std::thread::current_id() == ra
+ || std::thread::current()
+ .name()
+ .is_some_and(|x| x.starts_with("RA"))
+ {
+ println!(
+ "RA panic @ {}",
+ info.location().unwrap()
+ );
+ }
+ }));
+ rust_analyzer::bin::run_server(b)
+ })
+ .unwrap();
+ let (c, t2, changed) = lsp::run(
+ (a.sender, a.receiver),
+ // lsp_server::stdio::stdio_transport(
+ // BufReader::new(c.stdout.take().unwrap()),
+ // c.stdin.take().unwrap(),
+ // ),
+ WorkspaceFolder {
+ uri: Url::from_file_path(&workspace).unwrap(),
+ name: workspace
+ .file_name()
+ .unwrap()
+ .to_string_lossy()
+ .into_owned(),
+ },
+ );
+ c.open(&origin, std::fs::read_to_string(&origin).unwrap())
+ .unwrap();
+ (&*Box::leak(Box::new(c)), (t2), Some(changed))
+ },
+ );
+ me.hist.last = me.text.clone();
+ me.lsp.as_ref().zip(me.origin.as_deref()).map(
+ |((x, ..), origin)| {
+ x.rq_semantic_tokens(&mut me.semantic_tokens, origin, None)
+ .unwrap()
+ },
+ );
+
+ me.mtime = Self::modify(me.origin.as_deref());
+
+ me
+ }
+
+ #[must_use = "please apply this"]
+ pub fn modify(origin: Option<&Path>) -> Option<SystemTime> {
+ origin.as_ref().map(|x| x.metadata().unwrap().modified().unwrap())
+ }
+
+ // #[must_use]
+ // pub fn inlay(
+ // &self,
+ // ) -> Option<
+ // JoinHandle<Result<Vec<InlayHint>, RequestError<InlayHintRequest>>>,
+ // > {
+ // lsp!(self + p).map(|(lsp, path)| {
+ // lsp.runtime.spawn(lsp.inlay(path, &self.text))
+ // })
+ // }
+
+ pub fn save(&mut self) {
+ let t = self.text.rope.to_string();
+ std::fs::write(self.origin.as_ref().unwrap(), &t).unwrap();
+ self.bar.last_action = "saved".into();
+ lsp!(self + p).map(|(l, o)| {
+ let v = l.runtime.block_on(l.format(o)).unwrap();
+ for v in v.coerce() {
+ self.text.apply_adjusting(&v);
+ }
+ change!(self);
+ self.hist.push(&self.text);
+ l.notify::<lsp_notification!("textDocument/didSave")>(
+ &DidSaveTextDocumentParams {
+ text_document: o.tid(),
+ text: Some(t),
+ },
+ )
+ .unwrap();
+ });
+ self.mtime = Self::modify(self.origin.as_deref());
+ }
+ pub fn poll(&mut self) {
+ let Some((l, ..)) = self.lsp else { return };
+ for rq in l.req_rx.try_iter() {
+ match rq {
+ LRq { method: "workspace/diagnostic/refresh", .. } => {
+ // let x = l.pull_diag(o.into(), diag.result.clone());
+ // diag.request(l.runtime.spawn(x));
+ }
+ rq => log::debug!("discarding request {rq:?}"),
+ }
+ }
+ let r = &l.runtime;
+ self.inlay.poll(
+ |x, p| {
+ x.ok().or(p.1).inspect(|x| {
+ self.text.set_inlay(x);
+ })
+ },
+ r,
+ );
+ self.document_highlights.poll(|x, _| x.ok(), r);
+ self.diag.poll(|x, _| x.ok().flatten(), r);
+ if let CompletionState::Complete(rq) = &mut self.complete {
+ rq.poll(
+ |f, (c, _)| {
+ f.ok().flatten().map(|x| Complete {
+ r: x,
+ start: c,
+ selection: 0,
+ vo: 0,
+ })
+ },
+ r,
+ );
+ };
+
+ if let State::Symbols(x) = &mut self.state {
+ x.poll(
+ |x, (_, p)| {
+ x.ok().map(|r| {
+ let tree =
+ self.tree.as_deref().unwrap().iter().map(
+ |x| SymbolInformation {
+ name: x
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .to_string(),
+ kind: SymbolKind::FILE,
+ location: Location {
+ range: lsp_types::Range {
+ end: default(),
+ start: default(),
+ },
+ uri: Url::from_file_path(&x)
+ .unwrap(),
+ },
+ container_name: None,
+ deprecated: None,
+ tags: None,
+ },
+ );
+ sym::Symbols {
+ tedit: p.map(|x| x.tedit).unwrap_or_default(),
+ r: tree.chain(r).collect(),
+ ..default() // dont care about previous selection
+ }
+ })
+ },
+ &r,
+ );
+ }
+ if let State::CodeAction(x) = &mut self.state {
+ x.poll(
+ |x, _| {
+ let lems: Vec<CodeAction> = x
+ .ok()??
+ .into_iter()
+ .map(|x| match x {
+ CodeActionOrCommand::CodeAction(x) => x,
+ _ => panic!("alas we dont like these"),
+ })
+ .collect();
+ if lems.is_empty() {
+ self.bar.last_action =
+ "no code actions available".into();
+ None
+ } else {
+ Some(act::CodeActions::new(lems))
+ }
+ },
+ &r,
+ );
+ }
+ self.def.poll(
+ |x, _| {
+ x.ok().flatten().and_then(|x| match &x {
+ GotoDefinitionResponse::Link([x, ..]) =>
+ Some(x.clone()),
+ _ => None,
+ })
+ },
+ &r,
+ );
+ self.semantic_tokens.poll(|x, _| x.ok(), &l.runtime);
+ self.sig_help.poll(
+ |x, ((), y)| {
+ x.ok().flatten().map(|x| {
+ if let Some((old_sig, vo, max)) = y
+ && &sig::active(&old_sig) == &sig::active(&x)
+ {
+ (x, vo, max)
+ } else {
+ (x, 0, None)
+ }
+ })
+ },
+ &r,
+ );
+ self.hovering.poll(|x, _| x.ok().flatten(), &r);
+ }
+ #[implicit_fn]
+ pub fn cursor_moved(
+ &mut self,
+ cursor_position: (usize, usize),
+ w: Arc<Window>,
+ c: usize,
+ ) {
+ match self.state.consume(Action::C(cursor_position)).unwrap() {
+ Some(Do::ExtendSelectionToMouse) => {
+ *self.state.sel() = self.text.extend_selection_to(
+ self.text.mapped_index_at(cursor_position),
+ self.state.sel().clone(),
+ );
+ w.request_redraw();
+ }
+ Some(Do::StartSelection) => {
+ let x = self.text.mapped_index_at(cursor_position);
+ self.hist.last.cursor = x;
+ self.text.cursor = x;
+ *self.state.sel() = x..x;
+ }
+ Some(Do::Hover)
+ if let Some(hover) =
+ self.text.visual_index_at(cursor_position)
+ && let Some((cl, o)) = lsp!(self + p) =>
+ 'out: {
+ let l = &mut self.hovering.result;
+ if let Some(Hovr {
+ span: Some([(_x, _y), (_x2, _)]),
+ ..
+ }) = &*l
+ {
+ let Some(_y) = _y.checked_sub(self.text.vo) else {
+ break 'out;
+ };
+ if cursor_position.1 == _y
+ && (_x..=_x2).contains(
+ &&(cursor_position.0
+ - self.text.line_number_offset()
+ - 1),
+ )
+ {
+ break 'out;
+ } else {
+ // println!("span no longer below cursor; cancel hover {_x}..{_x2} {}", cursor_position.0 - text.line_number_offset() - 1);
+ *l = None;
+ w.request_redraw();
+ }
+ }
+ let text = self.text.clone();
+ let mut rang = None;
+ let z = match hover {
+ Mapping::Char(_, _, i) => TextDocumentPositionParams {
+ position: text.to_l_position(i).unwrap(),
+ text_document: o.tid(),
+ },
+ Mapping::Fake(mark, relpos, abspos, _) => {
+ let Some(ref loc) = mark.l[relpos].1 else {
+ break 'out;
+ };
+ let (x, y) = text.xy(abspos).unwrap();
+ let Some(mut begin) = text.reverse_source_map(y)
+ else {
+ break 'out;
+ };
+ let start = begin.nth(x - 1).unwrap() + 1;
+ let left = mark.l[..relpos]
+ .iter()
+ .rev()
+ .take_while(_.1.as_ref() == Some(loc))
+ .count();
+ let start = start + relpos - left;
+ let length = mark.l[relpos..]
+ .iter()
+ .take_while(_.1.as_ref() == Some(loc))
+ .count()
+ + left;
+ rang = Some([(start, y), (start + length, y)]);
+ TextDocumentPositionParams {
+ text_document: TextDocumentIdentifier {
+ uri: loc.uri.clone(),
+ },
+ position: loc.range.start,
+ }
+ }
+ };
+ if ctrl() {
+ if self
+ .def
+ .request
+ .as_ref()
+ .is_none_or(|&(_, x)| x != cursor_position)
+ {
+ let handle = cl.runtime.spawn(w.redraw_after(cl.request::<lsp_request!("textDocument/definition")>(&GotoDefinitionParams {
+ text_document_position_params: z.clone(),
+ work_done_progress_params: default(),
+ partial_result_params: default(),
+ }).unwrap().0));
+ self.def.request =
+ Some((DropH::new(handle), cursor_position));
+ } else if self.def.result.as_ref().is_some_and(|em| {
+ let z = em.origin_selection_range.unwrap();
+ (z.start.character..z.end.character).contains(
+ &((cursor_position.0
+ - text.line_number_offset()
+ - 1) as _),
+ )
+ }) {
+ self.def.result = None;
+ }
+ } else {
+ self.def.result = None;
+ }
+ if let Some((_, c)) = self.hovering.request
+ && c == cursor_position
+ {
+ break 'out;
+ }
+ // if !running.insert(hover) {return}
+ let (rx, _) = cl
+ .request::<HoverRequest>(&HoverParams {
+ text_document_position_params: z,
+ work_done_progress_params: default(),
+ })
+ .unwrap();
+ // println!("rq hov of {hover:?} (cur {})", hovering.request.is_some());
+ let handle: tokio::task::JoinHandle<
+ Result<Option<Hovr>, anyhow::Error>,
+ > = cl.runtime.spawn(w.redraw_after(async move {
+ let Some(x) = rx.await? else {
+ return Ok(None::<Hovr>);
+ };
+ let (w, cells) = spawn_blocking(move || {
+ let x = match &x.contents {
+ lsp_types::HoverContents::Scalar(
+ marked_string,
+ ) => match marked_string {
+ MarkedString::LanguageString(x) =>
+ Cow::Borrowed(&*x.value),
+ MarkedString::String(x) =>
+ Cow::Borrowed(&**x),
+ },
+ lsp_types::HoverContents::Array(
+ marked_strings,
+ ) => Cow::Owned(
+ marked_strings
+ .iter()
+ .map(|x| match x {
+ MarkedString::LanguageString(
+ x,
+ ) => &*x.value,
+ MarkedString::String(x) => &*x,
+ })
+ .collect::<String>(),
+ ),
+ lsp_types::HoverContents::Markup(
+ markup_content,
+ ) => Cow::Borrowed(&*markup_content.value),
+ };
+ let x = hov::p(&x).unwrap();
+ let m = hov::l(&x)
+ .into_iter()
+ .max()
+ .map(_ + 2)
+ .unwrap_or(usize::MAX)
+ .min(c - 10);
+ (m, hov::markdown2(m, &x))
+ })
+ .await
+ .unwrap();
+ let span = rang.or_else(|| {
+ x.range.and_then(|range| try {
+ let (startx, starty) =
+ text.l_pos_to_char(range.start)?;
+ let (endx, endy) =
+ text.l_pos_to_char(range.end)?;
+ let x1 = text
+ .reverse_source_map(starty)?
+ .nth(startx)?;
+ let x2 = text
+ .reverse_source_map(endy)?
+ .nth(endx)?;
+ [
+ (x1, range.start.line as _),
+ (x2, range.start.line as _),
+ ]
+ })
+ });
+ anyhow::Ok(Some(
+ hov::Hovr {
+ span,
+ item: text::CellBuffer {
+ c: w,
+ vo: 0,
+ cells: cells.into(),
+ },
+ }
+ .into(),
+ ))
+ }));
+ self.hovering.request =
+ (DropH::new(handle), cursor_position).into();
+ // hovering.result = None;
+ // lsp!().map(|(cl, o)| {
+ // let window = window.clone();
+ // });
+ // });
+ }
+ Some(Do::Hover) => {
+ self.def.result = None;
+ self.hovering.result = None;
+ w.request_redraw();
+ }
+ None => {}
+ x => unreachable!("{x:?}"),
+ }
+ }
+ pub fn click(
+ &mut self,
+ bt: MouseButton,
+ cursor_position: (usize, usize),
+ w: Arc<Window>,
+ ) {
+ let text = &mut self.text;
+ _ = self.complete.consume(CompletionAction::Click).unwrap();
+ match self.state.consume(Action::M(bt)).unwrap() {
+ Some(Do::MoveCursor) => {
+ text.cursor = text.mapped_index_at(cursor_position);
+ if let Some((lsp, path)) = lsp!(self + p) {
+ self.sig_help.request(lsp.runtime.spawn(
+ w.redraw_after(
+ lsp.request_sig_help(path, text.cursor()),
+ ),
+ ));
+ self.document_highlights.request(lsp.runtime.spawn(
+ w.redraw_after(lsp.document_highlights(
+ path,
+ text.to_l_position(text.cursor).unwrap(),
+ )),
+ ));
+ }
+ self.hist.last.cursor = text.cursor;
+ self.chist.push(text.cursor());
+ text.setc();
+ }
+ Some(Do::NavForward) => {
+ self.chist.forth().map(|x| {
+ text.cursor = text.rope.line_to_char(x.1) + x.0;
+ text.scroll_to_cursor();
+ });
+ }
+ Some(Do::NavBack) => {
+ self.chist.back().map(|x| {
+ text.cursor = text.rope.line_to_char(x.1) + x.0;
+ text.scroll_to_cursor();
+ });
+ }
+ Some(Do::ExtendSelectionToMouse) => {
+ *self.state.sel() = text.extend_selection_to(
+ text.mapped_index_at(cursor_position),
+ self.state.sel().clone(),
+ );
+ }
+ Some(Do::StartSelection) => {
+ let x = text.mapped_index_at(cursor_position);
+ self.hist.last.cursor = x;
+ *self.state.sel() =
+ text.extend_selection_to(x, text.cursor..text.cursor);
+ }
+ Some(Do::GoToDefinition) => {
+ if let Some(LocationLink {
+ ref target_uri,
+ target_range,
+ ..
+ }) = self.def.result
+ && let Some(p) = self.origin.as_deref()
+ {
+ if target_uri == &p.tid().uri {
+ text.cursor =
+ text.l_position(target_range.start).unwrap();
+ text.scroll_to_cursor();
+ }
+ }
+ }
+ None => {}
+ _ => unreachable!(),
+ }
+ }
+ pub fn scroll(&mut self, rows: f32) {
+ let rows = if alt() { rows * 8. } else { rows * 3. };
+ let (vo, max) = lower::saturating::math! { if let Some(x)= &mut self.hovering.result && shift() {
+ let n = x.item.l();
+ (&mut x.item.vo, n - 15)
+ } else if let Some((_, ref mut vo, Some(max))) = self.sig_help.result && shift(){
+ (vo, max - 15)
+ } else {
+ let n =self. text.l() - 1; (&mut self.text.vo, n)
+ }};
+ if rows < 0.0 {
+ let rows = rows.ceil().abs() as usize;
+ *vo = (*vo + rows).min(max);
+ } else {
+ let rows = rows.floor() as usize;
+ *vo = vo.saturating_sub(rows);
+ }
+ inlay!(self);
+ }
+ pub fn keyboard(
+ &mut self,
+ event: KeyEvent,
+ window: &mut Arc<Window>,
+ ) -> ControlFlow<()> {
+ let mut o: Option<Do> = self
+ .state
+ .consume(Action::K(event.logical_key.clone()))
+ .unwrap();
+ match o {
+ Some(Do::Reinsert) =>
+ o = self
+ .state
+ .consume(Action::K(event.logical_key.clone()))
+ .unwrap(),
+ _ => {}
+ }
+ match o {
+ Some(Do::SpawnTerminal) => {
+ trm::toggle(
+ self.workspace
+ .as_deref()
+ .unwrap_or(Path::new("/home/os/")),
+ );
+ }
+ Some(Do::MatchingBrace) => {
+ if let Some((l, f)) = lsp!(self + p) {
+ l.matching_brace(f, &mut self.text);
+ }
+ }
+ Some(Do::Symbols) =>
+ if let Some(lsp) = lsp!(self) {
+ self.state =
+ State::Symbols(Rq::new(lsp.runtime.spawn(
+ window.redraw_after(lsp.symbols("".into())),
+ )));
+ },
+ Some(Do::SymbolsHandleKey) => {
+ if let Some(lsp) = lsp!(self) {
+ let State::Symbols(Rq { result: Some(x), request }) =
+ &mut self.state
+ else {
+ unreachable!()
+ };
+ let ptedit = x.tedit.rope.clone();
+ if handle2(
+ &event.logical_key,
+ &mut x.tedit,
+ lsp!(self + p),
+ )
+ .is_some()
+ || ptedit != x.tedit.rope
+ {
+ *request = Some((
+ DropH::new(lsp.runtime.spawn(
+ window.redraw_after(
+ lsp.symbols(x.tedit.rope.to_string()),
+ ),
+ )),
+ (),
+ ));
+ // state = State::Symbols(Rq::new(lsp.runtime.spawn(lsp.symbols("".into()))));
+ }
+ }
+ }
+ Some(Do::SymbolsSelectNext) => {
+ let State::Symbols(Rq { result: Some(x), .. }) =
+ &mut self.state
+ else {
+ unreachable!()
+ };
+ x.next();
+ }
+ Some(Do::SymbolsSelectPrev) => {
+ let State::Symbols(Rq { result: Some(x), .. }) =
+ &mut self.state
+ else {
+ unreachable!()
+ };
+ x.back();
+ }
+ Some(Do::SymbolsSelect) => {
+ let State::Symbols(Rq { result: Some(x), .. }) =
+ &mut self.state
+ else {
+ unreachable!()
+ };
+ let x = x.sel(); // TODO dedup
+ let _: anyhow::Result<()> = try bikeshed _ {
+ let f = x
+ .location
+ .uri
+ .to_file_path()
+ .map_err(|()| anyhow::anyhow!("dammit"))?
+ .canonicalize()?;
+ self.origin = Some(f.clone());
+ let r = self.text.r;
+ self.text = default();
+ self.text.r = r;
+ let new = std::fs::read_to_string(f)
+ .map_err(anyhow::Error::from)?;
+ self.text.insert(&new)?;
+ self.text.cursor = self.text
+ .l_position(x.location.range.start)
+ .ok_or(anyhow::anyhow!("dangit"))?;
+ self.text.scroll_to_cursor_centering();
+ self.hist = Hist {
+ history: vec![],
+ redo_history: vec![],
+ last: self.text.clone(),
+ last_edit: Instant::now(),
+ changed: false,
+ };
+ self.complete = CompletionState::None;
+ self.mtime = Self::modify(self.origin.as_deref());
+
+ lsp!(self + p).map(|(x, origin)| {
+ (
+ self.def,
+ self.semantic_tokens,
+ self.inlay,
+ self.sig_help,
+ self.complete,
+ self.hovering,
+ ) = (
+ default(),
+ default(),
+ default(),
+ default(),
+ default(),
+ default(),
+ );
+ x.open(&origin, new).unwrap();
+ x.rq_semantic_tokens(
+ &mut self.semantic_tokens,
+ origin,
+ Some(window.clone()),
+ )
+ .unwrap();
+ });
+ self.state = State::Default;
+ self.bar.last_action = "open".to_string();
+ };
+ }
+ Some(Do::CodeAction) => {
+ if let Some((lsp, f)) = lsp!(self + p) {
+ let r = lsp.request::<lsp_request!("textDocument/codeAction")>(&CodeActionParams {
+ text_document: f.tid(), range: self.text.to_l_range(self.text.cursor..self.text.cursor).unwrap(), context: CodeActionContext {
+ trigger_kind: Some(CodeActionTriggerKind::INVOKED),
+ // diagnostics: if let Some((lsp, p)) = lsp!() && let uri = Url::from_file_path(p).unwrap() && let Some(diag) = lsp.diagnostics.get(&uri, &lsp.diagnostics.guard()) { dbg!(diag.iter().filter(|x| {
+ // self.text.l_range(x.range).unwrap().contains(&self.text.cursor)
+ // }).cloned().collect()) } else { vec![] },
+ ..default()
+ }, work_done_progress_params: default(), partial_result_params: default() }).unwrap();
+ let mut r2 = Rq::default();
+
+ r2.request(lsp.runtime.spawn(async { r.0.await }));
+ self.state = State::CodeAction(r2);
+ }
+ }
+ Some(Do::CASelectLeft) => {
+ let State::CodeAction(Rq { result: Some(c), .. }) =
+ &mut self.state
+ else {
+ panic!()
+ };
+ c.left();
+ }
+ Some(Do::CASelectRight) => 'out: {
+ let Some((lsp, f)) = lsp!(self + p) else {
+ unreachable!()
+ };
+ let State::CodeAction(Rq { result: Some(c), .. }) =
+ &mut self.state
+ else {
+ panic!()
+ };
+ let Some(act) = c.right() else { break 'out };
+ let act = act.clone();
+ self.state = State::Default;
+ self.hist.last.cursor = self.text.cursor;
+ self.hist.test_push(&self.text);
+ let act = lsp
+ .runtime
+ .block_on(
+ lsp.request::<CodeActionResolveRequest>(&act)
+ .unwrap()
+ .0,
+ )
+ .unwrap();
+ let mut f_ = |edits: &[SnippetTextEdit]| {
+ // let mut first = false;
+ for edit in edits {
+ self.text.apply_snippet_tedit(edit).unwrap();
+ }
+ };
+ match act.edit {
+ Some(WorkspaceEdit {
+ document_changes: Some(DocumentChanges::Edits(x)),
+ ..
+ }) =>
+ for x in x {
+ if x.text_document.uri != f.tid().uri {
+ continue;
+ }
+ f_(&x.edits);
+ },
+ Some(WorkspaceEdit {
+ document_changes:
+ Some(DocumentChanges::Operations(x)),
+ ..
+ }) => {
+ for op in x {
+ match op {
+ DocumentChangeOperation::Edit(
+ TextDocumentEdit {
+ edits,
+ text_document,
+ ..
+ },
+ ) => {
+ if text_document.uri != f.tid().uri {
+ continue;
+ }
+ f_(&edits);
+ }
+ x => log::error!("didnt apply {x:?}"),
+ };
+ // if text_document.uri!= f.tid().uri { continue }
+ // for lsp_types::OneOf::Left(x)| lsp_types::OneOf::Right(AnnotatedTextEdit { text_edit: x, .. }) in edits {
+ // self.text.apply(&x).unwrap();
+ // }
+ }
+ }
+ _ => {}
+ }
+ change!(self);
+ self.hist.record(&self.text);
+ }
+ Some(Do::CASelectNext) => {
+ let State::CodeAction(Rq { result: Some(c), .. }) =
+ &mut self.state
+ else {
+ panic!()
+ };
+ c.down();
+ }
+ Some(Do::CASelectPrev) => {
+ let State::CodeAction(Rq { result: Some(c), .. }) =
+ &mut self.state
+ else {
+ panic!()
+ };
+ c.up();
+ }
+ Some(
+ Do::Reinsert
+ | Do::GoToDefinition
+ | Do::NavBack
+ | Do::NavForward,
+ ) => panic!(),
+ Some(Do::Save) => match &self.origin {
+ Some(x) => {
+ self.state.consume(Action::Saved).unwrap();
+ self.save();
+ }
+ None => {
+ self.state.consume(Action::RequireFilename).unwrap();
+ }
+ },
+ Some(Do::SaveTo(x)) => {
+ self.origin = Some(PathBuf::try_from(x).unwrap());
+ self.save();
+ }
+ Some(Do::Edit) => {
+ self.hist.test_push(&self.text);
+ let cb4 = self.text.cursor;
+ if let Key::Named(Enter | ArrowUp | ArrowDown | Tab) =
+ event.logical_key
+ && let CompletionState::Complete(..) = self.complete
+ {
+ } else {
+ handle2(
+ &event.logical_key,
+ &mut self.text,
+ lsp!(self + p),
+ );
+ }
+ self.text.scroll_to_cursor();
+ inlay!(self);
+ if cb4 != self.text.cursor
+ && let CompletionState::Complete(Rq {
+ result: Some(c),
+ ..
+ }) = &self.complete
+ && ((self.text.cursor < c.start)
+ || (!super::is_word(self.text.at_())
+ && (self.text.at_() != '.'
+ || self.text.at_() != ':')))
+ {
+ self.complete = CompletionState::None;
+ }
+ if self.sig_help.running()
+ && cb4 != self.text.cursor
+ && let Some((lsp, path)) = lsp!(self + p)
+ {
+ self.sig_help.request(lsp.runtime.spawn(
+ window.redraw_after(
+ lsp.request_sig_help(path, self.text.cursor()),
+ ),
+ ));
+ }
+ if self.hist.record(&self.text)
+ && let Some((lsp, path)) = lsp!(self + p)
+ {
+ change!(self);
+ }
+ lsp!(self + p).map(|(lsp, o)| {
+ let window = window.clone();
+ match event.logical_key.as_ref() {
+ Key::Character(y)
+ if let Some(x) = &lsp.initialized
+ && let Some(x) = &x
+ .capabilities
+ .signature_help_provider
+ && let Some(x) = &x.trigger_characters
+ && x.contains(&y.to_string()) =>
+ {
+ self.sig_help.request(lsp.runtime.spawn(
+ window.redraw_after(lsp.request_sig_help(
+ o,
+ self.text.cursor(),
+ )),
+ ));
+ }
+ _ => {}
+ }
+ match self
+ .complete
+ .consume(CompletionAction::K(
+ event.logical_key.as_ref(),
+ ))
+ .unwrap()
+ {
+ Some(CDo::Request(ctx)) => {
+ let h = DropH::new(lsp.runtime.spawn(
+ window.redraw_after(lsp.request_complete(
+ o,
+ self.text.cursor(),
+ ctx,
+ )),
+ ));
+ let CompletionState::Complete(Rq {
+ request: x,
+ result: c,
+ }) = &mut self.complete
+ else {
+ panic!()
+ };
+ *x = Some((
+ h,
+ c.as_ref()
+ .map(|x| x.start)
+ .or(x.as_ref().map(|x| x.1))
+ .unwrap_or(self.text.cursor),
+ ));
+ }
+ Some(CDo::SelectNext) => {
+ let CompletionState::Complete(Rq {
+ result: Some(c),
+ ..
+ }) = &mut self.complete
+ else {
+ panic!()
+ };
+ c.next(&filter(&self.text));
+ }
+ Some(CDo::SelectPrevious) => {
+ let CompletionState::Complete(Rq {
+ result: Some(c),
+ ..
+ }) = &mut self.complete
+ else {
+ panic!()
+ };
+ c.back(&filter(&self.text));
+ }
+ Some(CDo::Finish(x)) => {
+ let sel = x.sel(&filter(&self.text));
+ let sel = lsp
+ .runtime
+ .block_on(
+ lsp.resolve(sel.clone()).unwrap(),
+ )
+ .unwrap();
+ let CompletionItem {
+ text_edit:
+ Some(CompletionTextEdit::Edit(ed)),
+ additional_text_edits,
+ insert_text_format,
+ ..
+ } = sel.clone()
+ else {
+ panic!()
+ };
+ match insert_text_format {
+ Some(InsertTextFormat::SNIPPET) => {
+ self.text.apply_snippet(&ed).unwrap();
+ }
+ _ => {
+ let (s, _) =
+ self.text.apply(&ed).unwrap();
+ self.text.cursor =
+ s + ed.new_text.chars().count();
+ }
+ }
+ for additional in
+ additional_text_edits.into_iter().flatten()
+ {
+ self.text
+ .apply_adjusting(&additional)
+ .unwrap();
+ }
+ if self.hist.record(&self.text) {
+ change!(self);
+ }
+ self.sig_help = Rq::new(lsp.runtime.spawn(
+ window.redraw_after(lsp.request_sig_help(
+ o,
+ self.text.cursor(),
+ )),
+ ));
+ }
+ None => return,
+ };
+ });
+ }
+ Some(Do::Undo) => {
+ self.hist.test_push(&self.text);
+ self.hist.undo(&mut self.text);
+ self.bar.last_action = "undid".to_string();
+ change!(self);
+ }
+ Some(Do::Redo) => {
+ self.hist.test_push(&self.text);
+ self.hist.redo(&mut self.text);
+ self.bar.last_action = "redid".to_string();
+ change!(self);
+ }
+ Some(Do::Quit) => return ControlFlow::Break(()),
+ Some(Do::StartSelection) => {
+ let Key::Named(y) = event.logical_key else { panic!() };
+ *self.state.sel() = self.text.extend_selection(
+ y,
+ self.text.cursor..self.text.cursor,
+ );
+ }
+ Some(Do::UpdateSelection) => {
+ let Key::Named(y) = event.logical_key else { panic!() };
+ *self.state.sel() = self
+ .text
+ .extend_selection(y, self.state.sel().clone());
+ self.text.scroll_to_cursor();
+ inlay!(self);
+ }
+ Some(Do::Insert(x, c)) => {
+ self.hist.push_if_changed(&self.text);
+ _ = self.text.remove(x.clone());
+ self.text.cursor = x.start;
+ self.text.setc();
+ self.text.insert(&c);
+ self.hist.push_if_changed(&self.text);
+ change!(self);
+ }
+ Some(Do::Delete(x)) => {
+ self.hist.push_if_changed(&self.text);
+ self.text.cursor = x.start;
+ _ = self.text.remove(x);
+ self.hist.push_if_changed(&self.text);
+ change!(self);
+ }
+ Some(Do::Copy(x)) => {
+ clipp::copy(self.text.rope.slice(x).to_string());
+ }
+ Some(Do::Cut(x)) => {
+ self.hist.push_if_changed(&self.text);
+ clipp::copy(self.text.rope.slice(x.clone()).to_string());
+ self.text.rope.remove(x.clone());
+ self.text.cursor = x.start;
+ self.hist.push_if_changed(&self.text);
+ change!(self);
+ }
+ Some(Do::Paste) => {
+ self.hist.push_if_changed(&self.text);
+ self.text.insert(&clipp::paste());
+ self.hist.push_if_changed(&self.text);
+ change!(self);
+ }
+ Some(Do::OpenFile(x)) => {
+ let _ = try {
+ self.origin = Some(PathBuf::from(&x).canonicalize()?);
+ self.text = TextArea::default();
+ let new = std::fs::read_to_string(x)?;
+ self.text.insert(&new);
+ self.text.cursor = 0;
+ self.hist = Hist {
+ history: vec![],
+ redo_history: vec![],
+ last: self.text.clone(),
+ last_edit: Instant::now(),
+ changed: false,
+ };
+ self.complete = CompletionState::None;
+ self.mtime = Self::modify(self.origin.as_deref());
+
+ lsp!(self + p).map(|(x, origin)| {
+ (
+ self.def,
+ self.semantic_tokens,
+ self.inlay,
+ self.sig_help,
+ self.complete,
+ self.hovering,
+ ) = (
+ default(),
+ default(),
+ default(),
+ default(),
+ default(),
+ default(),
+ );
+ x.open(&origin, new).unwrap();
+ x.rq_semantic_tokens(
+ &mut self.semantic_tokens,
+ origin,
+ Some(window.clone()),
+ )
+ .unwrap();
+ });
+ self.bar.last_action = "open".to_string();
+ };
+ }
+ Some(
+ Do::MoveCursor | Do::ExtendSelectionToMouse | Do::Hover,
+ ) => {
+ unreachable!()
+ }
+ Some(Do::StartSearch(x)) => {
+ let s = Regex::new(&x).unwrap();
+ let n = s
+ .find_iter(&self.text.rope.to_string())
+ .enumerate()
+ .count();
+ s.clone()
+ .find_iter(&self.text.rope.to_string())
+ .enumerate()
+ .find(|(_, x)| x.start() > self.text.cursor)
+ .map(|(x, m)| {
+ self.state = State::Search(s, x, n);
+ self.text.cursor =
+ self.text.rope.byte_to_char(m.end());
+ self.text.scroll_to_cursor_centering();
+ inlay!(self);
+ })
+ .unwrap_or_else(|| {
+ self.bar.last_action = "no matches".into()
+ });
+ }
+ Some(Do::SearchChanged) => {
+ let (re, index, _) = self.state.search();
+ let s = self.text.rope.to_string();
+ let m = re.find_iter(&s).nth(*index).unwrap();
+ self.text.cursor = self.text.rope.byte_to_char(m.end());
+ self.text.scroll_to_cursor_centering();
+ inlay!(self);
+ }
+ Some(Do::Boolean(BoolRequest::ReloadFile, true)) => {
+ self.text.rope = Rope::from_str(
+ &std::fs::read_to_string(
+ self.origin.as_ref().unwrap(),
+ )
+ .unwrap(),
+ );
+ self.text.cursor =
+ self.text.cursor.min(self.text.rope.len_chars());
+ self.mtime = Self::modify(self.origin.as_deref());
+ self.bar.last_action = "reloaded".into();
+ }
+ Some(Do::Boolean(BoolRequest::ReloadFile, false)) => {}
+ None => {}
+ }
+ ControlFlow::Continue(())
+ }
}
+use NamedKey::*;
+
+pub fn handle2<'a>(
+ key: &'a Key,
+ text: &mut TextArea,
+ l: Option<(&Client, &Path)>,
+) -> Option<&'a str> {
+ use Key::*;
-impl Editor {}
+ match key {
+ Named(Space) => text.insert(" ").unwrap(),
+ Named(Backspace) if ctrl() => text.backspace_word(),
+ Named(Backspace) => text.backspace(),
+ Named(Home) if ctrl() => {
+ text.cursor = 0;
+ text.vo = 0;
+ }
+ Named(End) if ctrl() => {
+ text.cursor = text.rope.len_chars();
+ text.vo = text.l().saturating_sub(text.r);
+ }
+ Named(Home) => text.home(),
+ Named(End) => text.end(),
+ Named(Tab) => text.tab(),
+ Named(Delete) => {
+ text.right();
+ text.backspace()
+ }
+ Named(ArrowLeft) if ctrl() => text.word_left(),
+ Named(ArrowRight) if ctrl() => text.word_right(),
+ Named(ArrowLeft) => text.left(),
+ Named(ArrowRight) => text.right(),
+ Named(ArrowUp) => text.up(),
+ Named(ArrowDown) => text.down(),
+ Named(PageDown) => text.page_down(),
+ Named(PageUp) => text.page_up(),
+ Named(Enter) if let Some((l, p)) = l => l.enter(p, text),
+ Named(Enter) => text.enter(),
+ Character(x) => {
+ text.insert(&x);
+ return Some(x);
+ }
+ _ => {}
+ };
+ None
+}
+
+impl State {
+ fn sel(&mut self) -> &mut std::ops::Range<usize> {
+ let State::Selection(x) = self else { panic!() };
+ x
+ }
+ fn search(&mut self) -> (&mut Regex, &mut usize, &mut usize) {
+ let State::Search(x, y, z) = self else { panic!() };
+ (x, y, z)
+ }
+}