Better handling of symlinks (#2718)

- Add file-picker.follow-symlinks configuration option (default is true), this
  also controls if filename and directory completers follow symlinks.

- Update FilePicker to set editor error if opening a file fails, instead of
  panicing.

Fix #1548
Fix #2246
pull/2781/head
Roland Kovacs 2 years ago committed by GitHub
parent 7983c71752
commit c2cc2037b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -118,6 +118,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
.hidden(config.file_picker.hidden) .hidden(config.file_picker.hidden)
.parents(config.file_picker.parents) .parents(config.file_picker.parents)
.ignore(config.file_picker.ignore) .ignore(config.file_picker.ignore)
.follow_links(config.file_picker.follow_symlinks)
.git_ignore(config.file_picker.git_ignore) .git_ignore(config.file_picker.git_ignore)
.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)
@ -146,14 +147,13 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
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(|ft| ft.is_dir()).unwrap_or(false); let is_dir = entry.file_type().map_or(false, |ft| ft.is_dir());
if is_dir { if is_dir {
// Will give a false positive if metadata cannot be read (eg. permission error) // Will give a false positive if metadata cannot be read (eg. permission error)
return None; None
} } else {
Some(entry.into_path()) Some(entry.into_path())
}
}); });
// Cap the number of files if we aren't in a git project, preventing // Cap the number of files if we aren't in a git project, preventing
@ -175,9 +175,14 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
path.strip_prefix(&root).unwrap_or(path).to_string_lossy() path.strip_prefix(&root).unwrap_or(path).to_string_lossy()
}, },
move |cx, path: &PathBuf, action| { move |cx, path: &PathBuf, action| {
cx.editor if let Err(e) = cx.editor.open(path.into(), action) {
.open(path.into(), action) let err = if let Some(err) = e.source() {
.expect("editor.open failed"); format!("{}", err)
} else {
format!("unable to open \"{}\"", path.display())
};
cx.editor.set_error(err);
}
}, },
|_editor, path| Some((path.clone(), None)), |_editor, path| Some((path.clone(), None)),
) )
@ -281,8 +286,8 @@ pub mod completers {
.collect() .collect()
} }
pub fn filename(_editor: &Editor, input: &str) -> Vec<Completion> { pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
filename_impl(input, |entry| { filename_impl(editor, input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
if is_dir { if is_dir {
@ -314,8 +319,8 @@ pub mod completers {
.collect() .collect()
} }
pub fn directory(_editor: &Editor, input: &str) -> Vec<Completion> { pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
filename_impl(input, |entry| { filename_impl(editor, input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
if is_dir { if is_dir {
@ -338,7 +343,7 @@ pub mod completers {
} }
// TODO: we could return an iter/lazy thing so it can fetch as many as it needs. // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
fn filename_impl<F>(input: &str, filter_fn: F) -> Vec<Completion> fn filename_impl<F>(editor: &Editor, input: &str, filter_fn: F) -> Vec<Completion>
where where
F: Fn(&ignore::DirEntry) -> FileMatch, F: Fn(&ignore::DirEntry) -> FileMatch,
{ {
@ -370,6 +375,7 @@ pub mod completers {
let mut files: Vec<_> = WalkBuilder::new(&dir) let mut files: Vec<_> = WalkBuilder::new(&dir)
.hidden(false) .hidden(false)
.follow_links(editor.config().file_picker.follow_symlinks)
.max_depth(Some(1)) .max_depth(Some(1))
.build() .build()
.filter_map(|file| { .filter_map(|file| {

@ -71,6 +71,9 @@ pub struct FilePickerConfig {
/// Enables ignoring hidden files. /// Enables ignoring hidden files.
/// Whether to hide hidden files in file picker and global search results. Defaults to true. /// Whether to hide hidden files in file picker and global search results. Defaults to true.
pub hidden: bool, pub hidden: bool,
/// Enables following symlinks.
/// Whether to follow symbolic links in file picker and file or directory completions. Defaults to true.
pub follow_symlinks: 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.
@ -94,6 +97,7 @@ impl Default for FilePickerConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
hidden: true, hidden: true,
follow_symlinks: true,
parents: true, parents: true,
ignore: true, ignore: true,
git_ignore: true, git_ignore: true,

Loading…
Cancel
Save