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

Hide duplicate symlinks from the picker #5658

Merged
merged 3 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ All git related options are only enabled in a git repository.
| Key | Description | Default |
|--|--|---------|
|`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
|`ignore` | Enables reading `.ignore` files. | true
|`git-ignore` | Enables reading `.gitignore` files. | true
Expand Down
13 changes: 9 additions & 4 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use movement::Movement;
use crate::{
args,
compositor::{self, Component, Compositor},
filter_picker_entry,
job::Callback,
keymap::ReverseKeymap,
ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
Expand Down Expand Up @@ -1913,6 +1914,11 @@ fn global_search(cx: &mut Context) {

let search_root = std::env::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)
.hidden(file_picker_config.hidden)
.parents(file_picker_config.parents)
Expand All @@ -1922,10 +1928,9 @@ fn global_search(cx: &mut Context) {
.git_global(file_picker_config.git_global)
.git_exclude(file_picker_config.git_exclude)
.max_depth(file_picker_config.max_depth)
// We always want to ignore the .git directory, otherwise if
// `ignore` is turned off above, we end up with a lot of noise
// in our picker.
.filter_entry(|entry| entry.file_name() != ".git")
.filter_entry(move |entry| {
filter_picker_entry(entry, &absolute_root, dedup_symlinks)
})
.build_parallel()
.run(|| {
let mut searcher = searcher.clone();
Expand Down
25 changes: 25 additions & 0 deletions helix-term/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ pub mod health;
pub mod job;
pub mod keymap;
pub mod ui;
use std::path::Path;

use ignore::DirEntry;
pub use keymap::macros::*;

#[cfg(not(windows))]
Expand All @@ -22,3 +25,25 @@ fn true_color() -> bool {
fn true_color() -> bool {
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
}
18 changes: 8 additions & 10 deletions helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod statusline;
mod text;

use crate::compositor::{Component, Compositor};
use crate::filter_picker_entry;
use crate::job::{self, Callback};
pub use completion::Completion;
pub use editor::EditorView;
Expand Down Expand Up @@ -162,6 +163,9 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi

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);
walk_builder
.hidden(config.file_picker.hidden)
Expand All @@ -172,10 +176,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
.git_global(config.file_picker.git_global)
.git_exclude(config.file_picker.git_exclude)
.max_depth(config.file_picker.max_depth)
// We always want to ignore the .git directory, otherwise if
// `ignore` is turned off above, we end up with a lot of noise
// in our picker.
.filter_entry(|entry| entry.file_name() != ".git");
.filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));

// We want to exclude files that the editor can't handle yet
let mut type_builder = TypesBuilder::new();
Expand All @@ -194,14 +195,11 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
// We want files along with their modification date for sorting
let files = walk_builder.build().filter_map(|entry| {
let entry = entry.ok()?;

// 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 is_dir {
// Will give a false positive if metadata cannot be read (eg. permission error)
None
} else {
if entry.file_type()?.is_file() {
Some(entry.into_path())
} else {
None
}
});

Expand Down
3 changes: 3 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ pub struct FilePickerConfig {
/// Enables following symlinks.
/// Whether to follow symbolic links in file picker and file or directory completions. Defaults to true.
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.
pub parents: bool,
/// Enables reading `.ignore` files.
Expand All @@ -195,6 +197,7 @@ impl Default for FilePickerConfig {
Self {
hidden: true,
follow_symlinks: true,
deduplicate_links: true,
parents: true,
ignore: true,
git_ignore: true,
Expand Down