perf: cache `Document`s relative path (#12385)

pull/12426/head
RoloEdits 2025-01-05 14:29:16 -08:00 committed by GitHub
parent 64b38d1a28
commit f80ae997f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 57 additions and 22 deletions

View File

@ -33,7 +33,9 @@ where
} }
/// Expands tilde `~` into users home directory if available, otherwise returns the path /// 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. /// and only slash follows it.
pub fn expand_tilde<'a, P>(path: P) -> Cow<'a, Path> pub fn expand_tilde<'a, P>(path: P) -> Cow<'a, Path>
where where
@ -54,11 +56,11 @@ where
} }
/// Normalize a path without resolving symlinks. /// 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. // join component, canonicalize new path, strip prefix and join to the final result.
pub fn normalize(path: impl AsRef<Path>) -> PathBuf { pub fn normalize(path: impl AsRef<Path>) -> PathBuf {
let mut components = path.as_ref().components().peekable(); 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(); components.next();
PathBuf::from(c.as_os_str()) PathBuf::from(c.as_os_str())
} else { } else {
@ -209,7 +211,7 @@ fn path_component_regex(windows: bool) -> String {
// TODO: support backslash path escape on windows (when using git bash for example) // TODO: support backslash path escape on windows (when using git bash for example)
let space_escape = if windows { r"[\^`]\s" } else { r"[\\]\s" }; 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 // 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 r"[\w@.\-+#$%?!,;~&]|".to_owned() + space_escape
} }

View File

@ -185,9 +185,7 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: Args) -> Vec<DocumentId>
for arg in args { for arg in args {
let doc_id = editor.documents().find_map(|doc| { let doc_id = editor.documents().find_map(|doc| {
let arg_path = Some(Path::new(arg)); let arg_path = Some(Path::new(arg));
if doc.path().map(|p| p.as_path()) == arg_path if doc.path().map(|p| p.as_path()) == arg_path || doc.relative_path() == arg_path {
|| doc.relative_path().as_deref() == arg_path
{
Some(doc.id()) Some(doc.id())
} else { } else {
None None
@ -625,11 +623,12 @@ fn force_write_quit(
/// error, otherwise returns `Ok(())`. If the current document is unmodified, /// error, otherwise returns `Ok(())`. If the current document is unmodified,
/// and there are modified documents, switches focus to one of them. /// and there are modified documents, switches focus to one of them.
pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> { 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() .documents()
.filter(|doc| doc.is_modified()) .filter(|doc| doc.is_modified())
.map(|doc| (doc.id(), doc.display_name())) .map(|doc| doc.id())
.unzip(); .collect();
if let Some(first) = modified_ids.first() { if let Some(first) = modified_ids.first() {
let current = doc!(editor); let current = doc!(editor);
// If the current document is unmodified, and there are modified // If the current document is unmodified, and there are modified
@ -637,6 +636,12 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()>
if !modified_ids.contains(&current.id()) { if !modified_ids.contains(&current.id()) {
editor.switch(*first, Action::Replace); editor.switch(*first, Action::Replace);
} }
let modified_names: Vec<_> = modified_ids
.iter()
.map(|doc_id| doc!(editor, doc_id).display_name())
.collect();
bail!( bail!(
"{} unsaved buffer{} remaining: {:?}", "{} unsaved buffer{} remaining: {:?}",
modified_names.len(), modified_names.len(),
@ -1023,14 +1028,14 @@ fn change_current_directory(
let dir = match args.next() { let dir = match args.next() {
Some("-") => cx Some("-") => cx
.editor .editor
.last_cwd .get_last_cwd()
.clone() .map(|path| Cow::Owned(path.to_path_buf()))
.ok_or_else(|| anyhow!("No previous working directory"))?, .ok_or_else(|| anyhow!("No previous working directory"))?,
Some(path) => helix_stdx::path::expand_tilde(Path::new(path)).to_path_buf(), Some(path) => helix_stdx::path::expand_tilde(Path::new(path)),
None => home_dir()?, 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!( cx.editor.set_status(format!(
"Current working directory is now {}", "Current working directory is now {}",

View File

@ -13,6 +13,7 @@ use helix_core::text_annotations::{InlineAnnotation, Overlay};
use helix_lsp::util::lsp_pos_to_pos; use helix_lsp::util::lsp_pos_to_pos;
use helix_stdx::faccess::{copy_metadata, readonly}; use helix_stdx::faccess::{copy_metadata, readonly};
use helix_vcs::{DiffHandle, DiffProviderRegistry}; use helix_vcs::{DiffHandle, DiffProviderRegistry};
use once_cell::sync::OnceCell;
use thiserror; use thiserror;
use ::parking_lot::Mutex; use ::parking_lot::Mutex;
@ -148,6 +149,7 @@ pub struct Document {
pub inlay_hints_oudated: bool, pub inlay_hints_oudated: bool,
path: Option<PathBuf>, path: Option<PathBuf>,
relative_path: OnceCell<Option<PathBuf>>,
encoding: &'static encoding::Encoding, encoding: &'static encoding::Encoding,
has_bom: bool, has_bom: bool,
@ -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 { enum Encoder {
Utf16Be, Utf16Be,
Utf16Le, Utf16Le,
@ -659,6 +669,7 @@ impl Document {
id: DocumentId::default(), id: DocumentId::default(),
active_snippet: None, active_snippet: None,
path: None, path: None,
relative_path: OnceCell::new(),
encoding, encoding,
has_bom, has_bom,
text, text,
@ -1172,6 +1183,10 @@ impl Document {
pub fn set_path(&mut self, path: Option<&Path>) { pub fn set_path(&mut self, path: Option<&Path>) {
let path = path.map(helix_stdx::path::canonicalize); 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 // if parent doesn't exist we still want to open the document
// and error out when document is saved // and error out when document is saved
self.path = path; self.path = path;
@ -1867,16 +1882,19 @@ impl Document {
self.view_data_mut(view_id).view_position = new_offset; self.view_data_mut(view_id).view_position = new_offset;
} }
pub fn relative_path(&self) -> Option<Cow<Path>> { pub fn relative_path(&self) -> Option<&Path> {
self.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() .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() self.relative_path()
.map(|path| path.to_string_lossy().to_string().into()) .map_or_else(|| SCRATCH_BUFFER_NAME.into(), |path| path.to_string_lossy())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
} }
// transact(Fn) ? // transact(Fn) ?

View File

@ -1079,7 +1079,7 @@ pub struct Editor {
redraw_timer: Pin<Box<Sleep>>, redraw_timer: Pin<Box<Sleep>>,
last_motion: Option<Motion>, last_motion: Option<Motion>,
pub last_completion: Option<CompleteAction>, pub last_completion: Option<CompleteAction>,
pub last_cwd: Option<PathBuf>, last_cwd: Option<PathBuf>,
pub exit_code: i32, pub exit_code: i32,
@ -2209,6 +2209,16 @@ impl Editor {
current_view.id 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) { fn try_restore_indent(doc: &mut Document, view: &mut View) {