mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
192e930670
commit
dfbe212859
14 changed files with 376 additions and 282 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
|
@ -34,6 +34,15 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
|
|
@ -341,6 +350,7 @@ dependencies = [
|
|||
"glob",
|
||||
"once_cell",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"shared",
|
||||
"toml",
|
||||
|
|
@ -1384,6 +1394,29 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ futures = "^0"
|
|||
glob = "^0"
|
||||
once_cell = "^1"
|
||||
ratatui = "^0"
|
||||
regex = "^1"
|
||||
serde = { version = "^1", features = [ "derive" ] }
|
||||
toml = { version = "^0", features = [ "preserve_order" ] }
|
||||
xdg = "^2"
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ text = [
|
|||
Available parameters are as follows:
|
||||
|
||||
- exec: The command to open the selected files, with the following variables available:
|
||||
- `"$n"`: The N-th selected file
|
||||
- `"$*"`: All selected files
|
||||
- `"foo"`: Literal string to be passed
|
||||
- `$n`: The N-th selected file
|
||||
- `$*`: All selected files
|
||||
- `foo`: Literal string to be passed
|
||||
- block: Open in a blocking manner. After setting this, Yazi will hide into a secondary screen and display the program on the main screen until it exits. During this time, it can receive I/O signals, which is useful for interactive programs.
|
||||
|
||||
## open
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@ text = [
|
|||
]
|
||||
image = [
|
||||
{ exec = "open $*", display_name = "Open" },
|
||||
{ exec = "exiftool $0", block = true, display_name = "Show EXIF" },
|
||||
{ exec = "sh -c 'exiftool $0; echo \"\n\nPress enter to exit\"; read'", block = true, display_name = "Show EXIF" },
|
||||
]
|
||||
video = [
|
||||
{ exec = "mpv $*" },
|
||||
{ exec = "mediainfo $0", block = true, display_name = "Show media info" },
|
||||
{ exec = "sh -c 'mediainfo $0; echo \"\n\nPress enter to exit\"; read'", block = true, display_name = "Show media info" },
|
||||
]
|
||||
audio = [
|
||||
{ exec = "mpv $*" },
|
||||
{ exec = "mediainfo $0", block = true, display_name = "Show media info" },
|
||||
{ exec = "sh -c 'mediainfo $0; echo \"\n\nPress enter to exit\"; read'", block = true, display_name = "Show media info" },
|
||||
]
|
||||
fallback = [
|
||||
{ exec = "open $*", display_name = "Open" },
|
||||
|
|
|
|||
189
config/src/exec/exec.rs
Normal file
189
config/src/exec/exec.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
use std::{collections::BTreeSet, fmt::{self, Debug}};
|
||||
|
||||
use regex::Regex;
|
||||
use serde::{de::{self, Visitor}, Deserializer};
|
||||
|
||||
use super::ExecItem;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Exec {
|
||||
items: Vec<ExecItem>,
|
||||
}
|
||||
|
||||
impl From<&str> for Exec {
|
||||
fn from(s: &str) -> Self { Self { items: Self::parse(s) } }
|
||||
}
|
||||
|
||||
impl ToString for Exec {
|
||||
fn to_string(&self) -> String {
|
||||
self.items.iter().map(|i| i.to_string()).collect::<Vec<_>>().join("")
|
||||
}
|
||||
}
|
||||
|
||||
impl Exec {
|
||||
pub fn parse(s: &str) -> Vec<ExecItem> {
|
||||
let mut item = ExecItem::Word(Default::default());
|
||||
let mut last = b'\0';
|
||||
let mut esc = 0;
|
||||
|
||||
let mut items = vec![];
|
||||
#[inline]
|
||||
fn add(items: &mut Vec<ExecItem>, item: ExecItem) {
|
||||
if !item.is_empty() {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
for c in s.trim().chars() {
|
||||
if last == b'\\' && !matches!(c, '\\' | '"' | '\'') {
|
||||
item.push('\\');
|
||||
esc = 0;
|
||||
last = b'\0';
|
||||
}
|
||||
|
||||
match c {
|
||||
' ' => match item {
|
||||
ExecItem::Str(ref mut s, ..) => s.push(c),
|
||||
ExecItem::Word(ref mut s) if last == b'\\' => {
|
||||
s.push('\\');
|
||||
s.push(c);
|
||||
}
|
||||
_ => {
|
||||
item.push(c);
|
||||
add(&mut items, item);
|
||||
item = ExecItem::Word(Default::default());
|
||||
}
|
||||
},
|
||||
'-' => match item {
|
||||
ExecItem::Word(ref mut w) => {
|
||||
if w.is_empty() {
|
||||
item = ExecItem::Arg(Default::default(), false);
|
||||
} else {
|
||||
w.push(c);
|
||||
}
|
||||
}
|
||||
ExecItem::Arg(_, ref mut b) => *b = true,
|
||||
ExecItem::Str(ref mut s, ..) => s.push(c),
|
||||
},
|
||||
'\\' => {
|
||||
if last == b'\\' {
|
||||
esc += 1;
|
||||
last = b'\0';
|
||||
} else {
|
||||
last = b'\\';
|
||||
}
|
||||
}
|
||||
'"' | '\'' => {
|
||||
if last == b'\\' {
|
||||
esc += 1;
|
||||
last = b'\0';
|
||||
}
|
||||
if matches!(item, ExecItem::Str(_, e) if e == esc) {
|
||||
item.push(c);
|
||||
add(&mut items, item);
|
||||
item = ExecItem::Str(Default::default(), esc);
|
||||
} else {
|
||||
add(&mut items, item);
|
||||
item = ExecItem::Str(c.to_string(), esc);
|
||||
}
|
||||
esc = 0;
|
||||
}
|
||||
c => {
|
||||
item.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(&mut items, item);
|
||||
items
|
||||
}
|
||||
|
||||
pub fn build(&self, args: Vec<String>) -> String {
|
||||
let re = Regex::new(r"\$(\d+|\*)").unwrap();
|
||||
let mut occurs = BTreeSet::new();
|
||||
let mut replace = |s: &mut String| {
|
||||
*s = re
|
||||
.replace_all(s, |caps: ®ex::Captures| {
|
||||
let idx = caps.get(1).unwrap().as_str();
|
||||
if idx == "*" {
|
||||
return args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| !occurs.contains(i))
|
||||
.map(|(_, s)| s.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
if let Ok(idx) = idx.parse::<usize>() {
|
||||
if idx < args.len() {
|
||||
occurs.insert(idx);
|
||||
return args[idx].to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
Default::default()
|
||||
})
|
||||
.into_owned();
|
||||
};
|
||||
|
||||
let items = self
|
||||
.items
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut i| {
|
||||
match i {
|
||||
ExecItem::Word(ref mut s) => replace(s),
|
||||
ExecItem::Arg(..) => (),
|
||||
ExecItem::Str(ref mut s, _) => replace(s),
|
||||
}
|
||||
i
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self { items }.to_string()
|
||||
}
|
||||
|
||||
pub fn has() {}
|
||||
|
||||
pub fn arg() {}
|
||||
|
||||
pub fn named() {}
|
||||
}
|
||||
|
||||
impl Exec {
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Exec>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ExecVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ExecVisitor {
|
||||
type Value = Vec<Exec>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a command string, e.g. tab_switch 0")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::SeqAccess<'de>,
|
||||
{
|
||||
let mut execs = Vec::new();
|
||||
while let Some(value) = &seq.next_element::<String>()? {
|
||||
execs.push(Exec::from(value.as_str()));
|
||||
}
|
||||
Ok(execs)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(vec![Exec::from(value)])
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ExecVisitor)
|
||||
}
|
||||
}
|
||||
88
config/src/exec/item.rs
Normal file
88
config/src/exec/item.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ExecItem {
|
||||
Word(String),
|
||||
Arg(String, bool),
|
||||
Str(String, usize),
|
||||
}
|
||||
|
||||
impl ExecItem {
|
||||
#[inline]
|
||||
pub(super) fn push(&mut self, c: char) {
|
||||
match self {
|
||||
ExecItem::Word(s) => s.push(c),
|
||||
ExecItem::Arg(s, _) => s.push(c),
|
||||
ExecItem::Str(s, _) => s.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
ExecItem::Word(s) => s.is_empty(),
|
||||
ExecItem::Arg(..) => false,
|
||||
ExecItem::Str(s, _) => s.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn slash(&self) -> Option<&str> {
|
||||
match self {
|
||||
ExecItem::Str(_, n) => {
|
||||
if *n == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let s = "\\\\".repeat(*n);
|
||||
Some(&s[..s.len() - 1])
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ExecItem {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExecItem::Word(s) => write!(f, "Word({s})"),
|
||||
ExecItem::Arg(s, _) => write!(f, "Arg({s})"),
|
||||
ExecItem::Str(s, n) => write!(f, "Str{n}({s})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for ExecItem {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
ExecItem::Word(s) => s.clone(),
|
||||
ExecItem::Arg(s, b) => format!("-{}{s}", if *b { "-" } else { "" }),
|
||||
ExecItem::Str(s, n) => {
|
||||
let slash = if let Some(s) = self.slash() {
|
||||
s
|
||||
} else {
|
||||
return s.to_owned();
|
||||
};
|
||||
|
||||
if s == "'" || s == "\"" {
|
||||
return format!("{slash}{s}");
|
||||
}
|
||||
|
||||
let mut s = s.clone();
|
||||
if let Some(sub) = s.strip_prefix('"') {
|
||||
s = format!("{slash}\"{sub}");
|
||||
}
|
||||
if let Some(sub) = s.strip_suffix('"') {
|
||||
s = format!("{sub}{slash}\"");
|
||||
}
|
||||
if let Some(sub) = s.strip_prefix('\'') {
|
||||
s = format!("{slash}'{sub}");
|
||||
}
|
||||
if let Some(sub) = s.strip_suffix('\'') {
|
||||
s = format!("{sub}{slash}'");
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
config/src/exec/mod.rs
Normal file
6
config/src/exec/mod.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
mod exec;
|
||||
mod item;
|
||||
mod tests;
|
||||
|
||||
pub use exec::*;
|
||||
pub use item::*;
|
||||
49
config/src/exec/tests.rs
Normal file
49
config/src/exec/tests.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// cargo test --package config --lib -- exec::tests::build --exact --nocapture
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
use crate::exec::Exec;
|
||||
|
||||
fn assert(a: &str) {
|
||||
let exec = Exec::parse(a);
|
||||
println!("{:?}", exec);
|
||||
|
||||
let a = a.trim();
|
||||
let b = Exec::from(a).to_string();
|
||||
|
||||
if a != b {
|
||||
println!("A: {}", a);
|
||||
println!("B: {}", b);
|
||||
}
|
||||
}
|
||||
|
||||
assert(r#" echo 123 "foo" 'bar' "#);
|
||||
assert(r#" sh -c "sh -c \"\";" "#);
|
||||
assert(r#" aaa - "bbb --opt \"ccc \\\"Meow\\\"\"" "#);
|
||||
assert(r#" python4 --code 'bash -c "echo \'\\\'\'"'; "#);
|
||||
assert(r#" sh -c "sh -c \"exiftool $0; echo \\\"\nPress enter to exit: \\\"; read\"" "#);
|
||||
|
||||
assert(r#"sh -c 'exiftool $0; echo \"\n\nPress enter to exit\"; read'"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build() {
|
||||
use crate::exec::Exec;
|
||||
|
||||
fn assert(s: &str, args: Vec<String>, expected: &str) {
|
||||
let exec = Exec::parse(s);
|
||||
println!("{:?}", exec);
|
||||
|
||||
let got = Exec::from(s).build(args);
|
||||
if got != expected {
|
||||
println!("A: {}", expected);
|
||||
println!("B: {}", got);
|
||||
}
|
||||
}
|
||||
|
||||
assert(
|
||||
r#"sh -c 'exiftool $0 "$1" \'$2\'; echo "\n\nPress enter to exit"; read'"#,
|
||||
vec!["fo o".into(), "b'a\"r".into(), "b\"a'z".into()],
|
||||
r#"sh -c 'exiftool foo; echo "\n\nPress enter to exit"; read'"#,
|
||||
);
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecNew {
|
||||
items: Vec<ExecItem>,
|
||||
}
|
||||
|
||||
pub enum ExecItem {
|
||||
Word(String),
|
||||
Arg(String, bool),
|
||||
Str(String, usize),
|
||||
}
|
||||
|
||||
impl ExecItem {
|
||||
#[inline]
|
||||
fn push(&mut self, c: char) {
|
||||
match self {
|
||||
ExecItem::Word(s) => s.push(c),
|
||||
ExecItem::Arg(s, _) => s.push(c),
|
||||
ExecItem::Str(s, _) => s.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
ExecItem::Word(s) => s.is_empty(),
|
||||
ExecItem::Arg(..) => false,
|
||||
ExecItem::Str(s, _) => s.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ExecItem {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExecItem::Word(s) => write!(f, "Word({s})"),
|
||||
ExecItem::Arg(s, _) => write!(f, "Arg({s})"),
|
||||
ExecItem::Str(s, n) => write!(f, "Str{n}({s})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for ExecItem {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
ExecItem::Word(s) => s.clone(),
|
||||
ExecItem::Arg(s, b) => format!("-{}{s}", if *b { "-" } else { "" }),
|
||||
ExecItem::Str(s, n) => {
|
||||
if *n == 0 {
|
||||
return s.to_string();
|
||||
}
|
||||
|
||||
let rep = "\\\\".repeat(*n);
|
||||
let rep = rep[..rep.len() - 1].to_string();
|
||||
|
||||
if s == "'" || s == "\"" {
|
||||
return format!("{rep}{s}");
|
||||
}
|
||||
|
||||
let mut s = s.clone();
|
||||
if let Some(ss) = s.strip_prefix('"') {
|
||||
s = format!("{rep}\"{ss}");
|
||||
}
|
||||
if let Some(ss) = s.strip_suffix('"') {
|
||||
s = format!("{ss}{rep}\"");
|
||||
}
|
||||
if let Some(ss) = s.strip_prefix('\'') {
|
||||
s = format!("{rep}'{ss}");
|
||||
}
|
||||
if let Some(ss) = s.strip_suffix('\'') {
|
||||
s = format!("{ss}{rep}'");
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecNew {
|
||||
pub fn parse(s: &str) -> Self {
|
||||
let mut item = ExecItem::Word(Default::default());
|
||||
let mut last = b'\0';
|
||||
let mut esc = 0;
|
||||
|
||||
let mut items = vec![];
|
||||
#[inline]
|
||||
fn add(items: &mut Vec<ExecItem>, item: ExecItem) {
|
||||
if !item.is_empty() {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
for c in s.trim().chars() {
|
||||
if last == b'\\' && !matches!(c, '\\' | '"' | '\'') {
|
||||
item.push('\\');
|
||||
esc = 0;
|
||||
last = b'\0';
|
||||
}
|
||||
|
||||
match c {
|
||||
' ' => match item {
|
||||
ExecItem::Str(ref mut s, ..) => s.push(c),
|
||||
ExecItem::Word(ref mut s) if last == b'\\' => {
|
||||
s.push('\\');
|
||||
s.push(c);
|
||||
}
|
||||
_ => {
|
||||
item.push(c);
|
||||
add(&mut items, item);
|
||||
item = ExecItem::Word(Default::default());
|
||||
}
|
||||
},
|
||||
'-' => match item {
|
||||
ExecItem::Word(ref mut w) => {
|
||||
if w.is_empty() {
|
||||
item = ExecItem::Arg(Default::default(), false);
|
||||
} else {
|
||||
w.push(c);
|
||||
}
|
||||
}
|
||||
ExecItem::Arg(_, ref mut b) => *b = true,
|
||||
ExecItem::Str(ref mut s, ..) => s.push(c),
|
||||
},
|
||||
'\\' => {
|
||||
if last == b'\\' {
|
||||
esc += 1;
|
||||
last = b'\0';
|
||||
} else {
|
||||
last = b'\\';
|
||||
}
|
||||
}
|
||||
'"' | '\'' => {
|
||||
if last == b'\\' {
|
||||
esc += 1;
|
||||
last = b'\0';
|
||||
}
|
||||
if matches!(item, ExecItem::Str(_, e) if e == esc) {
|
||||
item.push(c);
|
||||
add(&mut items, item);
|
||||
item = ExecItem::Str(Default::default(), esc);
|
||||
} else {
|
||||
add(&mut items, item);
|
||||
item = ExecItem::Str(c.to_string(), esc);
|
||||
}
|
||||
esc = 0;
|
||||
}
|
||||
c => {
|
||||
item.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(&mut items, item);
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for ExecNew {
|
||||
fn to_string(&self) -> String {
|
||||
self.items.iter().map(|i| i.to_string()).collect::<Vec<_>>().join("")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
fn assert(a: &str) {
|
||||
let exec = ExecNew::parse(a);
|
||||
|
||||
let a = a.trim();
|
||||
let b = exec.to_string().trim().to_string();
|
||||
|
||||
println!("{:?}", exec);
|
||||
if a != b {
|
||||
println!("A: {}", a);
|
||||
println!("B: {}", b);
|
||||
}
|
||||
}
|
||||
|
||||
assert(r#" echo 123 "foo" 'bar' "#);
|
||||
assert(r#" sh -c "sh -c \"\";" "#);
|
||||
assert(r#" aaa - "bbb --opt \"ccc \\\"Meow\\\"\"" "#);
|
||||
assert(r#" python4 --code 'bash -c "echo \'\\\'\'"'; "#);
|
||||
assert(r#" sh -c "sh -c \"exiftool $0; echo \\\"\nPress enter to exit: \\\"; read\"" "#);
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
use std::{collections::BTreeMap, fmt};
|
||||
|
||||
use serde::{de::{self, Visitor}, Deserializer};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Exec {
|
||||
pub cmd: String,
|
||||
pub args: Vec<String>,
|
||||
pub named: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl From<&str> for Exec {
|
||||
fn from(value: &str) -> Self {
|
||||
let mut exec = Self::default();
|
||||
for x in value.split_whitespace() {
|
||||
if let Some(kv) = x.strip_prefix("--") {
|
||||
let mut it = kv.splitn(2, '=');
|
||||
let key = it.next().unwrap();
|
||||
let value = it.next().unwrap_or("");
|
||||
exec.named.insert(key.to_string(), value.to_string());
|
||||
} else if exec.cmd.is_empty() {
|
||||
exec.cmd = x.to_string();
|
||||
} else {
|
||||
exec.args.push(x.to_string());
|
||||
}
|
||||
}
|
||||
exec
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Exec {
|
||||
fn to_string(&self) -> String {
|
||||
let mut s = self.cmd.clone();
|
||||
for arg in &self.args {
|
||||
s.push(' ');
|
||||
s.push_str(arg);
|
||||
}
|
||||
for (name, value) in &self.named {
|
||||
s.push_str(" --");
|
||||
s.push_str(name);
|
||||
if !value.is_empty() {
|
||||
s.push('=');
|
||||
s.push_str(value);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl Exec {
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Exec>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ExecVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ExecVisitor {
|
||||
type Value = Vec<Exec>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a command string, e.g. tab_switch 0")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::SeqAccess<'de>,
|
||||
{
|
||||
let mut execs = Vec::new();
|
||||
while let Some(value) = &seq.next_element::<String>()? {
|
||||
execs.push(Exec::from(value.as_str()));
|
||||
}
|
||||
Ok(execs)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(value.split(';').map(Exec::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ExecVisitor)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use super::{Exec, Key};
|
||||
use crate::MERGED_KEYMAP;
|
||||
use super::Key;
|
||||
use crate::{exec::Exec, MERGED_KEYMAP};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Control {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
mod exec;
|
||||
mod key;
|
||||
mod keymap;
|
||||
|
||||
pub use exec::*;
|
||||
pub use key::*;
|
||||
pub use keymap::*;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
mod boot;
|
||||
mod exec_new;
|
||||
mod exec;
|
||||
pub mod keymap;
|
||||
mod log;
|
||||
pub mod manager;
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp"],"version":"0.2","language":"en"}
|
||||
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nocapture"],"version":"0.2"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue