Skip to content

Commit 6d6f603

Browse files
author
Stephan Dilly
authored
More fuzzy finder (#892)
* allow selecting entries in fuzzy finder * fix fuzzy finder also in files popup * changelog
1 parent 26a9aaa commit 6d6f603

File tree

7 files changed

+122
-60
lines changed

7 files changed

+122
-60
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
**fuzzy find files**
11+
12+
![fuzzy-find](assets/fuzzy-find.gif)
13+
1014
**emojified commit message**
1115

1216
![emojified-commit-message](assets/emojified-commit-message.png)
1317

1418
## Added
19+
- fuzzy find files ([#891](https://github.com/extrawurst/gitui/issues/891))
1520
- visualize progress during async syntax highlighting ([#889](https://github.com/extrawurst/gitui/issues/889))
1621
- added support for markdown emoji's in commits [[@andrewpollack](https://github.com/andrewpollack)] ([#768](https://github.com/extrawurst/gitui/issues/768))
1722
- added scrollbar to revlog [[@ashvin021](https://github.com/ashvin021)] ([#868](https://github.com/extrawurst/gitui/issues/868))

assets/fuzzy-find.gif

415 KB
Loading

src/app.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ impl App {
438438
accessors!(
439439
self,
440440
[
441+
find_file_popup,
441442
msg,
442443
reset,
443444
commit,
@@ -454,7 +455,6 @@ impl App {
454455
rename_branch_popup,
455456
select_branch_popup,
456457
revision_files_popup,
457-
find_file_popup,
458458
tags_popup,
459459
options_popup,
460460
help,
@@ -726,7 +726,8 @@ impl App {
726726
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
727727
}
728728
InternalEvent::FileFinderChanged(file) => {
729-
self.files_tab.file_finder_update(file);
729+
self.files_tab.file_finder_update(&file);
730+
self.revision_files_popup.file_finder_update(&file);
730731
flags
731732
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
732733
}

src/components/file_find_popup.rs

Lines changed: 105 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{
22
visibility_blocking, CommandBlocking, CommandInfo, Component,
3-
DrawableComponent, EventState, TextInputComponent,
3+
DrawableComponent, EventState, ScrollType, TextInputComponent,
44
};
55
use crate::{
66
keys::SharedKeyConfig,
@@ -29,7 +29,8 @@ pub struct FileFindPopup {
2929
query: Option<String>,
3030
theme: SharedTheme,
3131
files: Vec<TreeFile>,
32-
selection: Option<usize>,
32+
selection: usize,
33+
selected_index: Option<usize>,
3334
files_filtered: Vec<usize>,
3435
key_config: SharedKeyConfig,
3536
}
@@ -58,8 +59,9 @@ impl FileFindPopup {
5859
theme,
5960
files: Vec::new(),
6061
files_filtered: Vec::new(),
62+
selected_index: None,
6163
key_config,
62-
selection: None,
64+
selection: 0,
6365
}
6466
}
6567

@@ -94,22 +96,21 @@ impl FileFindPopup {
9496
})
9597
}),
9698
);
97-
98-
self.refresh_selection();
99-
} else {
100-
self.files_filtered
101-
.extend(self.files.iter().enumerate().map(|a| a.0));
10299
}
100+
101+
self.selection = 0;
102+
self.refresh_selection();
103103
}
104104

105105
fn refresh_selection(&mut self) {
106-
let selection = self.files_filtered.first().copied();
106+
let selection =
107+
self.files_filtered.get(self.selection).copied();
107108

108-
if self.selection != selection {
109-
self.selection = selection;
109+
if self.selected_index != selection {
110+
self.selected_index = selection;
110111

111112
let file = self
112-
.selection
113+
.selected_index
113114
.and_then(|index| self.files.get(index))
114115
.map(|f| f.path.clone());
115116

@@ -129,6 +130,25 @@ impl FileFindPopup {
129130

130131
Ok(())
131132
}
133+
134+
fn move_selection(&mut self, move_type: ScrollType) -> bool {
135+
let new_selection = match move_type {
136+
ScrollType::Up => self.selection.saturating_sub(1),
137+
ScrollType::Down => self.selection.saturating_add(1),
138+
_ => self.selection,
139+
};
140+
141+
let new_selection = new_selection
142+
.clamp(0, self.files_filtered.len().saturating_sub(1));
143+
144+
if new_selection != self.selection {
145+
self.selection = new_selection;
146+
self.refresh_selection();
147+
return true;
148+
}
149+
150+
false
151+
}
132152
}
133153

134154
impl DrawableComponent for FileFindPopup {
@@ -138,9 +158,28 @@ impl DrawableComponent for FileFindPopup {
138158
area: Rect,
139159
) -> Result<()> {
140160
if self.is_visible() {
141-
const SIZE: (u16, u16) = (50, 25);
142-
let area =
143-
ui::centered_rect_absolute(SIZE.0, SIZE.1, area);
161+
const MAX_SIZE: (u16, u16) = (50, 20);
162+
163+
let any_hits = !self.files_filtered.is_empty();
164+
165+
let area = ui::centered_rect_absolute(
166+
MAX_SIZE.0, MAX_SIZE.1, area,
167+
);
168+
169+
let area = if any_hits {
170+
area
171+
} else {
172+
Layout::default()
173+
.direction(Direction::Vertical)
174+
.constraints(
175+
[
176+
Constraint::Length(3),
177+
Constraint::Percentage(100),
178+
]
179+
.as_ref(),
180+
)
181+
.split(area)[0]
182+
};
144183

145184
f.render_widget(Clear, area);
146185
f.render_widget(
@@ -155,7 +194,7 @@ impl DrawableComponent for FileFindPopup {
155194
area,
156195
);
157196

158-
let area = Layout::default()
197+
let chunks = Layout::default()
159198
.direction(Direction::Vertical)
160199
.constraints(
161200
[
@@ -169,45 +208,46 @@ impl DrawableComponent for FileFindPopup {
169208
vertical: 1,
170209
}));
171210

172-
self.find_text.draw(f, area[0])?;
173-
174-
let height = usize::from(area[1].height);
175-
let width = usize::from(area[1].width);
176-
177-
let items =
178-
self.files_filtered.iter().take(height).map(|idx| {
179-
let selected = self
180-
.selection
181-
.map_or(false, |selection| selection == *idx);
182-
Span::styled(
183-
Cow::from(trim_length_left(
184-
self.files[*idx]
185-
.path
186-
.to_str()
187-
.unwrap_or_default(),
188-
width,
189-
)),
190-
self.theme.text(selected, false),
191-
)
192-
});
193-
194-
let title = format!(
195-
"Hits: {}/{}",
196-
height.min(self.files_filtered.len()),
197-
self.files_filtered.len()
198-
);
199-
200-
ui::draw_list_block(
201-
f,
202-
area[1],
203-
Block::default()
204-
.title(Span::styled(
205-
title,
206-
self.theme.title(true),
207-
))
208-
.borders(Borders::TOP),
209-
items,
210-
);
211+
self.find_text.draw(f, chunks[0])?;
212+
213+
if any_hits {
214+
let title =
215+
format!("Hits: {}", self.files_filtered.len());
216+
217+
let height = usize::from(chunks[1].height);
218+
let width = usize::from(chunks[1].width);
219+
220+
let items =
221+
self.files_filtered.iter().take(height).map(
222+
|idx| {
223+
let selected = self
224+
.selected_index
225+
.map_or(false, |index| index == *idx);
226+
Span::styled(
227+
Cow::from(trim_length_left(
228+
self.files[*idx]
229+
.path
230+
.to_str()
231+
.unwrap_or_default(),
232+
width,
233+
)),
234+
self.theme.text(selected, false),
235+
)
236+
},
237+
);
238+
239+
ui::draw_list_block(
240+
f,
241+
chunks[1],
242+
Block::default()
243+
.title(Span::styled(
244+
title,
245+
self.theme.title(true),
246+
))
247+
.borders(Borders::TOP),
248+
items,
249+
);
250+
}
211251
}
212252
Ok(())
213253
}
@@ -228,6 +268,12 @@ impl Component for FileFindPopup {
228268
)
229269
.order(1),
230270
);
271+
272+
out.push(CommandInfo::new(
273+
strings::commands::scroll(&self.key_config),
274+
true,
275+
true,
276+
));
231277
}
232278

233279
visibility_blocking(self)
@@ -243,6 +289,10 @@ impl Component for FileFindPopup {
243289
|| *key == self.key_config.enter
244290
{
245291
self.hide();
292+
} else if *key == self.key_config.move_down {
293+
self.move_selection(ScrollType::Down);
294+
} else if *key == self.key_config.move_up {
295+
self.move_selection(ScrollType::Up);
246296
}
247297
}
248298

src/components/revision_files.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ impl RevisionFilesComponent {
146146
.push(InternalEvent::OpenFileFinder(self.files.clone()));
147147
}
148148

149-
pub fn find_file(&mut self, file: Option<PathBuf>) {
149+
pub fn find_file(&mut self, file: &Option<PathBuf>) {
150150
if let Some(file) = file {
151151
self.tree.collapse_but_root();
152-
if self.tree.select_file(&file) {
152+
if self.tree.select_file(file) {
153153
self.selection_changed();
154154
}
155155
}

src/components/revision_files_popup.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::PathBuf;
2+
13
use super::{
24
revision_files::RevisionFilesComponent, visibility_blocking,
35
CommandBlocking, CommandInfo, Component, DrawableComponent,
@@ -59,6 +61,10 @@ impl RevisionFilesPopup {
5961
pub fn any_work_pending(&self) -> bool {
6062
self.files.any_work_pending()
6163
}
64+
65+
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
66+
self.files.find_file(file);
67+
}
6268
}
6369

6470
impl DrawableComponent for RevisionFilesPopup {

src/tabs/files.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl FilesTab {
7171
}
7272
}
7373

74-
pub fn file_finder_update(&mut self, file: Option<PathBuf>) {
74+
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
7575
self.files.find_file(file);
7676
}
7777
}

0 commit comments

Comments
 (0)