2021-03-02 08:54:43 +00:00
|
|
|
use crate::app;
|
2021-04-03 06:01:40 +00:00
|
|
|
use crate::app::HelpMenuLine;
|
2021-04-16 19:54:46 +00:00
|
|
|
use crate::app::{Node, ResolvedNode};
|
2021-05-08 11:31:40 +00:00
|
|
|
use crate::config::PanelUiConfig;
|
2021-03-01 11:23:04 +00:00
|
|
|
use handlebars::Handlebars;
|
2021-05-08 03:47:13 +00:00
|
|
|
use indexmap::IndexSet;
|
2021-04-14 02:29:13 +00:00
|
|
|
use lazy_static::lazy_static;
|
2021-03-31 11:50:37 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-04-05 05:36:53 +00:00
|
|
|
use std::cmp::Ordering;
|
2021-04-09 06:14:36 +00:00
|
|
|
use std::collections::HashMap;
|
2021-04-14 02:29:13 +00:00
|
|
|
use std::env;
|
2021-03-01 11:23:04 +00:00
|
|
|
use tui::backend::Backend;
|
2021-03-31 11:50:37 +00:00
|
|
|
use tui::layout::Rect;
|
2021-05-07 17:21:21 +00:00
|
|
|
use tui::layout::{Constraint as TuiConstraint, Direction, Layout as TuiLayout};
|
2021-05-08 03:47:13 +00:00
|
|
|
use tui::style::{Color, Modifier as TuiModifier, Style as TuiStyle};
|
2021-04-11 15:35:46 +00:00
|
|
|
use tui::text::{Span, Spans};
|
2021-05-08 03:24:20 +00:00
|
|
|
use tui::widgets::{Block, Borders as TuiBorders, Cell, List, ListItem, Paragraph, Row, Table};
|
2021-03-01 11:23:04 +00:00
|
|
|
use tui::Frame;
|
|
|
|
|
2021-04-14 02:29:13 +00:00
|
|
|
lazy_static! {
|
|
|
|
pub static ref NO_COLOR: bool = env::var("NO_COLOR").ok().map(|_| true).unwrap_or(false);
|
|
|
|
pub static ref DEFAULT_STYLE: TuiStyle = TuiStyle::default();
|
|
|
|
}
|
|
|
|
|
2021-05-09 17:09:49 +00:00
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct LayoutOptions {
|
|
|
|
#[serde(default)]
|
|
|
|
margin: Option<u16>,
|
|
|
|
#[serde(default)]
|
|
|
|
horizontal_margin: Option<u16>,
|
|
|
|
#[serde(default)]
|
|
|
|
vertical_margin: Option<u16>,
|
|
|
|
#[serde(default)]
|
|
|
|
constraints: Option<Vec<Constraint>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LayoutOptions {
|
|
|
|
pub fn extend(mut self, other: Self) -> Self {
|
|
|
|
self.margin = other.margin.or(self.margin);
|
|
|
|
self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin);
|
|
|
|
self.vertical_margin = other.vertical_margin.or(self.vertical_margin);
|
|
|
|
self.constraints = other.constraints.or(self.constraints);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the layout options's constraints.
|
|
|
|
pub fn constraints(&self) -> &Option<Vec<Constraint>> {
|
|
|
|
&self.constraints
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the layout options's margin.
|
|
|
|
pub fn margin(&self) -> Option<u16> {
|
|
|
|
self.margin
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the layout options's horizontal margin.
|
|
|
|
pub fn horizontal_margin(&self) -> Option<u16> {
|
|
|
|
self.horizontal_margin
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the layout options's vertical margin.
|
|
|
|
pub fn vertical_margin(&self) -> Option<u16> {
|
|
|
|
self.vertical_margin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub enum Layout {
|
|
|
|
Nothing(Option<PanelUiConfig>),
|
|
|
|
Table(Option<PanelUiConfig>),
|
|
|
|
InputAndLogs(Option<PanelUiConfig>),
|
|
|
|
Selection(Option<PanelUiConfig>),
|
|
|
|
HelpMenu(Option<PanelUiConfig>),
|
|
|
|
SortAndFilter(Option<PanelUiConfig>),
|
|
|
|
Horizontal {
|
|
|
|
config: LayoutOptions,
|
|
|
|
splits: Vec<Layout>,
|
|
|
|
},
|
|
|
|
Vertical {
|
|
|
|
config: LayoutOptions,
|
|
|
|
splits: Vec<Layout>,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Layout {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Nothing(Default::default())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Layout {
|
|
|
|
pub fn extend(self, other: Self) -> Self {
|
|
|
|
match (self, other) {
|
|
|
|
(s, Self::Nothing(_)) => s,
|
|
|
|
(Self::Table(s), Self::Table(o)) => Self::Table(o.or(s)),
|
|
|
|
(Self::InputAndLogs(s), Self::InputAndLogs(o)) => Self::InputAndLogs(o.or(s)),
|
|
|
|
(Self::Selection(s), Self::Selection(o)) => Self::Selection(o.or(s)),
|
|
|
|
(Self::HelpMenu(s), Self::HelpMenu(o)) => Self::HelpMenu(o.or(s)),
|
|
|
|
(Self::SortAndFilter(s), Self::SortAndFilter(o)) => Self::SortAndFilter(o.or(s)),
|
|
|
|
(
|
|
|
|
Self::Horizontal {
|
|
|
|
config: sconfig,
|
|
|
|
splits: _,
|
|
|
|
},
|
|
|
|
Self::Horizontal {
|
|
|
|
config: oconfig,
|
|
|
|
splits: osplits,
|
|
|
|
},
|
|
|
|
) => Self::Horizontal {
|
|
|
|
config: sconfig.extend(oconfig),
|
|
|
|
splits: osplits,
|
|
|
|
},
|
|
|
|
|
|
|
|
(
|
|
|
|
Self::Vertical {
|
|
|
|
config: sconfig,
|
|
|
|
splits: _,
|
|
|
|
},
|
|
|
|
Self::Vertical {
|
|
|
|
config: oconfig,
|
|
|
|
splits: osplits,
|
|
|
|
},
|
|
|
|
) => Self::Vertical {
|
|
|
|
config: sconfig.extend(oconfig),
|
|
|
|
splits: osplits,
|
|
|
|
},
|
|
|
|
(_, other) => other,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-08 03:47:13 +00:00
|
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub enum Border {
|
|
|
|
Top,
|
|
|
|
Right,
|
|
|
|
Bottom,
|
|
|
|
Left,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Border {
|
|
|
|
pub fn bits(self) -> u32 {
|
|
|
|
match self {
|
|
|
|
Self::Top => TuiBorders::TOP.bits(),
|
|
|
|
Self::Right => TuiBorders::RIGHT.bits(),
|
|
|
|
Self::Bottom => TuiBorders::BOTTOM.bits(),
|
|
|
|
Self::Left => TuiBorders::LEFT.bits(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub enum Modifier {
|
|
|
|
Bold,
|
|
|
|
Dim,
|
|
|
|
Italic,
|
|
|
|
Underlined,
|
|
|
|
SlowBlink,
|
|
|
|
RapidBlink,
|
|
|
|
Reversed,
|
|
|
|
Hidden,
|
|
|
|
CrossedOut,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Modifier {
|
|
|
|
pub fn bits(self) -> u16 {
|
|
|
|
match self {
|
|
|
|
Self::Bold => TuiModifier::BOLD.bits(),
|
|
|
|
Self::Dim => TuiModifier::DIM.bits(),
|
|
|
|
Self::Italic => TuiModifier::ITALIC.bits(),
|
|
|
|
Self::Underlined => TuiModifier::UNDERLINED.bits(),
|
|
|
|
Self::SlowBlink => TuiModifier::SLOW_BLINK.bits(),
|
|
|
|
Self::RapidBlink => TuiModifier::RAPID_BLINK.bits(),
|
|
|
|
Self::Reversed => TuiModifier::REVERSED.bits(),
|
|
|
|
Self::Hidden => TuiModifier::HIDDEN.bits(),
|
|
|
|
Self::CrossedOut => TuiModifier::CROSSED_OUT.bits(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
2021-04-14 02:29:13 +00:00
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct Style {
|
2021-04-25 10:30:22 +00:00
|
|
|
fg: Option<Color>,
|
|
|
|
bg: Option<Color>,
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: Option<IndexSet<Modifier>>,
|
|
|
|
sub_modifiers: Option<IndexSet<Modifier>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Style {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.fg == other.fg
|
|
|
|
&& self.bg == other.bg
|
|
|
|
&& self.add_modifiers == other.add_modifiers
|
|
|
|
&& self.sub_modifiers == other.sub_modifiers
|
|
|
|
}
|
2021-04-14 02:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Style {
|
|
|
|
pub fn extend(mut self, other: Self) -> Self {
|
|
|
|
self.fg = other.fg.or(self.fg);
|
|
|
|
self.bg = other.bg.or(self.bg);
|
2021-05-08 03:47:13 +00:00
|
|
|
self.add_modifiers = other.add_modifiers.or(self.add_modifiers);
|
|
|
|
self.sub_modifiers = other.sub_modifiers.or(self.sub_modifiers);
|
2021-04-14 02:29:13 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Into<TuiStyle> for Style {
|
|
|
|
fn into(self) -> TuiStyle {
|
|
|
|
if *NO_COLOR {
|
|
|
|
*DEFAULT_STYLE
|
|
|
|
} else {
|
|
|
|
TuiStyle {
|
|
|
|
fg: self.fg,
|
|
|
|
bg: self.bg,
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifier: TuiModifier::from_bits_truncate(
|
|
|
|
self.add_modifiers
|
|
|
|
.unwrap_or_default()
|
|
|
|
.into_iter()
|
|
|
|
.map(|m| m.bits())
|
2021-05-09 17:09:49 +00:00
|
|
|
.fold(0, |a, b| (a ^ b)),
|
2021-05-08 03:47:13 +00:00
|
|
|
),
|
|
|
|
|
|
|
|
sub_modifier: TuiModifier::from_bits_truncate(
|
|
|
|
self.sub_modifiers
|
|
|
|
.unwrap_or_default()
|
|
|
|
.into_iter()
|
|
|
|
.map(|m| m.bits())
|
2021-05-09 17:09:49 +00:00
|
|
|
.fold(0, |a, b| (a ^ b)),
|
2021-05-08 03:47:13 +00:00
|
|
|
),
|
2021-04-14 02:29:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-09 17:09:49 +00:00
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub enum Constraint {
|
|
|
|
Percentage(u16),
|
|
|
|
Ratio(u32, u32),
|
|
|
|
Length(u16),
|
|
|
|
LengthLessThanScreenHeight(u16),
|
|
|
|
LengthLessThanScreenWidth(u16),
|
|
|
|
LengthLessThanLayoutHeight(u16),
|
|
|
|
LengthLessThanLayoutWidth(u16),
|
|
|
|
Max(u16),
|
|
|
|
MaxLessThanScreenHeight(u16),
|
|
|
|
MaxLessThanScreenWidth(u16),
|
|
|
|
MaxLessThanLayoutHeight(u16),
|
|
|
|
MaxthLessThanLayoutWidth(u16),
|
|
|
|
Min(u16),
|
|
|
|
MinLessThanScreenHeight(u16),
|
|
|
|
MinLessThanScreenWidth(u16),
|
|
|
|
MinLessThanLayoutHeight(u16),
|
|
|
|
MinLessThanLayoutWidth(u16),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Constraint {
|
|
|
|
pub fn to_tui(self, screen_size: Rect, layout_size: Rect) -> TuiConstraint {
|
|
|
|
match self {
|
|
|
|
Self::Percentage(n) => TuiConstraint::Percentage(n),
|
|
|
|
Self::Ratio(x, y) => TuiConstraint::Ratio(x, y),
|
|
|
|
Self::Length(n) => TuiConstraint::Length(n),
|
|
|
|
Self::LengthLessThanScreenHeight(n) => {
|
|
|
|
TuiConstraint::Length(screen_size.height.max(n) - n)
|
|
|
|
}
|
|
|
|
Self::LengthLessThanScreenWidth(n) => {
|
|
|
|
TuiConstraint::Length(screen_size.width.max(n) - n)
|
|
|
|
}
|
|
|
|
Self::LengthLessThanLayoutHeight(n) => {
|
|
|
|
TuiConstraint::Length(layout_size.height.max(n) - n)
|
|
|
|
}
|
|
|
|
Self::LengthLessThanLayoutWidth(n) => {
|
|
|
|
TuiConstraint::Length(layout_size.width.max(n) - n)
|
|
|
|
}
|
|
|
|
Self::Max(n) => TuiConstraint::Max(n),
|
|
|
|
Self::MaxLessThanScreenHeight(n) => TuiConstraint::Max(screen_size.height.max(n) - n),
|
|
|
|
Self::MaxLessThanScreenWidth(n) => TuiConstraint::Max(screen_size.width.max(n) - n),
|
|
|
|
Self::MaxLessThanLayoutHeight(n) => TuiConstraint::Max(layout_size.height.max(n) - n),
|
|
|
|
Self::MaxthLessThanLayoutWidth(n) => TuiConstraint::Max(layout_size.width.max(n) - n),
|
|
|
|
Self::Min(n) => TuiConstraint::Min(n),
|
|
|
|
Self::MinLessThanScreenHeight(n) => TuiConstraint::Min(screen_size.height.max(n) - n),
|
|
|
|
Self::MinLessThanScreenWidth(n) => TuiConstraint::Min(screen_size.width.max(n) - n),
|
|
|
|
Self::MinLessThanLayoutHeight(n) => TuiConstraint::Min(layout_size.height.max(n) - n),
|
|
|
|
Self::MinLessThanLayoutWidth(n) => TuiConstraint::Min(layout_size.width.max(n) - n),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-12 16:00:51 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2021-04-16 19:54:46 +00:00
|
|
|
pub struct ResolvedNodeUiMetadata {
|
2021-04-25 10:30:22 +00:00
|
|
|
absolute_path: String,
|
|
|
|
extension: String,
|
|
|
|
is_dir: bool,
|
|
|
|
is_file: bool,
|
|
|
|
is_readonly: bool,
|
|
|
|
mime_essence: String,
|
|
|
|
size: u64,
|
2021-04-12 16:00:51 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 19:54:46 +00:00
|
|
|
impl From<ResolvedNode> for ResolvedNodeUiMetadata {
|
|
|
|
fn from(node: ResolvedNode) -> Self {
|
2021-04-12 16:00:51 +00:00
|
|
|
Self {
|
2021-04-25 10:30:22 +00:00
|
|
|
absolute_path: node.absolute_path().clone(),
|
|
|
|
extension: node.extension().clone(),
|
|
|
|
is_dir: node.is_dir(),
|
|
|
|
is_file: node.is_file(),
|
|
|
|
is_readonly: node.is_readonly(),
|
|
|
|
mime_essence: node.mime_essence().clone(),
|
|
|
|
size: node.size(),
|
2021-04-12 16:00:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-31 11:50:37 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2021-04-05 05:36:53 +00:00
|
|
|
struct NodeUiMetadata {
|
2021-03-31 11:50:37 +00:00
|
|
|
// From Node
|
2021-04-25 10:30:22 +00:00
|
|
|
parent: String,
|
|
|
|
relative_path: String,
|
|
|
|
absolute_path: String,
|
|
|
|
extension: String,
|
|
|
|
is_symlink: bool,
|
|
|
|
is_broken: bool,
|
|
|
|
is_dir: bool,
|
|
|
|
is_file: bool,
|
|
|
|
is_readonly: bool,
|
|
|
|
mime_essence: String,
|
|
|
|
size: u64,
|
|
|
|
canonical: Option<ResolvedNodeUiMetadata>,
|
|
|
|
symlink: Option<ResolvedNodeUiMetadata>,
|
2021-03-31 11:50:37 +00:00
|
|
|
|
|
|
|
// Extra
|
2021-04-25 10:30:22 +00:00
|
|
|
index: usize,
|
|
|
|
relative_index: usize,
|
|
|
|
is_before_focus: bool,
|
|
|
|
is_after_focus: bool,
|
|
|
|
tree: String,
|
|
|
|
prefix: String,
|
|
|
|
suffix: String,
|
|
|
|
is_selected: bool,
|
|
|
|
is_focused: bool,
|
|
|
|
total: usize,
|
|
|
|
meta: HashMap<String, String>,
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
|
|
|
|
2021-04-05 05:36:53 +00:00
|
|
|
impl NodeUiMetadata {
|
2021-03-31 11:50:37 +00:00
|
|
|
fn new(
|
|
|
|
node: &Node,
|
|
|
|
index: usize,
|
|
|
|
relative_index: usize,
|
|
|
|
is_before_focus: bool,
|
|
|
|
is_after_focus: bool,
|
|
|
|
tree: String,
|
|
|
|
prefix: String,
|
|
|
|
suffix: String,
|
|
|
|
is_selected: bool,
|
|
|
|
is_focused: bool,
|
|
|
|
total: usize,
|
2021-04-10 11:35:10 +00:00
|
|
|
meta: HashMap<String, String>,
|
2021-03-31 11:50:37 +00:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
2021-04-25 10:30:22 +00:00
|
|
|
parent: node.parent().clone(),
|
|
|
|
relative_path: node.relative_path().clone(),
|
|
|
|
absolute_path: node.absolute_path().clone(),
|
|
|
|
extension: node.extension().clone(),
|
|
|
|
is_symlink: node.is_symlink(),
|
|
|
|
is_broken: node.is_broken(),
|
|
|
|
is_dir: node.is_dir(),
|
|
|
|
is_file: node.is_file(),
|
|
|
|
is_readonly: node.is_readonly(),
|
|
|
|
mime_essence: node.mime_essence().clone(),
|
|
|
|
size: node.size(),
|
|
|
|
canonical: node.canonical().to_owned().map(|s| s.into()),
|
|
|
|
symlink: node.symlink().to_owned().map(|s| s.into()),
|
2021-03-31 11:50:37 +00:00
|
|
|
index,
|
|
|
|
relative_index,
|
|
|
|
is_before_focus,
|
|
|
|
is_after_focus,
|
|
|
|
tree,
|
|
|
|
prefix,
|
|
|
|
suffix,
|
|
|
|
is_selected,
|
|
|
|
is_focused,
|
|
|
|
total,
|
2021-04-10 11:35:10 +00:00
|
|
|
meta,
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-08 11:31:40 +00:00
|
|
|
fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
|
2021-05-08 03:24:20 +00:00
|
|
|
Block::default()
|
|
|
|
.borders(TuiBorders::from_bits_truncate(
|
|
|
|
config
|
|
|
|
.borders()
|
|
|
|
.clone()
|
|
|
|
.unwrap_or_default()
|
|
|
|
.iter()
|
|
|
|
.map(|b| b.bits())
|
2021-05-09 17:09:49 +00:00
|
|
|
.fold(0, |a, b| (a ^ b)),
|
2021-05-08 03:24:20 +00:00
|
|
|
))
|
|
|
|
.title(Span::styled(
|
|
|
|
config.title().format().clone().unwrap_or(default_title),
|
2021-05-08 03:47:13 +00:00
|
|
|
config.title().style().clone().into(),
|
2021-05-08 03:24:20 +00:00
|
|
|
))
|
2021-05-08 03:47:13 +00:00
|
|
|
.style(config.style().clone().into())
|
2021-05-08 03:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_table<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
|
|
|
app: &app::App,
|
|
|
|
hb: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config
|
|
|
|
.default()
|
|
|
|
.clone()
|
|
|
|
.extend(panel_config.table().clone());
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-05-08 03:24:20 +00:00
|
|
|
let app_config = app.config().to_owned();
|
|
|
|
let header_height = app_config.general().table().header().height().unwrap_or(1);
|
|
|
|
let height: usize = (layout_size.height.max(header_height + 2) - (header_height + 2)).into();
|
2021-03-31 11:50:37 +00:00
|
|
|
|
|
|
|
let rows = app
|
|
|
|
.directory_buffer()
|
|
|
|
.map(|dir| {
|
2021-04-25 10:30:22 +00:00
|
|
|
dir.nodes()
|
2021-03-31 11:50:37 +00:00
|
|
|
.iter()
|
|
|
|
.enumerate()
|
2021-04-25 10:30:22 +00:00
|
|
|
.skip(height * (dir.focus() / height.max(1)))
|
2021-04-12 15:33:50 +00:00
|
|
|
.take(height)
|
2021-03-31 11:50:37 +00:00
|
|
|
.map(|(index, node)| {
|
2021-04-25 10:30:22 +00:00
|
|
|
let is_focused = dir.focus() == index;
|
2021-03-31 11:50:37 +00:00
|
|
|
|
|
|
|
// TODO : Optimize
|
2021-04-18 19:13:25 +00:00
|
|
|
let is_selected = app.selection().contains(node);
|
2021-03-31 11:50:37 +00:00
|
|
|
|
|
|
|
let is_first = index == 0;
|
2021-04-25 10:30:22 +00:00
|
|
|
let is_last = index == dir.total().max(1) - 1;
|
2021-03-31 11:50:37 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
let tree = app_config
|
2021-04-26 06:22:46 +00:00
|
|
|
.general()
|
|
|
|
.table()
|
|
|
|
.tree()
|
2021-03-31 11:50:37 +00:00
|
|
|
.clone()
|
|
|
|
.map(|t| {
|
|
|
|
if is_last {
|
2021-04-26 06:22:46 +00:00
|
|
|
t.2.format().clone()
|
2021-03-31 11:50:37 +00:00
|
|
|
} else if is_first {
|
2021-04-26 06:22:46 +00:00
|
|
|
t.0.format().clone()
|
2021-03-31 11:50:37 +00:00
|
|
|
} else {
|
2021-04-26 06:22:46 +00:00
|
|
|
t.1.format().clone()
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
let node_type = app_config
|
2021-04-26 06:22:46 +00:00
|
|
|
.node_types()
|
|
|
|
.special()
|
2021-04-25 10:30:22 +00:00
|
|
|
.get(node.relative_path())
|
2021-05-08 03:24:20 +00:00
|
|
|
.or_else(|| app_config.node_types().extension().get(node.extension()))
|
|
|
|
.or_else(|| {
|
|
|
|
app_config
|
|
|
|
.node_types()
|
|
|
|
.mime_essence()
|
|
|
|
.get(node.mime_essence())
|
|
|
|
})
|
2021-03-31 11:50:37 +00:00
|
|
|
.unwrap_or_else(|| {
|
2021-04-25 10:30:22 +00:00
|
|
|
if node.is_symlink() {
|
2021-05-08 03:24:20 +00:00
|
|
|
&app_config.node_types().symlink()
|
2021-04-25 10:30:22 +00:00
|
|
|
} else if node.is_dir() {
|
2021-05-08 03:24:20 +00:00
|
|
|
&app_config.node_types().directory()
|
2021-03-31 11:50:37 +00:00
|
|
|
} else {
|
2021-05-08 03:24:20 +00:00
|
|
|
&app_config.node_types().file()
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-04-05 05:36:53 +00:00
|
|
|
let (relative_index, is_before_focus, is_after_focus) =
|
2021-04-25 10:30:22 +00:00
|
|
|
match dir.focus().cmp(&index) {
|
|
|
|
Ordering::Greater => (dir.focus() - index, true, false),
|
|
|
|
Ordering::Less => (index - dir.focus(), false, true),
|
2021-04-05 05:36:53 +00:00
|
|
|
Ordering::Equal => (0, false, false),
|
|
|
|
};
|
2021-03-31 11:50:37 +00:00
|
|
|
|
2021-04-15 09:13:15 +00:00
|
|
|
let (mut prefix, mut suffix, mut style) = {
|
2021-05-08 03:24:20 +00:00
|
|
|
let ui = app_config.general().default_ui().clone();
|
2021-04-26 06:22:46 +00:00
|
|
|
(
|
|
|
|
ui.prefix().clone(),
|
|
|
|
ui.suffix().clone(),
|
2021-05-08 03:47:13 +00:00
|
|
|
ui.style().clone().extend(node_type.style().clone()),
|
2021-04-26 06:22:46 +00:00
|
|
|
)
|
2021-04-15 09:13:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if is_selected {
|
2021-05-08 03:24:20 +00:00
|
|
|
let ui = app_config.general().selection_ui().clone();
|
2021-04-26 06:22:46 +00:00
|
|
|
prefix = ui.prefix().clone().or(prefix);
|
|
|
|
suffix = ui.suffix().clone().or(suffix);
|
2021-05-08 03:47:13 +00:00
|
|
|
style = style.extend(ui.style().clone());
|
2021-04-15 09:13:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if is_focused {
|
2021-05-08 03:24:20 +00:00
|
|
|
let ui = app_config.general().focus_ui().clone();
|
2021-04-26 06:22:46 +00:00
|
|
|
prefix = ui.prefix().clone().or(prefix);
|
|
|
|
suffix = ui.suffix().clone().or(suffix);
|
2021-05-08 03:47:13 +00:00
|
|
|
style = style.extend(ui.style().clone());
|
2021-04-15 09:13:15 +00:00
|
|
|
};
|
|
|
|
|
2021-04-05 05:36:53 +00:00
|
|
|
let meta = NodeUiMetadata::new(
|
2021-03-31 11:50:37 +00:00
|
|
|
&node,
|
|
|
|
index,
|
|
|
|
relative_index,
|
|
|
|
is_before_focus,
|
|
|
|
is_after_focus,
|
2021-04-10 11:35:10 +00:00
|
|
|
tree.unwrap_or_default(),
|
2021-04-15 09:13:15 +00:00
|
|
|
prefix.unwrap_or_default(),
|
|
|
|
suffix.unwrap_or_default(),
|
2021-03-31 11:50:37 +00:00
|
|
|
is_selected,
|
|
|
|
is_focused,
|
2021-04-25 10:30:22 +00:00
|
|
|
dir.total(),
|
2021-04-26 06:22:46 +00:00
|
|
|
node_type.meta().clone(),
|
2021-03-31 11:50:37 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let cols = hb
|
|
|
|
.render(app::TEMPLATE_TABLE_ROW, &meta)
|
|
|
|
.ok()
|
|
|
|
.unwrap_or_else(|| app::UNSUPPORTED_STR.into())
|
2021-04-05 05:36:53 +00:00
|
|
|
.split('\t')
|
2021-03-31 11:50:37 +00:00
|
|
|
.map(|x| Cell::from(x.to_string()))
|
|
|
|
.collect::<Vec<Cell>>();
|
|
|
|
|
2021-04-10 11:35:10 +00:00
|
|
|
Row::new(cols).style(style.into())
|
2021-03-31 11:50:37 +00:00
|
|
|
})
|
|
|
|
.collect::<Vec<Row>>()
|
2021-03-02 08:54:43 +00:00
|
|
|
})
|
2021-03-31 11:50:37 +00:00
|
|
|
.unwrap_or_default();
|
2021-03-01 11:23:04 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
let table_constraints: Vec<TuiConstraint> = app_config
|
2021-04-26 06:22:46 +00:00
|
|
|
.general()
|
|
|
|
.table()
|
|
|
|
.col_widths()
|
2021-03-02 08:54:43 +00:00
|
|
|
.clone()
|
2021-04-10 11:35:10 +00:00
|
|
|
.unwrap_or_default()
|
2021-03-02 08:54:43 +00:00
|
|
|
.into_iter()
|
2021-05-08 03:24:20 +00:00
|
|
|
.map(|c| c.to_tui(screen_size, layout_size))
|
2021-03-02 08:54:43 +00:00
|
|
|
.collect();
|
2021-03-01 11:23:04 +00:00
|
|
|
|
2021-03-31 11:50:37 +00:00
|
|
|
let table = Table::new(rows)
|
2021-03-02 08:54:43 +00:00
|
|
|
.widths(&table_constraints)
|
2021-05-08 03:47:13 +00:00
|
|
|
.style(app_config.general().table().style().clone().into())
|
|
|
|
.highlight_style(app_config.general().focus_ui().style().clone().into())
|
2021-05-08 03:24:20 +00:00
|
|
|
.column_spacing(
|
|
|
|
app_config
|
|
|
|
.general()
|
|
|
|
.table()
|
|
|
|
.col_spacing()
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
|
|
|
.block(block(
|
|
|
|
config,
|
|
|
|
format!(
|
|
|
|
" {} ({}) ",
|
|
|
|
app.pwd(),
|
|
|
|
app.directory_buffer()
|
|
|
|
.map(|d| d.total())
|
|
|
|
.unwrap_or_default()
|
|
|
|
),
|
|
|
|
));
|
2021-03-31 11:50:37 +00:00
|
|
|
|
2021-04-10 11:35:10 +00:00
|
|
|
let table = table.clone().header(
|
|
|
|
Row::new(
|
2021-05-08 03:24:20 +00:00
|
|
|
app_config
|
2021-04-26 06:22:46 +00:00
|
|
|
.general()
|
|
|
|
.table()
|
|
|
|
.header()
|
|
|
|
.cols()
|
|
|
|
.clone()
|
2021-04-10 11:35:10 +00:00
|
|
|
.unwrap_or_default()
|
|
|
|
.iter()
|
2021-04-26 06:22:46 +00:00
|
|
|
.map(|c| Cell::from(c.format().to_owned().unwrap_or_default()))
|
2021-04-10 11:35:10 +00:00
|
|
|
.collect::<Vec<Cell>>(),
|
|
|
|
)
|
2021-04-12 15:33:50 +00:00
|
|
|
.height(header_height)
|
2021-05-08 03:47:13 +00:00
|
|
|
.style(app_config.general().table().header().style().clone().into()),
|
2021-04-10 11:35:10 +00:00
|
|
|
);
|
2021-03-01 11:23:04 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
f.render_widget(table, layout_size);
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
2021-03-02 08:54:43 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
fn draw_selection<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
_screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
|
|
|
app: &app::App,
|
|
|
|
_: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config
|
|
|
|
.default()
|
|
|
|
.clone()
|
|
|
|
.extend(panel_config.selection().clone());
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-04-02 02:15:00 +00:00
|
|
|
let selection: Vec<ListItem> = app
|
2021-04-02 02:10:51 +00:00
|
|
|
.selection()
|
2021-03-02 08:54:43 +00:00
|
|
|
.iter()
|
2021-04-12 15:33:50 +00:00
|
|
|
.rev()
|
2021-05-08 03:24:20 +00:00
|
|
|
.take((layout_size.height.max(2) - 2).into())
|
2021-04-12 15:33:50 +00:00
|
|
|
.rev()
|
2021-04-25 10:30:22 +00:00
|
|
|
.map(|n| n.absolute_path().clone())
|
2021-03-02 08:54:43 +00:00
|
|
|
.map(ListItem::new)
|
|
|
|
.collect();
|
2021-03-01 11:23:04 +00:00
|
|
|
|
2021-04-02 02:15:00 +00:00
|
|
|
let selection_count = selection.len();
|
2021-03-01 11:23:04 +00:00
|
|
|
|
2021-03-02 08:54:43 +00:00
|
|
|
// Selected items
|
2021-05-08 03:24:20 +00:00
|
|
|
let selection_list =
|
|
|
|
List::new(selection).block(block(config, format!(" Selection ({}) ", selection_count)));
|
2021-03-04 08:03:14 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
f.render_widget(selection_list, layout_size);
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
fn draw_help_menu<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
_screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
|
|
|
app: &app::App,
|
|
|
|
_: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config
|
|
|
|
.default()
|
|
|
|
.clone()
|
|
|
|
.extend(panel_config.help_menu().clone());
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-04-03 06:01:40 +00:00
|
|
|
let help_menu_rows = app
|
|
|
|
.mode()
|
|
|
|
.help_menu()
|
|
|
|
.into_iter()
|
|
|
|
.map(|l| match l {
|
|
|
|
HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()),
|
2021-04-11 15:35:46 +00:00
|
|
|
HelpMenuLine::KeyMap(k, h) => {
|
|
|
|
let remaps = app
|
|
|
|
.mode()
|
2021-04-26 06:22:46 +00:00
|
|
|
.key_bindings()
|
|
|
|
.remaps()
|
2021-04-11 15:35:46 +00:00
|
|
|
.iter()
|
|
|
|
.filter(|(_, t)| t == &&k)
|
|
|
|
.map(|(f, _)| f.clone())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join("|");
|
|
|
|
Row::new([Cell::from(k), Cell::from(remaps), Cell::from(h)].to_vec())
|
|
|
|
}
|
2021-03-31 11:50:37 +00:00
|
|
|
})
|
2021-04-03 06:01:40 +00:00
|
|
|
.collect::<Vec<Row>>();
|
2021-03-04 08:03:14 +00:00
|
|
|
|
2021-04-26 06:22:46 +00:00
|
|
|
let read_only_indicator = if app.config().general().read_only().unwrap_or_default() {
|
2021-04-19 08:49:13 +00:00
|
|
|
"(r)"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
2021-03-04 08:03:14 +00:00
|
|
|
let help_menu = Table::new(help_menu_rows)
|
2021-05-08 03:24:20 +00:00
|
|
|
.block(block(
|
|
|
|
config,
|
|
|
|
format!(" Help [{}{}] ", &app.mode().name(), read_only_indicator),
|
|
|
|
))
|
2021-04-11 15:35:46 +00:00
|
|
|
.widths(&[
|
|
|
|
TuiConstraint::Percentage(20),
|
|
|
|
TuiConstraint::Percentage(20),
|
|
|
|
TuiConstraint::Percentage(60),
|
|
|
|
]);
|
2021-05-08 03:24:20 +00:00
|
|
|
f.render_widget(help_menu, layout_size);
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
2021-03-04 08:03:14 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
fn draw_input_buffer<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
_screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
|
|
|
app: &app::App,
|
|
|
|
_: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config
|
|
|
|
.default()
|
|
|
|
.clone()
|
|
|
|
.extend(panel_config.input_and_logs().clone());
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-04-11 15:35:46 +00:00
|
|
|
let input_buf = Paragraph::new(Spans::from(vec![
|
|
|
|
Span::styled(
|
|
|
|
app.config()
|
2021-04-26 06:22:46 +00:00
|
|
|
.general()
|
|
|
|
.prompt()
|
|
|
|
.format()
|
2021-04-11 15:35:46 +00:00
|
|
|
.clone()
|
|
|
|
.unwrap_or_default(),
|
2021-05-08 03:47:13 +00:00
|
|
|
app.config().general().prompt().style().clone().into(),
|
2021-04-11 15:35:46 +00:00
|
|
|
),
|
|
|
|
Span::raw(app.input_buffer().unwrap_or_else(|| "".into())),
|
|
|
|
Span::styled(
|
|
|
|
app.config()
|
2021-04-26 06:22:46 +00:00
|
|
|
.general()
|
|
|
|
.cursor()
|
|
|
|
.format()
|
2021-04-11 15:35:46 +00:00
|
|
|
.clone()
|
|
|
|
.unwrap_or_default(),
|
2021-05-08 03:47:13 +00:00
|
|
|
app.config().general().cursor().style().clone().into(),
|
2021-04-11 15:35:46 +00:00
|
|
|
),
|
|
|
|
]))
|
2021-05-08 03:24:20 +00:00
|
|
|
.block(block(config, " Input ".into()));
|
|
|
|
f.render_widget(input_buf, layout_size);
|
2021-03-31 11:50:37 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
fn draw_sort_n_filter<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
_screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
|
|
|
app: &app::App,
|
|
|
|
_: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config
|
|
|
|
.default()
|
|
|
|
.clone()
|
|
|
|
.extend(panel_config.sort_and_filter().clone());
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-04-26 06:22:46 +00:00
|
|
|
let ui = app.config().general().sort_and_filter_ui().clone();
|
2021-04-16 19:54:46 +00:00
|
|
|
let filter_by = app.explorer_config().filters();
|
|
|
|
let sort_by = app.explorer_config().sorters();
|
2021-05-08 11:31:40 +00:00
|
|
|
let defaultui = ui.default_identifier();
|
|
|
|
let forwardui = defaultui
|
|
|
|
.clone()
|
|
|
|
.extend(ui.sort_direction_identifiers().forward().clone());
|
|
|
|
let reverseui = defaultui
|
|
|
|
.clone()
|
|
|
|
.extend(ui.sort_direction_identifiers().reverse().clone());
|
2021-04-16 19:54:46 +00:00
|
|
|
|
|
|
|
let mut spans = filter_by
|
|
|
|
.iter()
|
|
|
|
.map(|f| {
|
2021-04-26 06:22:46 +00:00
|
|
|
ui.filter_identifiers()
|
2021-04-25 10:30:22 +00:00
|
|
|
.get(&f.filter())
|
2021-04-16 19:54:46 +00:00
|
|
|
.map(|u| {
|
2021-05-08 11:31:40 +00:00
|
|
|
let ui = defaultui.clone().extend(u.clone());
|
2021-04-16 19:54:46 +00:00
|
|
|
(
|
2021-05-08 03:47:13 +00:00
|
|
|
Span::styled(
|
2021-05-08 11:31:40 +00:00
|
|
|
ui.format().to_owned().unwrap_or_default(),
|
|
|
|
ui.style().clone().into(),
|
2021-05-08 03:47:13 +00:00
|
|
|
),
|
2021-05-08 11:31:40 +00:00
|
|
|
Span::styled(f.input().clone(), ui.style().clone().into()),
|
2021-04-16 19:54:46 +00:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| (Span::raw("f"), Span::raw("")))
|
|
|
|
})
|
|
|
|
.chain(sort_by.iter().map(|s| {
|
2021-05-08 11:31:40 +00:00
|
|
|
let direction = if s.reverse() { &reverseui } else { &forwardui };
|
2021-04-16 19:54:46 +00:00
|
|
|
|
2021-04-26 06:22:46 +00:00
|
|
|
ui.sorter_identifiers()
|
2021-04-25 10:30:22 +00:00
|
|
|
.get(&s.sorter())
|
2021-04-16 19:54:46 +00:00
|
|
|
.map(|u| {
|
2021-05-08 11:31:40 +00:00
|
|
|
let ui = defaultui.clone().extend(u.clone());
|
2021-04-16 19:54:46 +00:00
|
|
|
(
|
2021-05-08 03:47:13 +00:00
|
|
|
Span::styled(
|
2021-05-08 11:31:40 +00:00
|
|
|
ui.format().to_owned().unwrap_or_default(),
|
|
|
|
ui.style().clone().into(),
|
|
|
|
),
|
|
|
|
Span::styled(
|
|
|
|
direction.format().to_owned().unwrap_or_default(),
|
|
|
|
direction.style().clone().into(),
|
2021-05-08 03:47:13 +00:00
|
|
|
),
|
2021-04-16 19:54:46 +00:00
|
|
|
)
|
|
|
|
})
|
2021-05-08 11:31:40 +00:00
|
|
|
.unwrap_or_else(|| (Span::raw("s"), Span::raw("")))
|
2021-04-16 19:54:46 +00:00
|
|
|
}))
|
|
|
|
.zip(std::iter::repeat(Span::styled(
|
2021-04-26 06:22:46 +00:00
|
|
|
ui.separator().format().to_owned().unwrap_or_default(),
|
2021-05-08 03:47:13 +00:00
|
|
|
ui.separator().style().clone().into(),
|
2021-04-16 19:54:46 +00:00
|
|
|
)))
|
|
|
|
.map(|((a, b), c)| vec![a, b, c])
|
|
|
|
.flatten()
|
|
|
|
.collect::<Vec<Span>>();
|
|
|
|
spans.pop();
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
let p = Paragraph::new(Spans::from(spans)).block(block(
|
|
|
|
config,
|
2021-04-16 19:54:46 +00:00
|
|
|
format!(" Sort & filter ({}) ", filter_by.len() + sort_by.len()),
|
|
|
|
));
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
f.render_widget(p, layout_size);
|
2021-04-16 19:54:46 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
fn draw_logs<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
_screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
|
|
|
app: &app::App,
|
|
|
|
_: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config
|
|
|
|
.default()
|
|
|
|
.clone()
|
|
|
|
.extend(panel_config.input_and_logs().clone());
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-05-08 03:24:20 +00:00
|
|
|
let logs_config = app.config().general().logs().clone();
|
2021-04-03 06:01:40 +00:00
|
|
|
let logs = app
|
|
|
|
.logs()
|
|
|
|
.iter()
|
|
|
|
.rev()
|
|
|
|
.take(1)
|
|
|
|
.rev()
|
2021-04-11 16:45:42 +00:00
|
|
|
.map(|l| {
|
2021-04-25 10:30:22 +00:00
|
|
|
let time = l.created_at().format("%r");
|
|
|
|
match l.level() {
|
2021-04-14 02:29:13 +00:00
|
|
|
app::LogLevel::Info => ListItem::new(format!(
|
|
|
|
"{} | {} | {}",
|
|
|
|
&time,
|
2021-05-08 03:24:20 +00:00
|
|
|
&logs_config.info().format().to_owned().unwrap_or_default(),
|
2021-04-25 10:30:22 +00:00
|
|
|
l.message()
|
2021-04-14 02:29:13 +00:00
|
|
|
))
|
2021-05-08 03:47:13 +00:00
|
|
|
.style(logs_config.info().style().clone().into()),
|
2021-04-14 02:29:13 +00:00
|
|
|
app::LogLevel::Success => ListItem::new(format!(
|
|
|
|
"{} | {} | {}",
|
|
|
|
&time,
|
2021-05-08 03:24:20 +00:00
|
|
|
&logs_config
|
|
|
|
.success()
|
|
|
|
.format()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap_or_default(),
|
2021-04-25 10:30:22 +00:00
|
|
|
l.message()
|
2021-04-14 02:29:13 +00:00
|
|
|
))
|
2021-05-08 03:47:13 +00:00
|
|
|
.style(logs_config.success().style().clone().into()),
|
2021-04-14 02:29:13 +00:00
|
|
|
app::LogLevel::Error => ListItem::new(format!(
|
|
|
|
"{} | {} | {}",
|
|
|
|
&time,
|
2021-05-08 03:24:20 +00:00
|
|
|
&logs_config.error().format().to_owned().unwrap_or_default(),
|
2021-04-25 10:30:22 +00:00
|
|
|
l.message()
|
2021-04-14 02:29:13 +00:00
|
|
|
))
|
2021-05-08 03:47:13 +00:00
|
|
|
.style(logs_config.error().style().clone().into()),
|
2021-04-03 06:01:40 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<ListItem>>();
|
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
let logs_list = List::new(logs).block(block(config, format!(" Logs ({}) ", app.logs().len())));
|
2021-04-03 06:01:40 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
f.render_widget(logs_list, layout_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw_nothing<B: Backend>(
|
2021-05-08 11:31:40 +00:00
|
|
|
config: Option<PanelUiConfig>,
|
2021-05-08 03:24:20 +00:00
|
|
|
f: &mut Frame<B>,
|
|
|
|
_screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
2021-05-08 11:31:40 +00:00
|
|
|
app: &app::App,
|
2021-05-08 03:24:20 +00:00
|
|
|
_hb: &Handlebars,
|
|
|
|
) {
|
2021-05-08 11:31:40 +00:00
|
|
|
let panel_config = app.config().general().panel_ui();
|
|
|
|
let default_panel_config = panel_config.default().clone();
|
|
|
|
let config = config
|
|
|
|
.map(|c| default_panel_config.clone().extend(c))
|
|
|
|
.unwrap_or(default_panel_config);
|
2021-05-08 03:24:20 +00:00
|
|
|
let nothing = Paragraph::new("").block(block(config, "".into()));
|
|
|
|
f.render_widget(nothing, layout_size);
|
2021-04-03 06:01:40 +00:00
|
|
|
}
|
|
|
|
|
2021-05-07 17:21:21 +00:00
|
|
|
pub fn draw_layout<B: Backend>(
|
|
|
|
layout: Layout,
|
|
|
|
f: &mut Frame<B>,
|
2021-05-08 03:24:20 +00:00
|
|
|
screen_size: Rect,
|
|
|
|
layout_size: Rect,
|
2021-05-07 17:21:21 +00:00
|
|
|
app: &app::App,
|
|
|
|
hb: &Handlebars,
|
|
|
|
) {
|
|
|
|
match layout {
|
2021-05-08 03:24:20 +00:00
|
|
|
Layout::Nothing(config) => draw_nothing(config, f, screen_size, layout_size, app, hb),
|
|
|
|
Layout::Table(config) => draw_table(config, f, screen_size, layout_size, app, hb),
|
|
|
|
Layout::SortAndFilter(config) => {
|
|
|
|
draw_sort_n_filter(config, f, screen_size, layout_size, app, hb)
|
|
|
|
}
|
|
|
|
Layout::HelpMenu(config) => draw_help_menu(config, f, screen_size, layout_size, app, hb),
|
|
|
|
Layout::Selection(config) => draw_selection(config, f, screen_size, layout_size, app, hb),
|
|
|
|
Layout::InputAndLogs(config) => {
|
2021-05-07 17:21:21 +00:00
|
|
|
if app.input_buffer().is_some() {
|
2021-05-08 03:24:20 +00:00
|
|
|
draw_input_buffer(config, f, screen_size, layout_size, app, hb);
|
2021-05-07 17:21:21 +00:00
|
|
|
} else {
|
2021-05-08 03:24:20 +00:00
|
|
|
draw_logs(config, f, screen_size, layout_size, app, hb);
|
2021-05-07 17:21:21 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
Layout::Horizontal { config, splits } => {
|
|
|
|
let chunks = TuiLayout::default()
|
|
|
|
.direction(Direction::Horizontal)
|
|
|
|
.constraints(
|
|
|
|
config
|
|
|
|
.constraints()
|
2021-05-08 03:24:20 +00:00
|
|
|
.clone()
|
|
|
|
.unwrap_or_default()
|
2021-05-07 17:21:21 +00:00
|
|
|
.iter()
|
2021-05-08 03:24:20 +00:00
|
|
|
.map(|c| c.to_tui(screen_size, layout_size))
|
2021-05-07 17:21:21 +00:00
|
|
|
.collect::<Vec<TuiConstraint>>(),
|
|
|
|
)
|
2021-05-08 00:31:20 +00:00
|
|
|
.horizontal_margin(
|
|
|
|
config
|
|
|
|
.horizontal_margin()
|
|
|
|
.or_else(|| config.margin())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
|
|
|
.vertical_margin(
|
|
|
|
config
|
|
|
|
.vertical_margin()
|
|
|
|
.or_else(|| config.margin())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
2021-05-08 03:24:20 +00:00
|
|
|
.split(layout_size);
|
|
|
|
|
2021-05-07 17:21:21 +00:00
|
|
|
splits
|
|
|
|
.into_iter()
|
2021-05-08 03:24:20 +00:00
|
|
|
.zip(chunks.into_iter())
|
|
|
|
.for_each(|(split, chunk)| draw_layout(split, f, screen_size, chunk, app, hb));
|
2021-05-07 17:21:21 +00:00
|
|
|
}
|
2021-04-03 06:01:40 +00:00
|
|
|
|
2021-05-07 17:21:21 +00:00
|
|
|
Layout::Vertical { config, splits } => {
|
|
|
|
let chunks = TuiLayout::default()
|
|
|
|
.direction(Direction::Vertical)
|
|
|
|
.constraints(
|
|
|
|
config
|
|
|
|
.constraints()
|
2021-05-08 03:24:20 +00:00
|
|
|
.clone()
|
|
|
|
.unwrap_or_default()
|
2021-05-07 17:21:21 +00:00
|
|
|
.iter()
|
2021-05-08 03:24:20 +00:00
|
|
|
.map(|c| c.to_tui(screen_size, layout_size))
|
2021-05-07 17:21:21 +00:00
|
|
|
.collect::<Vec<TuiConstraint>>(),
|
|
|
|
)
|
2021-05-08 00:31:20 +00:00
|
|
|
.horizontal_margin(
|
|
|
|
config
|
|
|
|
.horizontal_margin()
|
|
|
|
.or_else(|| config.margin())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
|
|
|
.vertical_margin(
|
|
|
|
config
|
|
|
|
.vertical_margin()
|
|
|
|
.or_else(|| config.margin())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
2021-05-08 03:24:20 +00:00
|
|
|
.split(layout_size);
|
|
|
|
|
2021-05-07 17:21:21 +00:00
|
|
|
splits
|
|
|
|
.into_iter()
|
2021-05-08 03:24:20 +00:00
|
|
|
.zip(chunks.into_iter())
|
|
|
|
.for_each(|(split, chunk)| draw_layout(split, f, screen_size, chunk, app, hb));
|
2021-05-07 17:21:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-03 06:01:40 +00:00
|
|
|
|
2021-05-07 17:21:21 +00:00
|
|
|
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &app::App, hb: &Handlebars) {
|
2021-05-08 03:24:20 +00:00
|
|
|
let screen_size = f.size();
|
2021-05-07 17:21:21 +00:00
|
|
|
let layout = app.layout().clone();
|
2021-03-15 09:19:30 +00:00
|
|
|
|
2021-05-08 03:24:20 +00:00
|
|
|
draw_layout(layout, f, screen_size, screen_size, app, hb);
|
2021-03-01 11:23:04 +00:00
|
|
|
}
|
2021-04-25 11:06:23 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
use crate::config;
|
|
|
|
use tui::style::Color;
|
2021-05-08 03:47:13 +00:00
|
|
|
|
|
|
|
fn modifier(m: Modifier) -> Option<IndexSet<Modifier>> {
|
|
|
|
let mut x = IndexSet::new();
|
|
|
|
x.insert(m);
|
|
|
|
Some(x)
|
|
|
|
}
|
2021-04-25 11:06:23 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_extend_style() {
|
|
|
|
let a = Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: None,
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: None,
|
2021-04-25 11:06:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let b = Style {
|
|
|
|
fg: None,
|
|
|
|
bg: Some(Color::Blue),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: None,
|
|
|
|
sub_modifiers: modifier(Modifier::Dim),
|
2021-04-25 11:06:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let c = Style {
|
|
|
|
fg: Some(Color::Cyan),
|
|
|
|
bg: Some(Color::Magenta),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::CrossedOut),
|
|
|
|
sub_modifiers: modifier(Modifier::Italic),
|
2021-04-25 11:06:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
a.clone().extend(b.clone()),
|
|
|
|
Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: Some(Color::Blue),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: modifier(Modifier::Dim),
|
2021-04-25 11:06:23 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
b.clone().extend(a.clone()),
|
|
|
|
Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: Some(Color::Blue),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: modifier(Modifier::Dim),
|
2021-04-25 11:06:23 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
a.clone().extend(c.clone()),
|
|
|
|
Style {
|
|
|
|
fg: Some(Color::Cyan),
|
|
|
|
bg: Some(Color::Magenta),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::CrossedOut),
|
|
|
|
sub_modifiers: modifier(Modifier::Italic),
|
2021-04-25 11:06:23 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
c.clone().extend(a.clone()),
|
|
|
|
Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: Some(Color::Magenta),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: modifier(Modifier::Italic),
|
2021-04-25 11:06:23 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_extend_ui_config() {
|
|
|
|
let a = config::UiConfig {
|
|
|
|
prefix: Some("a".to_string()),
|
|
|
|
suffix: None,
|
|
|
|
style: Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: None,
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: None,
|
2021-04-25 11:06:23 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let b = config::UiConfig {
|
|
|
|
prefix: None,
|
|
|
|
suffix: Some("b".to_string()),
|
|
|
|
style: Style {
|
|
|
|
fg: None,
|
|
|
|
bg: Some(Color::Blue),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: None,
|
|
|
|
sub_modifiers: modifier(Modifier::Dim),
|
2021-04-25 11:06:23 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let c = config::UiConfig {
|
|
|
|
prefix: Some("cp".to_string()),
|
|
|
|
suffix: Some("cs".to_string()),
|
|
|
|
style: Style {
|
|
|
|
fg: Some(Color::Cyan),
|
|
|
|
bg: Some(Color::Magenta),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::CrossedOut),
|
|
|
|
sub_modifiers: modifier(Modifier::Italic),
|
2021-04-25 11:06:23 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
a.clone().extend(b.clone()),
|
|
|
|
config::UiConfig {
|
|
|
|
prefix: Some("a".to_string()),
|
|
|
|
suffix: Some("b".to_string()),
|
|
|
|
style: Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: Some(Color::Blue),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: modifier(Modifier::Dim),
|
2021-04-25 11:06:23 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
b.clone().extend(a.clone()),
|
|
|
|
config::UiConfig {
|
|
|
|
prefix: Some("a".to_string()),
|
|
|
|
suffix: Some("b".to_string()),
|
|
|
|
style: Style {
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
bg: Some(Color::Blue),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::Bold),
|
|
|
|
sub_modifiers: modifier(Modifier::Dim),
|
2021-04-25 11:06:23 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
a.clone().extend(c.clone()),
|
|
|
|
config::UiConfig {
|
|
|
|
prefix: Some("cp".to_string()),
|
|
|
|
suffix: Some("cs".to_string()),
|
|
|
|
style: Style {
|
|
|
|
fg: Some(Color::Cyan),
|
|
|
|
bg: Some(Color::Magenta),
|
2021-05-08 03:47:13 +00:00
|
|
|
add_modifiers: modifier(Modifier::CrossedOut),
|
|
|
|
sub_modifiers: modifier(Modifier::Italic),
|
2021-04-25 11:06:23 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|