1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! # tui-file-explorer
//!
//! A self-contained, keyboard-driven file-browser widget for
//! [Ratatui](https://ratatui.rs).
//!
//! ## Design goals
//!
//! * **Zero application-specific dependencies** — only `ratatui`, `crossterm`,
//! and the standard library are required.
//! * **Narrow public surface** — the public API is intentionally small so the
//! crate can evolve without breaking changes.
//! * **Extension filtering** — pass a list of allowed extensions so that only
//! relevant files are selectable (e.g. `["iso", "img"]`); directories are
//! always navigable.
//! * **Keyboard-driven** — `↑`/`↓`/`←`/`→` scroll the list, `Enter` / `l`
//! to descend or confirm, `Backspace` / `h` to ascend, `/` to search,
//! `s` to cycle sort, `Esc` / `q` to dismiss.
//! * **Searchable** — press `/` to enter incremental search; entries are
//! filtered live as you type. `Esc` clears the query; a second `Esc`
//! dismisses the explorer.
//! * **Sortable** — press `s` to cycle through `Name`, `Size ↓`, and
//! `Extension` sort modes, or set one programmatically via
//! [`FileExplorer::set_sort_mode`].
//! * **Themeable** — every colour is overridable via [`Theme`] and
//! [`render_themed`].
//! * **Dual-pane** — [`DualPane`] owns two independent [`FileExplorer`]s,
//! manages focus switching (`Tab`), and supports a single-pane toggle (`w`).
//!
//! ## Quick start
//!
//! ```no_run
//! use tui_file_explorer::{FileExplorer, ExplorerOutcome, SortMode, render};
//! use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
//! # use ratatui::{Terminal, backend::TestBackend};
//! # let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap();
//!
//! // 1. Create once (e.g. in your App::new).
//! let mut explorer = FileExplorer::builder(std::env::current_dir().unwrap())
//! .allow_extension("iso")
//! .allow_extension("img")
//! .sort_mode(SortMode::SizeDesc)
//! .build();
//!
//! // 2. In your Terminal::draw closure:
//! // render(&mut explorer, frame, frame.area());
//!
//! // 3. In your key-handler:
//! # let key = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
//! match explorer.handle_key(key) {
//! ExplorerOutcome::Selected(path) => println!("chosen: {}", path.display()),
//! ExplorerOutcome::Dismissed => { /* close the overlay */ }
//! _ => {}
//! }
//! ```
//!
//! ## Builder / configuration
//!
//! Use [`FileExplorer::builder`] for a more ergonomic construction API:
//!
//! ```no_run
//! use tui_file_explorer::{FileExplorer, SortMode};
//!
//! let explorer = FileExplorer::builder(std::env::current_dir().unwrap())
//! .allow_extension("rs")
//! .allow_extension("toml")
//! .show_hidden(true)
//! .sort_mode(SortMode::Extension)
//! .build();
//! ```
//!
//! ## Incremental search
//!
//! Press `/` to activate search mode. Subsequent keystrokes append to the
//! query and the entry list is filtered live (case-insensitive substring
//! match on the file name). `Backspace` removes the last character; an
//! extra `Backspace` on an empty query deactivates search. `Esc` clears
//! the query and deactivates search without dismissing the explorer; a
//! second `Esc` (when search is already inactive) dismisses it.
//!
//! Search state is also accessible programmatically:
//!
//! ```no_run
//! use tui_file_explorer::FileExplorer;
//!
//! let explorer = FileExplorer::new(std::env::current_dir().unwrap(), vec![]);
//! println!("searching: {}", explorer.is_searching());
//! println!("query : {}", explorer.search_query());
//! ```
//!
//! ## Sort modes
//!
//! Press `s` to cycle through the three sort modes, or set one directly:
//!
//! ```no_run
//! use tui_file_explorer::{FileExplorer, SortMode};
//!
//! let mut explorer = FileExplorer::new(std::env::current_dir().unwrap(), vec![]);
//! explorer.set_sort_mode(SortMode::SizeDesc); // largest files first
//!
//! println!("{}", explorer.sort_mode().label()); // "size ↓"
//! ```
//!
//! ## Theming
//!
//! Every colour is customisable via [`Theme`] and [`render_themed`]:
//!
//! ```no_run
//! use tui_file_explorer::{FileExplorer, Theme, render_themed};
//! use ratatui::style::Color;
//!
//! let theme = Theme::default()
//! .brand(Color::Magenta)
//! .accent(Color::Cyan)
//! .dir(Color::LightYellow);
//!
//! // terminal.draw(|frame| {
//! // render_themed(&mut explorer, frame, frame.area(), &theme);
//! // });
//! ```
//!
//! ## Named presets
//!
//! Twenty well-known editor / terminal colour schemes are available as
//! associated constructors on [`Theme`], mirroring the catalogue found in
//! [Iced](https://docs.rs/iced/latest/iced/theme/enum.Theme.html):
//!
//! ```
//! use tui_file_explorer::Theme;
//!
//! let t = Theme::dracula();
//! let t = Theme::nord();
//! let t = Theme::catppuccin_mocha();
//! let t = Theme::tokyo_night();
//! let t = Theme::gruvbox_dark();
//! let t = Theme::kanagawa_wave();
//! let t = Theme::oxocarbon();
//!
//! // Iterate the full catalogue (name, description, theme):
//! for (name, desc, _theme) in Theme::all_presets() {
//! println!("{name} — {desc}");
//! }
//! ```
//!
//! The complete list: `Default`, `Dracula`, `Nord`, `Solarized Dark`,
//! `Solarized Light`, `Gruvbox Dark`, `Gruvbox Light`, `Catppuccin Latte`,
//! `Catppuccin Frappé`, `Catppuccin Macchiato`, `Catppuccin Mocha`,
//! `Tokyo Night`, `Tokyo Night Storm`, `Tokyo Night Light`, `Kanagawa Wave`,
//! `Kanagawa Dragon`, `Kanagawa Lotus`, `Moonfly`, `Nightfly`, `Oxocarbon`.
//!
//! ## Key bindings reference
//!
//! | Key | Action |
//! |-----|--------|
//! | `↑` / `k` | Move cursor up |
//! | `↓` / `j` | Move cursor down |
//! | `PgUp` / `PgDn` | Jump 10 entries |
//! | `Home` / `g` | Jump to top |
//! | `End` / `G` | Jump to bottom |
//! | `→` / `l` / `Enter` | Descend into directory; on a file `→` moves cursor down, `l`/`Enter` confirm |
//! | `←` / `h` / `Backspace` | Ascend to parent directory |
//! | `/` | Activate incremental search |
//! | `s` | Cycle sort mode (`Name` → `Size ↓` → `Extension`) |
//! | `.` | Toggle hidden (dot-file) entries |
//! | `Esc` | Clear search (if active), then dismiss |
//! | `q` | Dismiss (when search is not active) |
//!
//! ## Dual-pane quick start
//!
//! ```no_run
//! use tui_file_explorer::{DualPane, DualPaneOutcome, render_dual_pane_themed, Theme};
//! use crossterm::event::{Event, KeyCode, KeyModifiers, self};
//! # use ratatui::{Terminal, backend::TestBackend};
//! # let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap();
//!
//! // 1. Create once — left pane defaults to cwd; right pane mirrors it.
//! let mut dual = DualPane::builder(std::env::current_dir().unwrap())
//! .right_dir(std::path::PathBuf::from("/tmp"))
//! .build();
//!
//! let theme = Theme::default();
//!
//! // 2. In your Terminal::draw closure:
//! // terminal.draw(|frame| {
//! // render_dual_pane_themed(&mut dual, frame, frame.area(), &theme);
//! // }).unwrap();
//!
//! // 3. In your event loop:
//! # let Event::Key(key) = event::read().unwrap() else { return; };
//! match dual.handle_key(key) {
//! DualPaneOutcome::Selected(path) => println!("chosen: {}", path.display()),
//! DualPaneOutcome::Dismissed => { /* close the overlay */ }
//! _ => {}
//! }
//! ```
//!
//! ### Extra key bindings provided by `DualPane`
//!
//! | Key | Action |
//! |-------|-------------------------------------|
//! | `Tab` | Switch focus between left and right pane |
//! | `w` | Toggle single-pane / dual-pane mode |
//!
//! All standard [`FileExplorer`] bindings continue to work on whichever pane
//! is currently active.
//!
//! ## Module layout
//!
//! | Module | Contents |
//! |-------------|---------------------------------------------------------------------------------------------------|
//! | `types` | [`FsEntry`], [`ExplorerOutcome`], [`SortMode`] |
//! | `palette` | Palette constants (all `pub`) + [`Theme`] + named presets |
//! | `explorer` | [`FileExplorer`], [`FileExplorerBuilder`], [`entry_icon`], [`fmt_size`] |
//! | `dual_pane` | [`DualPane`], [`DualPaneBuilder`], [`DualPaneActive`], [`DualPaneOutcome`] |
//! | `render` | [`render`], [`render_themed`], [`render_dual_pane`], [`render_dual_pane_themed`] |
// ── Convenience re-exports ────────────────────────────────────────────────────
pub use ;
pub use ;
pub use Theme;
pub use ;
pub use ;