Skip to content

Commit 6b65ac9

Browse files
committed
Update dependencies and refactor search functionality in File Explorer
1 parent 4e9f87c commit 6b65ac9

2 files changed

Lines changed: 124 additions & 88 deletions

File tree

File-Explorer/Cargo.toml

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,9 @@ name = "file_explorer"
33
version = "0.1.0"
44
edition = "2021"
55

6-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7-
86
[dependencies]
9-
walkdir = "2.3"
107
druid = "0.8.3"
11-
regex = "1.5"
12-
13-
[profile.dev]
14-
panic = "abort"
15-
16-
[profile.release]
17-
panic = "abort"
8+
regex = "1.7"
9+
walkdir = "2.3"
10+
im = "15.0"
11+
rfd = "0.11.0"

File-Explorer/src/main.rs

Lines changed: 120 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,141 @@
1-
use druid::{widget, AppLauncher, Data, Lens, LocalizedString, Widget, WidgetExt, WindowDesc};
1+
use druid::widget::{Button, Flex, Label, List, Scroll, TextBox};
2+
use druid::widget::prelude::*;
3+
use druid::{
4+
AppDelegate, AppLauncher, Command, Data, DelegateCtx, Env, Lens, Selector, Target,
5+
Widget, WidgetExt, WindowDesc,
6+
};
27
use regex::Regex;
3-
4-
use std::path::{Path, PathBuf};
5-
use std::sync::{mpsc, Arc, Mutex};
8+
use std::path::PathBuf;
9+
use std::sync::Arc;
610
use std::thread;
711
use walkdir::WalkDir;
812

9-
#[derive(Clone, Data, Lens)]
10-
struct AppState {
11-
#[lens(ignore)]
12-
root_path: Arc<Mutex<PathBuf>>,
13-
search_term: String,
14-
result: String,
15-
}
13+
// A selector for updating search results from a background thread.
14+
// Note: Now the payload is an Arc<Vec<String>>
15+
const UPDATE_SEARCH_RESULTS: Selector<Arc<Vec<String>>> =
16+
Selector::new("update_search_results");
1617

1718
#[derive(Clone, Data, Lens)]
18-
struct SearchUpdate {
19-
result: String,
19+
struct AppState {
20+
pub root_path: String,
21+
pub search_term: String,
22+
// Change from im::Vector<String> to Arc<Vec<String>> for compatibility with ListIter
23+
pub search_results: Arc<Vec<String>>,
2024
}
2125

2226
fn build_ui() -> impl Widget<AppState> {
23-
let label = widget::Label::new(|data: &AppState, _env: &_| data.result.clone()).with_text_size(20.0);
24-
25-
let scrollable_label = druid::widget::Scroll::new(label);
26-
27-
widget::Flex::column()
28-
.with_child(widget::TextBox::new().lens(AppState::search_term))
29-
.with_child(
30-
widget::Button::new("Search").on_click(|_, data: &mut AppState, _| {
31-
let root_path = data.root_path.lock().unwrap().clone();
32-
let search_term = data.search_term.clone();
33-
34-
let (tx, rx) = mpsc::channel();
35-
let tx_copy = tx.clone();
36-
37-
thread::spawn(move || {
38-
let result = search_files(&root_path, &search_term, Some(tx));
39-
let _ = tx_copy.send(SearchUpdate { result });
40-
});
41-
42-
let mut result = String::new();
43-
for update in rx.iter() {
44-
result.push_str(&update.result);
45-
data.result = result.clone();
46-
}
47-
}),
48-
)
49-
.with_child(scrollable_label)
27+
// Button to let the user choose a directory (using rfd for a native dialog)
28+
let choose_dir_btn = Button::new("Choose Directory").on_click(|_ctx, data: &mut AppState, _env| {
29+
// Use rfd's file dialog (this will show a native folder chooser on macOS)
30+
if let Some(path) = rfd::FileDialog::new().pick_folder() {
31+
data.root_path = path.to_string_lossy().to_string();
32+
// Clear any previous search results when the directory changes.
33+
data.search_results = Arc::new(Vec::new());
34+
}
35+
});
36+
37+
// A label showing the currently selected directory.
38+
let dir_label = Label::new(|data: &AppState, _env: &_| {
39+
format!("Current Directory: {}", data.root_path)
40+
})
41+
.with_text_size(14.0);
42+
43+
// Text box for entering the search term.
44+
let search_box = TextBox::new()
45+
.with_placeholder("Enter search term")
46+
.lens(AppState::search_term);
47+
48+
// Button to kick off the search.
49+
let search_btn = Button::new("Search").on_click(|ctx, data: &mut AppState, _env| {
50+
let root = data.root_path.clone();
51+
let term = data.search_term.clone();
52+
53+
// Clear any previous search results.
54+
data.search_results = Arc::new(Vec::new());
55+
56+
let sink = ctx.get_external_handle();
57+
58+
thread::spawn(move || {
59+
let results = search_files(&root, &term);
60+
// Send the search results back to the UI thread.
61+
sink.submit_command(UPDATE_SEARCH_RESULTS, results, Target::Auto)
62+
.expect("Failed to submit command");
63+
});
64+
});
65+
66+
// Create a list widget to display search results.
67+
let results_list = List::new(|| {
68+
Label::new(|item: &String, _env: &_| format!("{}", item))
69+
.padding(5.0)
70+
})
71+
.with_spacing(2.0)
72+
// Lens into the search_results field (which is now an Arc<Vec<String>>)
73+
.lens(AppState::search_results);
74+
75+
// Layout the UI elements vertically.
76+
Flex::column()
77+
.with_child(choose_dir_btn.padding(5.0))
78+
.with_child(dir_label.padding(5.0))
79+
.with_child(search_box.padding(5.0))
80+
.with_child(search_btn.padding(5.0))
81+
.with_flex_child(Scroll::new(results_list), 1.0)
5082
}
5183

52-
// This function takes in a root path and a search term and returns a string of the search results
53-
fn search_files(root_path: &Path, search_term: &str, tx: Option<mpsc::Sender<SearchUpdate>>) -> String {
54-
55-
let mut result = String::new();
56-
let search_term_regex = Regex::new(&format!(r"(?i){}", search_term)).expect("Invalid regex");
57-
58-
WalkDir::new(root_path)
59-
.into_iter()
60-
.filter_map(|entry| entry.ok())
61-
.filter(|entry| entry.path().is_file())
62-
.filter(|entry| {
63-
entry
64-
.file_name()
65-
.to_str()
66-
.map(|name| search_term_regex.is_match(name))
67-
.unwrap_or(false)
68-
})
69-
.for_each(|entry| {
70-
let found_path = format!("{}\n", entry.path().display());
71-
result.push_str(&found_path);
72-
if let Some(tx) = &tx {
73-
let _ = tx.send(SearchUpdate {
74-
result: found_path.clone(),
75-
});
84+
/// Searches files under the given directory whose names match the search term (case-insensitive)
85+
/// and returns an Arc<Vec<String>>.
86+
fn search_files(root_path: &str, search_term: &str) -> Arc<Vec<String>> {
87+
let regex = Regex::new(&format!(r"(?i){}", search_term)).unwrap();
88+
let root = PathBuf::from(root_path);
89+
let mut results = Vec::new();
90+
for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
91+
if entry.path().is_file() {
92+
if let Some(name) = entry.path().file_name().and_then(|n| n.to_str()) {
93+
if regex.is_match(name) {
94+
results.push(entry.path().display().to_string());
95+
}
7696
}
77-
});
78-
79-
result
97+
}
98+
}
99+
Arc::new(results)
80100
}
81101

102+
/// A delegate to handle commands coming from the background thread.
103+
struct Delegate;
104+
105+
impl AppDelegate<AppState> for Delegate {
106+
fn command(
107+
&mut self,
108+
_ctx: &mut DelegateCtx,
109+
_target: Target,
110+
cmd: &Command,
111+
data: &mut AppState,
112+
_env: &Env,
113+
) -> druid::Handled {
114+
if let Some(results) = cmd.get(UPDATE_SEARCH_RESULTS) {
115+
data.search_results = results.clone();
116+
return druid::Handled::Yes;
117+
}
118+
druid::Handled::No
119+
}
120+
}
82121

83122
fn main() {
84-
let main_window = WindowDesc::new(build_ui())
85-
.title(LocalizedString::new("File Explorer"));
86-
87-
let root_path = Arc::new(Mutex::new(std::env::current_dir().unwrap()));
88-
89-
let app_state = AppState {
90-
root_path: root_path.clone(),
91-
search_term: String::new(),
92-
result: String::new(),
123+
// Create the main window.
124+
let main_window = WindowDesc::new(build_ui()).title("macOS File Explorer");
125+
126+
// Initialize the state with the current directory.
127+
let initial_state = AppState {
128+
root_path: std::env::current_dir()
129+
.unwrap_or_else(|_| PathBuf::from("."))
130+
.display()
131+
.to_string(),
132+
search_term: "".to_string(),
133+
search_results: Arc::new(Vec::new()),
93134
};
94135

136+
// Launch the application with the delegate to handle background commands.
95137
AppLauncher::with_window(main_window)
96-
.log_to_console()
97-
.launch(app_state)
138+
.delegate(Delegate)
139+
.launch(initial_state)
98140
.expect("Failed to launch application");
99141
}

0 commit comments

Comments
 (0)