mirror of
https://github.com/sayanarijit/xplr
synced 2024-11-04 18:00:14 +00:00
An attempt at safer message passing.
This commit is contained in:
parent
895d55ca23
commit
2b5755aa8a
11
src/app.rs
11
src/app.rs
@ -15,6 +15,7 @@ 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;
|
||||
@ -174,6 +175,7 @@ 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,
|
||||
@ -308,6 +310,7 @@ impl App {
|
||||
mode,
|
||||
layout,
|
||||
input,
|
||||
input_unescaped: Default::default(),
|
||||
pid,
|
||||
session_path: session_path.clone(),
|
||||
pipe: Pipe::from_session_path(&session_path)?,
|
||||
@ -554,6 +557,14 @@ 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)));
|
||||
}
|
||||
|
118
src/init.lua
118
src/init.lua
@ -1156,7 +1156,7 @@ xplr.config.modes.builtin.default = {
|
||||
{
|
||||
BashExecSilently = [===[
|
||||
NAME=$(basename "${XPLR_FOCUS_PATH:?}")
|
||||
echo SetInputBuffer: "'${NAME:?}'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "SetInputBuffer: %s\0" "${NAME:?}" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
]===],
|
||||
},
|
||||
},
|
||||
@ -1169,7 +1169,7 @@ xplr.config.modes.builtin.default = {
|
||||
{
|
||||
BashExecSilently = [===[
|
||||
NAME=$(basename "${XPLR_FOCUS_PATH:?}")
|
||||
echo SetInputBuffer: "'${NAME:?}'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "SetInputBuffer: %s\0" "${NAME:?}" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
]===],
|
||||
},
|
||||
},
|
||||
@ -1205,7 +1205,7 @@ xplr.config.modes.builtin.default = {
|
||||
messages = {
|
||||
{
|
||||
BashExecSilently = [===[
|
||||
echo ChangeDirectory: "'${HOME:?}'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ChangeDirectory: %s\0" "${HOME:?}" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
]===],
|
||||
},
|
||||
},
|
||||
@ -1364,9 +1364,9 @@ xplr.config.modes.builtin.go_to_path = {
|
||||
{
|
||||
BashExecSilently = [===[
|
||||
if [ -d "$XPLR_INPUT_BUFFER" ]; then
|
||||
echo ChangeDirectory: "'$XPLR_INPUT_BUFFER'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ChangeDirectory: %s\0" "$XPLR_INPUT_BUFFER" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
elif [ -e "$XPLR_INPUT_BUFFER" ]; then
|
||||
echo FocusPath: "'$XPLR_INPUT_BUFFER'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "FocusPath: %s\0" "$XPLR_INPUT_BUFFER" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
]===],
|
||||
},
|
||||
@ -1400,15 +1400,15 @@ xplr.config.modes.builtin.selection_ops = {
|
||||
messages = {
|
||||
{
|
||||
BashExec = [===[
|
||||
(while IFS= read -r line; do
|
||||
if cp -vr -- "${line:?}" ./; then
|
||||
echo LogSuccess: "'$line copied to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
echo LogError: "'Failed to copy $line to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
(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
|
||||
done < "${XPLR_PIPE_SELECTION_OUT:?}")
|
||||
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ClearSelection\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
read -p "[enter to continue]"
|
||||
]===],
|
||||
},
|
||||
@ -1420,14 +1420,14 @@ xplr.config.modes.builtin.selection_ops = {
|
||||
messages = {
|
||||
{
|
||||
BashExec = [===[
|
||||
(while IFS= read -r line; do
|
||||
if mv -v -- "${line:?}" ./; then
|
||||
echo LogSuccess: "'$line moved to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
echo LogError: "'Failed to move $line to $PWD'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
(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
|
||||
done < "${XPLR_PIPE_SELECTION_OUT:?}")
|
||||
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
read -p "[enter to continue]"
|
||||
]===],
|
||||
},
|
||||
@ -1452,12 +1452,12 @@ xplr.config.modes.builtin.selection_ops = {
|
||||
elif command -v open; then
|
||||
OPENER=open
|
||||
else
|
||||
echo LogError: '$OPENER not found' >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogError: %s\0" "$OPENER not found" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
(while IFS= read -r line; do
|
||||
$OPENER "${line:?}" > /dev/null 2>&1
|
||||
(while IFS= read -r -d '' line; do
|
||||
$OPENER "${line:?}" > /dev/null 2>&1
|
||||
done < "${XPLR_PIPE_RESULT_OUT:?}")
|
||||
]===],
|
||||
},
|
||||
@ -1518,12 +1518,12 @@ xplr.config.modes.builtin.create_directory = {
|
||||
PTH="$XPLR_INPUT_BUFFER"
|
||||
if [ "${PTH}" ]; then
|
||||
mkdir -p -- "${PTH:?}" \
|
||||
&& echo SetInputBuffer: "''" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo LogSuccess: "'$PTH created'" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo FocusPath: "'$PTH'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
&& 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:?}"
|
||||
else
|
||||
echo PopMode >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "PopMode\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
]===],
|
||||
},
|
||||
@ -1561,12 +1561,12 @@ xplr.config.modes.builtin.create_file = {
|
||||
if [ "$PTH" ]; then
|
||||
mkdir -p -- "$(dirname $PTH)" \
|
||||
&& touch -- "$PTH" \
|
||||
&& echo SetInputBuffer: "''" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo LogSuccess: "'$PTH created'" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo FocusPath: "'$PTH'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
&& printf "SetInputBuffer: ''\0" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& printf "LogSuccess: %s\0" "$PTH created" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& printf "ExplorePwd\0" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& printf "FocusPath: %s\0" "$PTH" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
echo PopMode >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "PopMode\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
]===],
|
||||
},
|
||||
@ -1668,7 +1668,7 @@ xplr.config.modes.builtin.go_to = {
|
||||
elif command -v open; then
|
||||
OPENER=open
|
||||
else
|
||||
echo LogError: '$OPENER not found' >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogError: %s\0" "$OPENER not found" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@ -1704,12 +1704,12 @@ xplr.config.modes.builtin.rename = {
|
||||
SRC="${XPLR_FOCUS_PATH:?}"
|
||||
TARGET="${XPLR_INPUT_BUFFER:?}"
|
||||
if [ -e "${TARGET:?}" ]; then
|
||||
echo LogError: "'$TARGET already exists'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogError: %s\0" "$TARGET already exists" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
mv -- "${SRC:?}" "${TARGET:?}" \
|
||||
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo FocusPath: "'$TARGET'" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo LogSuccess: "'$SRC renamed to $TARGET'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
&& 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:?}"
|
||||
fi
|
||||
]===],
|
||||
},
|
||||
@ -1746,12 +1746,12 @@ xplr.config.modes.builtin.duplicate_as = {
|
||||
SRC="${XPLR_FOCUS_PATH:?}"
|
||||
TARGET="${XPLR_INPUT_BUFFER:?}"
|
||||
if [ -e "${TARGET:?}" ]; then
|
||||
echo LogError: "'$TARGET already exists'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogError: %s\0" "$TARGET already exists" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
cp -r -- "${SRC:?}" "${TARGET:?}" \
|
||||
&& echo ExplorePwd >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo FocusPath: "'$TARGET'" >> "${XPLR_PIPE_MSG_IN:?}" \
|
||||
&& echo LogSuccess: "'$SRC duplicated as $TARGET'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
&& 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:?}"
|
||||
fi
|
||||
]===],
|
||||
},
|
||||
@ -1779,14 +1779,14 @@ xplr.config.modes.builtin.delete = {
|
||||
messages = {
|
||||
{
|
||||
BashExec = [===[
|
||||
(while IFS= read -r line; do
|
||||
if rm -rfv -- "${line:?}"; then
|
||||
echo LogSuccess: "'$line deleted'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
echo LogError: "'Failed to delete $line'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
(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
|
||||
done < "${XPLR_PIPE_RESULT_OUT:?}")
|
||||
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
read -p "[enter to continue]"
|
||||
]===],
|
||||
},
|
||||
@ -1798,22 +1798,22 @@ xplr.config.modes.builtin.delete = {
|
||||
messages = {
|
||||
{
|
||||
BashExec = [===[
|
||||
(while IFS= read -r line; do
|
||||
(while IFS= read -r -d '' line; do
|
||||
if [ -d "$line" ] && [ ! -L "$line" ]; then
|
||||
if rmdir -v -- "${line:?}"; then
|
||||
echo LogSuccess: "'$line deleted'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
echo LogError: "'Failed to delete $line'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
else
|
||||
if rm -v -- "${line:?}"; then
|
||||
echo LogSuccess: "'$line deleted'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogSuccess: %s\0" "$line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
else
|
||||
echo LogError: "'Failed to delete $line'" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "LogError: %s\0" "Failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
fi
|
||||
fi
|
||||
done < "${XPLR_PIPE_RESULT_OUT:?}")
|
||||
echo ExplorePwdAsync >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
printf "ExplorePwdAsync\0" >> "${XPLR_PIPE_MSG_IN:?}"
|
||||
read -p "[enter to continue]"
|
||||
]===],
|
||||
},
|
||||
@ -2431,13 +2431,17 @@ end
|
||||
xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
|
||||
local r = m.tree .. m.prefix
|
||||
|
||||
local function path_escape(path)
|
||||
return string.gsub(string.gsub(path, "\\", "\\\\"), "\n", "\\n")
|
||||
end
|
||||
|
||||
if m.meta.icon == nil then
|
||||
r = r .. ""
|
||||
else
|
||||
r = r .. m.meta.icon .. " "
|
||||
end
|
||||
|
||||
r = r .. m.relative_path
|
||||
r = r .. path_escape(m.relative_path)
|
||||
|
||||
if m.is_dir then
|
||||
r = r .. "/"
|
||||
@ -2451,7 +2455,7 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m)
|
||||
if m.is_broken then
|
||||
r = r .. "×"
|
||||
else
|
||||
r = r .. m.symlink.absolute_path
|
||||
r = r .. path_escape(m.symlink.absolute_path)
|
||||
|
||||
if m.symlink.is_dir then
|
||||
r = r .. "/"
|
||||
|
@ -17,6 +17,7 @@ pub mod pipe;
|
||||
pub mod pwd_watcher;
|
||||
pub mod runner;
|
||||
pub mod ui;
|
||||
pub mod newlines;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -3,6 +3,7 @@ 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 {
|
||||
@ -1070,7 +1071,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(':'), args);
|
||||
let msg = format!("!{} {}", msg.trim_end_matches(':'), escape_string(args));
|
||||
serde_yaml::from_str(&msg)
|
||||
} else {
|
||||
serde_yaml::from_str(value)
|
||||
|
79
src/newlines.rs
Normal file
79
src/newlines.rs
Normal file
@ -0,0 +1,79 @@
|
||||
struct UnescapedString<'a> {
|
||||
s: std::str::Chars<'a>,
|
||||
}
|
||||
|
||||
impl<'a> UnescapedString<'a> {
|
||||
fn new(s: &'a str) -> Self {
|
||||
Self { s: s.chars() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for UnescapedString<'_> {
|
||||
type Item = Result<char, Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.s.next().map(|c| match c {
|
||||
'\\' => match self.s.next() {
|
||||
None => Err(Error::EscapeAtEndOfString),
|
||||
Some('n') => Ok('\n'),
|
||||
Some('\\') => Ok('\\'),
|
||||
Some(c) => Err(Error::UnrecognizedEscapedChar(c)),
|
||||
},
|
||||
c => Ok(c),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
EscapeAtEndOfString,
|
||||
UnrecognizedEscapedChar(char),
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::EscapeAtEndOfString => {
|
||||
write!(f, "Escape character at the end of the string")
|
||||
}
|
||||
Error::UnrecognizedEscapedChar(c) => {
|
||||
write!(f, "Unrecognized escaped char: '{}'", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
struct EscapedString<'a> {
|
||||
s: std::str::Chars<'a>,
|
||||
}
|
||||
|
||||
impl<'a> EscapedString<'a> {
|
||||
fn new(s: &'a str) -> Self {
|
||||
Self { s: s.chars() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for EscapedString<'_> {
|
||||
type Item = String;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.s.next() {
|
||||
None => None,
|
||||
Some('\\') => Some(String::from("\\\\")),
|
||||
Some('\n') => Some(String::from("\\n")),
|
||||
Some(c) => Some(String::from(c)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn escape_string(s: &str) -> String {
|
||||
EscapedString::new(s).collect()
|
||||
}
|
||||
|
||||
pub fn unescape_string(s: &str) -> Result<String, Error> {
|
||||
UnescapedString::new(s).collect()
|
||||
}
|
@ -65,10 +65,12 @@ pub fn read_all(pipe: &str) -> Result<Vec<ExternalMsg>> {
|
||||
file.read_to_string(&mut in_str)?;
|
||||
file.set_len(0)?;
|
||||
|
||||
let delim = '\0';
|
||||
|
||||
if !in_str.is_empty() {
|
||||
let mut msgs = vec![];
|
||||
for msg in in_str.lines() {
|
||||
msgs.push(msg.trim().try_into()?);
|
||||
for msg in in_str.trim_matches(delim).split(delim) {
|
||||
msgs.push(msg.try_into()?);
|
||||
}
|
||||
Ok(msgs)
|
||||
} else {
|
||||
|
@ -77,14 +77,7 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result<ExitStatus> {
|
||||
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
|
||||
.buffer
|
||||
.as_ref()
|
||||
.map(|i| i.value().to_string())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.env("XPLR_INPUT_BUFFER", &app.input_unescaped)
|
||||
.env("XPLR_FOCUS_PATH", app.focused_node_str())
|
||||
.env("XPLR_FOCUS_INDEX", focus_index)
|
||||
.env("XPLR_SESSION_PATH", &app.session_path)
|
||||
|
Loading…
Reference in New Issue
Block a user