From bffe1d43ec05f4242078b2d063dcdf9969d3227d Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Tue, 25 Oct 2022 00:26:53 +0530 Subject: [PATCH] Escape newline in selection list --- .../en/src/environment-variables-and-pipes.md | 66 ++++++++++++------- docs/en/src/messages.md | 6 +- docs/en/src/writing-plugins.md | 3 +- src/init.lua | 30 ++++++--- src/msg/in_/external.rs | 6 +- src/ui.rs | 2 +- 6 files changed, 74 insertions(+), 39 deletions(-) diff --git a/docs/en/src/environment-variables-and-pipes.md b/docs/en/src/environment-variables-and-pipes.md index c061f91..79c1dea 100644 --- a/docs/en/src/environment-variables-and-pipes.md +++ b/docs/en/src/environment-variables-and-pipes.md @@ -1,7 +1,7 @@ # Environment Variables and Pipes Alternative to `CallLua`, `CallLuaSilently` messages that call Lua functions, -there are `Call`, `CallSilently`, `BashExec`, `BashExecSilently` messages +there are `Call0`, `CallSilently0`, `BashExec0`, `BashExecSilently0` messages that call shell commands. However, unlike the Lua functions, these shell commands have to read the useful @@ -16,12 +16,13 @@ To see the environment variables and pipes, invoke the shell by typing `:!` in d mode and run the following command: ``` -env | grep ^XPLR_ +env | grep ^XPLR ``` You will see something like: ``` +XPLR=xplr XPLR_FOCUS_INDEX=0 XPLR_MODE=action to XPLR_PIPE_SELECTION_OUT=/run/user/1000/xplr/session/122278/pipe/selection_out @@ -43,6 +44,7 @@ called ["pipe"s][18]. The other variables are single-line variables containing simple information: +- [XPLR][38] - [XPLR_APP_VERSION][30] - [XPLR_FOCUS_INDEX][31] - [XPLR_FOCUS_PATH][32] @@ -53,13 +55,18 @@ The other variables are single-line variables containing simple information: ### Environment variables +#### XPLR + +The binary path of xplr command. + #### XPLR_APP_VERSION Self-explanatory. #### XPLR_FOCUS_INDEX -Contains the index of the currently focused item, as seen in [column-renderer/index][10]. +Contains the index of the currently focused item, as seen in +[column-renderer/index][10]. #### XPLR_FOCUS_PATH @@ -67,7 +74,8 @@ Contains the full path of the currently focused node. #### XPLR_INPUT_BUFFER -The line currently in displaying in the xplr input buffer. For e.g. the search input while searching. See [Reading Input][37]. +The line currently in displaying in the xplr input buffer. For e.g. the search +input while searching. See [Reading Input][37]. #### XPLR_MODE @@ -79,7 +87,8 @@ Contains the process ID of the current xplr process. #### XPLR_SESSION_PATH -Contains the current session path, like /tmp/runtime-"$USER"/xplr/session/"$XPLR_PID"/, you can find temporary files here, such as pipes. +Contains the current session path, like /tmp/runtime-"$USER"/xplr/session/"$XPLR_PID"/, +you can find temporary files here, such as pipes. ### Pipes @@ -93,7 +102,10 @@ Currently there is only one input pipe. `XPLR_PIPE_*_OUT` are the output pipes that contain data which cannot be exposed directly via environment variables, like multi-line strings. -These pipes can be accessed as plaintext files located in $XPLR_SESSION_PATH. +These pipes can be accessed as plain text files located in $XPLR_SESSION_PATH. + +Depending on the message (e.g. `Call` or `Call0`), each line will be separated +by newline or null character (`\n` or `\0`). - [XPLR_PIPE_SELECTION_OUT][21] - [XPLR_PIPE_GLOBAL_HELP_MENU_OUT][22] @@ -104,13 +116,21 @@ These pipes can be accessed as plaintext files located in $XPLR_SESSION_PATH. #### XPLR_PIPE_MSG_IN -Append new-line delimited messages to this pipe in [YAML][27] -(or [JSON][7]) syntax. These messages will be read and -handled by xplr after the command execution. +Append new messages to this pipe in [YAML][27] (or [JSON][7]) syntax. These +messages will be read and handled by xplr after the command execution. + +Depending on the message (e.g. `Call` or `Call0`) you need to separate each +message using newline or null character (`\n` or `\0`). + +> **_NOTE:_** Since version `v0.20.0`, it's recommended to avoid writing +> directly to this file, as safely escaping YAML strings is a lot of work. Use +> `xplr -m` / `xplr --pipe-msg-in` to pass messages to xplr in a safer way. +> +> Example: `"$XPLR" -m 'ChangeDirectory: %q' "${HOME:?}"` #### XPLR_PIPE_SELECTION_OUT -New-line delimited list of selected paths. +List of selected paths. #### XPLR_PIPE_GLOBAL_HELP_MENU_OUT @@ -118,20 +138,19 @@ The full help menu. #### XPLR_PIPE_LOGS_OUT -New-line delimited list of logs. +List of logs. #### XPLR_PIPE_RESULT_OUT -New-line delimited result (selected paths if any, else the focused path) +Result (selected paths if any, else the focused path) #### XPLR_PIPE_HISTORY_OUT -New-line delimited list of last visited paths (similar to jump list in vim). +List of last visited paths (similar to jump list in vim). #### XPLR_PIPE_DIRECTORY_NODES_OUT -New-line delimited list of paths, filtered and sorted as displayed in the -[files table][28]. +List of paths, filtered and sorted as displayed in the [files table][28]. ### Example: Using Environment Variables and Pipes @@ -140,14 +159,14 @@ xplr.config.modes.builtin.default.key_bindings.on_key.space = { help = "ask name and greet", messages = { { - BashExec = [===[ - echo "What's your name?" + BashExec0 = [===[ + echo "What's your name?" - read name - greeting="Hello $name!" - message="$greeting You are inside $PWD" + read name + greeting="Hello $name!" + message="$greeting You are inside $PWD" - echo LogSuccess: '"'$message'"' >> "${XPLR_PIPE_MSG_IN:?}" + "$XPLR" -m 'LogSuccess: %q' "$message" ]===] } } @@ -164,8 +183,8 @@ xplr.config.modes.builtin.default.key_bindings.on_key.X = { help = "open", messages = { { - BashExecSilently = [===[ - xdg-open "${XPLR_FOCUS_PATH:?}" + BashExecSilently0 = [===[ + xdg-open "${XPLR_FOCUS_PATH:?}" ]===], }, }, @@ -194,3 +213,4 @@ xplr.config.modes.builtin.default.key_bindings.on_key.X = { [35]: #xplr_pid [36]: #xplr_session_path [37]: messages.md#reading-input +[38]: #xplr diff --git a/docs/en/src/messages.md b/docs/en/src/messages.md index 8ba7b94..106d95e 100644 --- a/docs/en/src/messages.md +++ b/docs/en/src/messages.md @@ -570,7 +570,7 @@ Example: 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. +You may want to use `CallSilently0` instead. #### CallSilently0 @@ -606,14 +606,14 @@ Example: 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. +You may want to use `BashExecSilently0` instead. #### BashExecSilently0 Like `BashExec0` but without the flicker. The stdin, stdout stderr will be piped to null. So it's non-interactive. -Type: { BashExec0Silently = "string" } +Type: { BashExecSilently0 = "string" } Example: diff --git a/docs/en/src/writing-plugins.md b/docs/en/src/writing-plugins.md index d3848f9..2dd6bd8 100644 --- a/docs/en/src/writing-plugins.md +++ b/docs/en/src/writing-plugins.md @@ -58,7 +58,8 @@ Finally, after publishing, don't hesitate to `BashExec` and so on. File names may contain newline characters (e.g. `foo$'\n'bar`). - File names may also contain quotes. Use the syntax - `printf 'Call0: "%s"' "${FOO_ESC}"` where `FOO_ESC=${FOO//\"/\\\"}`. + `printf 'Call0: "%s"' "${FOO_ESC}"` where `FOO_ESC=${FOO//\"/\\\"}` and + `FOO_ESC=${FOO_ESC//$'\n'/\\n}`. - Check for empty variables using the syntax `${FOO:?}` or use a default value `${FOO:-defaultvalue}`. diff --git a/src/init.lua b/src/init.lua index c099bb8..11f478c 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1045,9 +1045,9 @@ xplr.config.modes.builtin.default = { help = "global help menu", messages = { { - BashExec0 = [===[ + BashExec = [===[ [ -z "$PAGER" ] && PAGER="less -+F" - cat -- "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" | tr '\0' '\n' | ${PAGER:?} + cat -- "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" | ${PAGER:?} ]===], }, }, @@ -1157,6 +1157,7 @@ xplr.config.modes.builtin.default = { BashExecSilently0 = [===[ NAME=$(basename "${XPLR_FOCUS_PATH:?}") NAME_ESC=${NAME//\"/\\\"} + NAME_ESC=${NAME_ESC//$'\n'/\\n} printf 'SetInputBuffer: "%s"\0' "${NAME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, @@ -1171,6 +1172,7 @@ xplr.config.modes.builtin.default = { BashExecSilently0 = [===[ NAME=$(basename "${XPLR_FOCUS_PATH:?}") NAME_ESC=${NAME//\"/\\\"} + NAME_ESC=${NAME_ESC//$'\n'/\\n} printf 'SetInputBuffer: "%s"\0' "${NAME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, @@ -1208,6 +1210,7 @@ xplr.config.modes.builtin.default = { { BashExecSilently0 = [===[ HOME_ESC=${HOME//\"/\\\"} + HOME_ESC=${HOME_ESC//$'\n'/\\n} printf 'ChangeDirectory: "%s"\0' "${HOME_ESC:?}" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, @@ -1304,8 +1307,8 @@ xplr.config.modes.builtin.debug_error = { help = "open logs in editor", messages = { { - BashExec0 = [===[ - cat "${XPLR_PIPE_LOGS_OUT:?}" | tr '\0' '\n' | ${EDITOR:-vi} - + BashExec = [===[ + cat "${XPLR_PIPE_LOGS_OUT:?}" | ${EDITOR:-vi} - ]===], }, }, @@ -1367,7 +1370,8 @@ xplr.config.modes.builtin.go_to_path = { { BashExecSilently0 = [===[ PTH=${XPLR_INPUT_BUFFER} - PTH_ESC=${XPLR_INPUT_BUFFER//\"/\\\"} + PTH_ESC=${PTH//\"/\\\"} + PTH_ESC=${PTH_ESC//$'\n'/\\n} if [ -d "$PTH" ]; then printf 'ChangeDirectory: "%s"\0' "$PTH_ESC" >> "${XPLR_PIPE_MSG_IN:?}" elif [ -e "$PTH" ]; then @@ -1409,6 +1413,7 @@ xplr.config.modes.builtin.selection_ops = { BashExec0 = [===[ (while IFS= read -r -d '' LINE; do LINE_ESC=${LINE//\"/\\\"} + LINE_ESC=${LINE_ESC//$'\n'/\\n} if cp -vr -- "${LINE:?}" ./; then printf 'LogSuccess: "%s"\0' "$LINE_ESC copied to ." >> "${XPLR_PIPE_MSG_IN:?}" else @@ -1430,6 +1435,7 @@ xplr.config.modes.builtin.selection_ops = { BashExec0 = [===[ (while IFS= read -r -d '' LINE; do LINE_ESC=${LINE//\"/\\\"} + LINE_ESC=${LINE_ESC//$'\n'/\\n} if mv -v -- "${LINE:?}" ./; then printf 'LogSuccess: "%s"\0' "$LINE_ESC moved to ." >> "${XPLR_PIPE_MSG_IN:?}" else @@ -1502,6 +1508,7 @@ xplr.config.modes.builtin.create_directory = { BashExecSilently0 = [===[ PTH="$XPLR_INPUT_BUFFER" PTH_ESC=${PTH//\"/\\\"} + PTH_ESC=${PTH_ESC//$'\n'/\\n} if [ "$PTH" ]; then mkdir -p -- "$PTH" \ && printf 'SetInputBuffer: ""\0' >> "${XPLR_PIPE_MSG_IN:?}" \ @@ -1545,6 +1552,7 @@ xplr.config.modes.builtin.create_file = { BashExecSilently0 = [===[ PTH="$XPLR_INPUT_BUFFER" PTH_ESC=${PTH//\"/\\\"} + PTH_ESC=${PTH_ESC//$'\n'/\\n} if [ "$PTH" ]; then mkdir -p -- "$(dirname $PTH)" \ && touch -- "$PTH" \ @@ -1693,8 +1701,10 @@ xplr.config.modes.builtin.rename = { BashExecSilently0 = [===[ SRC="${XPLR_FOCUS_PATH:?}" SRC_ESC=${SRC//\"/\\\"} + SRC_ESC=${SRC_ESC//$'\n'/\\n} TARGET="${XPLR_INPUT_BUFFER:?}" TARGET_ESC=${TARGET//\"/\\\"} + TARGET_ESC=${TARGET_ESC//$'\n'/\\n} if [ -e "${TARGET:?}" ]; then printf 'LogError: "%s"\0' "$TARGET_ESC already exists" >> "${XPLR_PIPE_MSG_IN:?}" else @@ -1737,8 +1747,10 @@ xplr.config.modes.builtin.duplicate_as = { BashExecSilently0 = [===[ SRC="${XPLR_FOCUS_PATH:?}" SRC_ESC=${SRC//\"/\\\"} + SRC_ESC=${SRC_ESC//$'\n'/\\n} TARGET="${XPLR_INPUT_BUFFER:?}" TARGET_ESC=${TARGET//\"/\\\"} + TARGET_ESC=${TARGET_ESC//$'\n'/\\n} if [ -e "${TARGET:?}" ]; then printf 'LogError: "%s"\0' "$TARGET_ESC already exists" >> "${XPLR_PIPE_MSG_IN:?}" else @@ -1775,6 +1787,7 @@ xplr.config.modes.builtin.delete = { BashExec0 = [===[ (while IFS= read -r -d '' LINE; do LINE_ESC=${LINE//\"/\\\"} + LINE_ESC=${LINE_ESC//$'\n'/\\n} if rm -rfv -- "${LINE:?}"; then printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}" else @@ -1795,6 +1808,7 @@ xplr.config.modes.builtin.delete = { BashExec0 = [===[ (while IFS= read -r -d '' LINE; do LINE_ESC=${LINE//\"/\\\"} + LINE_ESC=${LINE_ESC//$'\n'/\\n} if [ -d "$LINE" ] && [ ! -L "$LINE" ]; then if rmdir -v -- "${LINE:?}"; then printf 'LogSuccess: "%s"\0' "$LINE_ESC deleted" >> "${XPLR_PIPE_MSG_IN:?}" @@ -1832,7 +1846,7 @@ xplr.config.modes.builtin.action = { messages = { { Call0 = { command = "bash", args = { "-i" } } }, "ExplorePwdAsync", - "PopMode", + "PopModeKeepingInputBuffer", }, }, ["c"] = { @@ -1857,9 +1871,9 @@ xplr.config.modes.builtin.action = { help = "logs", messages = { { - BashExec0 = [===[ + BashExec = [===[ [ -z "$PAGER" ] && PAGER="less -+F" - cat -- "${XPLR_PIPE_LOGS_OUT}" | tr '\0' '\n' | ${PAGER:?} + cat -- "${XPLR_PIPE_LOGS_OUT}" | ${PAGER:?} ]===], }, "PopMode", diff --git a/src/msg/in_/external.rs b/src/msg/in_/external.rs index 53bd1c3..f20532e 100644 --- a/src/msg/in_/external.rs +++ b/src/msg/in_/external.rs @@ -503,7 +503,7 @@ pub enum ExternalMsg { /// 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. + /// You may want to use `CallSilently0` instead. CallSilently(Command), /// Like `Call0` but without the flicker. The stdin, stdout @@ -535,13 +535,13 @@ pub enum ExternalMsg { /// 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. + /// You may want to use `BashExecSilently0` instead. BashExecSilently(String), /// Like `BashExec0` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// - /// Type: { BashExec0Silently = "string" } + /// Type: { BashExecSilently0 = "string" } /// /// Example: /// diff --git a/src/ui.rs b/src/ui.rs index ad57683..ac3382b 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -727,7 +727,7 @@ fn draw_selection( .rev() .take((layout_size.height.max(2) - 2).into()) .rev() - .map(|n| n.absolute_path.to_owned()) + .map(|n| n.absolute_path.replace('\\', "\\\\").replace('\n', "\\n")) .map(ListItem::new) .collect();