use std::fs::OpenOptions;
use std::path::Path;
use lsp_types::*;
use rootcause::prelude::{IteratorExt, ResultExt};
use rootcause::report;
use ropey::Rope;
use rust_analyzer::lsp::ext::{
SnippetDocumentChangeOperation, SnippetTextDocumentEdit,
SnippetWorkspaceEdit,
};
use super::*;
use crate::error::WDebug;
use crate::text::{SortTedits, TextArea};
impl Editor {
fn apply_tds<T>(
&mut self,
to: &Path,
mut edits: Vec<T>,
mut apply: impl FnMut(&mut TextArea, &T) -> rootcause::Result<()>,
mut apply2: impl FnMut(&T, &mut Rope) -> rootcause::Result<()>,
) -> rootcause::Result<()>
where
[T]: SortTedits,
{
edits.sort_tedits();
if Some(to) != self.origin.as_deref() {
let f = OpenOptions::new()
.read(true)
.create(true)
.write(true)
.open(to)?;
let mut r = Rope::from_reader(f)?;
let () = edits
.iter()
.map(|x| apply2(x, &mut r))
.collect_reports()
.context("applying one or more snippet tedits failed")?;
r.write_to(
OpenOptions::new().write(true).truncate(true).open(to)?,
)?;
} else {
let () = edits
.iter()
.map(|x| apply(&mut self.text, x))
.collect_reports()
.context("applying one or more sneddits failed")?;
}
Ok(())
}
fn apply_tde(
&mut self,
TextDocumentEdit { edits, text_document, .. }: TextDocumentEdit,
) -> rootcause::Result<()> {
self.apply_tds(
&text_document
.text_document_identifier
.uri
.to_file_path()
.map_err(|_| report!("sad"))?,
edits,
|t, e| match e {
Edit::AnnotatedTextEdit(AnnotatedTextEdit {
text_edit,
..
})
| Edit::TextEdit(text_edit) =>
Ok(drop(t.apply(text_edit)?)),
Edit::SnippetTextEdit(SnippetTextEdit {
range,
snippet,
annotation_id,
}) => {
let x2 = rust_analyzer::lsp::ext::SnippetTextEdit {
range: *range,
new_text: snippet.value.clone(),
insert_text_format: None,
annotation_id: annotation_id.clone(),
};
t.apply_snippet_tedit(&x2)
}
},
|e, t| match e {
Edit::AnnotatedTextEdit(AnnotatedTextEdit {
text_edit,
..
})
| Edit::TextEdit(text_edit) =>
TextArea::apply_raw(text_edit, t),
Edit::SnippetTextEdit(SnippetTextEdit {
range,
snippet,
annotation_id,
}) => {
let x2 = rust_analyzer::lsp::ext::SnippetTextEdit {
range: *range,
new_text: snippet.value.clone(),
insert_text_format: None,
annotation_id: annotation_id.clone(),
};
TextArea::apply_snippet_tedit_raw(&x2, t)
}
},
// TextArea::apply_snippet_tedit,
// TextArea::apply_snippet_tedit_raw,
)
}
fn apply_sdc(
&mut self,
op: SnippetDocumentChangeOperation,
) -> rootcause::Result<()> {
match op.clone() {
SnippetDocumentChangeOperation::Edit(
SnippetTextDocumentEdit { text_document, edits },
) => self.apply_tds(
&text_document
.text_document_identifier
.uri
.to_file_path()
.map_err(|_| report!("sad"))?,
edits,
TextArea::apply_snippet_tedit,
TextArea::apply_snippet_tedit_raw,
),
SnippetDocumentChangeOperation::Change(dc) =>
self.apply_dc(dc),
}
}
// pub fn apply_sdc(
// &mut self,
// x: SnippetDocumentChangeOperation,
// ) -> rootcause::Result<()> {
// match x {
// // self.apply_tde(x)
// DocumentChange::TextDocumentEdit(text_document_edit) =>
// self.apply_tde(text_document_edit)?,
// DocumentChange::CreateFile(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))?,
// };
// }
// DocumentChange::RenameFile(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());
// }
// }
// DocumentChange::DeleteFile(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(())
// }
pub fn apply_dc(
&mut self,
x: DocumentChange,
) -> rootcause::Result<()> {
match x {
// self.apply_tde(x)
DocumentChange::TextDocumentEdit(text_document_edit) =>
self.apply_tde(text_document_edit)?,
DocumentChange::CreateFile(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))?,
};
}
DocumentChange::RenameFile(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());
}
}
DocumentChange::DeleteFile(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(())
}
pub fn apply_swsedit(
&mut self,
x: SnippetWorkspaceEdit,
) -> rootcause::Result<()> {
match x {
SnippetWorkspaceEdit { document_changes: Some(x), .. } => x
.into_iter()
.map(|x| self.apply_sdc(x))
.collect_reports()
.context("couldnt apply one or more wsedits")?,
SnippetWorkspaceEdit { changes: Some(x), .. } => x
.into_iter()
.map(|(p, e)| {
self.apply_tds(
&p.to_file_path().map_err(|_| {
report!("evil path")
.context_custom::<WDebug, _>(e.clone())
})?,
e.clone(),
|x, y| x.apply(y).map(drop),
TextArea::apply_raw,
)
.context_custom::<WDebug, _>(e)
})
.collect_reports()
.context("couldnt apply one or more fs operations")?,
x =>
do yeet report!("strange workspace edit")
.context_custom::<WDebug, _>(x),
}
change!(self);
self.hist.record(&self.text);
Ok(())
}
#[must_use]
pub fn apply_wsedit(
&mut self,
x: WorkspaceEdit,
) -> rootcause::Result<()> {
match x {
WorkspaceEdit { document_changes: Some(x), .. } => x
.into_iter()
.map(|x| self.apply_dc(x))
.collect_reports()
.context("couldnt apply one or more wsedits")?,
WorkspaceEdit { changes: Some(x), .. } => x
.into_iter()
.map(|(p, e)| {
self.apply_tds(
&p.to_file_path().map_err(|_| {
report!("evil path")
.context_custom::<WDebug, _>(e.clone())
})?,
e.clone(),
|x, y| x.apply(y).map(drop),
TextArea::apply_raw,
)
.context_custom::<WDebug, _>(e)
})
.collect_reports()
.context("couldnt apply one or more fs operations")?,
x =>
do yeet report!("strange workspace edit")
.context_custom::<WDebug, _>(x),
}
change!(self);
self.hist.record(&self.text);
Ok(())
}
}