Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2e1858c

Browse files
committed
use gix-glob for matching; support for matching dirs only.
That way, git-like globs can be used which support nice extras, like searching for directories only.
1 parent 3804a1f commit 2e1858c

8 files changed

Lines changed: 151 additions & 52 deletions

File tree

Cargo.lock

Lines changed: 91 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ wild = "2.0.4"
4141
owo-colors = "3.5.0"
4242
human_format = "1.0.3"
4343
once_cell = "1.19"
44-
globset = "0.4.14"
44+
gix-glob = "0.14.1"
45+
gix-path = "0.10.1"
46+
bstr = "1.8.0"
4547

4648
[[bin]]
4749
name="dua"

src/interactive/app/common.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub struct EntryDataBundle {
5656
pub exists: bool,
5757
}
5858

59+
/// Note that with `glob_root` present, we will not obtain metadata anymore as we might be seeing
60+
/// a lot of entries. That way, displaying 250k entries is no problem.
5961
pub fn sorted_entries(
6062
tree: &Tree,
6163
node_idx: TreeIndex,
@@ -72,8 +74,15 @@ pub fn sorted_entries(
7274
.filter_map(|idx| {
7375
tree.node_weight(idx).map(|entry| {
7476
let use_glob_path = glob_root.map_or(false, |glob_root| glob_root == node_idx);
75-
let path = path_of(tree, idx, glob_root);
76-
let meta = path.symlink_metadata();
77+
let (path, exists, is_dir) = {
78+
let path = path_of(tree, idx, glob_root);
79+
if glob_root.is_some() {
80+
(path, true, entry.is_dir)
81+
} else {
82+
let meta = path.symlink_metadata();
83+
(path, meta.is_ok(), meta.ok().map_or(false, |m| m.is_dir()))
84+
}
85+
};
7786
EntryDataBundle {
7887
index: idx,
7988
name: if use_glob_path {
@@ -84,8 +93,8 @@ pub fn sorted_entries(
8493
size: entry.size,
8594
mtime: entry.mtime,
8695
entry_count: entry.entry_count,
87-
exists: meta.is_ok(),
88-
is_dir: meta.ok().map_or(false, |m| m.is_dir()),
96+
exists,
97+
is_dir,
8998
}
9099
})
91100
})

src/interactive/app/tests/unit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn it_can_handle_ending_traversal_reaching_top_but_skipping_levels() -> Result<(
2222
#[test]
2323
fn it_can_handle_ending_traversal_without_reaching_the_top() -> Result<()> {
2424
let (_, app) = initialized_app_and_terminal_from_fixture(&["sample-02"])?;
25-
let (expected_tree, _) = sample_02_tree();
25+
let (expected_tree, _) = sample_02_tree(true);
2626

2727
assert_eq!(
2828
debug(app.traversal.tree),
@@ -34,7 +34,7 @@ fn it_can_handle_ending_traversal_without_reaching_the_top() -> Result<()> {
3434

3535
#[test]
3636
fn it_can_do_a_glob_search() {
37-
let (tree, root_index) = sample_02_tree();
37+
let (tree, root_index) = sample_02_tree(false);
3838
let result = glob_search(&tree, root_index, "tests/fixtures/sample-02").unwrap();
3939
let expected = vec![TreeIndex::from(1)];
4040
assert_eq!(result, expected);

src/interactive/app/tests/utils.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ pub fn sample_01_tree() -> Tree {
255255
tree
256256
}
257257

258-
pub fn sample_02_tree() -> (Tree, TreeIndex) {
258+
pub fn sample_02_tree(use_native_separator: bool) -> (Tree, TreeIndex) {
259259
let mut tree = Tree::new();
260260
let root_index: TreeIndex;
261261
{
@@ -264,7 +264,15 @@ pub fn sample_02_tree() -> (Tree, TreeIndex) {
264264
root_index = add_node("", root_size, 10, None);
265265
{
266266
let sn = add_node(
267-
Path::new(FIXTURE_PATH).join("sample-02").to_str().unwrap(),
267+
format!(
268+
"{FIXTURE_PATH}{}sample-02",
269+
if use_native_separator {
270+
std::path::MAIN_SEPARATOR_STR
271+
} else {
272+
"/"
273+
}
274+
)
275+
.as_str(),
268276
root_size,
269277
9,
270278
Some(root_index),

src/interactive/widgets/glob.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use anyhow::Result;
1+
use anyhow::{anyhow, Context, Result};
2+
use bstr::BString;
23
use crosstermion::input::Key;
34
use dua::traverse::{Tree, TreeIndex};
4-
use globset::{Glob, GlobMatcher};
55
use petgraph::Direction;
66
use std::borrow::Borrow;
7-
use std::path::PathBuf;
87
use tui::backend::Backend;
98
use tui::prelude::Buffer;
109
use tui::{
@@ -112,7 +111,7 @@ impl GlobPane {
112111
has_focus,
113112
} = props.borrow();
114113

115-
let title = "Glob search from top";
114+
let title = "Git-Glob";
116115
let block = Block::default()
117116
.title(title)
118117
.border_style(*border_style)
@@ -178,26 +177,40 @@ fn glob_search_neighbours(
178177
results: &mut Vec<TreeIndex>,
179178
tree: &Tree,
180179
root_index: TreeIndex,
181-
glob: &GlobMatcher,
182-
path: &mut PathBuf,
180+
glob: &gix_glob::Pattern,
181+
path: &mut BString,
183182
) {
184183
for node_index in tree.neighbors_directed(root_index, Direction::Outgoing) {
185184
if let Some(node) = tree.node_weight(node_index) {
186-
path.push(&node.name);
187-
if glob.is_match(&path) {
185+
let previous_len = path.len();
186+
let basename_start = if path.is_empty() {
187+
None
188+
} else {
189+
path.push(b'/');
190+
Some(previous_len + 1)
191+
};
192+
path.extend_from_slice(gix_path::into_bstr(&node.name).as_ref());
193+
if glob.matches_repo_relative_path(
194+
path.as_ref(),
195+
basename_start,
196+
Some(node.is_dir),
197+
gix_glob::pattern::Case::Fold,
198+
gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL,
199+
) {
188200
results.push(node_index);
189201
} else {
190202
glob_search_neighbours(results, tree, node_index, glob, path);
191203
}
192-
path.pop();
204+
path.truncate(previous_len);
193205
}
194206
}
195207
}
196208

197209
pub fn glob_search(tree: &Tree, root_index: TreeIndex, glob: &str) -> Result<Vec<TreeIndex>> {
198-
let glob = Glob::new(glob)?.compile_matcher();
210+
let glob = gix_glob::Pattern::from_bytes_without_negation(glob.as_bytes())
211+
.with_context(|| anyhow!("Glob was empty or only whitespace"))?;
199212
let mut results = Vec::new();
200-
let mut path = PathBuf::new();
213+
let mut path = Default::default();
201214
glob_search_neighbours(&mut results, tree, root_index, &glob, &mut path);
202215
Ok(results)
203216
}

src/interactive/widgets/help.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@ impl HelpPane {
161161
);
162162
hotkey("<Space>", "Toggle the currently selected entry", None);
163163
hotkey("a", "Toggle all entries", None);
164-
hotkey("/", "Glob search", None);
164+
hotkey(
165+
"/",
166+
"Git-style glob search, case-insensitive, always from the top of the tree",
167+
None,
168+
);
165169
spacer();
166170
}
167171
title("Keys in the Mark pane");

src/traverse.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct EntryData {
2222
pub entry_count: Option<u64>,
2323
/// If set, the item meta-data could not be obtained
2424
pub metadata_io_error: bool,
25+
pub is_dir: bool,
2526
}
2627

2728
impl Default for EntryData {
@@ -32,6 +33,7 @@ impl Default for EntryData {
3233
mtime: UNIX_EPOCH,
3334
entry_count: None,
3435
metadata_io_error: bool::default(),
36+
is_dir: false,
3537
}
3638
}
3739
}
@@ -191,6 +193,7 @@ impl Traversal {
191193
}
192194
} else {
193195
data.entry_count = Some(0);
196+
data.is_dir = true;
194197
}
195198

196199
match m.modified() {

0 commit comments

Comments
 (0)