diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c1a07d68..3dba9333 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -898,6 +898,12 @@ mod cmd { fn write(editor: &mut Editor, args: &[&str], event: PromptEvent) { let (view, doc) = editor.current(); + if let Some(path) = args.get(0) { + if let Err(err) = doc.set_path(Path::new(path)) { + editor.set_error(format!("invalid filepath: {}", err)); + return; + }; + } if doc.path().is_none() { editor.set_error("cannot write a buffer without a filename".to_string()); return; @@ -941,7 +947,7 @@ mod cmd { Command { name: "write", alias: Some("w"), - doc: "Write changes to disk.", + doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)", fun: write, completer: Some(completers::filename), }, diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 7d912ec0..51d8a795 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Error}; use std::future::Future; -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; use helix_core::{ @@ -64,6 +64,42 @@ where } } +/// Normalize a path, removing things like `.` and `..`. +/// +/// CAUTION: This does not resolve symlinks (unlike +/// [`std::fs::canonicalize`]). This may cause incorrect or surprising +/// behavior at times. This should be used carefully. Unfortunately, +/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often +/// fail, or on Windows returns annoying device paths. This is a problem Cargo +/// needs to improve on. +/// Copied from cargo: https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81 +pub fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + use helix_lsp::lsp; use url::Url; @@ -176,6 +212,20 @@ impl Document { } } + pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> { + // canonicalize path to absolute value + let current_dir = std::env::current_dir()?; + let path = normalize_path(¤t_dir.join(path)); + + if let Some(parent) = path.parent() { + // TODO: return error as necessary + if parent.exists() { + self.path = Some(path); + } + } + Ok(()) + } + pub fn set_language( &mut self, language_config: Option>,