From 91e3990df1ceada8316d1cdf6dc5d4810942a1c8 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Mon, 24 Oct 2022 19:00:06 +0530 Subject: [PATCH] Rename messages --- docs/en/src/messages.md | 54 +++++++--- src/app.rs | 91 +++++++++------- src/init.lua | 207 ++++++++++++++++++----------------- src/msg/in_/external.rs | 65 +++++++---- src/msg/out/mod.rs | 2 + src/runner.rs | 231 ++++++++++++++++++++++------------------ 6 files changed, 370 insertions(+), 280 deletions(-) diff --git a/docs/en/src/messages.md b/docs/en/src/messages.md index b7a8ae5..8ba7b94 100644 --- a/docs/en/src/messages.md +++ b/docs/en/src/messages.md @@ -547,54 +547,78 @@ Example: #### Call +Like `Call0`, but it uses `\n` as the delimiter in input/output pipes, +hence it cannot handle files with `\n` in the name. +You may want to use `Call0` instead. + +#### Call0 + Call a shell command with the given arguments. Note that the arguments will be shell-escaped. So to read the variables, the `-c` option of the shell can be used. -You may need to pass `ExplorePwd` depening on the expectation. +You may need to pass `ExplorePwd` depending on the expectation. -Type: { Call = { command = string, args = { "list", "of", "string" } } +Type: { Call0 = { command = "string", args = { "list", "of", "string" } } Example: -- Lua: `{ Call = { command = "bash", args = { "-c", "read -p test" } } }` -- YAML: `Call: { command: bash, args: ["-c", "read -p test"] }` +- Lua: `{ Call0 = { command = "bash", args = { "-c", "read -p test" } } }` +- YAML: `Call0: { command: bash, args: ["-c", "read -p test"] }` #### CallSilently -Like `Call` but without the flicker. The stdin, stdout +Like `CallSilently0`, but it uses `\n` as the delimiter in input/output +pipes, hence it cannot handle files with `\n` in the name. +You may want to use `Call0Silently` instead. + +#### CallSilently0 + +Like `Call0` but without the flicker. The stdin, stdout stderr will be piped to null. So it's non-interactive. -Type: { CallSilently = "string" } +Type: { CallSilently0 = { command = "string", args = {"list", "of", "string"} } } Example: -- Lua: `{ CallSilently = { command = "tput", args = { "bell" } } }` -- YAML: `CallSilently: { command: tput, args: ["bell"] }` +- Lua: `{ CallSilently0 = { command = "tput", args = { "bell" } } }` +- YAML: `CallSilently0: { command: tput, args: ["bell"] }` #### BashExec +Like `BashExec0`, but it uses `\n` as the delimiter in input/output +pipes, hence it cannot handle files with `\n` in the name. +You may want to use `BashExec0` instead. + +#### BashExec0 + An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}` where `{string}` is the given value. -Type: { BashExec = "string" } +Type: { BashExec0 = "string" } Example: -- Lua: `{ BashExec = "read -p test" }` -- YAML: `BashExec: "read -p test"` +- Lua: `{ BashExec0 = "read -p test" }` +- YAML: `BashExec0: "read -p test"` #### BashExecSilently -Like `BashExec` but without the flicker. The stdin, stdout +Like `BashExecSilently0`, but it uses `\n` as the delimiter in +input/output pipes, hence it cannot handle files with `\n` in the name. +You may want to use `BashExec0Silently` instead. + +#### BashExecSilently0 + +Like `BashExec0` but without the flicker. The stdin, stdout stderr will be piped to null. So it's non-interactive. -Type: { BashExecSilently = "string" } +Type: { BashExec0Silently = "string" } Example: -- Lua: `{ BashExecSilently = "tput bell" }` -- YAML: `BashExecSilently: "tput bell"` +- Lua: `{ BashExecSilently0 = "tput bell" }` +- YAML: `BashExecSilently0: "tput bell"` ### Calling Lua Functions diff --git a/src/app.rs b/src/app.rs index 57e2443..e810fba 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,6 @@ pub use crate::msg::in_::ExternalMsg; pub use crate::msg::in_::InternalMsg; pub use crate::msg::in_::MsgIn; pub use crate::msg::out::MsgOut; -use crate::newlines::unescape_string; pub use crate::node::Node; pub use crate::node::ResolvedNode; pub use crate::pipe::Pipe; @@ -175,7 +174,6 @@ pub struct App { pub mode: Mode, pub layout: Layout, pub input: InputBuffer, - pub input_unescaped: String, pub pid: u32, pub session_path: String, pub pipe: Pipe, @@ -310,7 +308,6 @@ impl App { mode, layout, input, - input_unescaped: Default::default(), pid, session_path: session_path.clone(), pipe: Pipe::from_session_path(&session_path)?, @@ -452,9 +449,13 @@ impl App { SwitchLayoutBuiltin(mode) => self.switch_layout_builtin(&mode), SwitchLayoutCustom(mode) => self.switch_layout_custom(&mode), Call(cmd) => self.call(cmd), + Call0(cmd) => self.call0(cmd), CallSilently(cmd) => self.call_silently(cmd), + CallSilently0(cmd) => self.call_silently0(cmd), BashExec(cmd) => self.bash_exec(cmd), + BashExec0(cmd) => self.bash_exec0(cmd), BashExecSilently(cmd) => self.bash_exec_silently(cmd), + BashExecSilently0(cmd) => self.bash_exec_silently0(cmd), CallLua(func) => self.call_lua(func), CallLuaSilently(func) => self.call_lua_silently(func), LuaEval(code) => self.lua_eval(code), @@ -557,14 +558,6 @@ impl App { } }); - self.input_unescaped = unescape_string( - &self.input - .buffer - .as_ref() - .map(|i| i.value().to_string()) - .unwrap_or_default(), - )?; - for msg in msgs { self = self.enqueue(Task::new(MsgIn::External(msg), Some(key))); } @@ -1108,12 +1101,24 @@ impl App { Ok(self) } + fn call0(mut self, command: Command) -> Result { + self.logs_hidden = true; + self.msg_out.push_back(MsgOut::Call0(command)); + Ok(self) + } + fn call_silently(mut self, command: Command) -> Result { self.logs_hidden = true; self.msg_out.push_back(MsgOut::CallSilently(command)); Ok(self) } + fn call_silently0(mut self, command: Command) -> Result { + self.logs_hidden = true; + self.msg_out.push_back(MsgOut::CallSilently0(command)); + Ok(self) + } + fn bash_exec(self, script: String) -> Result { self.call(Command { command: "bash".into(), @@ -1121,6 +1126,13 @@ impl App { }) } + fn bash_exec0(self, script: String) -> Result { + self.call0(Command { + command: "bash".into(), + args: vec!["-c".into(), script], + }) + } + fn bash_exec_silently(self, script: String) -> Result { self.call_silently(Command { command: "bash".into(), @@ -1128,6 +1140,13 @@ impl App { }) } + fn bash_exec_silently0(self, script: String) -> Result { + self.call_silently0(Command { + command: "bash".into(), + args: vec!["-c".into(), script], + }) + } + fn call_lua(mut self, func: String) -> Result { self.logs_hidden = true; self.msg_out.push_back(MsgOut::CallLua(func)); @@ -1560,48 +1579,48 @@ impl App { } } - pub fn directory_nodes_str(&self) -> String { + pub fn directory_nodes_str(&self, delimiter: char) -> String { self.directory_buffer .as_ref() .map(|d| { d.nodes .iter() - .map(|n| format!("{}\n", n.absolute_path)) + .map(|n| format!("{}{}", n.absolute_path, delimiter)) .collect::>() .join("") }) .unwrap_or_default() } - pub fn pwd_str(&self) -> String { - format!("{}\n", &self.pwd) + pub fn pwd_str(&self, delimiter: char) -> String { + format!("{}{}", &self.pwd, delimiter) } - pub fn selection_str(&self) -> String { + pub fn selection_str(&self, delimiter: char) -> String { self.selection .iter() - .map(|n| format!("{}\n", n.absolute_path)) + .map(|n| format!("{}{}", n.absolute_path, delimiter)) .collect::>() .join("") } - pub fn result_str(&self) -> String { + pub fn result_str(&self, delimiter: char) -> String { self.result() .into_iter() - .map(|n| format!("{}\n", n.absolute_path)) + .map(|n| format!("{}{}", n.absolute_path, delimiter)) .collect::>() .join("") } - pub fn logs_str(&self) -> String { + pub fn logs_str(&self, delimiter: char) -> String { self.logs .iter() - .map(|l| format!("{}\n", l)) + .map(|l| format!("{}{}", l, delimiter)) .collect::>() .join("") } - pub fn global_help_menu_str(&self) -> String { + pub fn global_help_menu_str(&self, delimiter: char) -> String { let builtin = self.config.modes.builtin.clone(); let custom = self.config.modes.custom.clone(); let read_only = self.config.general.read_only; @@ -1617,53 +1636,53 @@ impl App { .help_menu() .iter() .map(|l| match l { - HelpMenuLine::Paragraph(p) => format!("\t{}\n", p), + HelpMenuLine::Paragraph(p) => format!("\t{}{}", p, delimiter), HelpMenuLine::KeyMap(k, remaps, h) => { let remaps = remaps.join(", "); - format!(" {:15} | {:25} | {}\n", k, remaps, h) + format!(" {:15} | {:25} | {}{}", k, remaps, h , delimiter) } }) .collect::>() .join(""); format!( - "### {}\n\n key | remaps | action\n --------------- | ------------------------- | ------\n{}\n", - name, help + "### {}{d}{d} key | remaps | action\n --------------- | ------------------------- | ------{d}{}{d}", + name, help, d = delimiter ) }) .collect::>() - .join("\n") + .join(&delimiter.to_string()) } - pub fn history_str(&self) -> String { + pub fn history_str(&self, delimiter: char) -> String { self.history .paths .iter() - .map(|p| format!("{}\n", &p)) + .map(|p| format!("{}{}", &p, delimiter)) .collect::>() .join("") } - pub fn write_pipes(&self) -> Result<()> { + pub fn write_pipes(&self, delimiter: char) -> Result<()> { fs::create_dir_all(self.pipe.path.clone())?; fs::write(&self.pipe.msg_in, "")?; - let selection_str = self.selection_str(); + let selection_str = self.selection_str(delimiter); fs::write(&self.pipe.selection_out, selection_str)?; - let history_str = self.history_str(); + let history_str = self.history_str(delimiter); fs::write(&self.pipe.history_out, history_str)?; - let directory_nodes_str = self.directory_nodes_str(); + let directory_nodes_str = self.directory_nodes_str(delimiter); fs::write(&self.pipe.directory_nodes_out, directory_nodes_str)?; - let logs_str = self.logs_str(); + let logs_str = self.logs_str(delimiter); fs::write(&self.pipe.logs_out, logs_str)?; - let result_str = self.result_str(); + let result_str = self.result_str(delimiter); fs::write(&self.pipe.result_out, result_str)?; - let global_help_menu_str = self.global_help_menu_str(); + let global_help_menu_str = self.global_help_menu_str(delimiter); fs::write(&self.pipe.global_help_menu_out, global_help_menu_str)?; Ok(()) diff --git a/src/init.lua b/src/init.lua index e434fbd..6118be5 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1045,9 +1045,9 @@ xplr.config.modes.builtin.default = { help = "global help menu", messages = { { - BashExec = [===[ + BashExec0 = [===[ [ -z "$PAGER" ] && PAGER="less -+F" - cat -- "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" | ${PAGER:?} + cat -- "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" | tr '\0' '\n' | ${PAGER:?} ]===], }, }, @@ -1154,9 +1154,10 @@ xplr.config.modes.builtin.default = { "PopMode", { SwitchModeBuiltin = "rename" }, { - BashExecSilently = [===[ + BashExecSilently0 = [===[ NAME=$(basename "${XPLR_FOCUS_PATH:?}") - printf "SetInputBuffer: %s\0" "${NAME:?}" >> "${XPLR_PIPE_MSG_IN:?}" + NAME_ESC=${NAME//\"/\\\"} + printf 'SetInputBuffer: "%s"\0' "${NAME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, }, @@ -1167,9 +1168,10 @@ xplr.config.modes.builtin.default = { "PopMode", { SwitchModeBuiltin = "duplicate_as" }, { - BashExecSilently = [===[ + BashExecSilently0 = [===[ NAME=$(basename "${XPLR_FOCUS_PATH:?}") - printf "SetInputBuffer: %s\0" "${NAME:?}" >> "${XPLR_PIPE_MSG_IN:?}" + NAME_ESC=${NAME//\"/\\\"} + printf 'SetInputBuffer: "%s"\0' "${NAME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, }, @@ -1204,8 +1206,9 @@ xplr.config.modes.builtin.default = { help = "go home", messages = { { - BashExecSilently = [===[ - printf "ChangeDirectory: %s\0" "${HOME:?}" >> "${XPLR_PIPE_MSG_IN:?}" + BashExecSilently0 = [===[ + HOME_ESC=${HOME//\"/\\\"} + printf 'ChangeDirectory: "%s"\0' "${HOME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, }, @@ -1301,8 +1304,8 @@ xplr.config.modes.builtin.debug_error = { help = "open logs in editor", messages = { { - BashExec = [===[ - cat "${XPLR_PIPE_LOGS_OUT:?}" | ${EDITOR:-vi} - + BashExec0 = [===[ + cat "${XPLR_PIPE_LOGS_OUT:?}" | tr '\0' '\n' | ${EDITOR:-vi} - ]===], }, }, @@ -1362,11 +1365,15 @@ xplr.config.modes.builtin.go_to_path = { help = "submit", messages = { { - BashExecSilently = [===[ - if [ -d "$XPLR_INPUT_BUFFER" ]; then - printf "ChangeDirectory: %s\0" "$XPLR_INPUT_BUFFER" >> "${XPLR_PIPE_MSG_IN:?}" - elif [ -e "$XPLR_INPUT_BUFFER" ]; then - printf "FocusPath: %s\0" "$XPLR_INPUT_BUFFER" >> "${XPLR_PIPE_MSG_IN:?}" + BashExecSilently0 = [===[ + PTH=${XPLR_INPUT_BUFFER} + PTH_ESC=${XPLR_INPUT_BUFFER//\"/\\\"} + if [ -d "$PTH" ]; then + printf 'ChangeDirectory: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}" + elif [ -e "$PTH" ]; then + printf 'FocusPath: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}" + else + printf 'LogError: "Could not find %s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}" fi ]===], }, @@ -1399,16 +1406,17 @@ xplr.config.modes.builtin.selection_ops = { help = "copy here", messages = { { - BashExec = [===[ - (while IFS= read -r -d '' line; do - if cp -vr -- "${line:?}" ./; then - printf "LogSuccess: %s\0" "$line copied to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - else - printf "LogError: %s\0" "Failed to copy $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - fi + BashExec0 = [===[ + (while IFS= read -r -d '' LINE; do + LINE_ESC=${LINE//\"/\\\"} + if cp -vr -- "${LINE:?}" ./; then + printf 'LogSuccess: "%s"\0' "$LINE_ESC copied to ." >> "${XPLR_PIPE_MSG_IN:?}" + else + printf 'LogError: "%s"\0' "Failed to copy $LINE_ESC to ." >> "${XPLR_PIPE_MSG_IN:?}" + fi done < "${XPLR_PIPE_SELECTION_OUT:?}") - printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}" - printf "ClearSelection\0" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}" + printf 'ClearSelection\0' >> "${XPLR_PIPE_MSG_IN:?}" read -p "[enter to continue]" ]===], }, @@ -1419,15 +1427,16 @@ xplr.config.modes.builtin.selection_ops = { help = "move here", messages = { { - BashExec = [===[ - (while IFS= read -r -d '' line; do - if mv -v -- "${line:?}" ./; then - printf "LogSuccess: %s\0" "$line moved to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - else - printf "LogError: %s\0" "Failed to move $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - fi + BashExec0 = [===[ + (while IFS= read -r -d '' LINE; do + LINE_ESC=${LINE//\"/\\\"} + if mv -v -- "${LINE:?}" ./; then + printf 'LogSuccess: "%s"\0' "$LINE_ESC moved to ." >> "${XPLR_PIPE_MSG_IN:?}" + else + printf 'LogError: "%s"\0' "Failed to move $LINE_ESC to ." >> "${XPLR_PIPE_MSG_IN:?}" + fi done < "${XPLR_PIPE_SELECTION_OUT:?}") - printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}" read -p "[enter to continue]" ]===], }, @@ -1441,30 +1450,6 @@ xplr.config.modes.builtin.selection_ops = { "PopMode", }, }, - ["x"] = { - help = "open in gui", - messages = { - { - BashExecSilently = [===[ - if [ -z "$OPENER" ]; then - if command -v xdg-open; then - OPENER=xdg-open - elif command -v open; then - OPENER=open - else - printf "LogError: %s\0" "$OPENER not found" >> "${XPLR_PIPE_MSG_IN:?}" - exit 1 - fi - fi - (while IFS= read -r -d '' line; do - $OPENER "${line:?}" > /dev/null 2>&1 - done < "${XPLR_PIPE_RESULT_OUT:?}") - ]===], - }, - "ClearScreen", - "PopMode", - }, - }, }, }, } @@ -1514,16 +1499,17 @@ xplr.config.modes.builtin.create_directory = { help = "submit", messages = { { - BashExecSilently = [===[ + BashExecSilently0 = [===[ PTH="$XPLR_INPUT_BUFFER" - if [ "${PTH}" ]; then - mkdir -p -- "${PTH:?}" \ - && printf "SetInputBuffer: ''\0" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "LogSuccess: %s\0" "$PTH created" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "FocusPath: %s\0" "$PTH" >> "${XPLR_PIPE_MSG_IN:?}" + PTH_ESC=${PTH//\"/\\\"} + if [ "$PTH" ]; then + mkdir -p -- "$PTH" \ + && printf 'SetInputBuffer: ""\0' >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'LogSuccess: "%s"\0' "$PTH_ESC created" >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'FocusPath: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}" else - printf "PopMode\0" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'PopMode\0' >> "${XPLR_PIPE_MSG_IN:?}" fi ]===], }, @@ -1556,8 +1542,9 @@ xplr.config.modes.builtin.create_file = { help = "submit", messages = { { - BashExecSilently = [===[ + BashExecSilently0 = [===[ PTH="$XPLR_INPUT_BUFFER" + PTH_ESC=${PTH//\"/\\\"} if [ "$PTH" ]; then mkdir -p -- "$(dirname $PTH)" \ && touch -- "$PTH" \ @@ -1566,7 +1553,7 @@ xplr.config.modes.builtin.create_file = { && printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \ && printf 'FocusPath: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}" else - printf "PopMode\0" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'PopMode\0' >> "${XPLR_PIPE_MSG_IN:?}" fi ]===], }, @@ -1657,22 +1644,25 @@ xplr.config.modes.builtin.go_to = { { SetInputBuffer = "" }, }, }, + ["x"] = { help = "open in gui", messages = { { - BashExecSilently = [===[ + BashExecSilently0 = [===[ if [ -z "$OPENER" ]; then if command -v xdg-open; then OPENER=xdg-open elif command -v open; then OPENER=open else - printf "LogError: %s\0" "$OPENER not found" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'LogError: "$OPENER not found"\0' >> "${XPLR_PIPE_MSG_IN:?}" exit 1 fi fi - $OPENER "${XPLR_FOCUS_PATH:?}" > /dev/null 2>&1 + (while IFS= read -r -d '' LINE; do + $OPENER "${LINE:?}" > /dev/null 2>&1 + done < "${XPLR_PIPE_RESULT_OUT:?}") ]===], }, "ClearScreen", @@ -1700,16 +1690,18 @@ xplr.config.modes.builtin.rename = { help = "submit", messages = { { - BashExecSilently = [===[ + BashExecSilently0 = [===[ SRC="${XPLR_FOCUS_PATH:?}" + SRC_ESC=${SRC//\"/\\\"} TARGET="${XPLR_INPUT_BUFFER:?}" + TARGET_ESC=${TARGET//\"/\\\"} if [ -e "${TARGET:?}" ]; then - printf "LogError: %s\0" "$TARGET already exists" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'LogError: "%s"\0' "$TARGET_ESC already exists" >> "${XPLR_PIPE_MSG_IN:?}" else mv -- "${SRC:?}" "${TARGET:?}" \ - && printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "FocusPath: %s\0" "$TARGET" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "LogSuccess: %s\0" "$SRC renamed to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" + && printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'FocusPath: "%s"\0' "$TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'LogSuccess: "%s"\0' "$SRC_ESC renamed to $TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}" fi ]===], }, @@ -1742,16 +1734,18 @@ xplr.config.modes.builtin.duplicate_as = { help = "submit", messages = { { - BashExecSilently = [===[ + BashExecSilently0 = [===[ SRC="${XPLR_FOCUS_PATH:?}" + SRC_ESC=${SRC//\"/\\\"} TARGET="${XPLR_INPUT_BUFFER:?}" + TARGET_ESC=${TARGET//\"/\\\"} if [ -e "${TARGET:?}" ]; then - printf "LogError: %s\0" "$TARGET already exists" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'LogError: "%s"\0' "$TARGET_ESC already exists" >> "${XPLR_PIPE_MSG_IN:?}" else cp -r -- "${SRC:?}" "${TARGET:?}" \ - && printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "FocusPath: %s\0" "$TARGET" >> "${XPLR_PIPE_MSG_IN:?}" \ - && printf "LogSuccess: %s\0" "$SRC duplicated as $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" + && printf 'ExplorePwd\0' >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'FocusPath: "%s"\0' "$TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}" \ + && printf 'LogSuccess: "%s"\0' "$SRC_ESC duplicated as $TARGET_ESC" >> "${XPLR_PIPE_MSG_IN:?}" fi ]===], }, @@ -1778,15 +1772,16 @@ xplr.config.modes.builtin.delete = { help = "force delete", messages = { { - BashExec = [===[ - (while IFS= read -r -d '' line; do - if rm -rfv -- "${line:?}"; then - printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}" - else - printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" - fi + BashExec0 = [===[ + (while IFS= read -r -d '' LINE; do + LINE_ESC=${LINE//\"/\\\"} + if rm -rfv -- "${LINE:?}"; then + printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}" + else + printf 'LogError: "%s"\0' "Failed to delete $LINE_ESC" >> "${XPLR_PIPE_MSG_IN:?}" + fi done < "${XPLR_PIPE_RESULT_OUT:?}") - printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}" read -p "[enter to continue]" ]===], }, @@ -1797,23 +1792,24 @@ xplr.config.modes.builtin.delete = { help = "delete", messages = { { - BashExec = [===[ - (while IFS= read -r -d '' line; do - if [ -d "$line" ] && [ ! -L "$line" ]; then - if rmdir -v -- "${line:?}"; then - printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}" + BashExec0 = [===[ + (while IFS= read -r -d '' LINE; do + LINE_ESC=${LINE//\"/\\\"} + if [ -d "$LINE" ] && [ ! -L "$LINE" ]; then + if rmdir -v -- "${LINE:?}"; then + printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}" + else + printf 'LogError: "%s"\0' "Failed to delete $LINE_ESC" >> "${XPLR_PIPE_MSG_IN:?}" + fi else - printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" + if rm -v -- "${LINE:?}"; then + printf 'LogSuccess: "%s"\0' "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}" + else + printf 'LogError: "%s"\0' "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" + fi fi - else - if rm -v -- "${line:?}"; then - printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}" - else - printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" - fi - fi done < "${XPLR_PIPE_RESULT_OUT:?}") - printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}" + printf 'ExplorePwdAsync\0' >> "${XPLR_PIPE_MSG_IN:?}" read -p "[enter to continue]" ]===], }, @@ -1834,7 +1830,7 @@ xplr.config.modes.builtin.action = { ["!"] = { help = "shell", messages = { - { Call = { command = "bash", args = { "-i" } } }, + { Call0 = { command = "bash", args = { "-i" } } }, "ExplorePwdAsync", "PopMode", }, @@ -1850,7 +1846,7 @@ xplr.config.modes.builtin.action = { help = "open in editor", messages = { { - BashExec = [===[ + BashExec0 = [===[ ${EDITOR:-vi} "${XPLR_FOCUS_PATH:?}" ]===], }, @@ -1861,9 +1857,9 @@ xplr.config.modes.builtin.action = { help = "logs", messages = { { - BashExec = [===[ + BashExec0 = [===[ [ -z "$PAGER" ] && PAGER="less -+F" - cat -- "${XPLR_PIPE_LOGS_OUT}" | ${PAGER:?} + cat -- "${XPLR_PIPE_LOGS_OUT}" | tr '\0' '\n' | ${PAGER:?} ]===], }, "PopMode", @@ -2570,3 +2566,4 @@ end -- You can also use nested tables such as -- `xplr.fn.custom.my_plugin.my_function` to define custom functions. xplr.fn.custom = {} + diff --git a/src/msg/in_/external.rs b/src/msg/in_/external.rs index d0dbb23..53bd1c3 100644 --- a/src/msg/in_/external.rs +++ b/src/msg/in_/external.rs @@ -3,7 +3,6 @@ use indexmap::IndexSet; use regex::Regex; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; -use crate::newlines::escape_string; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum ExternalMsg { @@ -483,52 +482,72 @@ pub enum ExternalMsg { /// ### Executing Commands ------------------------------------------------ + /// Like `Call0`, but it uses `\n` as the delimiter in input/output pipes, + /// hence it cannot handle files with `\n` in the name. + /// You may want to use `Call0` instead. + Call(Command), + /// Call a shell command with the given arguments. /// Note that the arguments will be shell-escaped. /// So to read the variables, the `-c` option of the shell /// can be used. - /// You may need to pass `ExplorePwd` depening on the expectation. + /// You may need to pass `ExplorePwd` depending on the expectation. /// - /// Type: { Call = { command = string, args = { "list", "of", "string" } } + /// Type: { Call0 = { command = "string", args = { "list", "of", "string" } } /// /// Example: /// - /// - Lua: `{ Call = { command = "bash", args = { "-c", "read -p test" } } }` - /// - YAML: `Call: { command: bash, args: ["-c", "read -p test"] }` - Call(Command), + /// - Lua: `{ Call0 = { command = "bash", args = { "-c", "read -p test" } } }` + /// - YAML: `Call0: { command: bash, args: ["-c", "read -p test"] }` + Call0(Command), - /// Like `Call` but without the flicker. The stdin, stdout + /// Like `CallSilently0`, but it uses `\n` as the delimiter in input/output + /// pipes, hence it cannot handle files with `\n` in the name. + /// You may want to use `Call0Silently` instead. + CallSilently(Command), + + /// Like `Call0` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// - /// Type: { CallSilently = "string" } + /// Type: { CallSilently0 = { command = "string", args = {"list", "of", "string"} } } /// /// Example: /// - /// - Lua: `{ CallSilently = { command = "tput", args = { "bell" } } }` - /// - YAML: `CallSilently: { command: tput, args: ["bell"] }` - CallSilently(Command), + /// - Lua: `{ CallSilently0 = { command = "tput", args = { "bell" } } }` + /// - YAML: `CallSilently0: { command: tput, args: ["bell"] }` + CallSilently0(Command), + + /// Like `BashExec0`, but it uses `\n` as the delimiter in input/output + /// pipes, hence it cannot handle files with `\n` in the name. + /// You may want to use `BashExec0` instead. + BashExec(String), /// An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}` /// where `{string}` is the given value. /// - /// Type: { BashExec = "string" } + /// Type: { BashExec0 = "string" } /// /// Example: /// - /// - Lua: `{ BashExec = "read -p test" }` - /// - YAML: `BashExec: "read -p test"` - BashExec(String), + /// - Lua: `{ BashExec0 = "read -p test" }` + /// - YAML: `BashExec0: "read -p test"` + BashExec0(String), - /// Like `BashExec` but without the flicker. The stdin, stdout + /// Like `BashExecSilently0`, but it uses `\n` as the delimiter in + /// input/output pipes, hence it cannot handle files with `\n` in the name. + /// You may want to use `BashExec0Silently` instead. + BashExecSilently(String), + + /// Like `BashExec0` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// - /// Type: { BashExecSilently = "string" } + /// Type: { BashExec0Silently = "string" } /// /// Example: /// - /// - Lua: `{ BashExecSilently = "tput bell" }` - /// - YAML: `BashExecSilently: "tput bell"` - BashExecSilently(String), + /// - Lua: `{ BashExecSilently0 = "tput bell" }` + /// - YAML: `BashExecSilently0: "tput bell"` + BashExecSilently0(String), /// ### Calling Lua Functions ---------------------------------------------- @@ -1071,7 +1090,7 @@ impl TryFrom<&str> for ExternalMsg { if value.starts_with('!') { serde_yaml::from_str(value) } else if let Some((msg, args)) = value.split_once(' ') { - let msg = format!("!{} {}", msg.trim_end_matches(':'), escape_string(args)); + let msg = format!("!{} {}", msg.trim_end_matches(':'), args); serde_yaml::from_str(&msg) } else { serde_yaml::from_str(value) @@ -1086,11 +1105,11 @@ impl ExternalMsg { Self::Call(_) | Self::Call0(_) | Self::CallSilently(_) - | Self::Call0Silently(_) + | Self::CallSilently0(_) | Self::BashExec(_) | Self::BashExec0(_) | Self::BashExecSilently(_) - | Self::BashExec0Silently(_) + | Self::BashExecSilently0(_) | Self::CallLua(_) | Self::CallLuaSilently(_) | Self::LuaEval(_) diff --git a/src/msg/out/mod.rs b/src/msg/out/mod.rs index 5f4cc8c..4d9759f 100644 --- a/src/msg/out/mod.rs +++ b/src/msg/out/mod.rs @@ -10,7 +10,9 @@ pub enum MsgOut { ClearScreen, Debug(String), Call(Command), + Call0(Command), CallSilently(Command), + CallSilently0(Command), CallLua(String), CallLuaSilently(String), LuaEval(String), diff --git a/src/runner.rs b/src/runner.rs index 2fc5481..4ebbf77 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -16,12 +16,14 @@ use crossterm::terminal as term; use mlua::LuaSerdeExt; use mlua::Value; use std::fs; +use std::fs::File; use std::io::Write; use std::path::PathBuf; -use std::process::{Command, ExitStatus, Stdio}; +use std::process::{Command, Stdio}; use std::sync::mpsc; use tui::backend::CrosstermBackend; use tui::Terminal; +use tui_input::Input; pub fn get_tty() -> Result { let tty = "/dev/tty"; @@ -60,7 +62,30 @@ fn call_lua_heavy( lua::call(lua, func, arg) } -fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result { +fn call( + mut app: app::App, + cmd: app::Command, + silent: bool, + terminal: &mut Terminal>, + event_reader: &mut EventReader, + mouse_enabled: &mut bool, + delimiter: char, +) -> Result { + if !silent { + if *mouse_enabled { + execute!(terminal.backend_mut(), event::DisableMouseCapture) + .unwrap_or_default(); + } + + event_reader.stop(); + + terminal.clear()?; + terminal.set_cursor(0, 0)?; + term::disable_raw_mode()?; + terminal.show_cursor()?; + } + + app.write_pipes(delimiter)?; let focus_index = app .directory_buffer .as_ref() @@ -74,10 +99,17 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result { (get_tty()?.into(), get_tty()?.into(), get_tty()?.into()) }; - Command::new(cmd.command.clone()) + let input_buffer = app + .input + .buffer + .as_ref() + .map(Input::to_string) + .unwrap_or_default(); + + let status = Command::new(cmd.command.clone()) .env("XPLR_APP_VERSION", app.version.clone()) .env("XPLR_PID", &app.pid.to_string()) - .env("XPLR_INPUT_BUFFER", &app.input_unescaped) + .env("XPLR_INPUT_BUFFER", input_buffer) .env("XPLR_FOCUS_PATH", app.focused_node_str()) .env("XPLR_FOCUS_INDEX", focus_index) .env("XPLR_SESSION_PATH", &app.session_path) @@ -100,7 +132,49 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result { .stderr(stderr) .args(cmd.args) .status() - .map_err(Error::new) + .map(|s| { + if s.success() { + Ok(()) + } else { + Err(format!("process exited with code {}", &s)) + } + }) + .unwrap_or_else(|e| Err(e.to_string())); + + match pipe::read_all(&app.pipe.msg_in, delimiter) { + Ok(msgs) => { + app = app.handle_batch_external_msgs(msgs)?; + } + Err(err) => { + app = app.log_error(err.to_string())?; + } + }; + + app.cleanup_pipes()?; + + if let Err(e) = status { + app = app.log_error(e)?; + }; + + if !silent { + terminal.clear()?; + term::enable_raw_mode()?; + terminal.hide_cursor()?; + event_reader.start(); + + if *mouse_enabled { + match execute!(terminal.backend_mut(), event::EnableMouseCapture) { + Ok(_) => { + *mouse_enabled = true; + } + Err(e) => { + app = app.log_error(e.to_string())?; + } + } + } + } + + Ok(app) } fn start_fifo(path: &str, focus_path: &str) -> Result { @@ -122,6 +196,7 @@ pub struct Runner { read_only: bool, print_pwd_as_result: bool, selection: Vec, + delimiter: char, } impl Runner { @@ -158,6 +233,7 @@ impl Runner { read_only: cli.read_only, print_pwd_as_result: cli.print_pwd_as_result, selection: paths.collect(), + delimiter: if cli.write0 { '\0' } else { '\n' }, }) } @@ -275,27 +351,27 @@ impl Runner { } PrintPwdAndQuit => { - result = Ok(Some(format!("{}\n", app.pwd))); + result = Ok(Some(app.pwd_str(self.delimiter))); break 'outer; } PrintFocusPathAndQuit => { - result = Ok(app - .focused_node() - .map(|n| format!("{}\n", n.absolute_path))); + result = Ok(app.focused_node().map(|n| { + format!("{}{}", n.absolute_path, self.delimiter) + })); break 'outer; } PrintSelectionAndQuit => { - result = Ok(Some(app.selection_str())); + result = Ok(Some(app.selection_str(self.delimiter))); break 'outer; } PrintResultAndQuit => { result = if self.print_pwd_as_result { - Ok(Some(app.pwd_str())) + Ok(Some(app.pwd_str(self.delimiter))) } else { - Ok(Some(app.result_str())) + Ok(Some(app.result_str(self.delimiter))) }; break 'outer; @@ -490,37 +566,6 @@ impl Runner { }; } - CallSilently(cmd) => { - app.write_pipes()?; - let status = call(&app, cmd, true) - .map(|s| { - if s.success() { - Ok(()) - } else { - Err(format!( - "process exited with code {}", - &s - )) - } - }) - .unwrap_or_else(|e| Err(e.to_string())); - - match pipe::read_all(&app.pipe.msg_in) { - Ok(msgs) => { - app = app.handle_batch_external_msgs(msgs)?; - } - Err(err) => { - app = app.log_error(err.to_string())?; - } - }; - - app.cleanup_pipes()?; - - if let Err(e) = status { - app = app.log_error(e.to_string())?; - }; - } - CallLua(func) => { execute!( terminal.backend_mut(), @@ -684,67 +729,51 @@ impl Runner { } Call(cmd) => { - execute!( - terminal.backend_mut(), - event::DisableMouseCapture - ) - .unwrap_or_default(); - - event_reader.stop(); - - terminal.clear()?; - terminal.set_cursor(0, 0)?; - term::disable_raw_mode()?; - terminal.show_cursor()?; - - app.write_pipes()?; - let status = call(&app, cmd, false) - .map(|s| { - if s.success() { - Ok(()) - } else { - Err(format!( - "process exited with code {}", - &s - )) - } - }) - .unwrap_or_else(|e| Err(e.to_string())); - - // TODO remove duplicate segment - match pipe::read_all(&app.pipe.msg_in) { - Ok(msgs) => { - app = app.handle_batch_external_msgs(msgs)?; - } - Err(err) => { - app = app.log_error(err.to_string())?; - } - }; - - app.cleanup_pipes()?; + app = call( + app, + cmd, + false, + &mut terminal, + &mut event_reader, + &mut mouse_enabled, + '\n', + )?; + } - if let Err(e) = status { - app = app.log_error(e.to_string())?; - }; + Call0(cmd) => { + app = call( + app, + cmd, + false, + &mut terminal, + &mut event_reader, + &mut mouse_enabled, + '\0', + )?; + } - terminal.clear()?; - term::enable_raw_mode()?; - terminal.hide_cursor()?; - event_reader.start(); + CallSilently(cmd) => { + app = call( + app, + cmd, + true, + &mut terminal, + &mut event_reader, + &mut mouse_enabled, + '\n', + )?; + } - if mouse_enabled { - match execute!( - terminal.backend_mut(), - event::EnableMouseCapture - ) { - Ok(_) => { - mouse_enabled = true; - } - Err(e) => { - app = app.log_error(e.to_string())?; - } - } - } + CallSilently0(cmd) => { + app = call( + app, + cmd, + true, + &mut terminal, + &mut event_reader, + &mut mouse_enabled, + '\0', + )?; } }; }