Improve builtin search mode

pull/585/head
Noah Mayr 1 year ago
parent 70989b5fc3
commit dff999e185

338
Cargo.lock generated

@ -54,6 +54,12 @@ version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "assert_cmd"
version = "2.0.8"
@ -85,6 +91,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -153,7 +165,7 @@ dependencies = [
"num-integer",
"num-traits",
"serde",
"time",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
@ -191,9 +203,13 @@ version = "3.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
@ -258,6 +274,20 @@ dependencies = [
"itertools",
]
[[package]]
name = "crossbeam"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
dependencies = [
"cfg-if",
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
@ -288,10 +318,20 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"memoffset 0.7.1",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
@ -370,6 +410,82 @@ dependencies = [
"syn",
]
[[package]]
name = "darling"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "defer-drop"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f613ec9fa66a6b28cdb1842b27f9adf24f39f9afc4dcdd9fdecee4aca7945c57"
dependencies = [
"crossbeam-channel",
"once_cell",
]
[[package]]
name = "derive_builder"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "difflib"
version = "0.4.0"
@ -385,6 +501,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
@ -396,6 +522,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
@ -408,6 +545,19 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "env_logger"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "erased-serde"
version = "0.3.24"
@ -417,6 +567,12 @@ dependencies = [
"serde",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
@ -489,6 +645,12 @@ dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@ -513,6 +675,12 @@ dependencies = [
"cxx-build",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.2"
@ -627,6 +795,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.7.1"
@ -694,6 +871,31 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "nix"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"libc",
"memoffset 0.6.5",
"pin-utils",
]
[[package]]
name = "nom"
version = "7.1.2"
@ -808,6 +1010,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.26"
@ -958,6 +1166,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "ryu"
version = "1.0.12"
@ -1029,6 +1243,12 @@ dependencies = [
"unsafe-libyaml",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
version = "0.3.14"
@ -1059,6 +1279,35 @@ dependencies = [
"libc",
]
[[package]]
name = "skim"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cebed5f897cd6c0d80fbe30adb36c0abf7400e93043a63ae56458495642b3485"
dependencies = [
"atty",
"beef",
"bitflags",
"chrono",
"clap",
"crossbeam",
"defer-drop",
"derive_builder",
"env_logger",
"fuzzy-matcher",
"lazy_static",
"log",
"nix 0.25.1",
"rayon",
"regex",
"shlex",
"time 0.3.17",
"timer",
"tuikit",
"unicode-width",
"vte",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@ -1081,6 +1330,12 @@ dependencies = [
"unicode_categories",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
@ -1092,6 +1347,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -1158,6 +1424,31 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [
"serde",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "timer"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b"
dependencies = [
"chrono",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@ -1193,6 +1484,20 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "tuikit"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e19c6ab038babee3d50c8c12ff8b910bdb2196f62278776422f50390d8e53d8"
dependencies = [
"bitflags",
"lazy_static",
"log",
"nix 0.24.3",
"term",
"unicode-width",
]
[[package]]
name = "unicase"
version = "2.6.0"
@ -1242,12 +1547,39 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
[[package]]
name = "utf8parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vte"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aae21c12ad2ec2d168c236f369c38ff332bc1134f7246350dca641437365045"
dependencies = [
"arrayvec",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wait-timeout"
version = "0.2.0"
@ -1469,7 +1801,6 @@ dependencies = [
"criterion",
"crossterm",
"dirs",
"fuzzy-matcher",
"gethostname",
"humansize",
"indexmap",
@ -1485,6 +1816,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"skim",
"snailquote",
"textwrap",
"tui",

@ -32,13 +32,13 @@ dirs = "4.0.0"
ansi-to-tui = "2.0.0"
regex = "1.7.1"
gethostname = "0.4.1"
fuzzy-matcher = "0.3.7"
serde_json = "1.0.91"
path-absolutize = "3.0.14"
which = "4.3.0"
nu-ansi-term = "0.46.0"
textwrap = "0.16"
snailquote = "0.3.1"
skim = "0.10.2"
[dependencies.lscolors]
version = "0.13.0"

@ -528,6 +528,9 @@ impl App {
ClearNodeSorters => self.clear_node_sorters(),
SearchFuzzy(p) => self.search_fuzzy(p),
SearchFuzzyFromInput => self.search_fuzzy_from_input(),
ToggleRankedSearch => self.toggle_ranked_search(),
EnableRankedSearch => self.set_ranked_search(true),
DisableRankedSearch => self.set_ranked_search(false),
AcceptSearch => self.accept_search(),
CancelSearch => self.cancel_search(),
EnableMouse => self.enable_mouse(),
@ -1612,14 +1615,16 @@ impl App {
}
pub fn search_fuzzy(mut self, pattern: String) -> Result<Self> {
let rf = self
let (rf, ranked) = self
.explorer_config
.searcher
.as_ref()
.map(|s| s.recoverable_focus.clone())
.unwrap_or_else(|| self.focused_node().map(|n| n.absolute_path.clone()));
.map(|s| (s.recoverable_focus.clone(), s.ranked))
.unwrap_or_else(|| {
(self.focused_node().map(|n| n.absolute_path.clone()), true)
});
self.explorer_config.searcher = Some(NodeSearcher::new(pattern, rf));
self.explorer_config.searcher = Some(NodeSearcher::new(pattern, rf, ranked));
Ok(self)
}
@ -1631,6 +1636,22 @@ impl App {
}
}
fn toggle_ranked_search(mut self) -> Result<Self> {
self.explorer_config.searcher = self
.explorer_config
.searcher
.map(|searcher| searcher.toggle_ranked());
Ok(self)
}
fn set_ranked_search(mut self, ranking: bool) -> Result<Self> {
self.explorer_config.searcher = self
.explorer_config
.searcher
.map(|searcher| searcher.with_ranked(ranking));
Ok(self)
}
fn accept_search(mut self) -> Result<Self> {
let focus = self
.directory_buffer

@ -2,16 +2,34 @@ use crate::app::{
DirectoryBuffer, ExplorerConfig, ExternalMsg, InternalMsg, MsgIn, Node, Task,
};
use anyhow::{Error, Result};
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use lazy_static::lazy_static;
use skim::prelude::ExactOrFuzzyEngineFactory;
use skim::{MatchEngineFactory, SkimItem};
use std::fs;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::thread;
lazy_static! {
static ref FUZZY_MATCHER: SkimMatcherV2 = SkimMatcherV2::default();
static ref ENGINE_FACTORY: ExactOrFuzzyEngineFactory =
ExactOrFuzzyEngineFactory::builder().build();
}
struct PathItem {
path: String,
}
impl From<String> for PathItem {
fn from(value: String) -> Self {
Self { path: value }
}
}
impl SkimItem for PathItem {
fn text(&self) -> std::borrow::Cow<str> {
std::borrow::Cow::from(&self.path)
}
}
pub fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result<Vec<Node>> {
@ -29,18 +47,25 @@ pub fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result<Vec<Node>> {
.filter(|n| config.filter(n))
.collect::<Vec<Node>>();
nodes = if let Some(pattern) = config.searcher.as_ref().map(|s| &s.pattern) {
let mut nodes = nodes
nodes = if let Some((pattern, ranked)) =
config.searcher.as_ref().map(|s| (&s.pattern, &s.ranked))
{
let engine = ENGINE_FACTORY.create_engine(pattern);
let mut ranked_nodes = nodes
.into_iter()
.filter_map(|n| {
FUZZY_MATCHER
.fuzzy_match(&n.relative_path, pattern)
.map(|score| (n, score))
let item = Arc::new(PathItem::from(n.relative_path.clone()));
engine.match_item(item).map(|res| (n, res.rank))
})
.collect::<Vec<(_, _)>>();
nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1));
nodes.into_iter().map(|(n, _)| n).collect::<Vec<_>>()
if *ranked {
ranked_nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1))
} else {
ranked_nodes.sort_by(|(a, _), (b, _)| config.sort(a, b))
}
ranked_nodes.into_iter().map(|(n, _)| n).collect::<Vec<_>>()
} else {
nodes.sort_by(|a, b| config.sort(a, b));
nodes

@ -2116,6 +2116,22 @@ xplr.config.modes.builtin.search = {
"FocusNext",
},
},
["ctrl-z"] = {
help = "toggle search ranking",
messages = {
"ToggleRankedSearch",
"ExplorePwdAsync",
},
},
["ctrl-s"] = {
help = "sort (disables ranking)",
messages = {
"DisableRankedSearch",
-- "PopMode",
"ExplorePwdAsync",
{ SwitchModeBuiltinKeepingInputBuffer = "sort" },
},
},
["right"] = {
help = "enter",
messages = {
@ -2365,7 +2381,12 @@ xplr.config.modes.builtin.sort = {
["enter"] = {
help = "submit",
messages = {
"PopMode",
"PopModeKeepingInputBuffer",
},
},
["esc"] = {
messages = {
"PopModeKeepingInputBuffer",
},
},
["m"] = {

@ -943,6 +943,33 @@ pub enum ExternalMsg {
/// - YAML: `SearchFuzzyFromInput`
SearchFuzzyFromInput,
/// Toggles sorting based on search match ranking or xplr default.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
///
/// Example:
///
/// - Lua: `"ToggleRankedSearch"`
/// - YAML: `ToggleRankedSearch`
ToggleRankedSearch,
/// Enables sorting based on search match ranking instead of xplr default.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
///
/// Example:
///
/// - Lua: `"EnableRankedSearch"`
/// - YAML: `EnableRankedSearch`
EnableRankedSearch,
/// Disables sorting based on search match ranking and uses xplr default.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
///
/// Example:
///
/// - Lua: `"DisableRankedSearch"`
/// - YAML: `DisableRankedSearch`
DisableRankedSearch,
/// Accepts the search by keeping the latest focus while in search mode.
/// Automatically calls `ExplorePwd`.
///
@ -1656,13 +1683,36 @@ pub struct NodeSearcher {
#[serde(default)]
pub recoverable_focus: Option<String>,
pub ranked: bool,
}
impl NodeSearcher {
pub fn new(pattern: String, recoverable_focus: Option<String>) -> Self {
pub fn new(
pattern: String,
recoverable_focus: Option<String>,
ranked: bool,
) -> Self {
Self {
pattern,
recoverable_focus,
ranked,
}
}
pub fn with_ranked(self, ranked: bool) -> Self {
Self {
pattern: self.pattern,
recoverable_focus: self.recoverable_focus,
ranked,
}
}
pub fn toggle_ranked(self) -> Self {
Self {
pattern: self.pattern,
recoverable_focus: self.recoverable_focus,
ranked: !self.ranked,
}
}
}

@ -1017,11 +1017,7 @@ fn draw_sort_n_filter<B: Backend>(
let ui = app.config.general.sort_and_filter_ui.to_owned();
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()
.map(|s| s.pattern.clone());
let search = app.explorer_config.searcher.as_ref();
let defaultui = &ui.default_identifier;
let forwardui = defaultui
@ -1071,7 +1067,11 @@ fn draw_sort_n_filter<B: Backend>(
})
.unwrap_or((Span::raw("s"), Span::raw("")))
})
.take(if search.is_some() { 0 } else { sort_by.len() }),
.take(if let Some(true) = search.map(|s| s.ranked) {
0
} else {
sort_by.len()
}),
)
.chain(search.iter().map(|s| {
ui.search_identifier
@ -1083,10 +1083,10 @@ fn draw_sort_n_filter<B: Backend>(
ui.format.to_owned().unwrap_or_default(),
ui.style.to_owned().into(),
),
Span::styled(s, ui.style.into()),
Span::styled(&s.pattern, ui.style.into()),
)
})
.unwrap_or((Span::raw("/"), Span::raw(s)))
.unwrap_or((Span::raw("/"), Span::raw(&s.pattern)))
}))
.zip(std::iter::repeat(Span::styled(
ui.separator.format.to_owned().unwrap_or_default(),

Loading…
Cancel
Save