More UI utilities and improvements (#582)

* More UI utilities and improvements

- Apply style only to the file column in the table.
- Properly quote paths.
- Expose the applicable style from config in the table renderer argument.
- Add utility functions:
  - xplr.util.node_type
  - xplr.util.style_mix
  - xplr.util.shell_escape

* Make escaping play nice with shorten

* Fix tests

* Fix doc
This commit is contained in:
Arijit Basu 2023-01-29 16:44:15 +05:30 committed by GitHub
parent adccbae8f5
commit 77b99ae413
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 332 additions and 113 deletions

17
Cargo.lock generated
View File

@ -1071,6 +1071,16 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "snailquote"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec62a949bda7f15800481a711909f946e1204f2460f89210eaf7f57730f88f86"
dependencies = [
"thiserror",
"unicode_categories",
]
[[package]]
name = "syn"
version = "1.0.107"
@ -1220,6 +1230,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unsafe-libyaml"
version = "0.2.5"
@ -1469,6 +1485,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"snailquote",
"textwrap",
"tui",
"tui-input",

View File

@ -38,6 +38,7 @@ path-absolutize = "3.0.14"
which = "4.3.0"
nu-ansi-term = "0.46.0"
textwrap = "0.16"
snailquote = "0.3.1"
[dependencies.lscolors]
version = "0.13.0"

View File

@ -73,6 +73,7 @@ The special argument contains the following fields
- [is_selected][25]
- [is_focused][26]
- [total][27]
- [style][38]
- [meta][28]
### parent
@ -254,6 +255,12 @@ Type: integer
The total number of the nodes.
### style
Type: [Style][39]
The applicable [style object][39] for the node.
### meta
Type: mapping of string and string
@ -333,3 +340,5 @@ It contains the following fields.
[35]: #last_modified
[36]: #uid
[37]: #gid
[38]: #style
[39]: style.md#style

View File

@ -127,7 +127,24 @@ xplr.util.node("/")
-- nil
```
[5]: https://xplr.dev/en/lua-function-calls#node
### xplr.util.node_type
Get the configured [Node Type][6] of a given [Node][5].
Type: function( [Node][5], [NodeTypesConfig][7]|nil ) -> [NodeType][5]
If the second argument is missing, global config `xplr.config.node_types`
will be used.
Example:
```lua
xplr.util.node_type(app.focused_node)
-- { style = { fg = ..., ... }, meta = { icon = ..., ... } ... }
xplr.util.node_type(xplr.util.node("/foo/bar"), xplr.config.node_types)
-- { style = { fg = ..., ... }, meta = { icon = ..., ... } ... }
```
### xplr.util.dirname
@ -259,9 +276,6 @@ xplr.util.explore("/tmp", app.explorer_config)
-- { { absolute_path = "/tmp/a", ... }, ... }
```
[1]: https://xplr.dev/en/lua-function-calls#explorer-config
[2]: https://xplr.dev/en/lua-function-calls#node
### xplr.util.shell_execute
Execute shell commands safely.
@ -291,6 +305,19 @@ xplr.util.shell_quote("a'b\"c")
-- 'a'"'"'b"c'
```
### xplr.util.shell_escape
Escape commands and paths safely.
Type: function( string ) -> string
Example:
```lua
xplr.util.shell_escape("a'b\"c")
-- "\"a'b\\\"c\""
```
### xplr.util.from_json
Load JSON string into Lua value.
@ -353,7 +380,7 @@ xplr.util.to_yaml({ foo = "bar" })
Get a [Style][3] object for the given path based on the LS_COLORS
environment variable.
Type: function( path:string ) -> Style[3]|nil
Type: function( path:string ) -> [Style][3]|nil
Example:
@ -362,8 +389,6 @@ xplr.util.lscolor("Desktop")
-- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }
```
[3]: https://xplr.dev/en/style
### xplr.util.paint
Apply style (escape sequence) to string using a given [Style][3] object.
@ -377,6 +402,19 @@ xplr.util.paint("Desktop", { fg = "Red", bg = nil, add_modifiers = {}, sub_modif
-- "\u001b[31mDesktop\u001b[0m"
```
### xplr.util.style_mix
Mix multiple [Style][3] objects into one.
Type: function( [Style][3], [Style][3], ... ) -> Style[3]
Example:
```lua
xplr.util.style_mix({ fg = "Red" }, { bg = "Blue" }, { add_modifiers = {"Bold"} })
-- { fg = "Red", bg = "Blue", add_modifiers = { "Bold" }, sub_modifiers = {} }
```
### xplr.util.textwrap
Wrap the given text to fit the specified width.
@ -431,4 +469,10 @@ xplr.util.layout_replace(layout, "Table", "Selection")
-- }
```
[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
[6]: https://xplr.dev/en/node-type
[7]: https://xplr.dev/en/node-types

View File

@ -260,6 +260,9 @@ def gen_xplr_util():
print("\n".join(function.doc))
print("\n".join(function.doc), file=f)
if reading:
print("\n".join(reading.doc), file=f)
def format_docs():
os.system("prettier --write docs/en/src")

View File

@ -3,6 +3,7 @@ use crate::app::HelpMenuLine;
use crate::app::NodeFilter;
use crate::app::NodeSorter;
use crate::app::NodeSorterApplicable;
use crate::node::Node;
use crate::ui::Border;
use crate::ui::BorderType;
use crate::ui::Constraint;
@ -80,6 +81,40 @@ pub struct NodeTypesConfig {
pub special: HashMap<String, NodeTypeConfig>,
}
impl NodeTypesConfig {
pub fn get(&self, node: &Node) -> NodeTypeConfig {
let mut node_type = if node.is_symlink {
self.symlink.to_owned()
} else if node.is_dir {
self.directory.to_owned()
} else {
self.file.to_owned()
};
let mut me = node.mime_essence.splitn(2, '/');
let mimetype: String = me.next().map(|s| s.into()).unwrap_or_default();
let mimesub: String = me.next().map(|s| s.into()).unwrap_or_default();
if let Some(conf) = self
.mime_essence
.get(&mimetype)
.and_then(|t| t.get(&mimesub).or_else(|| t.get("*")))
{
node_type = node_type.extend(conf);
}
if let Some(conf) = self.extension.get(&node.extension) {
node_type = node_type.extend(conf);
}
if let Some(conf) = self.special.get(&node.relative_path) {
node_type = node_type.extend(conf);
}
node_type
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UiConfig {

View File

@ -710,7 +710,9 @@ xplr.config.general.global_key_bindings = {
-- The style for the directory nodes
--
-- Type: [Style](https://xplr.dev/en/style)
xplr.config.node_types.directory.style = {}
xplr.config.node_types.directory.style = {
fg = "Blue",
}
-- Metadata for the directory nodes.
-- You can set as many metadata as you want.
@ -746,7 +748,10 @@ xplr.config.node_types.file.meta.icon = "ƒ"
-- The style for the symlink nodes.
--
-- Type: [Style](https://xplr.dev/en/style)
xplr.config.node_types.symlink.style = {}
xplr.config.node_types.symlink.style = {
fg = "Magenta",
add_modifiers = { "Italic" },
}
-- Metadata for the symlink nodes.
-- You can set as many metadata as you want.
@ -2615,9 +2620,16 @@ xplr.fn.builtin.try_complete_path = function(m)
end
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 }
local shortened = xplr.util.shorten(n.absolute_path, sh_config)
return xplr.util.paint(shortened, xplr.util.lscolor(n.absolute_path))
if n.is_dir then
shortened = shortened .. "/"
end
local ls_style = xplr.util.lscolor(n.absolute_path)
local meta_style = xplr.util.node_type(n).style
local style = xplr.util.style_mix({ meta_style, ls_style })
return xplr.util.paint(shortened:gsub("\n", nl), style)
end
-- Renders the first column in the table
@ -2636,11 +2648,10 @@ end
-- Renders the second column in the table
xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } })
local r = m.tree .. m.prefix
local function path_escape(path)
return string.gsub(string.gsub(path, "\\", "\\\\"), "\n", "\\n")
end
local style = xplr.util.lscolor(m.absolute_path)
style = xplr.util.style_mix({ m.style, style })
if m.meta.icon == nil then
r = r .. ""
@ -2648,11 +2659,11 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
r = r .. m.meta.icon .. " "
end
r = r .. path_escape(m.relative_path)
local rel = m.relative_path
if m.is_dir then
r = r .. "/"
rel = rel .. "/"
end
r = r .. xplr.util.paint(xplr.util.shell_escape(rel), style)
r = r .. m.suffix .. " "
@ -2663,15 +2674,14 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
r = r .. "×"
else
local symlink_path = xplr.util.shorten(m.symlink.absolute_path)
r = r .. path_escape(symlink_path)
if m.symlink.is_dir then
r = r .. "/"
symlink_path = symlink_path .. "/"
end
r = r .. symlink_path:gsub("\n", nl)
end
end
local style = xplr.util.lscolor(m.absolute_path)
return xplr.util.paint(r, style)
return r
end
-- Renders the third column in the table

View File

@ -1,4 +1,5 @@
use crate::app::VERSION;
use crate::config::NodeTypesConfig;
use crate::explorer;
use crate::lua;
use crate::msg::in_::external::ExplorerConfig;
@ -25,38 +26,6 @@ use std::borrow::Cow;
use std::path::PathBuf;
use std::process::Command;
pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
let mut util = lua.create_table()?;
util = version(util, lua)?;
util = clone(util, lua)?;
util = exists(util, lua)?;
util = is_dir(util, lua)?;
util = is_file(util, lua)?;
util = is_symlink(util, lua)?;
util = is_absolute(util, lua)?;
util = path_split(util, lua)?;
util = node(util, lua)?;
util = dirname(util, lua)?;
util = basename(util, lua)?;
util = absolute(util, lua)?;
util = relative_to(util, lua)?;
util = shorten(util, lua)?;
util = explore(util, lua)?;
util = shell_execute(util, lua)?;
util = shell_quote(util, lua)?;
util = from_json(util, lua)?;
util = to_json(util, lua)?;
util = from_yaml(util, lua)?;
util = to_yaml(util, lua)?;
util = lscolor(util, lua)?;
util = paint(util, lua)?;
util = textwrap(util, lua)?;
util = layout_replace(util, lua)?;
Ok(util)
}
/// Get the xplr version details.
///
/// Type: function() -> { major: number, minor: number, patch: number }
@ -241,8 +210,6 @@ pub fn path_split<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// xplr.util.node("/")
/// -- nil
/// ```
///
/// [5]: https://xplr.dev/en/lua-function-calls#node
pub fn node<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(move |lua, path: String| {
let path = PathBuf::from(path);
@ -262,6 +229,43 @@ 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].
///
/// Type: function( [Node][5], [NodeTypesConfig][7]|nil ) -> [NodeType][5]
///
/// If the second argument is missing, global config `xplr.config.node_types`
/// will be used.
///
/// Example:
///
/// ```lua
/// xplr.util.node_type(app.focused_node)
/// -- { style = { fg = ..., ... }, meta = { icon = ..., ... } ... }
///
/// xplr.util.node_type(xplr.util.node("/foo/bar"), xplr.config.node_types)
/// -- { style = { fg = ..., ... }, meta = { icon = ..., ... } ... }
/// ```
pub fn node_type<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func =
lua.create_function(move |lua, (node, config): (Table, Option<Table>)| {
let node: Node = lua.from_value(Value::Table(node))?;
let config: Table = if let Some(config) = config {
config
} else {
lua.globals()
.get::<_, Table>("xplr")?
.get::<_, Table>("config")?
.get::<_, Table>("node_types")?
};
let config: NodeTypesConfig = lua.from_value(Value::Table(config))?;
let node_type = config.get(&node);
let node_type = lua::serialize(lua, &node_type).map_err(LuaError::custom)?;
Ok(node_type)
})?;
util.set("node_type", func)?;
Ok(util)
}
/// Get the directory name of a given path.
///
/// Type: function( path:string ) -> path:string|nil
@ -430,9 +434,6 @@ pub fn shorten<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// xplr.util.explore("/tmp", app.explorer_config)
/// -- { { absolute_path = "/tmp/a", ... }, ... }
/// ```
///
/// [1]: https://xplr.dev/en/lua-function-calls#explorer-config
/// [2]: https://xplr.dev/en/lua-function-calls#node
pub fn explore<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(|lua, (path, config): (String, Option<Table>)| {
let config: ExplorerConfig = if let Some(cfg) = config {
@ -501,6 +502,25 @@ pub fn shell_quote<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
Ok(util)
}
/// Escape commands and paths safely.
///
/// Type: function( string ) -> string
///
/// Example:
///
/// ```lua
/// xplr.util.shell_escape("a'b\"c")
/// -- "\"a'b\\\"c\""
/// ```
pub fn shell_escape<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(move |_, string: String| {
let val = path::escape(&string).to_string();
Ok(val)
})?;
util.set("shell_escape", func)?;
Ok(util)
}
/// Load JSON string into Lua value.
///
/// Type: function( string ) -> any
@ -604,7 +624,7 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// Get a [Style][3] object for the given path based on the LS_COLORS
/// environment variable.
///
/// Type: function( path:string ) -> Style[3]|nil
/// Type: function( path:string ) -> [Style][3]|nil
///
/// Example:
///
@ -612,15 +632,9 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// xplr.util.lscolor("Desktop")
/// -- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }
/// ```
///
/// [3]: https://xplr.dev/en/style
pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let lscolors = LsColors::from_env().unwrap_or_default();
let func = lua.create_function(move |lua, path: String| {
if *ui::NO_COLOR {
return Ok(mlua::Nil);
}
let style = lscolors.style_for_path(path).map(Style::from);
lua::serialize(lua, &style).map_err(LuaError::custom)
})?;
@ -657,6 +671,30 @@ pub fn paint<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
Ok(util)
}
/// Mix multiple [Style][3] objects into one.
///
/// Type: function( [Style][3], [Style][3], ... ) -> Style[3]
///
/// Example:
///
/// ```lua
/// xplr.util.style_mix({ fg = "Red" }, { bg = "Blue" }, { add_modifiers = {"Bold"} })
/// -- { fg = "Red", bg = "Blue", add_modifiers = { "Bold" }, sub_modifiers = {} }
/// ```
pub fn style_mix<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(|lua, styles: Vec<Table>| {
let mut style = Style::default();
for other in styles {
let other: Style = lua.from_value(Value::Table(other))?;
style = style.extend(&other);
}
lua::serialize(lua, &style).map_err(LuaError::custom)
})?;
util.set("style_mix", func)?;
Ok(util)
}
/// Wrap the given text to fit the specified width.
/// It will try to not split words when possible.
///
@ -721,8 +759,6 @@ pub fn textwrap<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
/// -- }
/// -- }
/// ```
///
/// [4]: https://xplr.dev/en/layout
pub fn layout_replace<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
let func = lua.create_function(
move |lua, (layout, target, replacement): (Table, Table, Table)| {
@ -739,3 +775,47 @@ pub fn layout_replace<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
util.set("layout_replaced", 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
/// [6]: https://xplr.dev/en/node-type
/// [7]: https://xplr.dev/en/node-types
pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
let mut util = lua.create_table()?;
util = version(util, lua)?;
util = clone(util, lua)?;
util = exists(util, lua)?;
util = is_dir(util, lua)?;
util = is_file(util, lua)?;
util = is_symlink(util, lua)?;
util = is_absolute(util, lua)?;
util = path_split(util, lua)?;
util = node(util, lua)?;
util = node_type(util, lua)?;
util = dirname(util, lua)?;
util = basename(util, lua)?;
util = absolute(util, lua)?;
util = relative_to(util, lua)?;
util = shorten(util, lua)?;
util = explore(util, lua)?;
util = shell_execute(util, lua)?;
util = shell_quote(util, lua)?;
util = shell_escape(util, lua)?;
util = from_json(util, lua)?;
util = to_json(util, lua)?;
util = from_yaml(util, lua)?;
util = to_yaml(util, lua)?;
util = lscolor(util, lua)?;
util = paint(util, lua)?;
util = style_mix(util, lua)?;
util = textwrap(util, lua)?;
util = layout_replace(util, lua)?;
Ok(util)
}

View File

@ -1,6 +1,7 @@
use anyhow::{bail, Result};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
pub use snailquote::escape;
use std::path::{Component, Path, PathBuf};
lazy_static! {
@ -470,4 +471,25 @@ mod tests {
.unwrap();
assert_eq!(res, "/");
}
#[test]
fn test_path_escape() {
let text = "foo".to_string();
assert_eq!(escape(&text), "foo");
let text = "foo bar".to_string();
assert_eq!(escape(&text), "'foo bar'");
let text = "foo\nbar".to_string();
assert_eq!(escape(&text), "\"foo\\nbar\"");
let text = "foo$bar".to_string();
assert_eq!(escape(&text), "'foo$bar'");
let text = "foo'$\n'bar".to_string();
assert_eq!(escape(&text), "\"foo'\\$\\n'bar\"");
let text = "a'b\"c".to_string();
assert_eq!(escape(&text), "\"a'b\\\"c\"");
}
}

View File

@ -1,9 +1,9 @@
use crate::app;
use crate::app::{HelpMenuLine, NodeFilterApplicable, NodeSorterApplicable};
use crate::app::{Node, ResolvedNode};
use crate::config::PanelUiConfig;
use crate::lua;
use crate::permissions::Permissions;
use crate::{app, path};
use ansi_to_tui::IntoText;
use indexmap::IndexSet;
use lazy_static::lazy_static;
@ -278,6 +278,21 @@ impl Modifier {
}
}
fn extend_optional_modifiers(
a: Option<IndexSet<Modifier>>,
b: Option<IndexSet<Modifier>>,
) -> Option<IndexSet<Modifier>> {
match (a, b) {
(Some(mut a), Some(b)) => {
a.extend(b);
Some(a)
}
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Style {
@ -291,8 +306,14 @@ 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 = other.add_modifiers.to_owned().or(self.add_modifiers);
self.sub_modifiers = other.sub_modifiers.to_owned().or(self.sub_modifiers);
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
}
}
@ -564,6 +585,7 @@ pub struct NodeUiMetadata {
pub is_focused: bool,
pub total: usize,
pub meta: HashMap<String, String>,
pub style: Style,
}
impl NodeUiMetadata {
@ -580,6 +602,7 @@ impl NodeUiMetadata {
is_focused: bool,
total: usize,
meta: HashMap<String, String>,
style: Style,
) -> Self {
Self {
parent: node.parent.to_owned(),
@ -612,6 +635,7 @@ impl NodeUiMetadata {
is_focused,
total,
meta,
style,
}
}
}
@ -686,40 +710,7 @@ fn draw_table<B: Backend>(
})
.unwrap_or_default();
let mut me = node.mime_essence.splitn(2, '/');
let mimetype: String =
me.next().map(|s| s.into()).unwrap_or_default();
let mimesub: String =
me.next().map(|s| s.into()).unwrap_or_default();
let mut node_type = if node.is_symlink {
app_config.node_types.symlink.to_owned()
} else if node.is_dir {
app_config.node_types.directory.to_owned()
} else {
app_config.node_types.file.to_owned()
};
if let Some(conf) = app_config
.node_types
.mime_essence
.get(&mimetype)
.and_then(|t| t.get(&mimesub).or_else(|| t.get("*")))
{
node_type = node_type.extend(conf);
}
if let Some(conf) =
app_config.node_types.extension.get(&node.extension)
{
node_type = node_type.extend(conf);
}
if let Some(conf) =
app_config.node_types.special.get(&node.relative_path)
{
node_type = node_type.extend(conf);
}
let node_type = app_config.node_types.get(node);
let (relative_index, is_before_focus, is_after_focus) =
match dir.focus.cmp(&index) {
@ -763,6 +754,7 @@ fn draw_table<B: Backend>(
is_focused,
dir.total,
node_type.meta,
style,
);
let cols = lua::serialize::<NodeUiMetadata>(lua, &meta)
@ -789,7 +781,7 @@ fn draw_table<B: Backend>(
.map(|x| Cell::from(x.to_owned()))
.collect::<Vec<Cell>>();
Row::new(cols).style(style.into())
Row::new(cols)
})
.collect::<Vec<Row>>()
})
@ -810,9 +802,9 @@ fn draw_table<B: Backend>(
} else {
&app.pwd
}
.trim_matches('/')
.replace('\\', "\\\\")
.replace('\n', "\\n");
.trim_matches('/');
let pwd = path::escape(pwd);
let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" };
@ -886,8 +878,6 @@ fn draw_selection<B: Backend>(
lua::serialize::<Node>(lua, n)
.map(|n| lua::call(lua, f, n).unwrap_or_else(|e| e.to_string()))
.unwrap_or_else(|e| e.to_string())
.replace('\\', "\\\\")
.replace('\n', "\\n")
})
.unwrap_or_else(|| n.absolute_path.clone());
string_to_text(out)
@ -1551,7 +1541,11 @@ mod tests {
Style {
fg: Some(Color::Cyan),
bg: Some(Color::Magenta),
add_modifiers: modifier(Modifier::CrossedOut),
add_modifiers: Some(
vec![Modifier::Bold, Modifier::CrossedOut]
.into_iter()
.collect()
),
sub_modifiers: modifier(Modifier::Italic),
}
);
@ -1561,7 +1555,11 @@ mod tests {
Style {
fg: Some(Color::Red),
bg: Some(Color::Magenta),
add_modifiers: modifier(Modifier::Bold),
add_modifiers: Some(
vec![Modifier::Bold, Modifier::CrossedOut]
.into_iter()
.collect()
),
sub_modifiers: modifier(Modifier::Italic),
}
);