mirror of
https://github.com/gitui-org/gitui
synced 2026-05-22 16:38:28 +00:00
More fuzzy finder (#892)
* allow selecting entries in fuzzy finder * fix fuzzy finder also in files popup * changelog
This commit is contained in:
parent
26a9aaacf5
commit
6d6f60349a
7 changed files with 118 additions and 56 deletions
|
|
@ -7,11 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Unreleased
|
||||
|
||||
**fuzzy find files**
|
||||
|
||||

|
||||
|
||||
**emojified commit message**
|
||||
|
||||

|
||||
|
||||
## Added
|
||||
- fuzzy find files ([#891](https://github.com/extrawurst/gitui/issues/891))
|
||||
- visualize progress during async syntax highlighting ([#889](https://github.com/extrawurst/gitui/issues/889))
|
||||
- added support for markdown emoji's in commits [[@andrewpollack](https://github.com/andrewpollack)] ([#768](https://github.com/extrawurst/gitui/issues/768))
|
||||
- added scrollbar to revlog [[@ashvin021](https://github.com/ashvin021)] ([#868](https://github.com/extrawurst/gitui/issues/868))
|
||||
|
|
|
|||
BIN
assets/fuzzy-find.gif
Normal file
BIN
assets/fuzzy-find.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 415 KiB |
|
|
@ -438,6 +438,7 @@ impl App {
|
|||
accessors!(
|
||||
self,
|
||||
[
|
||||
find_file_popup,
|
||||
msg,
|
||||
reset,
|
||||
commit,
|
||||
|
|
@ -454,7 +455,6 @@ impl App {
|
|||
rename_branch_popup,
|
||||
select_branch_popup,
|
||||
revision_files_popup,
|
||||
find_file_popup,
|
||||
tags_popup,
|
||||
options_popup,
|
||||
help,
|
||||
|
|
@ -726,7 +726,8 @@ impl App {
|
|||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::FileFinderChanged(file) => {
|
||||
self.files_tab.file_finder_update(file);
|
||||
self.files_tab.file_finder_update(&file);
|
||||
self.revision_files_popup.file_finder_update(&file);
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::{
|
||||
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
||||
DrawableComponent, EventState, TextInputComponent,
|
||||
DrawableComponent, EventState, ScrollType, TextInputComponent,
|
||||
};
|
||||
use crate::{
|
||||
keys::SharedKeyConfig,
|
||||
|
|
@ -29,7 +29,8 @@ pub struct FileFindPopup {
|
|||
query: Option<String>,
|
||||
theme: SharedTheme,
|
||||
files: Vec<TreeFile>,
|
||||
selection: Option<usize>,
|
||||
selection: usize,
|
||||
selected_index: Option<usize>,
|
||||
files_filtered: Vec<usize>,
|
||||
key_config: SharedKeyConfig,
|
||||
}
|
||||
|
|
@ -58,8 +59,9 @@ impl FileFindPopup {
|
|||
theme,
|
||||
files: Vec::new(),
|
||||
files_filtered: Vec::new(),
|
||||
selected_index: None,
|
||||
key_config,
|
||||
selection: None,
|
||||
selection: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,22 +96,21 @@ impl FileFindPopup {
|
|||
})
|
||||
}),
|
||||
);
|
||||
|
||||
self.refresh_selection();
|
||||
} else {
|
||||
self.files_filtered
|
||||
.extend(self.files.iter().enumerate().map(|a| a.0));
|
||||
}
|
||||
|
||||
self.selection = 0;
|
||||
self.refresh_selection();
|
||||
}
|
||||
|
||||
fn refresh_selection(&mut self) {
|
||||
let selection = self.files_filtered.first().copied();
|
||||
let selection =
|
||||
self.files_filtered.get(self.selection).copied();
|
||||
|
||||
if self.selection != selection {
|
||||
self.selection = selection;
|
||||
if self.selected_index != selection {
|
||||
self.selected_index = selection;
|
||||
|
||||
let file = self
|
||||
.selection
|
||||
.selected_index
|
||||
.and_then(|index| self.files.get(index))
|
||||
.map(|f| f.path.clone());
|
||||
|
||||
|
|
@ -129,6 +130,25 @@ impl FileFindPopup {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_selection(&mut self, move_type: ScrollType) -> bool {
|
||||
let new_selection = match move_type {
|
||||
ScrollType::Up => self.selection.saturating_sub(1),
|
||||
ScrollType::Down => self.selection.saturating_add(1),
|
||||
_ => self.selection,
|
||||
};
|
||||
|
||||
let new_selection = new_selection
|
||||
.clamp(0, self.files_filtered.len().saturating_sub(1));
|
||||
|
||||
if new_selection != self.selection {
|
||||
self.selection = new_selection;
|
||||
self.refresh_selection();
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for FileFindPopup {
|
||||
|
|
@ -138,9 +158,28 @@ impl DrawableComponent for FileFindPopup {
|
|||
area: Rect,
|
||||
) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
const SIZE: (u16, u16) = (50, 25);
|
||||
let area =
|
||||
ui::centered_rect_absolute(SIZE.0, SIZE.1, area);
|
||||
const MAX_SIZE: (u16, u16) = (50, 20);
|
||||
|
||||
let any_hits = !self.files_filtered.is_empty();
|
||||
|
||||
let area = ui::centered_rect_absolute(
|
||||
MAX_SIZE.0, MAX_SIZE.1, area,
|
||||
);
|
||||
|
||||
let area = if any_hits {
|
||||
area
|
||||
} else {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3),
|
||||
Constraint::Percentage(100),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area)[0]
|
||||
};
|
||||
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(
|
||||
|
|
@ -155,7 +194,7 @@ impl DrawableComponent for FileFindPopup {
|
|||
area,
|
||||
);
|
||||
|
||||
let area = Layout::default()
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
|
|
@ -169,45 +208,46 @@ impl DrawableComponent for FileFindPopup {
|
|||
vertical: 1,
|
||||
}));
|
||||
|
||||
self.find_text.draw(f, area[0])?;
|
||||
self.find_text.draw(f, chunks[0])?;
|
||||
|
||||
let height = usize::from(area[1].height);
|
||||
let width = usize::from(area[1].width);
|
||||
if any_hits {
|
||||
let title =
|
||||
format!("Hits: {}", self.files_filtered.len());
|
||||
|
||||
let items =
|
||||
self.files_filtered.iter().take(height).map(|idx| {
|
||||
let selected = self
|
||||
.selection
|
||||
.map_or(false, |selection| selection == *idx);
|
||||
Span::styled(
|
||||
Cow::from(trim_length_left(
|
||||
self.files[*idx]
|
||||
.path
|
||||
.to_str()
|
||||
.unwrap_or_default(),
|
||||
width,
|
||||
)),
|
||||
self.theme.text(selected, false),
|
||||
)
|
||||
});
|
||||
let height = usize::from(chunks[1].height);
|
||||
let width = usize::from(chunks[1].width);
|
||||
|
||||
let title = format!(
|
||||
"Hits: {}/{}",
|
||||
height.min(self.files_filtered.len()),
|
||||
self.files_filtered.len()
|
||||
);
|
||||
let items =
|
||||
self.files_filtered.iter().take(height).map(
|
||||
|idx| {
|
||||
let selected = self
|
||||
.selected_index
|
||||
.map_or(false, |index| index == *idx);
|
||||
Span::styled(
|
||||
Cow::from(trim_length_left(
|
||||
self.files[*idx]
|
||||
.path
|
||||
.to_str()
|
||||
.unwrap_or_default(),
|
||||
width,
|
||||
)),
|
||||
self.theme.text(selected, false),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
ui::draw_list_block(
|
||||
f,
|
||||
area[1],
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
title,
|
||||
self.theme.title(true),
|
||||
))
|
||||
.borders(Borders::TOP),
|
||||
items,
|
||||
);
|
||||
ui::draw_list_block(
|
||||
f,
|
||||
chunks[1],
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
title,
|
||||
self.theme.title(true),
|
||||
))
|
||||
.borders(Borders::TOP),
|
||||
items,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -228,6 +268,12 @@ impl Component for FileFindPopup {
|
|||
)
|
||||
.order(1),
|
||||
);
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::scroll(&self.key_config),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
visibility_blocking(self)
|
||||
|
|
@ -243,6 +289,10 @@ impl Component for FileFindPopup {
|
|||
|| *key == self.key_config.enter
|
||||
{
|
||||
self.hide();
|
||||
} else if *key == self.key_config.move_down {
|
||||
self.move_selection(ScrollType::Down);
|
||||
} else if *key == self.key_config.move_up {
|
||||
self.move_selection(ScrollType::Up);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,10 +146,10 @@ impl RevisionFilesComponent {
|
|||
.push(InternalEvent::OpenFileFinder(self.files.clone()));
|
||||
}
|
||||
|
||||
pub fn find_file(&mut self, file: Option<PathBuf>) {
|
||||
pub fn find_file(&mut self, file: &Option<PathBuf>) {
|
||||
if let Some(file) = file {
|
||||
self.tree.collapse_but_root();
|
||||
if self.tree.select_file(&file) {
|
||||
if self.tree.select_file(file) {
|
||||
self.selection_changed();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use super::{
|
||||
revision_files::RevisionFilesComponent, visibility_blocking,
|
||||
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
||||
|
|
@ -59,6 +61,10 @@ impl RevisionFilesPopup {
|
|||
pub fn any_work_pending(&self) -> bool {
|
||||
self.files.any_work_pending()
|
||||
}
|
||||
|
||||
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
|
||||
self.files.find_file(file);
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for RevisionFilesPopup {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl FilesTab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_finder_update(&mut self, file: Option<PathBuf>) {
|
||||
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
|
||||
self.files.find_file(file);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue