Hide duplicate symlinks from the picker (#5658)

* hide duplicate symlinks from the picker

* Apply suggestions from code review

Co-authored-by: g-re-g <123515925+g-re-g@users.noreply.github.com>

* minor stylistic fix

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

---------

Co-authored-by: g-re-g <123515925+g-re-g@users.noreply.github.com>
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
pull/5790/head
Pascal Kuthe 2 years ago committed by GitHub
parent 62d046fa21
commit 6ed2348078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -150,6 +150,8 @@ All git related options are only enabled in a git repository.
| Key | Description | Default | | Key | Description | Default |
|--|--|---------| |--|--|---------|
|`hidden` | Enables ignoring hidden files. | true |`hidden` | Enables ignoring hidden files. | true
|`follow-links` | Follow symlinks instead of ignoring them | true
|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
|`parents` | Enables reading ignore files from parent directories. | true |`parents` | Enables reading ignore files from parent directories. | true
|`ignore` | Enables reading `.ignore` files. | true |`ignore` | Enables reading `.ignore` files. | true
|`git-ignore` | Enables reading `.gitignore` files. | true |`git-ignore` | Enables reading `.gitignore` files. | true

@ -49,6 +49,7 @@ use movement::Movement;
use crate::{ use crate::{
args, args,
compositor::{self, Component, Compositor}, compositor::{self, Component, Compositor},
filter_picker_entry,
job::Callback, job::Callback,
keymap::ReverseKeymap, keymap::ReverseKeymap,
ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent}, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
@ -2013,6 +2014,11 @@ fn global_search(cx: &mut Context) {
let search_root = std::env::current_dir() let search_root = std::env::current_dir()
.expect("Global search error: Failed to get current dir"); .expect("Global search error: Failed to get current dir");
let dedup_symlinks = file_picker_config.deduplicate_links;
let absolute_root = search_root
.canonicalize()
.unwrap_or_else(|_| search_root.clone());
WalkBuilder::new(search_root) WalkBuilder::new(search_root)
.hidden(file_picker_config.hidden) .hidden(file_picker_config.hidden)
.parents(file_picker_config.parents) .parents(file_picker_config.parents)
@ -2022,10 +2028,9 @@ fn global_search(cx: &mut Context) {
.git_global(file_picker_config.git_global) .git_global(file_picker_config.git_global)
.git_exclude(file_picker_config.git_exclude) .git_exclude(file_picker_config.git_exclude)
.max_depth(file_picker_config.max_depth) .max_depth(file_picker_config.max_depth)
// We always want to ignore the .git directory, otherwise if .filter_entry(move |entry| {
// `ignore` is turned off above, we end up with a lot of noise filter_picker_entry(entry, &absolute_root, dedup_symlinks)
// in our picker. })
.filter_entry(|entry| entry.file_name() != ".git")
.build_parallel() .build_parallel()
.run(|| { .run(|| {
let mut searcher = searcher.clone(); let mut searcher = searcher.clone();

@ -10,6 +10,9 @@ pub mod health;
pub mod job; pub mod job;
pub mod keymap; pub mod keymap;
pub mod ui; pub mod ui;
use std::path::Path;
use ignore::DirEntry;
pub use keymap::macros::*; pub use keymap::macros::*;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -22,3 +25,25 @@ fn true_color() -> bool {
fn true_color() -> bool { fn true_color() -> bool {
true true
} }
/// Function used for filtering dir entries in the various file pickers.
fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> bool {
// We always want to ignore the .git directory, otherwise if
// `ignore` is turned off, we end up with a lot of noise
// in our picker.
if entry.file_name() == ".git" {
return false;
}
// We also ignore symlinks that point inside the current directory
// if `dedup_links` is enabled.
if dedup_symlinks && entry.path_is_symlink() {
return entry
.path()
.canonicalize()
.ok()
.map_or(false, |path| !path.starts_with(&root));
}
true
}

@ -15,6 +15,7 @@ mod statusline;
mod text; mod text;
use crate::compositor::{Component, Compositor}; use crate::compositor::{Component, Compositor};
use crate::filter_picker_entry;
use crate::job::{self, Callback}; use crate::job::{self, Callback};
pub use completion::Completion; pub use completion::Completion;
pub use editor::EditorView; pub use editor::EditorView;
@ -163,6 +164,9 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
let now = Instant::now(); let now = Instant::now();
let dedup_symlinks = config.file_picker.deduplicate_links;
let absolute_root = root.canonicalize().unwrap_or_else(|_| root.clone());
let mut walk_builder = WalkBuilder::new(&root); let mut walk_builder = WalkBuilder::new(&root);
walk_builder walk_builder
.hidden(config.file_picker.hidden) .hidden(config.file_picker.hidden)
@ -173,10 +177,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
.git_global(config.file_picker.git_global) .git_global(config.file_picker.git_global)
.git_exclude(config.file_picker.git_exclude) .git_exclude(config.file_picker.git_exclude)
.max_depth(config.file_picker.max_depth) .max_depth(config.file_picker.max_depth)
// We always want to ignore the .git directory, otherwise if .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
// `ignore` is turned off above, we end up with a lot of noise
// in our picker.
.filter_entry(|entry| entry.file_name() != ".git");
// We want to exclude files that the editor can't handle yet // We want to exclude files that the editor can't handle yet
let mut type_builder = TypesBuilder::new(); let mut type_builder = TypesBuilder::new();
@ -195,14 +196,11 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
// We want files along with their modification date for sorting // We want files along with their modification date for sorting
let files = walk_builder.build().filter_map(|entry| { let files = walk_builder.build().filter_map(|entry| {
let entry = entry.ok()?; let entry = entry.ok()?;
// This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir // This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir
let is_dir = entry.file_type().map_or(false, |ft| ft.is_dir()); if entry.file_type()?.is_file() {
if is_dir {
// Will give a false positive if metadata cannot be read (eg. permission error)
None
} else {
Some(entry.into_path()) Some(entry.into_path())
} else {
None
} }
}); });

@ -173,6 +173,8 @@ pub struct FilePickerConfig {
/// Enables following symlinks. /// Enables following symlinks.
/// Whether to follow symbolic links in file picker and file or directory completions. Defaults to true. /// Whether to follow symbolic links in file picker and file or directory completions. Defaults to true.
pub follow_symlinks: bool, pub follow_symlinks: bool,
/// Hides symlinks that point into the current directory. Defaults to true.
pub deduplicate_links: bool,
/// Enables reading ignore files from parent directories. Defaults to true. /// Enables reading ignore files from parent directories. Defaults to true.
pub parents: bool, pub parents: bool,
/// Enables reading `.ignore` files. /// Enables reading `.ignore` files.
@ -197,6 +199,7 @@ impl Default for FilePickerConfig {
Self { Self {
hidden: true, hidden: true,
follow_symlinks: true, follow_symlinks: true,
deduplicate_links: true,
parents: true, parents: true,
ignore: true, ignore: true,
git_ignore: true, git_ignore: true,

Loading…
Cancel
Save