A simple CPU rendered GUI IDE experience.
-rw-r--r--src/edi.rs106
-rw-r--r--src/edi/wsedit.rs236
-rw-r--r--src/error.rs27
-rw-r--r--src/main.rs2
-rw-r--r--src/sni.rs9
-rw-r--r--src/text.rs24
-rw-r--r--src/text/hist.rs13
7 files changed, 335 insertions, 82 deletions
diff --git a/src/edi.rs b/src/edi.rs
index e5c37bf..489167a 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -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};
diff --git a/src/sni.rs b/src/sni.rs
index 749ad21..10fe392 100644
--- a/src/sni.rs
+++ b/src/sni.rs
@@ -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>,