|
|
|
@ -60,6 +60,32 @@ pub fn string_to_text<'a>(string: String) -> Text<'a> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct Rect {
|
|
|
|
|
x: u16,
|
|
|
|
|
y: u16,
|
|
|
|
|
height: u16,
|
|
|
|
|
width: u16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<TuiRect> for Rect {
|
|
|
|
|
fn from(tui: TuiRect) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
x: tui.x,
|
|
|
|
|
y: tui.y,
|
|
|
|
|
height: tui.height,
|
|
|
|
|
width: tui.width,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
|
|
|
pub struct ContentRendererArg {
|
|
|
|
|
pub app: app::LuaContextLight,
|
|
|
|
|
pub screen_size: Rect,
|
|
|
|
|
pub layout_size: Rect,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct LayoutOptions {
|
|
|
|
@ -81,7 +107,7 @@ impl LayoutOptions {
|
|
|
|
|
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.to_owned().or(self.constraints);
|
|
|
|
|
self.constraints = other.constraints.clone().or(self.constraints);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -154,7 +180,7 @@ impl Layout {
|
|
|
|
|
},
|
|
|
|
|
) => Self::Horizontal {
|
|
|
|
|
config: sconfig.extend(oconfig),
|
|
|
|
|
splits: osplits.to_owned(),
|
|
|
|
|
splits: osplits.clone(),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
@ -168,9 +194,9 @@ impl Layout {
|
|
|
|
|
},
|
|
|
|
|
) => Self::Vertical {
|
|
|
|
|
config: sconfig.extend(oconfig),
|
|
|
|
|
splits: osplits.to_owned(),
|
|
|
|
|
splits: osplits.clone(),
|
|
|
|
|
},
|
|
|
|
|
(_, other) => other.to_owned(),
|
|
|
|
|
(_, other) => other.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -192,7 +218,7 @@ impl Layout {
|
|
|
|
|
},
|
|
|
|
|
other => {
|
|
|
|
|
if other == *target {
|
|
|
|
|
replacement.to_owned()
|
|
|
|
|
replacement.clone()
|
|
|
|
|
} else {
|
|
|
|
|
other
|
|
|
|
|
}
|
|
|
|
@ -364,14 +390,10 @@ impl Style {
|
|
|
|
|
pub fn extend(mut self, other: &Self) -> Self {
|
|
|
|
|
self.fg = other.fg.or(self.fg);
|
|
|
|
|
self.bg = other.bg.or(self.bg);
|
|
|
|
|
self.add_modifiers = extend_optional_modifiers(
|
|
|
|
|
self.add_modifiers,
|
|
|
|
|
other.add_modifiers.to_owned(),
|
|
|
|
|
);
|
|
|
|
|
self.sub_modifiers = extend_optional_modifiers(
|
|
|
|
|
self.sub_modifiers,
|
|
|
|
|
other.sub_modifiers.to_owned(),
|
|
|
|
|
);
|
|
|
|
|
self.add_modifiers =
|
|
|
|
|
extend_optional_modifiers(self.add_modifiers, other.add_modifiers.clone());
|
|
|
|
|
self.sub_modifiers =
|
|
|
|
|
extend_optional_modifiers(self.sub_modifiers, other.sub_modifiers.clone());
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -594,12 +616,12 @@ pub struct ResolvedNodeUiMetadata {
|
|
|
|
|
impl From<ResolvedNode> for ResolvedNodeUiMetadata {
|
|
|
|
|
fn from(node: ResolvedNode) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
absolute_path: node.absolute_path.to_owned(),
|
|
|
|
|
extension: node.extension.to_owned(),
|
|
|
|
|
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.to_owned(),
|
|
|
|
|
mime_essence: node.mime_essence.clone(),
|
|
|
|
|
size: node.size,
|
|
|
|
|
human_size: node.human_size,
|
|
|
|
|
created: node.created,
|
|
|
|
@ -663,21 +685,21 @@ impl NodeUiMetadata {
|
|
|
|
|
style: Style,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
parent: node.parent.to_owned(),
|
|
|
|
|
relative_path: node.relative_path.to_owned(),
|
|
|
|
|
absolute_path: node.absolute_path.to_owned(),
|
|
|
|
|
extension: node.extension.to_owned(),
|
|
|
|
|
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.to_owned(),
|
|
|
|
|
mime_essence: node.mime_essence.clone(),
|
|
|
|
|
size: node.size,
|
|
|
|
|
human_size: node.human_size.to_owned(),
|
|
|
|
|
permissions: node.permissions.to_owned(),
|
|
|
|
|
canonical: node.canonical.to_owned().map(ResolvedNode::into),
|
|
|
|
|
symlink: node.symlink.to_owned().map(ResolvedNode::into),
|
|
|
|
|
human_size: node.human_size.clone(),
|
|
|
|
|
permissions: node.permissions,
|
|
|
|
|
canonical: node.canonical.clone().map(ResolvedNode::into),
|
|
|
|
|
symlink: node.symlink.clone().map(ResolvedNode::into),
|
|
|
|
|
created: node.created,
|
|
|
|
|
last_modified: node.last_modified,
|
|
|
|
|
uid: node.uid,
|
|
|
|
@ -703,7 +725,7 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
|
|
|
|
|
.borders(TuiBorders::from_bits_truncate(
|
|
|
|
|
config
|
|
|
|
|
.borders
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|b| b.bits())
|
|
|
|
@ -718,37 +740,39 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
|
|
|
|
|
.border_style(config.border_style)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_table(
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &mut app::App,
|
|
|
|
|
lua: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
pub struct UI<'lua> {
|
|
|
|
|
pub lua: &'lua Lua,
|
|
|
|
|
pub screen_size: TuiRect,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'lua> UI<'lua> {
|
|
|
|
|
pub fn new(lua: &'lua Lua) -> Self {
|
|
|
|
|
let screen_size = Default::default();
|
|
|
|
|
Self { lua, screen_size }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UI<'_> {
|
|
|
|
|
fn draw_table(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
let config = panel_config.default.to_owned().extend(&panel_config.table);
|
|
|
|
|
let app_config = app.config.to_owned();
|
|
|
|
|
let config = panel_config.default.clone().extend(&panel_config.table);
|
|
|
|
|
let app_config = app.config.clone();
|
|
|
|
|
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();
|
|
|
|
|
let row_style = app_config.general.table.row.style.to_owned();
|
|
|
|
|
let row_style = app_config.general.table.row.style.clone();
|
|
|
|
|
|
|
|
|
|
let rows = app
|
|
|
|
|
.directory_buffer
|
|
|
|
|
.as_mut()
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|dir| {
|
|
|
|
|
dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows(
|
|
|
|
|
height,
|
|
|
|
|
dir.total,
|
|
|
|
|
app.config.general.vimlike_scrolling,
|
|
|
|
|
);
|
|
|
|
|
dir.nodes
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.skip(dir.scroll_state.skipped_rows)
|
|
|
|
|
.skip(height * (dir.focus / height.max(1)))
|
|
|
|
|
.take(height)
|
|
|
|
|
.map(|(index, node)| {
|
|
|
|
|
let is_focused = dir.scroll_state.get_focus() == index;
|
|
|
|
|
let is_focused = dir.focus == index;
|
|
|
|
|
|
|
|
|
|
let is_selected = app
|
|
|
|
|
.selection
|
|
|
|
@ -762,7 +786,7 @@ fn draw_table(
|
|
|
|
|
.general
|
|
|
|
|
.table
|
|
|
|
|
.tree
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.map(|t| {
|
|
|
|
|
if is_last {
|
|
|
|
|
t.2.format
|
|
|
|
@ -777,35 +801,31 @@ fn draw_table(
|
|
|
|
|
let node_type = app_config.node_types.get(node);
|
|
|
|
|
|
|
|
|
|
let (relative_index, is_before_focus, is_after_focus) =
|
|
|
|
|
match dir.scroll_state.get_focus().cmp(&index) {
|
|
|
|
|
Ordering::Greater => {
|
|
|
|
|
(dir.scroll_state.get_focus() - index, true, false)
|
|
|
|
|
}
|
|
|
|
|
Ordering::Less => {
|
|
|
|
|
(index - dir.scroll_state.get_focus(), false, true)
|
|
|
|
|
}
|
|
|
|
|
match dir.focus.cmp(&index) {
|
|
|
|
|
Ordering::Greater => (dir.focus - index, true, false),
|
|
|
|
|
Ordering::Less => (index - dir.focus, false, true),
|
|
|
|
|
Ordering::Equal => (0, false, false),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (mut prefix, mut suffix, mut style) = {
|
|
|
|
|
let ui = app_config.general.default_ui.to_owned();
|
|
|
|
|
let ui = app_config.general.default_ui.clone();
|
|
|
|
|
(ui.prefix, ui.suffix, ui.style.extend(&node_type.style))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if is_focused && is_selected {
|
|
|
|
|
let ui = app_config.general.focus_selection_ui.to_owned();
|
|
|
|
|
prefix = ui.prefix.to_owned().or(prefix);
|
|
|
|
|
suffix = ui.suffix.to_owned().or(suffix);
|
|
|
|
|
let ui = app_config.general.focus_selection_ui.clone();
|
|
|
|
|
prefix = ui.prefix.clone().or(prefix);
|
|
|
|
|
suffix = ui.suffix.clone().or(suffix);
|
|
|
|
|
style = style.extend(&ui.style);
|
|
|
|
|
} else if is_selected {
|
|
|
|
|
let ui = app_config.general.selection_ui.to_owned();
|
|
|
|
|
prefix = ui.prefix.to_owned().or(prefix);
|
|
|
|
|
suffix = ui.suffix.to_owned().or(suffix);
|
|
|
|
|
let ui = app_config.general.selection_ui.clone();
|
|
|
|
|
prefix = ui.prefix.clone().or(prefix);
|
|
|
|
|
suffix = ui.suffix.clone().or(suffix);
|
|
|
|
|
style = style.extend(&ui.style);
|
|
|
|
|
} else if is_focused {
|
|
|
|
|
let ui = app_config.general.focus_ui.to_owned();
|
|
|
|
|
prefix = ui.prefix.to_owned().or(prefix);
|
|
|
|
|
suffix = ui.suffix.to_owned().or(suffix);
|
|
|
|
|
let ui = app_config.general.focus_ui.clone();
|
|
|
|
|
prefix = ui.prefix.clone().or(prefix);
|
|
|
|
|
suffix = ui.suffix.clone().or(suffix);
|
|
|
|
|
style = style.extend(&ui.style);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -825,21 +845,21 @@ fn draw_table(
|
|
|
|
|
style,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let cols = lua::serialize::<NodeUiMetadata>(lua, &meta)
|
|
|
|
|
let cols = lua::serialize::<NodeUiMetadata>(self.lua, &meta)
|
|
|
|
|
.map(|v| {
|
|
|
|
|
app_config
|
|
|
|
|
.general
|
|
|
|
|
.table
|
|
|
|
|
.row
|
|
|
|
|
.cols
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|c| {
|
|
|
|
|
c.format.as_ref().map(|f| {
|
|
|
|
|
let out = lua::call(lua, f, v.clone())
|
|
|
|
|
let out = lua::call(self.lua, f, v.clone())
|
|
|
|
|
.unwrap_or_else(|e| format!("{e:?}"));
|
|
|
|
|
(string_to_text(out), c.style.to_owned())
|
|
|
|
|
(string_to_text(out), c.style.clone())
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<(Text, Style)>>()
|
|
|
|
@ -849,7 +869,7 @@ fn draw_table(
|
|
|
|
|
.map(|(text, style)| Cell::from(text).style(style))
|
|
|
|
|
.collect::<Vec<Cell>>();
|
|
|
|
|
|
|
|
|
|
Row::new(cols).style(row_style.to_owned())
|
|
|
|
|
Row::new(cols).style(row_style.clone())
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<Row>>()
|
|
|
|
|
})
|
|
|
|
@ -859,10 +879,10 @@ fn draw_table(
|
|
|
|
|
.general
|
|
|
|
|
.table
|
|
|
|
|
.col_widths
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|c| c.to_tui(screen_size, layout_size))
|
|
|
|
|
.map(|c| c.to_tui(self.screen_size, layout_size))
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let pwd = if let Some(vroot) = app.vroot.as_ref() {
|
|
|
|
@ -884,49 +904,40 @@ fn draw_table(
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let table = Table::new(rows, table_constraints)
|
|
|
|
|
.style(app_config.general.table.style.to_owned())
|
|
|
|
|
.highlight_style(app_config.general.focus_ui.style.to_owned())
|
|
|
|
|
.style(app_config.general.table.style.clone())
|
|
|
|
|
.highlight_style(app_config.general.focus_ui.style.clone())
|
|
|
|
|
.column_spacing(app_config.general.table.col_spacing.unwrap_or_default())
|
|
|
|
|
.block(block(
|
|
|
|
|
config,
|
|
|
|
|
format!(" {vroot_indicator}/{pwd} {node_count}"),
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
let table = table.to_owned().header(
|
|
|
|
|
let table = table.clone().header(
|
|
|
|
|
Row::new(
|
|
|
|
|
app_config
|
|
|
|
|
.general
|
|
|
|
|
.table
|
|
|
|
|
.header
|
|
|
|
|
.cols
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|c| {
|
|
|
|
|
Cell::from(c.format.to_owned().unwrap_or_default())
|
|
|
|
|
.style(c.style.to_owned())
|
|
|
|
|
Cell::from(c.format.clone().unwrap_or_default())
|
|
|
|
|
.style(c.style.clone())
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<Cell>>(),
|
|
|
|
|
)
|
|
|
|
|
.height(header_height)
|
|
|
|
|
.style(app_config.general.table.header.style.to_owned()),
|
|
|
|
|
.style(app_config.general.table.header.style.clone()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
f.render_widget(table, layout_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_selection(
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
_screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
lua: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
fn draw_selection(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
let config = panel_config
|
|
|
|
|
.default
|
|
|
|
|
.to_owned()
|
|
|
|
|
.extend(&panel_config.selection);
|
|
|
|
|
let config = panel_config.default.clone().extend(&panel_config.selection);
|
|
|
|
|
|
|
|
|
|
let selection_count = app.selection.len();
|
|
|
|
|
|
|
|
|
@ -945,15 +956,15 @@ fn draw_selection(
|
|
|
|
|
.format
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|f| {
|
|
|
|
|
lua::serialize::<Node>(lua, n)
|
|
|
|
|
.and_then(|n| lua::call(lua, f, n))
|
|
|
|
|
lua::serialize::<Node>(self.lua, n)
|
|
|
|
|
.and_then(|n| lua::call(self.lua, f, n))
|
|
|
|
|
.unwrap_or_else(|e| format!("{e:?}"))
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_else(|| n.absolute_path.clone());
|
|
|
|
|
string_to_text(out)
|
|
|
|
|
})
|
|
|
|
|
.map(|i| {
|
|
|
|
|
ListItem::new(i).style(app.config.general.selection.item.style.to_owned())
|
|
|
|
|
ListItem::new(i).style(app.config.general.selection.item.style.clone())
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
@ -970,19 +981,10 @@ fn draw_selection(
|
|
|
|
|
f.render_widget(selection_list, layout_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_help_menu(
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
_screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
_: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
fn draw_help_menu(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
|
|
|
|
|
let config = panel_config
|
|
|
|
|
.default
|
|
|
|
|
.to_owned()
|
|
|
|
|
.extend(&panel_config.help_menu);
|
|
|
|
|
let config = panel_config.default.clone().extend(&panel_config.help_menu);
|
|
|
|
|
|
|
|
|
|
let help_menu_rows = app
|
|
|
|
|
.mode
|
|
|
|
@ -994,7 +996,8 @@ fn draw_help_menu(
|
|
|
|
|
if app.config.general.hide_remaps_in_help_menu {
|
|
|
|
|
[Cell::from(k), Cell::from(h)].to_vec()
|
|
|
|
|
} else {
|
|
|
|
|
[Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)].to_vec()
|
|
|
|
|
[Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)]
|
|
|
|
|
.to_vec()
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
@ -1017,17 +1020,16 @@ fn draw_help_menu(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_input_buffer(
|
|
|
|
|
&mut self,
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
_screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
_: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
if let Some(input) = app.input.buffer.as_ref() {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
let config = panel_config
|
|
|
|
|
.default
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&panel_config.input_and_logs);
|
|
|
|
|
|
|
|
|
|
let cursor_offset_left = config
|
|
|
|
@ -1050,8 +1052,8 @@ fn draw_input_buffer(
|
|
|
|
|
|
|
|
|
|
let input_buf = Paragraph::new(Line::from(vec![
|
|
|
|
|
Span::styled(
|
|
|
|
|
app.input.prompt.to_owned(),
|
|
|
|
|
app.config.general.prompt.style.to_owned(),
|
|
|
|
|
app.input.prompt.clone(),
|
|
|
|
|
app.config.general.prompt.style.clone(),
|
|
|
|
|
),
|
|
|
|
|
Span::raw(input.value()),
|
|
|
|
|
]))
|
|
|
|
@ -1079,35 +1081,34 @@ fn draw_input_buffer(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_sort_n_filter(
|
|
|
|
|
&mut self,
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
_screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
_: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
let config = panel_config
|
|
|
|
|
.default
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&panel_config.sort_and_filter);
|
|
|
|
|
let ui = app.config.general.sort_and_filter_ui.to_owned();
|
|
|
|
|
let ui = app.config.general.sort_and_filter_ui.clone();
|
|
|
|
|
let filter_by: &IndexSet<NodeFilterApplicable> = &app.explorer_config.filters;
|
|
|
|
|
let sort_by: &IndexSet<NodeSorterApplicable> = &app.explorer_config.sorters;
|
|
|
|
|
let search = app.explorer_config.searcher.as_ref();
|
|
|
|
|
|
|
|
|
|
let defaultui = &ui.default_identifier;
|
|
|
|
|
let forwardui = defaultui
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&ui.sort_direction_identifiers.forward);
|
|
|
|
|
let reverseui = defaultui
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&ui.sort_direction_identifiers.reverse);
|
|
|
|
|
|
|
|
|
|
let orderedui = defaultui
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&ui.search_direction_identifiers.ordered);
|
|
|
|
|
let unorderedui = defaultui
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&ui.search_direction_identifiers.unordered);
|
|
|
|
|
|
|
|
|
|
let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false);
|
|
|
|
@ -1118,13 +1119,13 @@ fn draw_sort_n_filter(
|
|
|
|
|
ui.filter_identifiers
|
|
|
|
|
.get(&f.filter)
|
|
|
|
|
.map(|u| {
|
|
|
|
|
let ui = defaultui.to_owned().extend(u);
|
|
|
|
|
let ui = defaultui.clone().extend(u);
|
|
|
|
|
(
|
|
|
|
|
Span::styled(
|
|
|
|
|
ui.format.to_owned().unwrap_or_default(),
|
|
|
|
|
ui.style.to_owned(),
|
|
|
|
|
ui.format.clone().unwrap_or_default(),
|
|
|
|
|
ui.style.clone(),
|
|
|
|
|
),
|
|
|
|
|
Span::styled(f.input.to_owned(), ui.style),
|
|
|
|
|
Span::styled(f.input.clone(), ui.style),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or((Span::raw("f"), Span::raw("")))
|
|
|
|
@ -1138,7 +1139,7 @@ fn draw_sort_n_filter(
|
|
|
|
|
} else {
|
|
|
|
|
&orderedui
|
|
|
|
|
};
|
|
|
|
|
let ui = defaultui.to_owned().extend(u);
|
|
|
|
|
let ui = defaultui.clone().extend(u);
|
|
|
|
|
let f = ui
|
|
|
|
|
.format
|
|
|
|
|
.as_ref()
|
|
|
|
@ -1147,8 +1148,8 @@ fn draw_sort_n_filter(
|
|
|
|
|
(
|
|
|
|
|
Span::styled(f, ui.style),
|
|
|
|
|
Span::styled(
|
|
|
|
|
direction.format.to_owned().unwrap_or_default(),
|
|
|
|
|
direction.style.to_owned(),
|
|
|
|
|
direction.format.clone().unwrap_or_default(),
|
|
|
|
|
direction.style.clone(),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
@ -1162,15 +1163,15 @@ fn draw_sort_n_filter(
|
|
|
|
|
ui.sorter_identifiers
|
|
|
|
|
.get(&s.sorter)
|
|
|
|
|
.map(|u| {
|
|
|
|
|
let ui = defaultui.to_owned().extend(u);
|
|
|
|
|
let ui = defaultui.clone().extend(u);
|
|
|
|
|
(
|
|
|
|
|
Span::styled(
|
|
|
|
|
ui.format.to_owned().unwrap_or_default(),
|
|
|
|
|
ui.format.clone().unwrap_or_default(),
|
|
|
|
|
ui.style,
|
|
|
|
|
),
|
|
|
|
|
Span::styled(
|
|
|
|
|
direction.format.to_owned().unwrap_or_default(),
|
|
|
|
|
direction.style.to_owned(),
|
|
|
|
|
direction.format.clone().unwrap_or_default(),
|
|
|
|
|
direction.style.clone(),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
@ -1179,8 +1180,8 @@ fn draw_sort_n_filter(
|
|
|
|
|
.take(if !is_ordered_search { sort_by.len() } else { 0 }),
|
|
|
|
|
)
|
|
|
|
|
.zip(std::iter::repeat(Span::styled(
|
|
|
|
|
ui.separator.format.to_owned().unwrap_or_default(),
|
|
|
|
|
ui.separator.style.to_owned(),
|
|
|
|
|
ui.separator.format.clone().unwrap_or_default(),
|
|
|
|
|
ui.separator.style.clone(),
|
|
|
|
|
)))
|
|
|
|
|
.flat_map(|((a, b), c)| vec![a, b, c])
|
|
|
|
|
.collect::<Vec<Span>>();
|
|
|
|
@ -1200,19 +1201,13 @@ fn draw_sort_n_filter(
|
|
|
|
|
f.render_widget(p, layout_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_logs(
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
_screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
_: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
fn draw_logs(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
let config = panel_config
|
|
|
|
|
.default
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.extend(&panel_config.input_and_logs);
|
|
|
|
|
let logs_config = app.config.general.logs.to_owned();
|
|
|
|
|
let logs_config = app.config.general.logs.clone();
|
|
|
|
|
let logs = if app.logs_hidden {
|
|
|
|
|
vec![]
|
|
|
|
|
} else {
|
|
|
|
@ -1222,7 +1217,8 @@ fn draw_logs(
|
|
|
|
|
.take(layout_size.height as usize)
|
|
|
|
|
.map(|log| {
|
|
|
|
|
let fd = format_description!("[hour]:[minute]:[second]");
|
|
|
|
|
let time = log.created_at.format(fd).unwrap_or_else(|_| "when?".into());
|
|
|
|
|
let time =
|
|
|
|
|
log.created_at.format(fd).unwrap_or_else(|_| "when?".into());
|
|
|
|
|
let cfg = match log.level {
|
|
|
|
|
app::LogLevel::Info => &logs_config.info,
|
|
|
|
|
app::LogLevel::Warning => &logs_config.warning,
|
|
|
|
@ -1231,7 +1227,7 @@ fn draw_logs(
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let prefix =
|
|
|
|
|
format!("{time}|{0}", cfg.format.to_owned().unwrap_or_default());
|
|
|
|
|
format!("{time}|{0}", cfg.format.clone().unwrap_or_default());
|
|
|
|
|
|
|
|
|
|
let padding = " ".repeat(prefix.chars().count());
|
|
|
|
|
|
|
|
|
@ -1250,7 +1246,7 @@ fn draw_logs(
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join("\n");
|
|
|
|
|
|
|
|
|
|
ListItem::new(txt).style(cfg.style.to_owned())
|
|
|
|
|
ListItem::new(txt).style(cfg.style.clone())
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<ListItem>>()
|
|
|
|
|
};
|
|
|
|
@ -1276,50 +1272,42 @@ fn draw_logs(
|
|
|
|
|
f.render_widget(logs_list, layout_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw_nothing(
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
_screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
_lua: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
fn draw_nothing(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
|
|
|
|
|
let panel_config = &app.config.general.panel_ui;
|
|
|
|
|
let config = panel_config.default.to_owned();
|
|
|
|
|
let config = panel_config.default.clone();
|
|
|
|
|
let nothing = Paragraph::new("").block(block(config, "".into()));
|
|
|
|
|
f.render_widget(nothing, layout_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw_dynamic(
|
|
|
|
|
fn draw_dynamic(
|
|
|
|
|
&mut self,
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &mut app::App,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
func: &str,
|
|
|
|
|
lua: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
let ctx = ContentRendererArg {
|
|
|
|
|
app: app.to_lua_ctx_light(),
|
|
|
|
|
layout_size: layout_size.into(),
|
|
|
|
|
screen_size: screen_size.into(),
|
|
|
|
|
screen_size: self.screen_size.into(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let panel: CustomPanel = lua::serialize(lua, &ctx)
|
|
|
|
|
.and_then(|arg| lua::call(lua, func, arg))
|
|
|
|
|
let panel: CustomPanel = lua::serialize(self.lua, &ctx)
|
|
|
|
|
.and_then(|arg| lua::call(self.lua, func, arg))
|
|
|
|
|
.unwrap_or_else(|e| CustomPanel::CustomParagraph {
|
|
|
|
|
ui: app.config.general.panel_ui.default.clone(),
|
|
|
|
|
body: format!("{e:?}"),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
draw_static(f, screen_size, layout_size, app, panel, lua);
|
|
|
|
|
self.draw_static(f, layout_size, app, panel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw_static(
|
|
|
|
|
fn draw_static(
|
|
|
|
|
&mut self,
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &mut app::App,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
panel: CustomPanel,
|
|
|
|
|
_lua: &Lua,
|
|
|
|
|
) {
|
|
|
|
|
let defaultui = app.config.general.panel_ui.default.clone();
|
|
|
|
|
match panel {
|
|
|
|
@ -1364,7 +1352,7 @@ pub fn draw_static(
|
|
|
|
|
|
|
|
|
|
let widths = widths
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|w| w.to_tui(screen_size, layout_size))
|
|
|
|
|
.map(|w| w.to_tui(self.screen_size, layout_size))
|
|
|
|
|
.collect::<Vec<TuiConstraint>>();
|
|
|
|
|
|
|
|
|
|
let content = Table::new(rows, widths)
|
|
|
|
@ -1375,68 +1363,35 @@ pub fn draw_static(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CustomPanel::CustomLayout(layout) => {
|
|
|
|
|
draw_layout(layout, f, screen_size, layout_size, app, _lua);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct Rect {
|
|
|
|
|
x: u16,
|
|
|
|
|
y: u16,
|
|
|
|
|
height: u16,
|
|
|
|
|
width: u16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<TuiRect> for Rect {
|
|
|
|
|
fn from(tui: TuiRect) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
x: tui.x,
|
|
|
|
|
y: tui.y,
|
|
|
|
|
height: tui.height,
|
|
|
|
|
width: tui.width,
|
|
|
|
|
self.draw_layout(layout, f, layout_size, app);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
|
|
|
pub struct ContentRendererArg {
|
|
|
|
|
pub app: app::LuaContextLight,
|
|
|
|
|
pub screen_size: Rect,
|
|
|
|
|
pub layout_size: Rect,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw_layout(
|
|
|
|
|
fn draw_layout(
|
|
|
|
|
&mut self,
|
|
|
|
|
layout: Layout,
|
|
|
|
|
f: &mut Frame,
|
|
|
|
|
screen_size: TuiRect,
|
|
|
|
|
layout_size: TuiRect,
|
|
|
|
|
app: &mut app::App,
|
|
|
|
|
lua: &Lua,
|
|
|
|
|
app: &app::App,
|
|
|
|
|
) {
|
|
|
|
|
match layout {
|
|
|
|
|
Layout::Nothing => draw_nothing(f, screen_size, layout_size, app, lua),
|
|
|
|
|
Layout::Table => draw_table(f, screen_size, layout_size, app, lua),
|
|
|
|
|
Layout::SortAndFilter => {
|
|
|
|
|
draw_sort_n_filter(f, screen_size, layout_size, app, lua)
|
|
|
|
|
}
|
|
|
|
|
Layout::HelpMenu => draw_help_menu(f, screen_size, layout_size, app, lua),
|
|
|
|
|
Layout::Selection => draw_selection(f, screen_size, layout_size, app, lua),
|
|
|
|
|
Layout::Nothing => self.draw_nothing(f, layout_size, app),
|
|
|
|
|
Layout::Table => self.draw_table(f, layout_size, app),
|
|
|
|
|
Layout::SortAndFilter => self.draw_sort_n_filter(f, layout_size, app),
|
|
|
|
|
Layout::HelpMenu => self.draw_help_menu(f, layout_size, app),
|
|
|
|
|
Layout::Selection => self.draw_selection(f, layout_size, app),
|
|
|
|
|
Layout::InputAndLogs => {
|
|
|
|
|
if app.input.buffer.is_some() {
|
|
|
|
|
draw_input_buffer(f, screen_size, layout_size, app, lua);
|
|
|
|
|
self.draw_input_buffer(f, layout_size, app);
|
|
|
|
|
} else {
|
|
|
|
|
draw_logs(f, screen_size, layout_size, app, lua);
|
|
|
|
|
self.draw_logs(f, layout_size, app);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
Layout::Static(panel) => {
|
|
|
|
|
draw_static(f, screen_size, layout_size, app, *panel, lua)
|
|
|
|
|
}
|
|
|
|
|
Layout::Dynamic(ref func) => {
|
|
|
|
|
draw_dynamic(f, screen_size, layout_size, app, func, lua)
|
|
|
|
|
}
|
|
|
|
|
Layout::Static(panel) => self.draw_static(f, layout_size, app, *panel),
|
|
|
|
|
Layout::Dynamic(ref func) => self.draw_dynamic(f, layout_size, app, func),
|
|
|
|
|
Layout::CustomContent(content) => {
|
|
|
|
|
draw_custom_content(f, screen_size, layout_size, app, *content, lua)
|
|
|
|
|
draw_custom_content(self, f, layout_size, app, *content)
|
|
|
|
|
}
|
|
|
|
|
Layout::Horizontal { config, splits } => {
|
|
|
|
|
let chunks = TuiLayout::default()
|
|
|
|
@ -1444,10 +1399,10 @@ pub fn draw_layout(
|
|
|
|
|
.constraints(
|
|
|
|
|
config
|
|
|
|
|
.constraints
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|c| c.to_tui(screen_size, layout_size))
|
|
|
|
|
.map(|c| c.to_tui(self.screen_size, layout_size))
|
|
|
|
|
.collect::<Vec<TuiConstraint>>(),
|
|
|
|
|
)
|
|
|
|
|
.horizontal_margin(
|
|
|
|
@ -1464,9 +1419,7 @@ pub fn draw_layout(
|
|
|
|
|
splits
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(chunks.iter())
|
|
|
|
|
.for_each(|(split, chunk)| {
|
|
|
|
|
draw_layout(split, f, screen_size, *chunk, app, lua)
|
|
|
|
|
});
|
|
|
|
|
.for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Layout::Vertical { config, splits } => {
|
|
|
|
@ -1475,10 +1428,10 @@ pub fn draw_layout(
|
|
|
|
|
.constraints(
|
|
|
|
|
config
|
|
|
|
|
.constraints
|
|
|
|
|
.to_owned()
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|c| c.to_tui(screen_size, layout_size))
|
|
|
|
|
.map(|c| c.to_tui(self.screen_size, layout_size))
|
|
|
|
|
.collect::<Vec<TuiConstraint>>(),
|
|
|
|
|
)
|
|
|
|
|
.horizontal_margin(
|
|
|
|
@ -1495,18 +1448,16 @@ pub fn draw_layout(
|
|
|
|
|
splits
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(chunks.iter())
|
|
|
|
|
.for_each(|(split, chunk)| {
|
|
|
|
|
draw_layout(split, f, screen_size, *chunk, app, lua)
|
|
|
|
|
});
|
|
|
|
|
.for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw(f: &mut Frame, app: &mut app::App, lua: &Lua) {
|
|
|
|
|
let screen_size = f.size();
|
|
|
|
|
let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned();
|
|
|
|
|
|
|
|
|
|
draw_layout(layout, f, screen_size, screen_size, app, lua);
|
|
|
|
|
pub fn draw(&mut self, f: &mut Frame, app: &app::App) {
|
|
|
|
|
self.screen_size = f.size();
|
|
|
|
|
let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).clone();
|
|
|
|
|
self.draw_layout(layout, f, self.screen_size, app);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
@ -1543,7 +1494,7 @@ mod tests {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
a.to_owned().extend(&b),
|
|
|
|
|
a.clone().extend(&b),
|
|
|
|
|
Style {
|
|
|
|
|
fg: Some(Color::Red),
|
|
|
|
|
bg: Some(Color::Blue),
|
|
|
|
@ -1563,7 +1514,7 @@ mod tests {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
a.to_owned().extend(&c),
|
|
|
|
|
a.clone().extend(&c),
|
|
|
|
|
Style {
|
|
|
|
|
fg: Some(Color::Cyan),
|
|
|
|
|
bg: Some(Color::Magenta),
|
|
|
|
|