Skip to content

Commit dff0be5

Browse files
committed
feat: use ignore hidden detection implementation for correctness
1 parent de5a96e commit dff0be5

File tree

6 files changed

+103
-67
lines changed

6 files changed

+103
-67
lines changed

Cargo.lock

+7-28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ clap = { version = "4.5.1", features = ["derive", "color"] }
2121
rayon = "1.9.0"
2222
parking_lot = "0.12.1"
2323
mimalloc = { version = "0.1.39", default-features = false }
24-
yansi = { version = "1.0.0-rc.1", features = ["detect-tty", "detect-env"] } # Colorize output (no alloc)
2524
colored = "2.1.0"
26-
clap_lex = "0.7.0"
25+
memchr = "2.7.1"
2726

2827
[profile.release]
2928
lto = true

build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
fn main() {
22
println!("cargo:rustc-link-lib=mimalloc");
3-
}
3+
}

src/main.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ mod structs;
88
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
99

1010
#[cfg(feature = "debug")]
11-
pub(crate) struct Perf {
11+
pub(crate) struct Perf {
1212
time: std::time::Duration,
13-
ctx: &'static str
13+
ctx: &'static str,
1414
}
1515

1616
#[cfg(feature = "debug")]
@@ -29,7 +29,7 @@ macro_rules! perf {
2929
#[cfg(feature = "debug")]
3030
let _end = std::time::Instant::now();
3131
#[cfg(feature = "debug")]
32-
$crate::Perf {
32+
$crate::Perf {
3333
time: _end - _start,
3434
ctx: $ctx
3535
};
@@ -45,11 +45,11 @@ macro_rules! perf {
4545
fn main() -> std::io::Result<()> {
4646
let search = structs::Cli::run();
4747
std::env::set_var("RUST_MIN_STACK", format!("{}", 1024 * 1024 * 1024));
48-
48+
4949
perf! {
50-
ctx = "search";
51-
let buffers = search.search();
50+
ctx = "search";
51+
let buffers = search.search();
5252
}
5353
search.print_results(buffers)?;
5454
Ok(())
55-
}
55+
}

src/print.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::structs::{Buffers, Output, Search};
2-
// use yansi::{Paint, Style};
32
use rayon::prelude::ParallelSliceMut;
43
use std::io::Write;
54

@@ -45,7 +44,7 @@ pub fn print_with_highlight(
4544
search: &Search,
4645
) -> std::io::Result<()> {
4746
let ancestors = path.parent().unwrap();
48-
47+
4948
crate::perf! {
5049
disable;
5150
ctx = "get highlight";
@@ -54,8 +53,8 @@ pub fn print_with_highlight(
5453
let start = sname.find(s).unwrap();
5554
(start, start + s.len())
5655
};
57-
58-
let starts_idx = if search.starts.is_empty() {
56+
57+
let starts_idx = if search.starts.is_empty() {
5958
(0, 0)
6059
} else {
6160
get_start_end(&search.starts)
@@ -71,11 +70,11 @@ pub fn print_with_highlight(
7170
get_start_end(&search.ends)
7271
};
7372
}
74-
73+
7574
crate::perf! {
7675
disable;
7776
ctx = "build highlight";
78-
77+
7978
use colored::Colorize;
8079

8180
let ancestors = ancestors.display();
@@ -108,4 +107,4 @@ pub fn format_with_highlight(
108107
let mut buffer = Vec::new();
109108
print_with_highlight(&mut buffer, fname, sname, path, search).unwrap();
110109
unsafe { String::from_utf8_unchecked(buffer) }
111-
}
110+
}

src/search.rs

+81-22
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
use rayon::iter::{IntoParallelIterator, ParallelBridge, ParallelIterator};
2-
use std::path::{Path, PathBuf};
31
use crate::structs::{Buffer, Buffers, FileType, Output, Search};
4-
5-
static FOUND: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
2+
use rayon::iter::{ParallelBridge, ParallelIterator};
3+
use std::path::Path;
64

75
type Receiver = std::sync::mpsc::Receiver<String>;
86
type Sender = std::sync::mpsc::Sender<String>;
@@ -40,10 +38,14 @@ impl Search {
4038
// Search in directories
4139
// par_fold(dirs, |dir| search_path(dir.as_ref(), self, sender.clone()));
4240
let received = rayon::scope(move |s| {
43-
s.spawn(move |_| dirs.into_iter().par_bridge().for_each(|dir| search_path(dir.as_ref(), self, sender.clone())));
41+
s.spawn(move |_| {
42+
dirs.into_iter()
43+
.par_bridge()
44+
.for_each(|dir| search_path(dir.as_ref(), self, sender.clone()))
45+
});
4446
receive_paths(receiver, self)
4547
});
46-
48+
4749
(Vec::new(), received)
4850
}
4951
}
@@ -52,14 +54,13 @@ fn receive_paths(receiver: Receiver, search: &Search) -> Buffer {
5254
let stdout = std::io::stdout();
5355
let mut stdout = std::io::BufWriter::new(stdout.lock());
5456
let mut received = Vec::new();
55-
57+
5658
while let Ok(path) = receiver.recv() {
5759
use std::io::Write;
5860
if search.first {
5961
println!("{path}");
6062
std::process::exit(0)
61-
}
62-
else if search.output == Output::SuperSimple {
63+
} else if search.output == Output::SuperSimple {
6364
writeln!(stdout, "{path}").unwrap();
6465
} else {
6566
received.push(path);
@@ -70,7 +71,9 @@ fn receive_paths(receiver: Receiver, search: &Search) -> Buffer {
7071

7172
fn search_path(dir: &Path, search: &Search, sender: Sender) {
7273
if let Ok(read) = std::fs::read_dir(dir) {
73-
read.flatten().par_bridge().for_each(|entry| search_dir(entry, search, sender.clone()));
74+
read.flatten()
75+
.par_bridge()
76+
.for_each(|entry| search_dir(entry, search, sender.clone()));
7477
// return par_fold(read.flatten(), |entry| search_dir(entry, search, sender.clone()));
7578
} else if search.verbose {
7679
eprintln!("Could not read {:?}", dir);
@@ -113,7 +116,7 @@ fn search_dir(entry: std::fs::DirEntry, search: &Search, sender: Sender) {
113116

114117
let starts = search.starts.is_empty() || sname.starts_with(&search.starts);
115118
let ends = search.ends.is_empty() || sname.ends_with(&search.ends);
116-
119+
117120
if starts && ends && ftype {
118121
// If file name is equal to search name, write it to the "Exact" buffer
119122
if sname == search.name {
@@ -129,30 +132,86 @@ fn search_dir(entry: std::fs::DirEntry, search: &Search, sender: Sender) {
129132
sender.send(s).unwrap();
130133
}
131134
}
132-
135+
133136
// If entry is not a directory, stop function
134137
if !is_dir {
135138
return;
136139
}
137140

138141
if let Ok(read) = std::fs::read_dir(&path) {
139-
read.flatten().par_bridge().for_each(|entry| search_dir(entry, search, sender.clone()));
142+
read.flatten()
143+
.par_bridge()
144+
.for_each(|entry| search_dir(entry, search, sender.clone()));
140145
} else if search.verbose {
141146
eprintln!("Could not read {:?}", path);
142147
}
143148
}
144149

145-
// OS-variable functions
146-
#[cfg(windows)]
147-
fn is_hidden(entry: &std::fs::DirEntry) -> bool {
148-
use std::os::windows::prelude::*;
149-
let metadata = entry.metadata().unwrap();
150-
let attributes = metadata.file_attributes();
150+
// from https://github.com/BurntSushi/ripgrep/blob/master/crates/ignore/src/pathutil.rs
151151

152-
attributes & 0x2 > 0
152+
/// Returns true if and only if this entry is considered to be hidden.
153+
///
154+
/// This only returns true if the base name of the path starts with a `.`.
155+
///
156+
/// On Unix, this implements a more optimized check.
157+
#[cfg(unix)]
158+
pub(crate) fn is_hidden(entry: &std::fs::DirEntry) -> bool {
159+
use std::os::unix::ffi::OsStrExt;
160+
161+
file_name(&entry.path()).is_some_and(|name| name.as_bytes().first() == Some(&b'.'))
153162
}
154163

164+
/// Returns true if and only if this entry is considered to be hidden.
165+
///
166+
/// On Windows, this returns true if one of the following is true:
167+
///
168+
/// * The base name of the path starts with a `.`.
169+
/// * The file attributes have the `HIDDEN` property set.
170+
#[cfg(windows)]
171+
pub(crate) fn is_hidden(entry: &std::fs::DirEntry) -> bool {
172+
use std::os::windows::fs::MetadataExt;
173+
use winapi_util::file;
174+
175+
// This looks like we're doing an extra stat call, but on Windows, the
176+
// directory traverser reuses the metadata retrieved from each directory
177+
// entry and stores it on the DirEntry itself. So this is "free."
178+
if let Ok(md) = entry.metadata() {
179+
if file::is_hidden(md.file_attributes() as u64) {
180+
return true;
181+
}
182+
}
183+
if let Some(name) = file_name(entry.path()) {
184+
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
185+
} else {
186+
false
187+
}
188+
}
189+
190+
/// The final component of the path, if it is a normal file.
191+
///
192+
/// If the path terminates in ., .., or consists solely of a root of prefix,
193+
/// file_name will return None.
155194
#[cfg(unix)]
156-
fn is_hidden(entry: &std::fs::DirEntry) -> bool {
157-
entry.file_name().to_string_lossy().starts_with('.')
195+
pub(crate) fn file_name<P: AsRef<Path> + ?Sized>(path: &P) -> Option<&std::ffi::OsStr> {
196+
use std::os::unix::ffi::OsStrExt;
197+
198+
let path = path.as_ref().as_os_str().as_bytes();
199+
if path.is_empty()
200+
|| path.len() == 1 && path[0] == b'.'
201+
|| path.last() == Some(&b'.')
202+
|| path.len() >= 2 && path[path.len() - 2..] == b".."[..]
203+
{
204+
return None;
205+
}
206+
let last_slash = memchr::memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
207+
Some(std::ffi::OsStr::from_bytes(&path[last_slash..]))
208+
}
209+
210+
/// The final component of the path, if it is a normal file.
211+
///
212+
/// If the path terminates in ., .., or consists solely of a root of prefix,
213+
/// file_name will return None.
214+
#[cfg(not(unix))]
215+
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(path: &'a P) -> Option<&'a OsStr> {
216+
path.as_ref().file_name()
158217
}

0 commit comments

Comments
 (0)