summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Dilly <dilly.stephan@gmail.com>2021-09-04 10:50:03 +0200
committerGitHub <noreply@github.com>2021-09-04 10:50:03 +0200
commitfb2b990072625eaecc23beb76bcd197c6536e80a (patch)
tree7ce2c42dccc7a4def7d983b51fd28a54719e330b
parent3b5d43ecb28d4846e4f5d16b3fa68ab63fd776c9 (diff)
find files via fuzzy finder (#890)
-rw-r--r--Cargo.lock19
-rw-r--r--Cargo.toml1
-rw-r--r--filetreelist/src/filetree.rs19
-rw-r--r--filetreelist/src/filetreeitems.rs232
-rw-r--r--filetreelist/src/item.rs13
-rw-r--r--src/app.rs20
-rw-r--r--src/components/diff.rs5
-rw-r--r--src/components/file_find.rs271
-rw-r--r--src/components/mod.rs21
-rw-r--r--src/components/reset.rs2
-rw-r--r--src/components/revision_files.rs32
-rw-r--r--src/components/syntax_text.rs5
-rw-r--r--src/components/textinput.rs33
-rw-r--r--src/keys.rs2
-rw-r--r--src/main.rs1
-rw-r--r--src/queue.rs12
-rw-r--r--src/string_utils.rs35
-rw-r--r--src/tabs/files.rs6
-rw-r--r--vim_style_key_config.ron1
19 files changed, 694 insertions, 36 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a72ea98e..484284fe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -411,6 +411,15 @@ dependencies = [
]
[[package]]
+name = "fuzzy-matcher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
+dependencies = [
+ "thread_local",
+]
+
+[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -491,6 +500,7 @@ dependencies = [
"dirs-next",
"easy-cast",
"filetreelist",
+ "fuzzy-matcher",
"gh-emoji",
"itertools",
"lazy_static",
@@ -1423,6 +1433,15 @@ dependencies = [
]
[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index dbba85bf..e3c5e0a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,6 +48,7 @@ bugreport = "0.4"
lazy_static = "1.4"
syntect = { version = "4.5", default-features = false, features = ["metadata", "default-fancy"]}
gh-emoji = "1.0.6"
+fuzzy-matcher = "0.3"
[target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies]
which = "4.1"
diff --git a/filetreelist/src/filetree.rs b/filetreelist/src/filetree.rs
index 17db731e..bdde9cf0 100644
--- a/filetreelist/src/filetree.rs
+++ b/filetreelist/src/filetree.rs
@@ -142,6 +142,25 @@ impl FileTree {
})
}
+ pub fn select_file(&mut self, path: &Path) -> bool {
+ let new_selection = self
+ .items
+ .tree_items
+ .iter()
+ .position(|item| item.info().full_path() == path);
+
+ if new_selection == self.selection {
+ return false;
+ }
+
+ self.selection = new_selection;
+ if let Some(selection) = self.selection {
+ self.items.show_element(selection);
+ }
+ self.visual_selection = self.calc_visual_selection();
+ true
+ }
+
fn visual_index_to_absolute(
&self,
visual_index: usize,
diff --git a/filetreelist/src/filetreeitems.rs b/filetreelist/src/filetreeitems.rs
index b545f841..a3857870 100644
--- a/filetreelist/src/filetreeitems.rs
+++ b/filetreelist/src/filetreeitems.rs
@@ -148,7 +148,7 @@ impl FileTreeItems {
if item_path.starts_with(&path) {
item.hide();
} else {
- return;
+ break;
}
}
}
@@ -188,6 +188,90 @@ impl FileTreeItems {
}
}
+ /// makes sure `index` is visible.
+ /// this expands all parents and shows all siblings
+ pub fn show_element(&mut self, index: usize) -> Option<usize> {
+ Some(
+ self.show_element_upward(index)?
+ + self.show_element_downward(index)?,
+ )
+ }
+
+ fn show_element_upward(&mut self, index: usize) -> Option<usize> {
+ let mut shown = 0_usize;
+
+ let item = self.tree_items.get(index)?;
+ let mut current_folder: (PathBuf, u8) = (
+ item.info().full_path().parent()?.to_path_buf(),
+ item.info().indent(),
+ );
+
+ let item_count = self.tree_items.len();
+ for item in self
+ .tree_items
+ .iter_mut()
+ .rev()
+ .skip(item_count - index - 1)
+ {
+ if item.info().indent() == current_folder.1 {
+ item.show();
+ shown += 1;
+ } else if item.info().indent() == current_folder.1 - 1 {
+ // this must be our parent
+
+ item.expand_path();
+
+ if item.info().is_visible() {
+ // early out if parent already visible
+ break;
+ }
+
+ item.show();
+ shown += 1;
+
+ current_folder = (
+ item.info().full_path().parent()?.to_path_buf(),
+ item.info().indent(),
+ );
+ }
+ }
+
+ Some(shown)
+ }
+
+ fn show_element_downward(
+ &mut self,
+ index: usize,
+ ) -> Option<usize> {
+ let mut shown = 0_usize;
+
+ let item = self.tree_items.get(index)?;
+ let mut current_folder: (PathBuf, u8) = (
+ item.info().full_path().parent()?.to_path_buf(),
+ item.info().indent(),
+ );
+
+ for item in self.tree_items.iter_mut().skip(index + 1) {
+ if item.info().indent() == current_folder.1 {
+ item.show();
+ shown += 1;
+ }
+ if item.info().indent() == current_folder.1 - 1 {
+ // this must be our parent
+
+ item.show();
+ shown += 1;
+
+ current_folder = (
+ item.info().full_path().parent()?.to_path_buf(),
+ item.info().indent(),
+ );
+ }
+ }
+
+ Some(shown)
+ }
+
fn update_visibility(
&mut self,
prefix: &Option<PathBuf>,
@@ -687,6 +771,152 @@ mod tests {
]
);
}
+
+ #[test]
+ fn test_show_element() {
+ let items = vec![
+ Path::new("a/b/c"), //
+ Path::new("a/b2/d"), //
+ Path::new("a/b2/e"), //
+ ];
+
+ //0 a/
+ //1 b/
+ //2 c
+ //3 b2/
+ //4 d
+ //5 e
+
+ let mut tree =
+ FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
+
+ tree.collapse(0, true);
+
+ let res = tree.show_element(5).unwrap();
+ assert_eq!(res, 4);
+ assert!(tree.tree_items[3].kind().is_path());
+ assert!(!tree.tree_items[3].kind().is_path_collapsed());
+
+ assert_eq!(
+ get_visibles(&tree),
+ vec![
+ true, //
+ true, //
+ false, //
+ true, //
+ true, //
+ true,
+ ]
+ );
+ }
+
+ #[test]
+ fn test_show_element_later_elements() {
+ let items = vec![
+ Path::new("a/b"), //
+ Path::new("a/c"), //
+ ];
+
+ //0 a/
+ //1 b
+ //2 c
+
+ let mut tree =
+ FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
+
+ tree.collapse(0, true);
+
+ assert_eq!(
+ get_visibles(&tree),
+ vec![
+ true, //
+ false, //
+ false, //
+ ]
+ );
+
+ let res = tree.show_element(1).unwrap();
+ assert_eq!(res, 2);
+
+ assert_eq!(
+ get_visibles(&tree),
+ vec![
+ true, //
+ true, //
+ true, //
+ ]
+ );
+ }
+
+ #[test]
+ fn test_show_element_downward_parent() {
+ let items = vec![
+ Path::new("a/b/c"), //
+ Path::new("a/d"), //
+ Path::new("a/e"), //
+ ];
+
+ //0 a/
+ //1 b/
+ //2 c
+ //3 d
+ //4 e
+
+ let mut tree =
+ FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
+
+ tree.collapse(0, true);
+
+ let res = tree.show_element(2).unwrap();
+ assert_eq!(res, 4);
+
+ assert_eq!(
+ get_visibles(&tree),
+ vec![
+ true, //
+ true, //
+ true, //
+ true, //
+ true, //
+ ]
+ );
+ }
+
+ #[test]
+ fn test_show_element_expand_visible_parent() {
+ let items = vec![
+ Path::new("a/b"), //
+ ];
+
+ //0 a/
+ //1 b
+
+ let mut tree =
+ FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
+
+ tree.collapse(0, true);
+
+ assert_eq!(
+ get_visibles(&tree),
+ vec![
+ true, //
+ false, //
+ ]
+ );
+
+ let res = tree.show_element(1).unwrap();
+ assert_eq!(res, 1);
+ assert!(tree.tree_items[0].kind().is_path());
+ assert!(!tree.tree_items[0].kind().is_path_collapsed());
+
+ assert_eq!(
+ get_visibles(&tree),
+ vec![
+ true, //
+ true, //
+ ]
+ );
+ }
}
#[cfg(test)]
diff --git a/filetreelist/src/item.rs b/filetreelist/src/item.rs
index f343e810..68f91d95 100644
--- a/filetreelist/src/item.rs
+++ b/filetreelist/src/item.rs
@@ -164,13 +164,17 @@ impl FileTreeItem {
&self.kind
}
- ///
+ /// # Panics
+ /// panics if self is not a path
pub fn collapse_path(&mut self) {
+ assert!(self.kind.is_path());
self.kind = FileTreeItemKind::Path(PathCollapsed(true));
}
- ///
+ /// # Panics
+ /// panics if self is not a path
pub fn expand_path(&mut self) {
+ assert!(self.kind.is_path());
self.kind = FileTreeItemKind::Path(PathCollapsed(false));
}
@@ -178,6 +182,11 @@ impl FileTreeItem {
pub fn hide(&mut self) {
self.info.visible = false;
}
+
+ ///
+ pub fn show(&mut self) {
+ self.info.visible = true;
+ }
}
impl Eq for FileTreeItem {}
diff --git a/src/app.rs b/src/app.rs
index a5506e8f..2692314a 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -6,7 +6,7 @@ use crate::{
BranchListComponent, CommandBlocking, CommandInfo,
CommitComponent, CompareCommitsComponent, Component,
ConfirmComponent, CreateBranchComponent, DrawableComponent,
- ExternalEditorComponent, HelpComponent,
+ ExternalEditorComponent, FileFindComponent, HelpComponent,
InspectCommitComponent, MsgComponent, OptionsPopupComponent,
PullComponent, PushComponent, PushTagsComponent,
RenameBranchComponent, RevisionFilesPopup, SharedOptions,
@@ -51,6 +51,7 @@ pub struct App {
compare_commits_popup: CompareCommitsComponent,
external_editor_popup: ExternalEditorComponent,
revision_files_popup: RevisionFilesPopup,
+ find_file_popup: FileFindComponent,
push_popup: PushComponent,
push_tags_popup: PushTagsComponent,
pull_popup: PullComponent,
@@ -189,6 +190,11 @@ impl App {
key_config.clone(),
options.clone(),
),
+ find_file_popup: FileFindComponent::new(
+ &queue,
+ theme.clone(),
+ key_config.clone(),
+ ),
do_quit: false,
cmdbar: RefCell::new(CommandBar::new(
theme.clone(),
@@ -448,6 +454,7 @@ impl App {
rename_branch_popup,
select_branch_popup,
revision_files_popup,
+ find_file_popup,
tags_popup,
options_popup,
help,
@@ -475,6 +482,7 @@ impl App {
create_branch_popup,
rename_branch_popup,
revision_files_popup,
+ find_file_popup,
push_popup,
push_tags_popup,
pull_popup,
@@ -693,6 +701,11 @@ impl App {
flags
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
}
+ InternalEvent::OpenFileFinder(files) => {
+ self.find_file_popup.open(&files)?;
+ flags
+ .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
+ }
InternalEvent::OptionSwitched(o) => {
match o {
AppOption::StatusShowUntracked => {
@@ -712,6 +725,11 @@ impl App {
flags
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
}
+ InternalEvent::FileFinderChanged(file) => {
+ self.files_tab.file_finder_update(file);
+ flags
+ .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
+ }
};
Ok(flags)
diff --git a/src/components/diff.rs b/src/components/diff.rs
index c4496400..598c96ce 100644
--- a/src/components/diff.rs
+++ b/src/components/diff.rs
@@ -3,11 +3,10 @@ use super::{
Direction, DrawableComponent, ScrollType,
};
use crate::{
- components::{
- tabs_to_spaces, CommandInfo, Component, EventState,
- },
+ components::{CommandInfo, Component, EventState},
keys::SharedKeyConfig,
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
+ string_utils::tabs_to_spaces,
strings, try_or_popup,
ui::style::SharedTheme,
};
diff --git a/src/components/file_find.rs b/src/components/file_find.rs
new file mode 100644
index 00000000..e302783b
--- /dev/null
+++ b/src/components/file_find.rs
@@ -0,0 +1,271 @@
+use super::{
+ visibility_blocking, CommandBlocking, CommandInfo, Component,
+ DrawableComponent, EventState, TextInputComponent,
+};
+use crate::{
+ keys::SharedKeyConfig,
+ queue::{InternalEvent, Queue},
+ string_utils::trim_length_left,
+ strings,
+ ui::{self, style::SharedTheme},
+};
+use anyhow::Result;
+use asyncgit::sync::TreeFile;
+use crossterm::event::Event;
+use fuzzy_matcher::FuzzyMatcher;
+use std::borrow::Cow;
+use tui::{
+ backend::Backend,
+ layout::{Constraint, Direction, Layout, Margin, Rect},
+ text::Span,
+ widgets::{Block, Borders, Clear},
+ Frame,
+};
+
+pub struct FileFindComponent {
+ queue: Queue,
+ visible: bool,
+ find_text: TextInputComponent,
+ query: Option<String>,
+ theme: SharedTheme,
+ files: Vec<TreeFile>,
+ selection: Option<usize>,
+ files_filtered: Vec<usize>,
+ key_config: SharedKeyConfig,
+}
+
+impl FileFindComponent {
+ ///
+ pub fn new(
+ queue: &Queue,
+ theme: SharedTheme,
+ key_config: SharedKeyConfig,
+ ) -> Self {
+ let mut find_text = TextInputComponent::new(
+ theme.clone(),
+ key_config.clone(),
+ "",
+ "start typing..",
+ false,
+ );
+ find_text.embed();
+
+ Self {
+ queue: queue.clone(),
+ visible: false,
+ query: None,
+ find_text,
+ theme,
+ files: Vec::new(),
+ files_filtered: Vec::new(),
+ key_config,
+ selection: None,
+ }
+ }
+
+ fn update_query(&mut self) {
+ if self.find_text.get_text().is_empty() {
+ self.set_query(None);
+ } else if self
+ .query
+ .as_ref()
+ .map_or(true, |q| q != self.find_text.get_text())
+ {
+ self.set_query(Some(
+ self.find_text.get_text().to_string(),
+ ));
+ }
+ }
+
+ fn set_query(&mut self, query: Option<String>) {
+ self.query = query;
+
+ self.files_filtered.clear();
+
+ if let Some(q) = &self.query {
+ let matcher =
+ fuzzy_matcher::skim::SkimMatcherV2::default();
+
+ self.files_filtered.extend(
+ self.files.iter().enumerate().filter_map(|a| {
+ a.1.path.to_str().and_then(|path| {
+ //TODO: use fuzzy_indices and highlight hits
+ matcher.fuzzy_match(path, q).map(|_| a.0)
+ })
+ }),
+ );
+
+ self.refresh_selection();
+ } else {
+ self.files_filtered
+ .extend(self.files.iter().enumerate().map(|a| a.0));
+ }
+ }
+
+ fn refresh_selection(&mut self) {
+ let selection = self.files_filtered.first().copied();
+
+ if self.selection != selection {
+ self.selection = selection;
+
+ let file = self
+ .selection
+ .and_then(|index| self.files.get(index))
+ .map(|f| f.path.clone());
+
+ self.queue.push(InternalEvent::FileFinderChanged(file));
+ }
+ }
+
+ pub fn open(&mut self, files: &[TreeFile]) -> Result<()> {
+ self.show()?;
+ self.find_text.show()?;
+ self.find_text.set_text(String::new());
+ self.query = None;
+ if self.files != *files {
+ self.files = files.to_owned();
+ }
+ self.update_query();
+
+ Ok(())
+ }
+}
+
+impl DrawableComponent for FileFindComponent {
+ fn draw<B: Backend>(
+ &self,
+ f: &mut Frame<B>,
+ area: Rect,
+ ) -> Result<()> {
+ if self.is_visible() {
+ const SIZE: (u16, u16) = (50, 25);
+ let area =
+ ui::centered_rect_absolute(SIZE.0, SIZE.1, area);
+
+ f.render_widget(Clear, area);
+ f.render_widget(
+ Block::default()
+ .borders(Borders::all())
+ .style(self.theme.title(true))
+ .title(Span::styled(
+ //TODO: strings
+ "Fuzzy find",
+ self.theme.title(true),
+ )),
+ area,
+ );
+
+ let area = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(
+ [
+ Constraint::Length(1),
+ Constraint::Percentage(100),
+ ]
+ .as_ref(),
+ )
+ .split(area.inner(&Margin {
+ horizontal: 1,
+ vertical: 1,
+ }));
+
+ self.find_text.draw(f, area[0])?;
+
+ let height = usize::from(area[1].height);
+ let width = usize::from(area[1].width);
+
+ 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 title = format!(
+ "Hits: {}/{}",
+ height.min(self.files_filtered.len()),
+ self.files_filtered.len()
+ );
+
+ ui::draw_list_block(
+ f,
+ area[1],
+ Block::default()
+ .title(Span::styled(
+ title,
+ self.theme.title(true),
+ ))
+ .borders(Borders::TOP),
+ items,
+ );
+ }
+ Ok(())
+ }
+}
+
+impl Component for FileFindComponent {
+ fn commands(
+ &self,
+ out: &mut Vec<CommandInfo>,
+ force_all: bool,
+ ) -> CommandBlocking {
+ if self.is_visible() || force_all {
+ out.push(
+ CommandInfo::new(
+ strings::commands::close_popup(&self.key_config),
+ true,
+ true,
+ )
+ .order(1),
+ );
+ }
+
+ visibility_blocking(self)
+ }
+
+ fn event(
+ &mut self,
+ event: crossterm::event::Event,
+ ) -> Result<EventState> {
+ if self.is_visible() {
+ if let Event::Key(key) = &event {
+ if *key == self.key_config.exit_popup
+ || *key == self.key_config.enter
+ {
+ self.hide();
+ }
+ }
+
+ if self.find_text.event(event)?.is_consumed() {
+ self.update_query();
+ }
+
+ return Ok(EventState::Consumed);
+ }
+
+ Ok(EventState::NotConsumed)
+ }
+
+ fn is_visible(&self) -> bool {
+ self.visible
+ }
+
+ fn hide(&mut self) {
+ self.visible = false;
+ }
+
+ fn show(&mut self) -> Result<()> {
+ self.visible = true;
+ Ok(())
+ }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 523c3f89..064f7edf 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -10,6 +10,7 @@ mod create_branch;
mod cred;
mod diff;
mod externaleditor;
+mod file_find;
mod filetree;
mod help;
mod inspect_commit;
@@ -41,6 +42,7 @@ pub use compare_commits::CompareCommitsComponent;
pub use create_branch::CreateBranchComponent;
pub use diff::DiffComponent;
pub use externaleditor::ExternalEditorComponent;
+pub use file_find::FileFindComponent;
pub use help::HelpComponent;
pub use inspect_commit::InspectCommitComponent;
pub use msg::MsgComponent;
@@ -297,27 +299,24 @@ fn popup_paragraph<'a, T>(
content: T,
theme: &Theme,
focused: bool,
+ block: bool,
) -> Paragraph<'a>
where
T: Into<Text<'a>>,
{
- Paragraph::new(content.into())
- .block(
+ let paragraph = Paragraph::new(content.into())
+ .alignment(Alignment::Left)
+ .wrap(Wrap { trim: true });
+
+ if block {
+ paragraph.block(
Block::default()
.title(Span::styled(title, theme.title(focused)))
.borders(Borders::ALL)
.border_type(BorderType::Thick)
.border_style(theme.block(focused)),
)
- .alignment(Alignment::Left)
- .wrap(Wrap { trim: true })
-}
-
-//TODO: allow customize tabsize
-pub fn tabs_to_spaces(input: String) -> String {
- if input.contains('\t') {
- input.replace("\t", " ")
} else {
- input
+ paragraph
}
}
diff --git a/src/components/reset.rs b/src/components/reset.rs
index 02bef26e..0ff0a704 100644
--- a/src/components/reset.rs
+++ b/src/components/reset.rs
@@ -41,7 +41,7 @@ impl DrawableComponent for ConfirmComponent {
let area = ui::centered_rect(50, 20, f.size());
f.render_widget(Clear, area);
f.render_widget(
- popup_paragraph(&title, txt, &self.theme, true),
+ popup_paragraph(&title, txt, &self.theme, true, true),
area,
);
}
diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs
index 4a36bbd0..0abcf457 100644
--- a/src/components/revision_files.rs
+++ b/src/components/revision_files.rs
@@ -18,7 +18,11 @@ use asyncgit::{
use crossbeam_channel::Sender;
use crossterm::event::Event;
use filetreelist::{FileTree, FileTreeItem};
-use std::{collections::BTreeSet, convert::From, path::Path};
+use std::{
+ collections::BTreeSet,
+ convert::From,
+ path::{Path, PathBuf},
+};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
@@ -137,6 +141,20 @@ impl RevisionFilesComponent {
})
}
+ fn open_finder(&self) {
+ self.queue
+ .push(InternalEvent::OpenFileFinder(self.files.clone()));
+ }
+
+ 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) {
+ self.selection_changed();
+ }
+ }
+ }
+
fn selection_changed(&mut self) {
//TODO: retrieve TreeFile from tree datastructure
if let Some(file) = self
@@ -144,6 +162,7 @@ impl RevisionFilesComponent {
.selected_file()
.map(|file| file.full_path_str().to_string())
{
+ log::info!("selected: {:?}", file);
let path = Path::new(&file);
if let Some(item) =
self.files.iter().find(|f| f.path == path)
@@ -188,7 +207,7 @@ impl RevisionFilesComponent {
"Files at [{}]",
self.revision
.map(|c| c.get_short_string())
- .unwrap_or_default()
+ .unwrap_or_default(),
);
ui::draw_list_block(
f,
@@ -241,7 +260,9 @@ impl Component for RevisionFilesComponent {
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
- if matches!(self.focus, Focus::Tree) || force_all {
+ let is_tree_focused = matches!(self.focus, Focus::Tree);
+
+ if is_tree_focused || force_all {
out.push(
CommandInfo::new(
strings::commands::blame_file(&self.key_config),
@@ -288,6 +309,11 @@ impl Component for RevisionFilesComponent {
self.focus(false);
return Ok(EventState::Consumed);
}
+ } else if key == self.key_config.file_find {
+ if is_tree_focused {
+ self.open_finder();
+ return Ok(EventState::Consumed);
+ }
} else if !is_tree_focused {
return self.current_file.event(event);
}
diff --git a/src/components/syntax_text.rs b/src/components/syntax_text.rs
index cfef3199..9df85a49 100644
--- a/src/components/syntax_text.rs
+++ b/src/components/syntax_text.rs
@@ -1,9 +1,10 @@
use super::{
- tabs_to_spaces, CommandBlocking, CommandInfo, Component,
- DrawableComponent, EventState,
+ CommandBlocking, CommandInfo, Component, DrawableComponent,
+ EventState,
};
use crate::{
keys::SharedKeyConfig,
+ string_utils::tabs_to_spaces,
strings,
ui::{
self, common_nav, style::SharedTheme, AsyncSyntaxJob,
diff --git a/src/components/textinput.rs b/src/components/textinput.rs
index a53dd258..6086753d 100644
--- a/src/components/textinput.rs
+++ b/src/components/textinput.rs
@@ -41,6 +41,7 @@ pub struct TextInputComponent {
cursor_position: usize,
input_type: InputType,
current_area: Cell<Rect>,
+ embed: bool,
}
impl TextInputComponent {
@@ -63,6 +64,7 @@ impl TextInputComponent {
cursor_position: 0,
input_type: InputType::Multiline,
current_area: Cell::new(Rect::default()),
+ embed: false,
}
}
@@ -90,6 +92,11 @@ impl TextInputComponent {
self.current_area.get()
}
+ /// embed into parent draw area
+ pub fn embed(&mut self) {
+ self.embed = true;
+ }
+
/// Move the cursor right one char.
fn incr_cursor(&mut self) {
if let Some(pos) = self.next_char_position() {
@@ -267,7 +274,7 @@ impl DrawableComponent for TextInputComponent {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
- _rect: Rect,
+ rect: Rect,
) -> Result<()> {
if self.visible {
let txt = if self.msg.is_empty() {
@@ -279,16 +286,21 @@ impl DrawableComponent for TextInputComponent {
self.get_draw_text()
};
- let area = match self.input_type {
- InputType::Multiline => {
- let area = ui::centered_rect(60, 20, f.size());
- ui::rect_inside(
- Size::new(10, 3),
- f.size().into(),
- area,
- )
+ let area = if self.embed {
+ rect
+ } else {
+ match self.input_type {
+ InputType::Multiline => {
+ let area =
+ ui::centered_rect(60, 20, f.size());
+ ui::rect_inside(
+ Size::new(10, 3),
+ f.size().into(),
+ area,
+ )
+ }
+ _ => ui::centered_rect_absolute(32, 3, f.size()),
}
- _ => ui::centered_rect_absolute(32, 3, f.size()),
};
f.render_widget(Clear, area);
@@ -298,6 +310,7 @@ impl DrawableComponent for TextInputComponent {
txt,
&self.theme,
true,
+ !self.embed,
),
area,
);
diff --git a/src/keys.rs b/src/keys.rs
index 201610cf..dd6c2cff 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -83,6 +83,7 @@ pub struct KeyConfig {
pub select_tag: KeyEvent,
pub push: KeyEvent,
pub open_file_tree: KeyEvent,
+ pub file_find: KeyEvent,