Implement preview pane

Display preview in the selection pane when selection is empty.

Adds:

- xplr.util.preview
- xplr.fn.builtin.fmt_general_preview_renderer
pull/690/head
Arijit Basu 4 months ago
parent 2ce7a91b71
commit 216a86f593
No known key found for this signature in database
GPG Key ID: 0F8EF5258DC38077

@ -47,6 +47,20 @@ that can be overwritten.
Tries to auto complete the path in the input buffer
#### xplr.fn.builtin.fmt_general_selection_item
Formats each node in the selection
#### xplr.fn.builtin.fmt_general_preview_renderer
Renders the focused node in preview pane
See: [xplr.util.preview](https://xplr.dev/en/xplr.util#xplrutilpreview)
The focused node is passed as the node value, and layout_hight is passed
dynamically.
When there is no item under focus, the node value will be nil.
#### xplr.fn.builtin.fmt_general_table_row_cols_0
Renders the first column in the table

@ -193,6 +193,18 @@ Style for each item in the selection list.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.preview.renderer.format
Preview renderer for the path under focus.
Type: nullable string
#### xplr.config.general.preview.renderer.style
Style for preview panel.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.search.algorithm
The default search algorithm

@ -110,12 +110,12 @@ xplr.util.path_split(".././foo")
### xplr.util.node
Get [Node][5] information of a given path.
Get [Node][2] information of a given path.
Doesn't check if the path exists.
Returns nil if the path is "/".
Errors out if absolute path can't be obtained.
Type: function( path:string ) -> [Node][5]|nil
Type: function( path:string ) -> [Node][2]|nil
Example:
@ -129,9 +129,9 @@ xplr.util.node("/")
### xplr.util.node_type
Get the configured [Node Type][6] of a given [Node][5].
Get the configured [Node Type][6] of a given [Node][2].
Type: function( [Node][5], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
Type: function( [Node][2], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
If the second argument is missing, global config `xplr.config.node_types`
will be used.
@ -501,11 +501,33 @@ xplr.util.permissions_octal(app.focused_node.permission)
-- { 0, 7, 5, 4 }
```
### xplr.util.preview
Renders a preview of the given node as string.
You probably want to use it inside the function mentioned in
[xplr.config.general.preview.renderer.format][9], or inside a
[custom dynamic layout][10].
Type: function( { node:[Node][2]|nil, layout_size:[Size][5] } ) -> string
Example:
```lua
xplr.util.preview({
node = xplr.util.node("/foo"),
layout_size = { x = 0, y = 0, height = 10, width = 10 },
})
-- "..."
```
[1]: https://xplr.dev/en/lua-function-calls#explorer-config
[2]: https://xplr.dev/en/lua-function-calls#node
[3]: https://xplr.dev/en/style
[4]: https://xplr.dev/en/layout
[5]: https://xplr.dev/en/lua-function-calls#node
[5]: https://xplr.dev/en/layout#size
[6]: https://xplr.dev/en/node-type
[7]: https://xplr.dev/en/node_types
[8]: https://xplr.dev/en/column-renderer#permission
[9]: https://xplr.dev/en/general-config#xplrconfiggeneralpreviewrendererformat
[10]: https://xplr.dev/en/layout#dynamic

@ -189,6 +189,13 @@ pub struct SelectionConfig {
pub item: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PreviewConfig {
#[serde(default)]
pub renderer: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SearchConfig {
@ -318,6 +325,9 @@ pub struct GeneralConfig {
#[serde(default)]
pub selection: SelectionConfig,
#[serde(default)]
pub preview: PreviewConfig,
#[serde(default)]
pub search: SearchConfig,

@ -256,6 +256,16 @@ xplr.config.general.selection.item.format = "builtin.fmt_general_selection_item"
-- Type: [Style](https://xplr.dev/en/style)
xplr.config.general.selection.item.style = {}
-- Preview renderer for the path under focus.
--
-- Type: nullable string
xplr.config.general.preview.renderer.format = "builtin.fmt_general_preview_renderer"
-- Style for preview panel.
--
-- Type: [Style](https://xplr.dev/en/style)
xplr.config.general.preview.renderer.style = {}
-- The default search algorithm
--
-- Type: [Search Algorithm](https://xplr.dev/en/searching#algorithm)
@ -2948,6 +2958,7 @@ xplr.fn.builtin.try_complete_path = function(m)
end
end
-- Formats each node in the selection
xplr.fn.builtin.fmt_general_selection_item = function(n)
local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } })
local sh_config = { with_prefix_dots = true, without_suffix_dots = true }
@ -2961,6 +2972,15 @@ xplr.fn.builtin.fmt_general_selection_item = function(n)
return xplr.util.paint(shortened:gsub("\n", nl), style)
end
-- Renders the focused node in preview pane
--
-- See: [xplr.util.preview](https://xplr.dev/en/xplr.util#xplrutilpreview)
--
-- The focused node is passed as the node value, and layout_hight is passed
-- dynamically.
-- When there is no item under focus, the node value will be nil.
xplr.fn.builtin.fmt_general_preview_renderer = xplr.util.preview
-- Renders the first column in the table
xplr.fn.builtin.fmt_general_table_row_cols_0 = function(m)
local r = ""

@ -10,6 +10,7 @@ use crate::permissions::Octal;
use crate::permissions::Permissions;
use crate::ui;
use crate::ui::Layout;
use crate::ui::PreviewRendererArgs;
use crate::ui::Style;
use crate::ui::WrapOptions;
use anyhow::Result;
@ -195,12 +196,12 @@ pub fn path_split<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
Ok(util)
}
/// Get [Node][5] information of a given path.
/// Get [Node][2] information of a given path.
/// Doesn't check if the path exists.
/// Returns nil if the path is "/".
/// Errors out if absolute path can't be obtained.
///
/// Type: function( path:string ) -> [Node][5]|nil
/// Type: function( path:string ) -> [Node][2]|nil
///
/// Example:
///
@ -230,9 +231,9 @@ pub fn node<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
Ok(util)
}
/// Get the configured [Node Type][6] of a given [Node][5].
/// Get the configured [Node Type][6] of a given [Node][2].
///
/// Type: function( [Node][5], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
/// Type: function( [Node][2], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
///
/// If the second argument is missing, global config `xplr.config.node_types`
/// will be used.
@ -824,15 +825,59 @@ pub fn permissions_octal<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
Ok(util)
}
/// Renders a preview of the given node as string.
///
/// You probably want to use it inside the function mentioned in
/// [xplr.config.general.preview.renderer.format][9], or inside a
/// [custom dynamic layout][10].
///
/// Type: function( { node:[Node][2]|nil, layout_size:[Size][5] } ) -> string
///
/// Example:
///
/// ```lua
/// xplr.util.preview({
/// node = xplr.util.node("/foo"),
/// layout_size = { x = 0, y = 0, height = 10, width = 10 },
/// })
/// -- "..."
/// ```
pub fn preview<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(|lua, args: Table| {
let args: PreviewRendererArgs = lua.from_value(Value::Table(args))?;
let Some(node) = args.node else {
return Ok("".into());
};
let size = args.layout_size;
let preview = if node.is_dir {
"Directory".to_string()
} else if node.is_file {
"File".to_string()
} else if node.is_symlink {
"Symlink".to_string()
} else {
"Unknown".to_string()
};
Ok(preview)
})?;
util.set("preview", func)?;
Ok(util)
}
///
/// [1]: https://xplr.dev/en/lua-function-calls#explorer-config
/// [2]: https://xplr.dev/en/lua-function-calls#node
/// [3]: https://xplr.dev/en/style
/// [4]: https://xplr.dev/en/layout
/// [5]: https://xplr.dev/en/lua-function-calls#node
/// [5]: https://xplr.dev/en/layout#size
/// [6]: https://xplr.dev/en/node-type
/// [7]: https://xplr.dev/en/node_types
/// [8]: https://xplr.dev/en/column-renderer#permission
/// [9]: https://xplr.dev/en/general-config#xplrconfiggeneralpreviewrendererformat
/// [10]: https://xplr.dev/en/layout#dynamic
pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
let mut util = lua.create_table()?;
@ -867,6 +912,7 @@ pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
util = layout_replace(util, lua)?;
util = permissions_rwx(util, lua)?;
util = permissions_octal(util, lua)?;
util = preview(util, lua)?;
Ok(util)
}

@ -853,6 +853,53 @@ fn draw_table(
f.render_widget(table, layout_size);
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct PreviewRendererArgs {
pub node: Option<Node>,
pub layout_size: TuiRect,
}
fn draw_preview(
f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect,
app: &app::App,
lua: &Lua,
) {
let panel_config = &app.config.general.panel_ui;
let config = panel_config
.default
.to_owned()
.extend(&panel_config.selection);
let renderer = app
.config
.general
.preview
.renderer
.format
.clone()
.unwrap_or_default();
let preview = if let Some(node) = app.focused_node() {
let args = PreviewRendererArgs {
node: Some(node.to_owned()),
layout_size,
};
lua::serialize::<PreviewRendererArgs>(lua, &args)
.and_then(|args| lua::call(lua, &renderer, args))
.unwrap_or_else(|e| format!("{e:?}"))
} else {
String::new()
};
let preview =
Paragraph::new(string_to_text(preview)).block(block(config, " Preview ".into()));
f.render_widget(preview, layout_size);
}
fn draw_selection(
f: &mut Frame,
_screen_size: TuiRect,
@ -1360,7 +1407,13 @@ pub fn draw_layout(
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::Selection => {
if app.selection.is_empty() {
draw_preview(f, screen_size, layout_size, app, lua);
} else {
draw_selection(f, screen_size, layout_size, app, lua);
}
}
Layout::InputAndLogs => {
if app.input.buffer.is_some() {
draw_input_buffer(f, screen_size, layout_size, app, lua);

Loading…
Cancel
Save