remove unused functions

pull/10/head
Takayuki Maeda 3 years ago
parent 8ca65cafa4
commit 23d77ca322

62
Cargo.lock generated

@ -26,6 +26,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "anyhow"
version = "1.0.41"
@ -279,6 +288,30 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ctor"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "database-tree"
version = "0.1.2"
dependencies = [
"pretty_assertions",
"thiserror",
]
[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]]
name = "digest"
version = "0.9.0"
@ -294,6 +327,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "easy-cast"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd102ee8c418348759919b83b81cdbdc933ffe29740b903df448b4bafaa348e"
[[package]]
name = "either"
version = "1.6.1"
@ -471,6 +510,8 @@ dependencies = [
"anyhow",
"chrono",
"crossterm 0.19.0",
"database-tree",
"easy-cast",
"futures",
"regex",
"serde",
@ -852,6 +893,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "output_vt100"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
@ -927,6 +977,18 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pretty_assertions"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
dependencies = [
"ansi_term",
"ctor",
"diff",
"output_vt100",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"

@ -24,3 +24,10 @@ toml = "0.4"
regex = "1"
strum = "0.21"
strum_macros = "0.21"
database-tree = { path = "./database-tree", version = "0.1" }
easy-cast = "0.4"
[workspace]
members=[
"database-tree"
]

@ -0,0 +1,16 @@
[package]
name = "database-tree"
version = "0.1.2"
authors = ["Stephan Dilly <dilly.stephan@gmail.com>"]
edition = "2018"
license = "MIT"
homepage = "https://github.com/TaKO8Ki/gobang"
repository = "https://github.com/TaKO8Ki/gobang"
readme = "README.md"
description = "A cross-platform terminal database tool written in Rust"
[dependencies]
thiserror = "1.0"
[dev-dependencies]
pretty_assertions = "0.7"

@ -0,0 +1,13 @@
use std::{num::TryFromIntError, path::PathBuf};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("InvalidPath: `{0}`")]
InvalidPath(PathBuf),
#[error("TryFromInt error:{0}")]
IntConversion(#[from] TryFromIntError),
}
pub type Result<T> = std::result::Result<T, Error>;

@ -0,0 +1,270 @@
use crate::{
error::Result, filetreeitems::DatabaseTreeItems, tree_iter::TreeIterator, TreeItemInfo,
};
use std::{collections::BTreeSet, usize};
///
#[derive(Copy, Clone, Debug)]
pub enum MoveSelection {
Up,
Down,
Left,
Right,
Top,
End,
PageDown,
PageUp,
}
#[derive(Debug, Clone, Copy)]
pub struct VisualSelection {
pub count: usize,
pub index: usize,
}
/// wraps `FileTreeItems` as a datastore and adds selection functionality
#[derive(Default)]
pub struct DatabaseTree {
items: DatabaseTreeItems,
pub selection: Option<usize>,
// caches the absolute selection translated to visual index
visual_selection: Option<VisualSelection>,
}
impl DatabaseTree {
///
pub fn new(list: &[&crate::Database], collapsed: &BTreeSet<&String>) -> Result<Self> {
let mut new_self = Self {
items: DatabaseTreeItems::new(list, collapsed)?,
selection: if list.is_empty() { None } else { Some(0) },
visual_selection: None,
};
new_self.visual_selection = new_self.calc_visual_selection();
Ok(new_self)
}
///
pub fn collapse_but_root(&mut self) {
self.items.collapse(0, true);
self.items.expand(0, false);
}
/// iterates visible elements starting from `start_index_visual`
pub fn iterate(&self, start_index_visual: usize, max_amount: usize) -> TreeIterator<'_> {
let start = self
.visual_index_to_absolute(start_index_visual)
.unwrap_or_default();
TreeIterator::new(self.items.iterate(start, max_amount), self.selection)
}
///
pub const fn visual_selection(&self) -> Option<&VisualSelection> {
self.visual_selection.as_ref()
}
///
pub fn selected_file(&self) -> Option<&TreeItemInfo> {
self.selection.and_then(|index| {
let item = &self.items.tree_items[index];
if item.kind().is_database() {
None
} else {
Some(item.info())
}
})
}
///
pub fn collapse_recursive(&mut self) {
if let Some(selection) = self.selection {
self.items.collapse(selection, true);
}
}
///
pub fn expand_recursive(&mut self) {
if let Some(selection) = self.selection {
self.items.expand(selection, true);
}
}
///
pub fn move_selection(&mut self, dir: MoveSelection) -> bool {
self.selection.map_or(false, |selection| {
let new_index = match dir {
MoveSelection::Up => self.selection_updown(selection, true),
MoveSelection::Down => self.selection_updown(selection, false),
MoveSelection::Left => self.selection_left(selection),
MoveSelection::Right => self.selection_right(selection),
MoveSelection::Top => Self::selection_start(selection),
MoveSelection::End => self.selection_end(selection),
MoveSelection::PageDown | MoveSelection::PageUp => None,
};
let changed_index = new_index.map(|i| i != selection).unwrap_or_default();
if changed_index {
self.selection = new_index;
self.visual_selection = self.calc_visual_selection();
}
changed_index || new_index.is_some()
})
}
fn visual_index_to_absolute(&self, visual_index: usize) -> Option<usize> {
self.items
.iterate(0, self.items.len())
.enumerate()
.find_map(
|(i, (abs, _))| {
if i == visual_index {
Some(abs)
} else {
None
}
},
)
}
fn calc_visual_selection(&self) -> Option<VisualSelection> {
self.selection.map(|selection_absolute| {
let mut count = 0;
let mut visual_index = 0;
for (index, _item) in self.items.iterate(0, self.items.len()) {
if selection_absolute == index {
visual_index = count;
}
count += 1;
}
VisualSelection {
index: visual_index,
count,
}
})
}
const fn selection_start(current_index: usize) -> Option<usize> {
if current_index == 0 {
None
} else {
Some(0)
}
}
fn selection_end(&self, current_index: usize) -> Option<usize> {
let items_max = self.items.len().saturating_sub(1);
let mut new_index = items_max;
loop {
if self.is_visible_index(new_index) {
break;
}
if new_index == 0 {
break;
}
new_index = new_index.saturating_sub(1);
new_index = std::cmp::min(new_index, items_max);
}
if new_index == current_index {
None
} else {
Some(new_index)
}
}
fn selection_updown(&self, current_index: usize, up: bool) -> Option<usize> {
let mut index = current_index;
loop {
index = {
let new_index = if up {
index.saturating_sub(1)
} else {
index.saturating_add(1)
};
// when reaching usize bounds
if new_index == index {
break;
}
if new_index >= self.items.len() {
break;
}
new_index
};
if self.is_visible_index(index) {
break;
}
}
if index == current_index {
None
} else {
Some(index)
}
}
fn select_parent(&mut self, current_index: usize) -> Option<usize> {
let indent = self.items.tree_items[current_index].info().indent();
let mut index = current_index;
while let Some(selection) = self.selection_updown(index, true) {
index = selection;
if self.items.tree_items[index].info().indent() < indent {
break;
}
}
if index == current_index {
None
} else {
Some(index)
}
}
fn selection_left(&mut self, current_index: usize) -> Option<usize> {
let item = &mut self.items.tree_items[current_index];
if item.kind().is_database() && !item.kind().is_database_collapsed() {
self.items.collapse(current_index, false);
return Some(current_index);
}
self.select_parent(current_index)
}
fn selection_right(&mut self, current_selection: usize) -> Option<usize> {
let item = &mut self.items.tree_items[current_selection];
if item.kind().is_database() {
if item.kind().is_database_collapsed() {
self.items.expand(current_selection, false);
return Some(current_selection);
}
return self.selection_updown(current_selection, false);
}
None
}
fn is_visible_index(&self, index: usize) -> bool {
self.items
.tree_items
.get(index)
.map(|item| item.info().is_visible())
.unwrap_or_default()
}
}

@ -0,0 +1,186 @@
use crate::Database;
use crate::{error::Result, treeitems_iter::TreeItemsIterator};
use crate::{
item::{DatabaseCollapsed, DatabaseTreeItemKind},
DatabaseTreeItem,
};
use std::{
collections::{BTreeSet, HashMap},
usize,
};
///
#[derive(Default)]
pub struct DatabaseTreeItems {
pub tree_items: Vec<DatabaseTreeItem>,
files: usize,
}
impl DatabaseTreeItems {
///
pub fn new(list: &[&Database], collapsed: &BTreeSet<&String>) -> Result<Self> {
Ok(Self {
tree_items: Self::create_items(list, collapsed)?,
files: list.len(),
})
}
fn create_items<'a>(
list: &'a [&Database],
collapsed: &BTreeSet<&String>,
) -> Result<Vec<DatabaseTreeItem>> {
// scopetime::scope_time!("create_items");
let mut items = Vec::with_capacity(list.len());
let mut paths_added: HashMap<String, usize> = HashMap::with_capacity(list.len());
for e in list {
{
Self::push_databases(e, &mut items, &mut paths_added, collapsed)?;
}
for table in e.tables.clone() {
items.push(DatabaseTreeItem::new_table(e.name.clone(), table)?);
}
}
Ok(items)
}
/// how many individual items (files/paths) are in the list
pub fn len(&self) -> usize {
self.tree_items.len()
}
/// how many files were added to this list
pub const fn file_count(&self) -> usize {
self.files
}
/// iterates visible elements
pub const fn iterate(&self, start: usize, max_amount: usize) -> TreeItemsIterator<'_> {
TreeItemsIterator::new(self, start, max_amount)
}
fn push_databases<'a>(
item_path: &'a Database,
nodes: &mut Vec<DatabaseTreeItem>,
// helps to only add new nodes for paths that were not added before
// we also count the number of children a node has for later folding
paths_added: &mut HashMap<String, usize>,
collapsed: &BTreeSet<&String>,
) -> Result<()> {
let c = item_path.name.clone();
if !paths_added.contains_key(&c) {
// add node and set count to have no children
paths_added.insert(c.clone(), 0);
// increase the number of children in the parent node count
*paths_added.entry(item_path.name.clone()).or_insert(0) += 1;
//TODO: make non alloc
let path_string = c.clone();
let is_collapsed = collapsed.contains(&path_string);
nodes.push(DatabaseTreeItem::new_database(c, is_collapsed)?);
}
// increase child count in parent node (the above ancenstor ignores the leaf component)
*paths_added.entry(item_path.name.clone()).or_insert(0) += 1;
Ok(())
}
pub fn collapse(&mut self, index: usize, recursive: bool) {
if self.tree_items[index].kind().is_database() {
self.tree_items[index].collapse_database();
let path = self.tree_items[index].info().full_path_str().to_string();
for i in index + 1..self.tree_items.len() {
let item = &mut self.tree_items[i];
if recursive && item.kind().is_database() {
item.collapse_database();
}
if let Some(db) = item.info().database.as_ref() {
if db.to_string() == path {
item.hide();
}
} else {
return;
}
}
}
}
pub fn expand(&mut self, index: usize, recursive: bool) {
if self.tree_items[index].kind().is_database() {
self.tree_items[index].expand_database();
let full_path = self.tree_items[index].info().full_path_str().to_string();
if recursive {
for i in index + 1..self.tree_items.len() {
let item = &mut self.tree_items[i];
if let Some(db) = item.info().database.as_ref() {
if db.to_string() != full_path {
break;
}
}
if item.kind().is_database() && item.kind().is_database_collapsed() {
item.expand_database();
}
}
}
self.update_visibility(&Some(full_path), index + 1, false);
}
}
fn update_visibility(&mut self, prefix: &Option<String>, start_idx: usize, set_defaults: bool) {
// if we are in any subpath that is collapsed we keep skipping over it
let mut inner_collapsed: Option<String> = None;
for i in start_idx..self.tree_items.len() {
if let Some(ref collapsed_path) = inner_collapsed {
if let Some(db) = self.tree_items[i].info().database.clone() {
if db == collapsed_path.to_string() {
if set_defaults {
self.tree_items[i].info_mut().set_visible(false);
}
// we are still in a collapsed inner path
continue;
}
}
inner_collapsed = None;
}
let item_kind = self.tree_items[i].kind().clone();
let item_info = self.tree_items[i].info();
if matches!(item_kind, DatabaseTreeItemKind::Database(DatabaseCollapsed(collapsed)) if collapsed)
{
// we encountered an inner path that is still collapsed
inner_collapsed = item_info.database.clone();
}
if let Some(db) = item_info.database.as_ref() {
if prefix
.as_ref()
.map_or(true, |prefix| prefix.to_string() == db.to_string())
{
self.tree_items[i].info_mut().set_visible(true);
}
} else {
// if we do not set defaults we can early out
if set_defaults {
self.tree_items[i].info_mut().set_visible(false);
} else {
return;
}
}
}
}
}

@ -0,0 +1,195 @@
use crate::error::Result;
use std::{convert::TryFrom, path::PathBuf};
/// holds the information shared among all `DatabaseTreeItem` in a `FileTree`
#[derive(Debug, Clone)]
pub struct TreeItemInfo {
/// indent level
indent: u8,
/// currently visible depending on the folder collapse states
visible: bool,
/// contains this paths last component and folded up paths added to it
/// if this is `None` nothing was folding into here
folded: Option<PathBuf>,
/// the full path
pub full_path: String,
pub database: Option<String>,
}
impl TreeItemInfo {
///
pub const fn new(indent: u8, database: Option<String>, full_path: String) -> Self {
Self {
indent,
visible: true,
folded: None,
full_path,
database,
}
}
///
pub const fn is_visible(&self) -> bool {
self.visible
}
///
//TODO: remove
pub fn full_path_str(&self) -> &str {
self.full_path.as_str()
}
// ///
// pub fn full_path(&self) -> &Path {
// self.full_path.as_path()
// }
/// like `path` but as `&str`
pub fn path_str(&self) -> &str {
match self.full_path.split('/').collect::<Vec<_>>().get(1) {
Some(path) => path,
None => self.full_path.as_str(),
}
}
/// returns the last component of `full_path`
/// or the last components plus folded up children paths
// pub fn path(&self) -> &Path {
// self.folded.as_ref().map_or_else(
// || {
// Path::new(
// self.full_path
// .components()
// .last()
// .and_then(|c| c.as_os_str().to_str())
// .unwrap_or_default(),
// )
// },
// |folding| folding.as_path(),
// )
// }
///
pub const fn indent(&self) -> u8 {
self.indent
}
///
pub fn unindent(&mut self) {
self.indent = self.indent.saturating_sub(1);
}
pub fn set_visible(&mut self, visible: bool) {
self.visible = visible;
}
}
/// attribute used to indicate the collapse/expand state of a path item
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct DatabaseCollapsed(pub bool);
/// `DatabaseTreeItem` can be of two kinds
#[derive(PartialEq, Debug, Clone)]
pub enum DatabaseTreeItemKind {
Database(DatabaseCollapsed),
File,
}
impl DatabaseTreeItemKind {
pub const fn is_database(&self) -> bool {
matches!(self, Self::Database(_))
}
pub const fn is_database_collapsed(&self) -> bool {
match self {
Self::Database(collapsed) => collapsed.0,
Self::File => false,
}
}
}
/// `DatabaseTreeItem` can be of two kinds: see `DatabaseTreeItem` but shares an info
#[derive(Debug, Clone)]
pub struct DatabaseTreeItem {
info: TreeItemInfo,
kind: DatabaseTreeItemKind,
}
impl DatabaseTreeItem {
pub fn new_table(database: String, path: String) -> Result<Self> {
let indent = u8::try_from((3 as usize).saturating_sub(2))?;
Ok(Self {
info: TreeItemInfo::new(indent, Some(database), path),
kind: DatabaseTreeItemKind::File,
})
}
pub fn new_database(path: String, collapsed: bool) -> Result<Self> {
Ok(Self {
info: TreeItemInfo::new(0, None, path),
kind: DatabaseTreeItemKind::Database(DatabaseCollapsed(collapsed)),
})
}
///
pub fn fold(&mut self, next: Self) {
if let Some(folded) = self.info.folded.as_mut() {
*folded = folded.join(&next.info.full_path);
} else {
// self.info.folded = Some(self.info.full_path.join(next.info.full_path));
}
self.info.full_path = next.info.full_path.clone();
}
///
pub const fn info(&self) -> &TreeItemInfo {
&self.info
}
///
pub fn info_mut(&mut self) -> &mut TreeItemInfo {
&mut self.info
}
///
pub const fn kind(&self) -> &DatabaseTreeItemKind {
&self.kind
}
///
pub fn collapse_database(&mut self) {
self.kind = DatabaseTreeItemKind::Database(DatabaseCollapsed(true));
}
///
pub fn expand_database(&mut self) {
self.kind = DatabaseTreeItemKind::Database(DatabaseCollapsed(false));
}
///
pub fn hide(&mut self) {
self.info.visible = false;
}
}
impl Eq for DatabaseTreeItem {}
impl PartialEq for DatabaseTreeItem {
fn eq(&self, other: &Self) -> bool {
self.info.full_path.eq(&other.info.full_path)
}
}
impl PartialOrd for DatabaseTreeItem {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.info.full_path.partial_cmp(&other.info.full_path)
}
}
impl Ord for DatabaseTreeItem {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.info.full_path.cmp(&other.info.full_path)
}
}

@ -0,0 +1,38 @@
// #![forbid(missing_docs)]
#![forbid(unsafe_code)]
#![deny(
unused_imports,
unused_must_use,
dead_code,
unstable_name_collisions,
unused_assignments
)]
#![deny(clippy::all, clippy::perf, clippy::nursery, clippy::pedantic)]
#![deny(clippy::expect_used)]
#![deny(clippy::filetype_is_file)]
#![deny(clippy::cargo)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
#![deny(clippy::match_like_matches_macro)]
#![deny(clippy::needless_update)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::missing_errors_doc)]
mod error;
mod filetree;
mod filetreeitems;
mod item;
mod tree_iter;
mod treeitems_iter;
pub use crate::{
filetree::DatabaseTree,
filetree::MoveSelection,
item::{DatabaseTreeItem, TreeItemInfo},
};
pub struct Database {
pub name: String,
pub tables: Vec<String>,
}

@ -0,0 +1,25 @@
use crate::{item::DatabaseTreeItem, treeitems_iter::TreeItemsIterator};
pub struct TreeIterator<'a> {
item_iter: TreeItemsIterator<'a>,
selection: Option<usize>,
}
impl<'a> TreeIterator<'a> {
pub const fn new(item_iter: TreeItemsIterator<'a>, selection: Option<usize>) -> Self {
Self {
item_iter,
selection,
}
}
}
impl<'a> Iterator for TreeIterator<'a> {
type Item = (&'a DatabaseTreeItem, bool);
fn next(&mut self) -> Option<Self::Item> {
self.item_iter
.next()
.map(|(index, item)| (item, self.selection.map(|i| i == index).unwrap_or_default()))
}
}

@ -0,0 +1,56 @@
use crate::{filetreeitems::DatabaseTreeItems, item::DatabaseTreeItem};
pub struct TreeItemsIterator<'a> {
tree: &'a DatabaseTreeItems,
index: usize,
increments: Option<usize>,
max_amount: usize,
}
impl<'a> TreeItemsIterator<'a> {
pub const fn new(tree: &'a DatabaseTreeItems, start: usize, max_amount: usize) -> Self {
TreeItemsIterator {
max_amount,
increments: None,
index: start,
tree,
}
}
}
impl<'a> Iterator for TreeItemsIterator<'a> {
type Item = (usize, &'a DatabaseTreeItem);
fn next(&mut self) -> Option<Self::Item> {
if self.increments.unwrap_or_default() < self.max_amount {
let items = &self.tree.tree_items;
let mut init = self.increments.is_none();
if let Some(i) = self.increments.as_mut() {
*i += 1;
} else {
self.increments = Some(0);
};
loop {
if !init {
self.index += 1;
}
init = false;
if self.index >= self.tree.len() {
break;
}
let elem = &items[self.index];
if elem.info().is_visible() {
return Some((self.index, &items[self.index]));
}
}
}
None
}
}

@ -22,7 +22,9 @@ impl std::fmt::Display for Tab {
impl Tab {
pub fn names() -> Vec<String> {
Self::iter().map(|tab| tab.to_string()).collect()
Self::iter()
.map(|tab| format!("{} [{}]", tab, tab as u8 + 1))
.collect()
}
}
@ -164,6 +166,7 @@ pub struct App {
pub selected_connection: ListState,
pub selected_database: ListState,
pub selected_table: ListState,
pub revision_files: crate::components::RevisionFilesComponent,
pub pool: Option<MySqlPool>,
pub error: Option<String>,
}
@ -183,6 +186,7 @@ impl Default for App {
selected_connection: ListState::default(),
selected_database: ListState::default(),
selected_table: ListState::default(),
revision_files: crate::components::RevisionFilesComponent::new(),
pool: None,
error: None,
}

@ -0,0 +1,27 @@
///
#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
pub struct CommandText {
///
pub name: String,
///
pub desc: &'static str,
///
pub group: &'static str,
///
pub hide_help: bool,
}
///
pub struct CommandInfo {
///
pub text: CommandText,
/// available but not active in the context
pub enabled: bool,
/// will show up in the quick bar
pub quick_bar: bool,
/// available in current app state
pub available: bool,
/// used to order commands in quickbar
pub order: i8,
}

@ -0,0 +1,165 @@
pub mod command;
pub mod revision_files;
pub mod utils;
pub use command::{CommandInfo, CommandText};
pub use revision_files::RevisionFilesComponent;
use anyhow::Result;
use crossterm::event::Event;
use std::convert::From;
use tui::{
backend::Backend,
layout::{Alignment, Rect},
style::Style,
text::{Span, Text},
widgets::{Block, BorderType, Borders, Paragraph, Wrap},
Frame,
};
/// creates accessors for a list of components
///
/// allows generating code to make sure
/// we always enumerate all components in both getter functions
#[macro_export]
macro_rules! accessors {
($self:ident, [$($element:ident),+]) => {
fn components(& $self) -> Vec<&dyn Component> {
vec![
$(&$self.$element,)+
]
}
fn components_mut(&mut $self) -> Vec<&mut dyn Component> {
vec![
$(&mut $self.$element,)+
]
}
};
}
/// creates a function to determine if any popup is visible
#[macro_export]
macro_rules! any_popup_visible {
($self:ident, [$($element:ident),+]) => {
fn any_popup_visible(& $self) -> bool{
($($self.$element.is_visible()) || +)
}
};
}
/// creates the draw popup function
#[macro_export]
macro_rules! draw_popups {
($self:ident, [$($element:ident),+]) => {
fn draw_popups<B: Backend>(& $self, mut f: &mut Frame<B>) -> Result<()>{
//TODO: move the layout part out and feed it into `draw_popups`
let size = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Min(1),
Constraint::Length($self.cmdbar.borrow().height()),
]
.as_ref(),
)
.split(f.size())[0];
($($self.$element.draw(&mut f, size)?) , +);
return Ok(());
}
};
}
/// simply calls
/// any_popup_visible!() and draw_popups!() macros
#[macro_export]
macro_rules! setup_popups {
($self:ident, [$($element:ident),+]) => {
crate::any_popup_visible!($self, [$($element),+]);
crate::draw_popups!($self, [ $($element),+ ]);
};
}
#[derive(Copy, Clone)]
pub enum ScrollType {
Up,
Down,
Home,
End,
PageUp,
PageDown,
}
#[derive(Copy, Clone)]
pub enum Direction {
Up,
Down,
}
///
#[derive(PartialEq)]
pub enum CommandBlocking {
Blocking,
PassingOn,
}
///
pub trait DrawableComponent {
///
fn draw<B: Backend>(&self, f: &mut Frame<B>, rect: Rect) -> Result<()>;
}
///
#[derive(PartialEq)]
pub enum EventState {
Consumed,
NotConsumed,
}
impl From<bool> for EventState {
fn from(consumed: bool) -> Self {
if consumed {
Self::Consumed
} else {
Self::NotConsumed
}
}
}
/// base component trait
pub trait Component {
///
fn commands(&self, out: &mut Vec<CommandInfo>, force_all: bool) -> CommandBlocking;
///
fn event(&mut self, ev: crate::event::Event<crate::event::Key>) -> Result<EventState>;
///
fn focused(&self) -> bool {
false
}
/// focus/unfocus this component depending on param
fn focus(&mut self, _focus: bool) {}
///
fn is_visible(&self) -> bool {
true
}
///
fn hide(&mut self) {}
///
fn show(&mut self) -> Result<()> {
Ok(())
}
///
fn toggle_visible(&mut self) -> Result<()> {
if self.is_visible() {
self.hide();
Ok(())
} else {
self.show()
}
}
}

@ -0,0 +1,174 @@
use super::{
utils::scroll_vertical::VerticalScroll, CommandBlocking, CommandInfo, Component,
DrawableComponent, EventState,
};
use crate::event::{Event as Ev, Key};
use crate::ui::common_nav;
use crate::ui::scrolllist::draw_list_block;
use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
// use DatabaseTreelist::{DatabaseTree, DatabaseTreeItem};
use database_tree::{DatabaseTree, DatabaseTreeItem};
use std::convert::From;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
text::Span,
widgets::{Block, Borders},
Frame,
};
/// `tree_files` returns a list of `DatabaseTree`
#[derive(Debug, PartialEq, Clone)]
pub struct TreeFile {
/// path of this file
pub path: std::path::PathBuf,
/// unix filemode
pub filemode: i32,
}
const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}"; //▸
const FOLDER_ICON_EXPANDED: &str = "\u{25be}"; //▾
const EMPTY_STR: &str = "";
pub struct RevisionFilesComponent {
pub tree: DatabaseTree,
pub scroll: VerticalScroll,
}
impl RevisionFilesComponent {
///
pub fn new() -> Self {
Self {
tree: DatabaseTree::default(),
scroll: VerticalScroll::new(),
}
}
fn tree_item_to_span<'a>(item: &'a DatabaseTreeItem, selected: bool, width: u16) -> Span<'a> {
let path = item.info().path_str();
let indent = item.info().indent();
let indent_str = if indent == 0 {
String::from("")
} else {
format!("{:w$}", " ", w = (indent as usize) * 2)
};
let is_database = item.kind().is_database();
let path_arrow = if is_database {
if item.kind().is_database_collapsed() {
FOLDER_ICON_COLLAPSED
} else {
FOLDER_ICON_EXPANDED
}
} else {
EMPTY_STR
};
let path = format!(
"{}{}{:w$}",
indent_str,
path_arrow,
path,
w = width as usize
);
Span::styled(
path,
if selected {
Style::default().fg(Color::Magenta).bg(Color::Green)
} else {
Style::default()
},
)
}
fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect) {
let tree_height = usize::from(area.height.saturating_sub(2));
self.tree.visual_selection().map_or_else(
|| {
self.scroll.reset();
},
|selection| {
self.scroll
.update(selection.index, selection.count, tree_height);
},
);
let items = self
.tree
.iterate(self.scroll.get_top(), tree_height)
.map(|(item, selected)| Self::tree_item_to_span(item, selected, area.width));
let title = "Tree";
draw_list_block(
f,
area,
Block::default()
.title(Span::styled(title, Style::default()))
.borders(Borders::ALL)
.border_style(Style::default()),
items,
);
// draw_list(f, area, "hoge", items, true);
self.scroll.draw(f, area);
}
}
impl DrawableComponent for RevisionFilesComponent {
fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect) -> Result<()> {
if true {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)].as_ref())
.split(area);
self.draw_tree(f, chunks[0]);
}
Ok(())
}
}
impl Component for RevisionFilesComponent {
fn commands(&self, _out: &mut Vec<CommandInfo>, _force_all: bool) -> CommandBlocking {
CommandBlocking::PassingOn
}
fn event(&mut self, event: Ev<Key>) -> Result<EventState> {
if let crate::event::Event::Input(key) = event {
if tree_nav(&mut self.tree, key) {
return Ok(EventState::Consumed);
} else if key == Key::Enter {
println!("hgoehgoehgoeh")
}
}
Ok(EventState::NotConsumed)
}
}
//TODO: reuse for other tree usages
fn tree_nav(tree: &mut DatabaseTree, key: Key) -> bool {
let tree_collapse_recursive = KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::SHIFT,
};
let tree_expand_recursive = KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::SHIFT,
};
if let Some(common_nav) = common_nav(key) {
tree.move_selection(common_nav)
} else if key == Key::from(tree_collapse_recursive) {
tree.collapse_recursive();
true
} else if key == Key::from(tree_expand_recursive) {
tree.expand_recursive();
true
} else {
false
}
}

@ -0,0 +1,18 @@
use chrono::{DateTime, Local, NaiveDateTime, Utc};
use unicode_width::UnicodeWidthStr;
pub mod scroll_vertical;
/// macro to simplify running code that might return Err.
/// It will show a popup in that case
#[macro_export]
macro_rules! try_or_popup {
($self:ident, $msg:literal, $e:expr) => {
if let Err(err) = $e {
::log::error!("{} {}", $msg, err);
$self
.queue
.push(InternalEvent::ShowErrorMsg(format!("{}\n{}", $msg, err)));
}
};
}

@ -0,0 +1,94 @@
use std::cell::Cell;
use tui::{backend::Backend, layout::Rect, Frame};
use crate::{components::ScrollType, ui::scrollbar::draw_scrollbar};
pub struct VerticalScroll {
top: Cell<usize>,
max_top: Cell<usize>,
}
impl VerticalScroll {
pub const fn new() -> Self {
Self {
top: Cell::new(0),
max_top: Cell::new(0),
}
}
pub fn get_top(&self) -> usize {
self.top.get()
}
pub fn reset(&self) {
self.top.set(0);
}
pub fn move_top(&self, move_type: ScrollType) -> bool {
let old = self.top.get();
let max = self.max_top.get();
let new_scroll_top = match move_type {
ScrollType::Down => old.saturating_add(1),
ScrollType::Up => old.saturating_sub(1),
ScrollType::Home => 0,
ScrollType::End => max,
_ => old,
};
let new_scroll_top = new_scroll_top.clamp(0, max);
if new_scroll_top == old {
return false;
}
self.top.set(new_scroll_top);
true
}
pub fn update(&self, selection: usize, selection_max: usize, visual_height: usize) -> usize {
let new_top = calc_scroll_top(self.get_top(), visual_height, selection, selection_max);
self.top.set(new_top);
if visual_height == 0 {
self.max_top.set(0);
} else {
let new_max = selection_max.saturating_sub(visual_height);
self.max_top.set(new_max);
}
new_top
}
pub fn update_no_selection(&self, line_count: usize, visual_height: usize) -> usize {
self.update(self.get_top(), line_count, visual_height)
}
pub fn draw<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
draw_scrollbar(f, r, self.max_top.get(), self.top.get());
}
}
const fn calc_scroll_top(
current_top: usize,
height_in_lines: usize,
selection: usize,
selection_max: usize,
) -> usize {
if height_in_lines == 0 {
return 0;
}
if selection_max <= height_in_lines {
return 0;
}
if current_top + height_in_lines <= selection {
selection.saturating_sub(height_in_lines) + 1
} else if current_top > selection {
selection
} else {
current_top
}
}

@ -21,6 +21,7 @@ impl Default for EventConfig {
}
/// An occurred event.
#[derive(Copy, Clone)]
pub enum Event<I> {
/// An input event occurred.
Input(I),

@ -4,7 +4,7 @@ pub mod query;
pub mod record_table;
pub mod table_list;
use crate::app::{App, FocusBlock};
use crate::app::{App, FocusBlock, Tab};
use crate::event::Key;
pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
@ -29,8 +29,10 @@ pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
_ => app.focus_block = FocusBlock::RecordTable(true),
},
Key::Char('e') => app.focus_block = FocusBlock::Query(true),
Key::Right => app.next_tab(),
Key::Left => app.previous_tab(),
Key::Char('1') => app.selected_tab = Tab::Records,
Key::Char('2') => app.selected_tab = Tab::Structure,
// Key::Right => app.next_tab(),
// Key::Left => app.previous_tab(),
Key::Esc => app.error = None,
_ => (),
}

@ -1,4 +1,5 @@
mod app;
mod components;
mod event;
mod handlers;
mod ui;
@ -37,9 +38,39 @@ async fn main() -> anyhow::Result<()> {
terminal.clear()?;
// let mut tree = FileTree::new(
// &[
// std::path::Path::new("world/city"),
// std::path::Path::new("world/country"),
// std::path::Path::new("c/bar.rs"),
// ],
// &BTreeSet::new(),
// )
// .unwrap();
use crate::components::Component as _;
use database_tree::{Database, DatabaseTree};
use std::collections::BTreeSet;
let mut tree = DatabaseTree::new(
&[
&Database {
name: "world".to_string(),
tables: vec!["country".to_string(), "city".to_string()],
},
&Database {
name: "foo".to_string(),
tables: vec!["bar".to_string(), "baz".to_string(), "city".to_string()],
},
],
&BTreeSet::new(),
)
.unwrap();
tree.selection = Some(0);
app.revision_files.tree = tree;
loop {
terminal.draw(|f| ui::draw(f, &mut app).unwrap())?;
match events.next()? {
let event = events.next()?;
app.revision_files.event(event)?;
match event {
Event::Input(key) => {
if key == Key::Char('q') {
break;

@ -0,0 +1,10 @@
use crate::app::App;
use tui::{backend::Backend, layout::Rect, Frame};
fn draw_database_list<B: Backend>(
f: &mut Frame<'_, B>,
app: &mut App,
layout_chunk: Rect,
) -> anyhow::Result<()> {
Ok(())
}

@ -1,4 +1,6 @@
use crate::app::{App, FocusBlock, Tab};
use crate::event::Key;
use database_tree::MoveSelection;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
@ -9,6 +11,10 @@ use tui::{
};
use unicode_width::UnicodeWidthStr;
pub mod database_list;
pub mod scrollbar;
pub mod scrolllist;
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<()> {
if let FocusBlock::ConnectionList = app.focus_block {
let percent_x = 60;
@ -71,23 +77,25 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
.as_ref(),
)
.split(main_chunks[0]);
let databases: Vec<ListItem> = app
.databases
.iter()
.map(|i| {
ListItem::new(vec![Spans::from(Span::raw(&i.name))])
.style(Style::default().fg(Color::White))
})
.collect();
let tasks = List::new(databases)
.block(Block::default().borders(Borders::ALL).title("Databases"))
.highlight_style(Style::default().fg(Color::Green))
.style(match app.focus_block {
FocusBlock::DabataseList(false) => Style::default(),
FocusBlock::DabataseList(true) => Style::default().fg(Color::Green),
_ => Style::default().fg(Color::DarkGray),
});
f.render_stateful_widget(tasks, left_chunks[0], &mut app.selected_database);
// let databases: Vec<ListItem> = app
// .databases
// .iter()
// .map(|i| {
// ListItem::new(vec![Spans::from(Span::raw(&i.name))])
// .style(Style::default().fg(Color::White))
// })
// .collect();
// let tasks = List::new(databases)
// .block(Block::default().borders(Borders::ALL).title("Databases"))
// .highlight_style(Style::default().fg(Color::Green))
// .style(match app.focus_block {
// FocusBlock::DabataseList(false) => Style::default(),
// FocusBlock::DabataseList(true) => Style::default().fg(Color::Green),
// _ => Style::default().fg(Color::DarkGray),
// });
// f.render_stateful_widget(tasks, left_chunks[0], &mut app.selected_database);
use crate::components::DrawableComponent as _;
app.revision_files.draw(f, left_chunks[0]).unwrap();
let databases = app.databases.clone();
let tables: Vec<ListItem> = databases[app.selected_database.selected().unwrap_or(0)]
@ -282,3 +290,21 @@ fn draw_error_popup<B: Backend>(f: &mut Frame<'_, B>, error: String) -> anyhow::
f.render_widget(error, area);
Ok(())
}
pub fn common_nav(key: Key) -> Option<MoveSelection> {
if key == Key::Down {
Some(MoveSelection::Down)
} else if key == Key::Up {
Some(MoveSelection::Up)
} else if key == Key::PageUp {
Some(MoveSelection::PageUp)
} else if key == Key::PageDown {
Some(MoveSelection::PageDown)
} else if key == Key::Right {
Some(MoveSelection::Right)
} else if key == Key::Left {
Some(MoveSelection::Left)
} else {
None
}
}

@ -0,0 +1,75 @@
use easy_cast::CastFloat;
use std::convert::TryFrom;
use tui::{
backend::Backend,
buffer::Buffer,
layout::{Margin, Rect},
style::Style,
symbols::{block::FULL, line::DOUBLE_VERTICAL},
widgets::Widget,
Frame,
};
///
struct Scrollbar {
max: u16,
pos: u16,
style_bar: Style,
style_pos: Style,
}
impl Scrollbar {
fn new(max: usize, pos: usize) -> Self {
Self {
max: u16::try_from(max).unwrap_or_default(),
pos: u16::try_from(pos).unwrap_or_default(),
style_pos: Style::default(),
style_bar: Style::default(),
}
}
}
impl Widget for Scrollbar {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.height <= 2 {
return;
}
if self.max == 0 {
return;
}
let right = area.right().saturating_sub(1);
if right <= area.left() {
return;
};
let (bar_top, bar_height) = {
let scrollbar_area = area.inner(&Margin {
horizontal: 0,
vertical: 1,
});
(scrollbar_area.top(), scrollbar_area.height)
};
for y in bar_top..(bar_top + bar_height) {
buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar);
}
let progress = f32::from(self.pos) / f32::from(self.max);
let progress = if progress > 1.0 { 1.0 } else { progress };
let pos = f32::from(bar_height) * progress;
let pos: u16 = pos.cast_nearest();
let pos = pos.saturating_sub(1);
buf.set_string(right, bar_top + pos, FULL, self.style_pos);
}
}
pub fn draw_scrollbar<B: Backend>(f: &mut Frame<B>, r: Rect, max: usize, pos: usize) {
let mut widget = Scrollbar::new(max, pos);
widget.style_pos = Style::default();
f.render_widget(widget, r);
}

@ -0,0 +1,79 @@
use std::iter::Iterator;
use tui::{
backend::Backend,
buffer::Buffer,
layout::Rect,
style::{Color, Style},
text::Span,
widgets::{Block, Borders, List, ListItem, Widget},
Frame,
};
///
struct ScrollableList<'b, L>
where
L: Iterator<Item = Span<'b>>,
{
block: Option<Block<'b>>,
/// Items to be displayed
items: L,
/// Base style of the widget
style: Style,
}
impl<'b, L> ScrollableList<'b, L>
where
L: Iterator<Item = Span<'b>>,
{
fn new(items: L) -> Self {
Self {
block: None,
items,
style: Style::default(),
}
}
fn block(mut self, block: Block<'b>) -> Self {
self.block = Some(block);
self
}
}
impl<'b, L> Widget for ScrollableList<'b, L>
where
L: Iterator<Item = Span<'b>>,
{
fn render(self, area: Rect, buf: &mut Buffer) {
// Render items
List::new(self.items.map(ListItem::new).collect::<Vec<ListItem>>())
.block(self.block.unwrap_or_default())
.style(self.style)
.render(area, buf);
}
}
pub fn draw_list_block<'b, B: Backend, L>(f: &mut Frame<B>, r: Rect, block: Block<'b>, items: L)
where
L: Iterator<Item = Span<'b>>,
{
let list = ScrollableList::new(items).block(block);
f.render_widget(list, r);
}
pub fn draw_list<'b, B: Backend, L>(
f: &mut Frame<B>,
r: Rect,
title: &'b str,
items: L,
selected: bool,
) where
L: Iterator<Item = Span<'b>>,
{
let list = ScrollableList::new(items).block(
Block::default()
.title(Span::styled(title, Style::default()))
.borders(Borders::ALL)
.border_style(Style::default()),
);
f.render_widget(list, r);
}
Loading…
Cancel
Save