Add support for LuaEval(Silently) messages

This PR adds support for quickly executing arbitrary lua functions,
without needing to define a function.

Example:

```lua
xplr.config.modes.builtin.default.key_bindings.on_key["#"] = {
  help = "test",
  messages = {
    { LuaEvalSilently = [[return { { LogInfo = "foo" } }]] },
    { LuaEval = [[return { { LogInfo = io.read() } }]] },
  },
}
```

Partly closes: https://github.com/sayanarijit/xplr/issues/394
pull/402/head
Arijit Basu 3 years ago committed by Arijit Basu
parent 165d992b75
commit 6a3b26cc18

@ -418,6 +418,28 @@ stderr will be piped to null. So it's non-interactive.
**Lua Example:** `{ CallSilently = { command = "tput", args = { "bell" } } }`
### { BashExec = "string" }
**YAML:** `BashExec: string`
An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}`
where `{string}` is the given value.
**YAML Example:** `BashExec: "read -p test"`
**Lua Example:** `{ BashExec = "read -p test" }`
### { BashExecSilently = "string" }
**YAML:** `BashExecSilently(String)`
Like `BashExec` but without the flicker. The stdin, stdout
stderr will be piped to null. So it's non-interactive.
**YAML Example:** `BashExecSilently: "tput bell"`
**Lua Example:** `{ BashExecSilently = "tput bell" }`
### { CallLua = "string" }
**YAML:** `CallLua: string`
@ -443,27 +465,27 @@ stderr will be piped to null. So it's non-interactive.
**Lua Example:** `{ CallLuaSilently = "custom.some_custom_function" }`
### { BashExec = "string" }
### { LuaEval = "string" }
**YAML:** `BashExec: string`
**YAML:** `LuaEval: string`
An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}`
where `{string}` is the given value.
Execute Lua code without needing to define a function.
However, `CallLuaArg` won't be available.
**YAML Example:** `BashExec: "read -p test"`
**YAML Example:** `LuaEval: "return { { LogInfo = io.read() } }"`
**Lua Example:** `{ BashExec = "read -p test" }`
**Lua Example:** `{ LuaEval = [[return { { LogInfo = io.read() } }]] }`
### { BashExecSilently = "string" }
### { LuaEvalSilently = "string" }
**YAML:** `BashExecSilently(String)`
**YAML:** `LuaEvalSilently: string`
Like `BashExec` but without the flicker. The stdin, stdout
Like `LuaEval` but without the flicker. The stdin, stdout
stderr will be piped to null. So it's non-interactive.
**YAML Example:** `BashExecSilently: "tput bell"`
**YAML Example:** `LuaEvalSilently: "return { { LogInfo = 'foo' } }"`
**Lua Example:** `{ BashExecSilently = "tput bell" }`
**Lua Example:** `{ LuaEvalSilently = [[return { { LogInfo = "foo" } }]] }`
### "Select"

@ -894,6 +894,18 @@ pub enum ExternalMsg {
/// **Example:** `CallSilently: {command: tput, args: ["bell"]}`
CallSilently(Command),
/// An alias to `Call: {command: bash, args: ["-c", "${command}"], silent: false}`
/// where ${command} is the given value.
///
/// **Example:** `BashExec: "read -p test"`
BashExec(String),
/// Like `BashExec` but without the flicker. The stdin, stdout
/// stderr will be piped to null. So it's non-interactive.
///
/// **Example:** `BashExecSilently: "tput bell"`
BashExecSilently(String),
/// Call a Lua function.
/// A `CallLuaArg` object will be passed to the function as argument.
/// The function can optionally return a list of messages for xplr to handle
@ -908,17 +920,17 @@ pub enum ExternalMsg {
/// **Example:** `CallLuaSilently: custom.some_custom_function`
CallLuaSilently(String),
/// An alias to `Call: {command: bash, args: ["-c", "${command}"], silent: false}`
/// where ${command} is the given value.
/// Execute Lua code without needing to define a function.
/// However, `CallLuaArg` won't be available.
///
/// **Example:** `BashExec: "read -p test"`
BashExec(String),
/// **Example:** `LuaEval: "return io.read()"`
LuaEval(String),
/// Like `BashExec` but without the flicker. The stdin, stdout
/// Like `LuaEval` but without the flicker. The stdin, stdout
/// stderr will be piped to null. So it's non-interactive.
///
/// **Example:** `BashExecSilently: "tput bell"`
BashExecSilently(String),
/// **Example:** `LuaEvalSilently: "return { LogInfo = 'foo' }"`
LuaEvalSilently(String),
/// Select the focused node.
Select,
@ -1096,6 +1108,10 @@ impl ExternalMsg {
| Self::CallSilently(_)
| Self::BashExec(_)
| Self::BashExecSilently(_)
| Self::CallLua(_)
| Self::CallLuaSilently(_)
| Self::LuaEval(_)
| Self::LuaEvalSilently(_)
)
}
}
@ -1127,6 +1143,8 @@ pub enum MsgOut {
CallSilently(Command),
CallLua(String),
CallLuaSilently(String),
LuaEval(String),
LuaEvalSilently(String),
Enque(Task),
EnableMouse,
DisableMouse,
@ -1440,9 +1458,7 @@ impl App {
key: Option<Key>,
) -> Result<Self> {
if self.config.general.read_only && !msg.is_read_only() {
self.log_error(
"Cannot call shell command in read-only mode.".into(),
)
self.log_error("Cannot execute code in read-only mode.".into())
} else {
match msg {
ExternalMsg::ExplorePwd => self.explore_pwd(),
@ -1535,13 +1551,17 @@ impl App {
}
ExternalMsg::Call(cmd) => self.call(cmd),
ExternalMsg::CallSilently(cmd) => self.call_silently(cmd),
ExternalMsg::BashExec(cmd) => self.bash_exec(cmd),
ExternalMsg::BashExecSilently(cmd) => {
self.bash_exec_silently(cmd)
}
ExternalMsg::CallLua(func) => self.call_lua(func),
ExternalMsg::CallLuaSilently(func) => {
self.call_lua_silently(func)
}
ExternalMsg::BashExec(cmd) => self.bash_exec(cmd),
ExternalMsg::BashExecSilently(cmd) => {
self.bash_exec_silently(cmd)
ExternalMsg::LuaEval(code) => self.lua_eval(code),
ExternalMsg::LuaEvalSilently(code) => {
self.lua_eval_silently(code)
}
ExternalMsg::Select => self.select(),
ExternalMsg::SelectAll => self.select_all(),
@ -2173,6 +2193,20 @@ impl App {
Ok(self)
}
fn bash_exec(self, script: String) -> Result<Self> {
self.call(Command {
command: "bash".into(),
args: vec!["-c".into(), script],
})
}
fn bash_exec_silently(self, script: String) -> Result<Self> {
self.call_silently(Command {
command: "bash".into(),
args: vec!["-c".into(), script],
})
}
fn call_lua(mut self, func: String) -> Result<Self> {
self.logs_hidden = true;
self.msg_out.push_back(MsgOut::CallLua(func));
@ -2185,18 +2219,16 @@ impl App {
Ok(self)
}
fn bash_exec(self, script: String) -> Result<Self> {
self.call(Command {
command: "bash".into(),
args: vec!["-c".into(), script],
})
fn lua_eval(mut self, code: String) -> Result<Self> {
self.logs_hidden = true;
self.msg_out.push_back(MsgOut::LuaEval(code));
Ok(self)
}
fn bash_exec_silently(self, script: String) -> Result<Self> {
self.call_silently(Command {
command: "bash".into(),
args: vec!["-c".into(), script],
})
fn lua_eval_silently(mut self, code: String) -> Result<Self> {
self.logs_hidden = true;
self.msg_out.push_back(MsgOut::LuaEvalSilently(code));
Ok(self)
}
pub fn set_directory(mut self, dir: DirectoryBuffer) -> Result<Self> {

@ -1,6 +1,7 @@
#![allow(clippy::too_many_arguments)]
use crate::app;
use crate::app::ExternalMsg;
use crate::cli::Cli;
use crate::event_reader::EventReader;
use crate::explorer;
@ -273,6 +274,8 @@ impl Runner {
match msg {
// NOTE: Do not schedule critical tasks via tx_msg_in in this loop.
// Try handling them immediately.
//
// TODO: Remove boilerplate code.
Enque(task) => {
tx_msg_in.send(task)?;
}
@ -563,6 +566,89 @@ impl Runner {
}
}
LuaEval(code) => {
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()?;
let res: Result<Option<Vec<ExternalMsg>>> = lua
.load(&code)
.eval()
.and_then(|v| lua.from_value(v))
.map_err(Error::from);
match res {
Ok(Some(msgs)) => {
for msg in msgs {
app = app.handle_task(
app::Task::new(
app::MsgIn::External(msg),
None,
),
)?;
}
}
Ok(None) => {}
Err(err) => {
app = app.log_error(err.to_string())?;
}
};
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())?;
}
}
}
}
LuaEvalSilently(code) => {
let res: Result<Option<Vec<ExternalMsg>>> = lua
.load(&code)
.eval()
.and_then(|v| lua.from_value(v))
.map_err(Error::from);
match res {
Ok(Some(msgs)) => {
for msg in msgs {
app = app.handle_task(
app::Task::new(
app::MsgIn::External(msg),
None,
),
)?;
}
}
Ok(None) => {}
Err(err) => {
app = app.log_error(err.to_string())?;
}
};
}
Call(cmd) => {
execute!(
terminal.backend_mut(),

Loading…
Cancel
Save