aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-term/src/application.rs26
-rw-r--r--helix-term/src/commands/typed.rs9
-rw-r--r--helix-term/tests/test/write.rs61
-rw-r--r--helix-view/src/document.rs52
4 files changed, 116 insertions, 32 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 2c1047da..0640de3c 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -3,6 +3,7 @@ use futures_util::Stream;
use helix_core::{
config::{default_syntax_loader, user_syntax_loader},
diagnostic::{DiagnosticTag, NumberOrString},
+ path::get_relative_path,
pos_at_coords, syntax, Selection,
};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
@@ -489,17 +490,26 @@ impl Application {
);
doc.set_last_saved_revision(doc_save_event.revision);
+
let lines = doc.text().len_lines();
let bytes = doc.text().len_bytes();
- let path_str = doc
- .path()
- .expect("document written without path")
- .to_string_lossy()
- .into_owned();
-
- self.editor
- .set_status(format!("'{}' written, {}L {}B", path_str, lines, bytes));
+ if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
+ log::error!(
+ "error setting path for doc '{:?}': {}",
+ doc.path(),
+ err.to_string(),
+ );
+ self.editor.set_error(err.to_string());
+ } else {
+ // TODO: fix being overwritten by lsp
+ self.editor.set_status(format!(
+ "'{}' written, {}L {}B",
+ get_relative_path(&doc_save_event.path).to_string_lossy(),
+ lines,
+ bytes
+ ));
+ }
}
pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 375e7b4f..35c84601 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -268,11 +268,6 @@ fn write_impl(
let jobs = &mut cx.jobs;
let doc = doc_mut!(cx.editor);
- if let Some(ref path) = path {
- doc.set_path(Some(path.as_ref().as_ref()))
- .context("invalid filepath")?;
- }
-
if doc.path().is_none() {
bail!("cannot write a buffer without a filename");
}
@@ -292,7 +287,7 @@ fn write_impl(
None
};
- doc.format_and_save(fmt, force)?;
+ doc.format_and_save(fmt, path.map(AsRef::as_ref), force)?;
if path.is_some() {
let id = doc.id();
@@ -607,7 +602,7 @@ fn write_all_impl(
None
};
- doc.format_and_save(fmt, force)?;
+ doc.format_and_save::<_, PathBuf>(fmt, None, force)?;
}
if quit {
diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs
index 4ac850c1..d2e6922f 100644
--- a/helix-term/tests/test/write.rs
+++ b/helix-term/tests/test/write.rs
@@ -35,7 +35,7 @@ async fn test_write() -> anyhow::Result<()> {
Ok(())
}
-#[tokio::test]
+#[tokio::test(flavor = "multi_thread")]
async fn test_write_quit() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;
@@ -129,7 +129,64 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
}
#[tokio::test]
-#[ignore]
+async fn test_write_new_path() -> anyhow::Result<()> {
+ let mut file1 = tempfile::NamedTempFile::new().unwrap();
+ let mut file2 = tempfile::NamedTempFile::new().unwrap();
+
+ test_key_sequences(
+ &mut Application::new(
+ Args {
+ files: vec![(file1.path().to_path_buf(), Position::default())],
+ ..Default::default()
+ },
+ Config::default(),
+ )?,
+ vec![
+ (
+ Some("ii can eat glass, it will not hurt me<ret><esc>:w<ret>"),
+ Some(&|app| {
+ let doc = doc!(app.editor);
+ assert!(!app.editor.is_err());
+ assert_eq!(file1.path(), doc.path().unwrap());
+ }),
+ ),
+ (
+ Some(&format!(":w {}<ret>", file2.path().to_string_lossy())),
+ Some(&|app| {
+ let doc = doc!(app.editor);
+ assert!(!app.editor.is_err());
+ assert_eq!(file2.path(), doc.path().unwrap());
+ assert!(app.editor.document_by_path(file1.path()).is_none());
+ }),
+ ),
+ ],
+ false,
+ )
+ .await?;
+
+ file1.as_file_mut().flush()?;
+ file1.as_file_mut().sync_all()?;
+ file2.as_file_mut().flush()?;
+ file2.as_file_mut().sync_all()?;
+
+ let mut file1_content = String::new();
+ file1.as_file_mut().read_to_string(&mut file1_content)?;
+ assert_eq!(
+ helpers::platform_line("i can eat glass, it will not hurt me\n"),
+ file1_content
+ );
+
+ let mut file2_content = String::new();
+ file2.as_file_mut().read_to_string(&mut file2_content)?;
+ assert_eq!(
+ helpers::platform_line("i can eat glass, it will not hurt me\n"),
+ file2_content
+ );
+
+ Ok(())
+}
+
+#[tokio::test]
async fn test_write_fail_new_path() -> anyhow::Result<()> {
let file = helpers::new_readonly_tempfile()?;
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 3045e3b7..82d526a9 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -91,6 +91,7 @@ impl Serialize for Mode {
pub struct DocumentSaveEvent {
pub revision: usize,
pub doc_id: DocumentId,
+ pub path: PathBuf,
}
pub type DocumentSaveEventResult = Result<DocumentSaveEvent, anyhow::Error>;
@@ -512,41 +513,61 @@ impl Document {
Some(fut.boxed())
}
- pub fn save(&mut self, force: bool) -> Result<(), anyhow::Error> {
- self.save_impl::<futures_util::future::Ready<_>>(None, force)
+ pub fn save<P: Into<PathBuf>>(
+ &mut self,
+ path: Option<P>,
+ force: bool,
+ ) -> Result<(), anyhow::Error> {
+ self.save_impl::<futures_util::future::Ready<_>, _>(None, path, force)
}
- pub fn format_and_save(
+ pub fn format_and_save<F, P>(
&mut self,
- formatting: Option<
- impl Future<Output = Result<Transaction, FormatterError>> + 'static + Send,
- >,
+ formatting: Option<F>,
+ path: Option<P>,
force: bool,
- ) -> anyhow::Result<()> {
- self.save_impl(formatting, force)
+ ) -> anyhow::Result<()>
+ where
+ F: Future<Output = Result<Transaction, FormatterError>> + 'static + Send,
+ P: Into<PathBuf>,
+ {
+ self.save_impl(formatting, path, force)
}
- // TODO: impl Drop to handle ensuring writes when closed
/// The `Document`'s text is encoded according to its encoding and written to the file located
/// at its `path()`.
///
/// If `formatting` is present, it supplies some changes that we apply to the text before saving.
- fn save_impl<F: Future<Output = Result<Transaction, FormatterError>> + 'static + Send>(
+ fn save_impl<F, P>(
&mut self,
formatting: Option<F>,
+ path: Option<P>,
force: bool,
- ) -> Result<(), anyhow::Error> {
+ ) -> Result<(), anyhow::Error>
+ where
+ F: Future<Output = Result<Transaction, FormatterError>> + 'static + Send,
+ P: Into<PathBuf>,
+ {
if self.save_sender.is_none() {
bail!("saves are closed for this document!");
}
// we clone and move text + path into the future so that we asynchronously save the current
// state without blocking any further edits.
-
let mut text = self.text().clone();
- let path = self.path.clone().expect("Can't save with no path set!");
- let identifier = self.identifier();
+ let path = match path {
+ Some(path) => helix_core::path::get_canonicalized_path(&path.into())?,
+ None => {
+ if self.path.is_none() {
+ bail!("Can't save with no path set!");
+ }
+
+ self.path.as_ref().unwrap().clone()
+ }
+ };
+
+ let identifier = self.identifier();
let language_server = self.language_server.clone();
// mark changes up to now as saved
@@ -586,12 +607,13 @@ impl Document {
}
}
- let mut file = File::create(path).await?;
+ let mut file = File::create(&path).await?;
to_writer(&mut file, encoding, &text).await?;
let event = DocumentSaveEvent {
revision: current_rev,
doc_id,
+ path,
};
if let Some(language_server) = language_server {