Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: cache Documents relative path #12385

Merged
merged 11 commits into from
Jan 5, 2025
10 changes: 6 additions & 4 deletions helix-stdx/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ where
}

/// Expands tilde `~` into users home directory if available, otherwise returns the path
/// unchanged. The tilde will only be expanded when present as the first component of the path
/// unchanged.
///
/// The tilde will only be expanded when present as the first component of the path
/// and only slash follows it.
pub fn expand_tilde<'a, P>(path: P) -> Cow<'a, Path>
where
Expand All @@ -54,11 +56,11 @@ where
}

/// Normalize a path without resolving symlinks.
// Strategy: start from the first component and move up. Cannonicalize previous path,
// Strategy: start from the first component and move up. Canonicalize previous path,
// join component, canonicalize new path, strip prefix and join to the final result.
pub fn normalize(path: impl AsRef<Path>) -> PathBuf {
let mut components = path.as_ref().components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
components.next();
PathBuf::from(c.as_os_str())
} else {
Expand Down Expand Up @@ -209,7 +211,7 @@ fn path_component_regex(windows: bool) -> String {
// TODO: support backslash path escape on windows (when using git bash for example)
let space_escape = if windows { r"[\^`]\s" } else { r"[\\]\s" };
// partially baesd on what's allowed in an url but with some care to avoid
// false positivies (like any kind of brackets or quotes)
// false positives (like any kind of brackets or quotes)
r"[\w@.\-+#$%?!,;~&]|".to_owned() + space_escape
}

Expand Down
27 changes: 16 additions & 11 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,7 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: Args) -> Vec<DocumentId>
for arg in args {
let doc_id = editor.documents().find_map(|doc| {
let arg_path = Some(Path::new(arg));
if doc.path().map(|p| p.as_path()) == arg_path
|| doc.relative_path().as_deref() == arg_path
{
if doc.path().map(|p| p.as_path()) == arg_path || doc.relative_path() == arg_path {
Some(doc.id())
} else {
None
Expand Down Expand Up @@ -625,18 +623,25 @@ fn force_write_quit(
/// error, otherwise returns `Ok(())`. If the current document is unmodified,
/// and there are modified documents, switches focus to one of them.
pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
let (modified_ids, modified_names): (Vec<_>, Vec<_>) = editor
let modified_ids: Vec<_> = editor
.documents()
.filter(|doc| doc.is_modified())
.map(|doc| (doc.id(), doc.display_name()))
.unzip();
.map(|doc| doc.id())
.collect();

if let Some(first) = modified_ids.first() {
let current = doc!(editor);
// If the current document is unmodified, and there are modified
// documents, switch focus to the first modified doc.
if !modified_ids.contains(&current.id()) {
editor.switch(*first, Action::Replace);
}

let modified_names: Vec<_> = modified_ids
.iter()
.map(|doc_id| doc!(editor, doc_id).display_name())
.collect();

bail!(
"{} unsaved buffer{} remaining: {:?}",
modified_names.len(),
Expand Down Expand Up @@ -1023,14 +1028,14 @@ fn change_current_directory(
let dir = match args.next() {
Some("-") => cx
.editor
.last_cwd
.clone()
.get_last_cwd()
.map(|path| Cow::Owned(path.to_path_buf()))
.ok_or_else(|| anyhow!("No previous working directory"))?,
Some(path) => helix_stdx::path::expand_tilde(Path::new(path)).to_path_buf(),
None => home_dir()?,
Some(path) => helix_stdx::path::expand_tilde(Path::new(path)),
None => Cow::Owned(home_dir()?),
};

cx.editor.last_cwd = helix_stdx::env::set_current_working_dir(dir)?;
cx.editor.set_cwd(&dir)?;

cx.editor.set_status(format!(
"Current working directory is now {}",
Expand Down
30 changes: 24 additions & 6 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use helix_core::text_annotations::{InlineAnnotation, Overlay};
use helix_lsp::util::lsp_pos_to_pos;
use helix_stdx::faccess::{copy_metadata, readonly};
use helix_vcs::{DiffHandle, DiffProviderRegistry};
use once_cell::sync::OnceCell;
use thiserror;

use ::parking_lot::Mutex;
Expand Down Expand Up @@ -148,6 +149,7 @@ pub struct Document {
pub inlay_hints_oudated: bool,

path: Option<PathBuf>,
relative_path: OnceCell<Option<PathBuf>>,
encoding: &'static encoding::Encoding,
has_bom: bool,

Expand Down Expand Up @@ -300,6 +302,14 @@ impl fmt::Debug for DocumentInlayHintsId {
}
}

impl Editor {
pub(crate) fn clear_doc_relative_paths(&mut self) {
for doc in self.documents_mut() {
doc.relative_path.take();
}
}
}

enum Encoder {
Utf16Be,
Utf16Le,
Expand Down Expand Up @@ -659,6 +669,7 @@ impl Document {
id: DocumentId::default(),
active_snippet: None,
path: None,
relative_path: OnceCell::new(),
encoding,
has_bom,
text,
Expand Down Expand Up @@ -1172,6 +1183,10 @@ impl Document {
pub fn set_path(&mut self, path: Option<&Path>) {
let path = path.map(helix_stdx::path::canonicalize);

// `take` to remove any prior relative path that may have existed.
// This will get set in `relative_path()`.
self.relative_path.take();

// if parent doesn't exist we still want to open the document
// and error out when document is saved
self.path = path;
Expand Down Expand Up @@ -1867,16 +1882,19 @@ impl Document {
self.view_data_mut(view_id).view_position = new_offset;
}

pub fn relative_path(&self) -> Option<Cow<Path>> {
self.path
pub fn relative_path(&self) -> Option<&Path> {
self.relative_path
.get_or_init(|| {
self.path
.as_ref()
.map(|path| helix_stdx::path::get_relative_path(path).to_path_buf())
})
.as_deref()
.map(helix_stdx::path::get_relative_path)
}

pub fn display_name(&self) -> Cow<'static, str> {
pub fn display_name(&self) -> Cow<'_, str> {
self.relative_path()
.map(|path| path.to_string_lossy().to_string().into())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
.map_or_else(|| SCRATCH_BUFFER_NAME.into(), |path| path.to_string_lossy())
}

// transact(Fn) ?
Expand Down
12 changes: 11 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ pub struct Editor {
redraw_timer: Pin<Box<Sleep>>,
last_motion: Option<Motion>,
pub last_completion: Option<CompleteAction>,
pub last_cwd: Option<PathBuf>,
last_cwd: Option<PathBuf>,

pub exit_code: i32,

Expand Down Expand Up @@ -2209,6 +2209,16 @@ impl Editor {
current_view.id
}
}

pub fn set_cwd(&mut self, path: &Path) -> std::io::Result<()> {
self.last_cwd = helix_stdx::env::set_current_working_dir(path)?;
self.clear_doc_relative_paths();
Ok(())
}

pub fn get_last_cwd(&mut self) -> Option<&Path> {
self.last_cwd.as_deref()
}
}

fn try_restore_indent(doc: &mut Document, view: &mut View) {
Expand Down
Loading