diff --git a/src/error.rs b/src/error.rs index 3501feec6..223e105f7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,7 @@ pub fn print_error(msg: impl Into) { eprintln!("[fd error]: {}", msg.into()); } + +pub fn print_warning(msg: impl Into) { + eprintln!("[fd warning]: {}", msg.into()); +} diff --git a/src/main.rs b/src/main.rs index f6c0d7e4f..616bbd0a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ use regex::bytes::{Regex, RegexBuilder, RegexSetBuilder}; use crate::cli::{ColorWhen, HyperlinkWhen, Opts}; use crate::config::Config; +use crate::error::print_warning; use crate::exec::CommandSet; use crate::exit_codes::ExitCode; use crate::filetypes::FileTypes; @@ -86,6 +87,7 @@ fn run() -> Result { } ensure_search_pattern_is_not_a_path(&opts)?; + warn_if_full_path_glob_missing_leading_anchor(&opts); let pattern = &opts.pattern; let exprs = &opts.exprs; let empty = Vec::new(); @@ -201,6 +203,31 @@ fn ensure_search_pattern_is_not_a_path(opts: &Opts) -> Result<()> { } } +/// With `--glob` and `--full-path`, patterns match the entire absolute path. +/// Warn when the pattern does not start with a path root or glob wildcard, which often means no +/// matches (#1650). +fn warn_if_full_path_glob_missing_leading_anchor(opts: &Opts) { + if !opts.glob || !opts.full_path || opts.pattern.is_empty() { + return; + } + if full_path_glob_has_leading_anchor(&opts.pattern) { + return; + } + print_warning( + "With --glob and --full-path, the pattern matches the whole absolute path and \ + usually should start with '/' or '**/' (for example: '**/foo.txt').", + ); +} + +fn full_path_glob_has_leading_anchor(pattern: &str) -> bool { + let bytes = pattern.as_bytes(); + matches!(bytes.first(), Some(b'*' | b'/' | b'\\')) + || matches!( + bytes, + [drive, b':', b'/' | b'\\', ..] if drive.is_ascii_alphabetic() + ) +} + fn build_pattern_regex(pattern: &str, opts: &Opts) -> Result { Ok(if opts.glob && !pattern.is_empty() { let glob = GlobBuilder::new(pattern).literal_separator(true).build()?; @@ -534,3 +561,27 @@ fn build_regex(pattern_regex: String, config: &Config) -> Result