A simple CPU rendered GUI IDE experience.
apply workspace edits
| -rw-r--r-- | src/edi.rs | 106 | ||||
| -rw-r--r-- | src/edi/wsedit.rs | 236 | ||||
| -rw-r--r-- | src/error.rs | 27 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/sni.rs | 9 | ||||
| -rw-r--r-- | src/text.rs | 24 | ||||
| -rw-r--r-- | src/text/hist.rs | 13 |
7 files changed, 335 insertions, 82 deletions
@@ -1,6 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; -use std::fs::OpenOptions; +use std::fmt::Debug; use std::mem::take; use std::ops::ControlFlow; use std::path::{Path, PathBuf}; @@ -13,7 +13,7 @@ use lsp_server::{Connection, Request as LRq, ResponseError}; use lsp_types::request::*; use lsp_types::*; use regex::Regex; -use rootcause::handlers::Debug; +use rootcause::prelude::ResultExt; use rootcause::report; use ropey::Rope; use rust_analyzer::lsp::ext::OnTypeFormatting; @@ -27,12 +27,14 @@ use winit::keyboard::{Key, NamedKey}; use winit::window::Window; pub mod st; +mod wsedit; use st::*; use crate::bar::Bar; use crate::commands::Cmds; use crate::complete::Complete; +use crate::error::WDebug; use crate::hov::{self, Hovr}; use crate::lsp::{ self, Anonymize, Client, Map_, PathURI, RequestError, Rq, @@ -134,9 +136,22 @@ pub struct Requests { #[serde(skip)] pub git_diff: Rq<imara_diff::Diff, imara_diff::Diff, (), ()>, } -#[derive( - Default, Debug, serde_derive::Serialize, serde_derive::Deserialize, -)] +impl Debug for Editor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Editor") + .field("files", &self.files) + .field("text", &self.text.len_chars()) + .field("origin", &self.origin) + .field("state", &self.state.name()) + .field("bar", &self.bar) + .field("workspace", &self.workspace) + .field("hist", &self.hist) + .field("mtime", &self.mtime) + .finish() + } +} + +#[derive(Default, serde_derive::Serialize, serde_derive::Deserialize)] pub struct Editor { pub files: HashMap<PathBuf, Editor>, pub text: TextArea, @@ -174,7 +189,7 @@ macro_rules! lsp { pub(crate) use lsp as lsp_m; macro_rules! inlay { ($self:ident) => { - lsp!($self + p).map(|(lsp, path)| { + $crate::edi::lsp_m!($self + p).map(|(lsp, path)| { $self .requests .inlay @@ -182,6 +197,7 @@ macro_rules! inlay { }) }; } +pub(crate) use inlay; macro_rules! change { ($self:ident) => { change!(@$self, None) @@ -190,12 +206,12 @@ macro_rules! change { change!(@$self, Some($w)) }; (just $self:ident) => { - lsp!($self + p).map(|(x, origin)| { + lsp_m!($self + p).map(|(x, origin)| { x.edit(&origin, $self.text.rope.to_string()).unwrap(); }) }; (@$self:ident, $w:expr) => { - lsp!($self + p).map(|(x, origin)| { + lsp_m!($self + p).map(|(x, origin)| { x.edit(&origin, $self.text.rope.to_string()).unwrap(); x.rq_semantic_tokens( &mut $self.requests.semantic_tokens, @@ -227,6 +243,7 @@ macro_rules! change { }); }; } +pub(crate) use change; fn rooter(x: &Path) -> Option<PathBuf> { for f in std::fs::read_dir(&x).unwrap().filter_map(Result::ok) { @@ -1118,7 +1135,7 @@ impl Editor { }; let p = self.text.l_position(r.start).ok_or( report!("provided range out of bound") - .context_custom::<Debug, _>(r), + .context_custom::<WDebug, _>(r), )?; if p != 0 { self.text.cursor.just(p, &self.text.rope); @@ -1153,7 +1170,15 @@ impl Editor { ); match x { - Ok(Some(x)) => self.apply_wsedit(x, &f.to_owned()), + 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, @@ -1229,7 +1254,11 @@ impl Editor { .request_immediate::<CodeActionResolveRequest>(&act) .unwrap(); let f = f.to_owned(); - act.edit.map(|x| self.apply_wsedit(x, &f)); + 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), .. }) = @@ -1718,61 +1747,6 @@ impl Editor { } ControlFlow::Continue(()) } - pub fn apply_wsedit(&mut self, x: WorkspaceEdit, f: &Path) { - let mut f2 = - |TextDocumentEdit { mut edits, text_document, .. }| { - edits.sort_tedits(); - if text_document.uri != f.tid().uri { - let f = OpenOptions::new() - .read(true) - .create(true) - .write(true) - .open(text_document.uri.path()) - .unwrap(); - let mut r = Rope::from_reader(f).unwrap(); - for edit in &edits { - TextArea::apply_snippet_tedit_raw(edit, &mut r); - } - r.write_to( - OpenOptions::new() - .write(true) - .truncate(true) - .open(text_document.uri.path()) - .unwrap(), - ) - .unwrap(); - } else { - for edit in &edits { - self.text.apply_snippet_tedit(edit).unwrap(); - } - } - }; - match x { - WorkspaceEdit { - document_changes: Some(DocumentChanges::Edits(x)), - .. - } => - for t in x { - f2(t) - }, - WorkspaceEdit { - document_changes: Some(DocumentChanges::Operations(x)), - .. - } => - for op in x { - match op { - DocumentChangeOperation::Edit(t) => { - f2(t); - } - x => log::error!("didnt apply {x:?}"), - }; - }, - _ => {} - } - change!(self); - self.hist.record(&self.text); - } - pub fn paste(&mut self) { self.hist.push_if_changed(&mut self.text); let r = clipp::paste(); diff --git a/src/edi/wsedit.rs b/src/edi/wsedit.rs new file mode 100644 index 0000000..c4f327c --- /dev/null +++ b/src/edi/wsedit.rs @@ -0,0 +1,236 @@ +use std::fs::OpenOptions; +use std::path::Path; + +use lsp_types::*; +use rootcause::prelude::{IteratorExt, ResultExt}; +use rootcause::report; +use ropey::Rope; + +use super::*; +use crate::error::WDebug; +use crate::lsp::PathURI; +use crate::text::{SortTedits, TextArea}; + +impl Editor { + fn apply_tde( + &mut self, + TextDocumentEdit { + mut edits, + text_document, + .. + }: TextDocumentEdit, + f: &Path, + ) -> rootcause::Result<()> { + edits.sort_tedits(); + if text_document.uri != f.tid().uri { + let f = OpenOptions::new() + .read(true) + .create(true) + .write(true) + .open(text_document.uri.path())?; + let mut r = Rope::from_reader(f)?; + let () = edits + .iter() + .map(|x| TextArea::apply_snippet_tedit_raw(x, &mut r)) + .collect_reports() + .context("applying one or more snippet tedits failed")?; + r.write_to( + OpenOptions::new() + .write(true) + .truncate(true) + .open(text_document.uri.path())?, + )?; + } else { + let () = edits + .iter() + .map(|x| self.text.apply_snippet_tedit(x)) + .collect_reports() + .context("applying one or more sneddits failed")?; + } + Ok(()) + } + fn apply_dco( + &mut self, + op: DocumentChangeOperation, + f: &Path, + ) -> rootcause::Result<()> { + match op.clone() { + DocumentChangeOperation::Edit(t) => self.apply_tde(t, f)?, + DocumentChangeOperation::Op(ResourceOp::Create( + CreateFile { uri, options, .. }, + )) => { + let f = uri + .to_file_path() + .map_err(|()| report!("not path"))?; + let mut opts = OpenOptions::new(); + opts.write(true) + // .create(true) + .create(matches!( + options, + Some(CreateFileOptions { + ignore_if_exists: Some(true) | None, + .. + }) | None + )) + .create_new(matches!( + options, + Some(CreateFileOptions { + ignore_if_exists: Some(false), + .. + }) + )) + .truncate(true); + match opts.open(&f) { + Ok(_f) => {} + Err(e) + if let Some(CreateFileOptions { + overwrite: Some(true), + .. + }) = options + && e.kind() + == std::io::ErrorKind::AlreadyExists => {} + Err(e) => + Err(e) + .context_custom::<WDebug, _>(f) + .context_custom::<WDebug, _>((opts, options))?, + }; + } + DocumentChangeOperation::Op(ResourceOp::Rename( + RenameFile { old_uri, new_uri, options, annotation_id }, + )) => { + let old_f = old_uri + .to_file_path() + .map_err(|()| report!("not path"))?; + let new_f = new_uri + .to_file_path() + .map_err(|()| report!("not path"))?; + + // FIXME: overwrite + if let Some(p) = new_f.parent() + && !p.exists() + { + std::fs::create_dir_all(p)?; + } + match std::fs::rename(&old_f, &new_f) { + Err(e) + if e.kind() + == std::io::ErrorKind::IsADirectory => + { + do yeet report!("unhandled case: dir rename"); + } + Err(e) + if let Some(RenameFileOptions { + ignore_if_exists: Some(true), + .. + }) = options + && e.kind() + == std::io::ErrorKind::AlreadyExists => {} + Ok(_) => {} + Err(e) => Err(e).context_with(|| { + format!("renaming {old_f:?} to {new_f:?}") + })?, + } + if let Some(f) = self.files.get_mut(&old_f) + // .or(Some(self).filter(|x| x.origin == Some(old_f))) + { + f.bar.last_action = + annotation_id.unwrap_or("renamed".into()); + f.origin = Some(new_f); + f.mtime = Editor::modify(f.origin.as_deref()); + } else if self.origin == Some(old_f) { + self.bar.last_action = + annotation_id.unwrap_or("renamed".into()); + self.origin = Some(new_f); + self.mtime = Editor::modify(self.origin.as_deref()); + } + } + DocumentChangeOperation::Op(ResourceOp::Delete( + DeleteFile { + uri, + options: + Some(DeleteFileOptions { + recursive: Some(true), // ??? seems redundant + ignore_if_not_exists, + .. + }), + }, + )) => { + let f = uri + .to_file_path() + .map_err(|()| report!("not path"))?; + match std::fs::remove_dir_all(f) { + Ok(()) => (), + Err(e) + if e.kind() == std::io::ErrorKind::NotFound + && ignore_if_not_exists == Some(true) => {} + Err(e) => do yeet e, + } + } + DocumentChangeOperation::Op(ResourceOp::Delete( + DeleteFile { uri, options }, + )) => { + let f = uri + .to_file_path() + .map_err(|()| report!("not path"))?; + match std::fs::remove_file(&f) { + Ok(()) => (), + Err(e) + if e.kind() + == std::io::ErrorKind::IsADirectory => + { + std::fs::remove_dir_all(f)?; + } + Err(e) + if let Some(DeleteFileOptions { + ignore_if_not_exists: Some(true), + .. + }) = options + && e.kind() + == std::io::ErrorKind::NotFound => {} + Err(e) => do yeet e, + } + } + } + + Ok(()) + } + #[must_use] + pub fn apply_wsedit( + &mut self, + x: WorkspaceEdit, + f: &Path, + ) -> rootcause::Result<()> { + match x { + WorkspaceEdit { + document_changes: Some(DocumentChanges::Edits(x)), + .. + } => x + .into_iter() + .map(|x| self.apply_tde(x, f)) + .collect_reports() + .context("couldnt apply one or more wsedits")?, + WorkspaceEdit { + document_changes: Some(DocumentChanges::Operations(x)), + .. + } => x + .clone() + .into_iter() + .map(|op: DocumentChangeOperation| { + self.apply_dco(op.clone(), f) + .context_custom::<WDebug, _>(op) + }) + .collect_reports() + .context("couldnt apply one or more fs operations") + .context_custom::<WDebug, _>(x)?, + WorkspaceEdit { changes: Some(x), .. } => + do yeet report!("we dont handle these kinds of changes") + .context_custom::<WDebug, _>(x), + x => + do yeet report!("strange workspace edit") + .context_custom::<WDebug, _>(x), + } + change!(self); + self.hist.record(&self.text); + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1cd945f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,27 @@ +#[derive(Copy, Clone)] +pub struct WDebug; + +impl<C> rootcause::handlers::ContextHandler<C> for WDebug +where + C: core::fmt::Debug, +{ + fn source( + _context: &C, + ) -> Option<&(dyn core::error::Error + 'static)> { + None + } + + fn display( + c: &C, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + core::fmt::Debug::fmt(c, f) + } + + fn debug( + context: &C, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + core::fmt::Debug::fmt(context, f) + } +} diff --git a/src/main.rs b/src/main.rs index b2f1e4a..6618bc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![feature( + yeet_expr, adt_const_params, inherent_associated_types, never_type, @@ -52,6 +53,7 @@ mod meta; mod rnd; mod runnables; mod sym; +mod error; mod trm; use std::fmt::{Debug, Display}; @@ -1,6 +1,7 @@ use std::ops::Range; use helix_core::snippets::parser::SnippetElement; +use rootcause::option_ext::OptionExt; #[derive( Debug, Clone, serde_derive::Serialize, serde_derive::Deserialize, @@ -42,8 +43,10 @@ impl StopP { } impl Snippet { - pub fn parse(x: &str, at: usize) -> Option<(Self, String)> { - let value = helix_core::snippets::parser::parse(x).ok()?; + pub fn parse(x: &str, at: usize) -> rootcause::Result<(Self, String)> { + let value = helix_core::snippets::parser::parse(x) + .ok() + .context("couldnt parse snippet")?; let mut stops = vec![]; let mut cursor = 0; @@ -55,7 +58,7 @@ impl Snippet { stops.sort_by_key(|x| x.0); stops.iter_mut().for_each(|x| x.1.manipulate(|x| x + at)); let last = stops.try_remove(0); - Some((Snippet { last: last.map(|x| x.1), stops, index: 0 }, o)) + Ok((Snippet { last: last.map(|x| x.1), stops, index: 0 }, o)) } pub fn manipulate(&mut self, f: impl FnMut(usize) -> usize + Clone) { self.stops.iter_mut().for_each(|x| x.1.manipulate(f.clone())); diff --git a/src/text.rs b/src/text.rs index e0ab239..04e07b5 100644 --- a/src/text.rs +++ b/src/text.rs @@ -18,6 +18,7 @@ use lsp_types::{ DocumentSymbol, Location, Position, SemanticTokensLegend, SnippetTextEdit, TextEdit, }; +use rootcause::option_ext::OptionExt; use rootcause::prelude::{IteratorExt, ResultExt}; use rootcause::report; use ropey::{Rope, RopeSlice}; @@ -489,24 +490,24 @@ impl TextArea { pub fn apply_snippet_tedit_raw( SnippetTextEdit { range,new_text, insert_text_format, .. }: &SnippetTextEdit, text: &'_ mut Rope, - ) -> Option<()> { + ) -> rootcause::Result<()> { match insert_text_format { Some(lsp_types::InsertTextFormat::SNIPPET) => { - let begin = text.l_position(range.start)?; - let end = text.l_position(range.end)?; - text.try_remove(begin..end).ok()?; + let begin = text.l_position(range.start).ok_or_report()?; + let end = text.l_position(range.end).ok_or_report()?; + text.try_remove(begin..end)?; let (_, tex) = crate::sni::Snippet::parse(&new_text, begin)?; - text.try_insert(begin, &tex).ok()?; + text.try_insert(begin, &tex)?; } _ => { - let begin = text.l_position(range.start)?; - let end = text.l_position(range.end)?; - text.try_remove(begin..end).ok()?; - text.try_insert(begin, &new_text).ok()?; + let begin = text.l_position(range.start).ok_or_report()?; + let end = text.l_position(range.end).ok_or_report()?; + text.try_remove(begin..end)?; + text.try_insert(begin, &new_text)?; } } - Some(()) + Ok(()) } pub fn apply_snippet_tedit( &mut self, @@ -540,8 +541,7 @@ impl TextArea { .ok_or(report!("couldnt get end"))?; self.remove(begin..end)?; let (mut sni, tex) = - crate::sni::Snippet::parse(&x.new_text, begin) - .ok_or(report!("failed to parse snippet"))?; + crate::sni::Snippet::parse(&x.new_text, begin)?; self.insert_at(begin, &tex)?; self.cursor.one(match sni.next() { Some(x) => { diff --git a/src/text/hist.rs b/src/text/hist.rs index ed0d571..97a05c7 100644 --- a/src/text/hist.rs +++ b/src/text/hist.rs @@ -129,7 +129,18 @@ impl Diff { } } -#[derive(serde::Deserialize, serde::Serialize, Debug)] +impl Debug for Hist { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Hist") + .field("history", &self.history.len()) + .field("redo_history", &self.redo_history.len()) + .field("last", &self.last) + .field("last_edit", &self.last_edit) + .finish() + } +} + +#[derive(serde::Deserialize, serde::Serialize)] pub struct Hist { pub history: Vec<Diff>, pub redo_history: Vec<Diff>, |