filter databases

pull/20/head
Takayuki Maeda 3 years ago
parent a9f918b53d
commit a6262ed5c4

23
Cargo.lock generated

@ -47,6 +47,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi 0.3.9",
]
[[package]]
name = "autocfg"
version = "0.1.7"
@ -197,6 +208,17 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi 0.3.9",
]
[[package]]
name = "copypasta"
version = "0.7.1"
@ -515,6 +537,7 @@ version = "0.1.0-alpha.0"
dependencies = [
"anyhow",
"chrono",
"colored",
"copypasta",
"crossterm 0.19.0",
"database-tree",

@ -32,6 +32,7 @@ strum_macros = "0.21"
database-tree = { path = "./database-tree", version = "0.1" }
easy-cast = "0.4"
copypasta = { version = "0.7.0", default-features = false }
colored = "2"
[target.'cfg(any(target_os = "macos", windows))'.dependencies]
copypasta = { version = "0.7.0", default-features = false }

@ -44,6 +44,16 @@ impl DatabaseTree {
Ok(new_self)
}
pub fn filter(&self, filter_text: String) -> Self {
let mut new_self = Self {
items: self.items.filter(filter_text),
selection: Some(0),
visual_selection: None,
};
new_self.visual_selection = new_self.calc_visual_selection();
new_self
}
pub fn collapse_but_root(&mut self) {
self.items.collapse(0, true);
self.items.expand(0, false);

@ -19,6 +19,27 @@ impl DatabaseTreeItems {
})
}
pub fn filter(&self, filter_text: String) -> Self {
Self {
tree_items: self
.tree_items
.iter()
.filter(|item| item.is_database() || item.is_match(&filter_text))
.map(|item| {
if item.is_database() {
let mut item = item.clone();
item.set_collapsed(false);
item.clone()
} else {
let mut item = item.clone();
item.show();
item.clone()
}
})
.collect::<Vec<DatabaseTreeItem>>(),
}
}
fn create_items(
list: &[Database],
collapsed: &BTreeSet<&String>,

@ -98,6 +98,15 @@ impl DatabaseTreeItem {
})
}
pub fn set_collapsed(&mut self, collapsed: bool) {
if let DatabaseTreeItemKind::Database { name, .. } = self.kind() {
self.kind = DatabaseTreeItemKind::Database {
name: name.to_string(),
collapsed,
}
}
}
pub const fn info(&self) -> &TreeItemInfo {
&self.info
}
@ -128,9 +137,24 @@ impl DatabaseTreeItem {
}
}
pub fn show(&mut self) {
self.info.visible = true;
}
pub fn hide(&mut self) {
self.info.visible = false;
}
pub fn is_match(&self, filter_text: &String) -> bool {
match self.kind.clone() {
DatabaseTreeItemKind::Database { name, .. } => name.contains(filter_text),
DatabaseTreeItemKind::Table { table, .. } => table.name.contains(filter_text),
}
}
pub fn is_database(&self) -> bool {
self.kind.is_database()
}
}
impl Eq for DatabaseTreeItem {}

@ -3,16 +3,19 @@ use crate::event::Key;
use crate::ui::common_nav;
use crate::ui::scrolllist::draw_list_block;
use anyhow::Result;
use colored::Colorize;
use database_tree::{DatabaseTree, DatabaseTreeItem};
use std::convert::From;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
symbols::line::HORIZONTAL,
text::Span,
widgets::{Block, Borders},
Frame,
};
use unicode_width::UnicodeWidthStr;
// ▸
const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}";
@ -20,20 +23,35 @@ const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}";
const FOLDER_ICON_EXPANDED: &str = "\u{25be}";
const EMPTY_STR: &str = "";
pub enum FocusBlock {
Filter,
Tree,
}
pub struct DatabasesComponent {
pub tree: DatabaseTree,
pub filterd_tree: Option<DatabaseTree>,
pub scroll: VerticalScroll,
pub input: String,
pub focus_block: FocusBlock,
}
impl DatabasesComponent {
pub fn new() -> Self {
Self {
tree: DatabaseTree::default(),
filterd_tree: None,
scroll: VerticalScroll::new(),
input: String::new(),
focus_block: FocusBlock::Tree,
}
}
fn tree_item_to_span(item: &DatabaseTreeItem, selected: bool, width: u16) -> Span<'_> {
pub fn tree(&self) -> &DatabaseTree {
self.filterd_tree.as_ref().unwrap_or(&self.tree)
}
fn tree_item_to_span(item: DatabaseTreeItem, selected: bool, width: u16) -> Span<'static> {
let name = item.kind().name();
let indent = item.info().indent();
@ -72,21 +90,62 @@ impl DatabasesComponent {
}
fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) {
let tree_height = usize::from(area.height.saturating_sub(2));
self.tree.visual_selection().map_or_else(
let tree_height = usize::from(area.height.saturating_sub(4));
let tree = if let Some(tree) = self.filterd_tree.as_ref() {
tree
} else {
&self.tree
};
tree.visual_selection().map_or_else(
|| {
self.scroll.reset();
},
|selection| {
self.scroll
.update(selection.index, selection.count, tree_height);
self.scroll.update(
selection.index,
selection.count.saturating_sub(2),
tree_height,
);
},
);
let items = self
.tree
let mut items = tree
.iterate(self.scroll.get_top(), tree_height)
.map(|(item, selected)| Self::tree_item_to_span(item, selected, area.width));
.map(|(item, selected)| Self::tree_item_to_span(item.clone(), selected, area.width))
.collect::<Vec<Span>>();
items.insert(
0,
Span::styled(
format!(
"{}",
(0..area.width as usize)
.map(|_| HORIZONTAL)
.collect::<Vec<&str>>()
.join("")
),
Style::default(),
),
);
items.insert(
0,
Span::styled(
format!(
"{}{:w$}",
if self.input.is_empty() && matches!(self.focus_block, FocusBlock::Tree) {
"Press / to filter".to_string()
} else {
self.input.clone()
},
w = area.width as usize
),
if let FocusBlock::Filter = self.focus_block {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
},
),
);
let title = "Databases";
draw_list_block(
@ -101,9 +160,12 @@ impl DatabasesComponent {
})
.borders(Borders::ALL)
.border_style(Style::default()),
items,
items.into_iter(),
);
self.scroll.draw(f, area);
if let FocusBlock::Filter = self.focus_block {
f.set_cursor(area.x + self.input.width() as u16 + 1, area.y + 1)
}
}
}
@ -123,17 +185,42 @@ impl DrawableComponent for DatabasesComponent {
impl Component for DatabasesComponent {
fn event(&mut self, key: Key) -> Result<()> {
if tree_nav(&mut self.tree, key) {
return Ok(());
match key {
Key::Char('/') if matches!(self.focus_block, FocusBlock::Tree) => {
self.focus_block = FocusBlock::Filter
}
Key::Char(c) if matches!(self.focus_block, FocusBlock::Filter) => {
self.input.push(c);
self.filterd_tree = Some(self.tree.filter(self.input.clone()))
}
Key::Delete | Key::Backspace if matches!(self.focus_block, FocusBlock::Filter) => {
self.input.pop();
if self.input.is_empty() {
self.filterd_tree = None
} else {
self.filterd_tree = Some(self.tree.filter(self.input.clone()))
}
}
Key::Esc if matches!(self.focus_block, FocusBlock::Filter) => {
self.focus_block = FocusBlock::Tree
}
key => tree_nav(
if let Some(tree) = self.filterd_tree.as_mut() {
tree
} else {
&mut self.tree
},
key,
),
}
Ok(())
}
}
fn tree_nav(tree: &mut DatabaseTree, key: Key) -> bool {
fn tree_nav(tree: &mut DatabaseTree, key: Key) {
if let Some(common_nav) = common_nav(key) {
tree.move_selection(common_nav)
tree.move_selection(common_nav);
} else {
false
false;
}
}

@ -6,11 +6,17 @@ use database_tree::Database;
pub async fn handler(key: Key, app: &mut App) -> anyhow::Result<()> {
match key {
Key::Esc => app.focus_block = FocusBlock::DabataseList,
Key::Char('c')
if matches!(
app.databases.focus_block,
crate::components::databases::FocusBlock::Tree
) =>
{
app.focus_block = FocusBlock::ConnectionList
}
Key::Right => app.focus_block = FocusBlock::Table,
Key::Char('c') => app.focus_block = FocusBlock::ConnectionList,
Key::Enter => {
if let Some((table, database)) = app.databases.tree.selected_table() {
if let Some((table, database)) = app.databases.tree().selected_table() {
app.focus_block = FocusBlock::Table;
let (headers, records) = get_records(
&Database {

@ -11,15 +11,15 @@ use crate::event::Key;
pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
match key {
Key::Char('d') => match app.focus_block {
FocusBlock::Query => (),
_ => app.focus_block = FocusBlock::DabataseList,
},
Key::Char('r') => match app.focus_block {
FocusBlock::Query => (),
_ => app.focus_block = FocusBlock::Table,
},
Key::Char('e') => app.focus_block = FocusBlock::Query,
// Key::Char('d') => match app.focus_block {
// FocusBlock::Query => (),
// _ => app.focus_block = FocusBlock::DabataseList,
// },
// Key::Char('r') => match app.focus_block {
// FocusBlock::Query => (),
// _ => app.focus_block = FocusBlock::Table,
// },
// Key::Ctrl('e') => app.focus_block = FocusBlock::Query,
Key::Esc if app.error.error.is_some() => {
app.error.error = None;
return Ok(());

Loading…
Cancel
Save