use crate::exit_status::ExitStatus;
use crate::EditorState;
use crate::command::{Command, CommandAction};
use mlua::Lua;
pub fn init(lua: &Lua) -> ExitStatus {
let main_lua = include_str!("lua/main.lua");
if let Err(e) = lua.load(main_lua).exec() {
return ExitStatus::Failure(e.to_string());
}
ExitStatus::Success(String::new())
}
pub fn evaluate_lua(code: String, state: &mut EditorState, feedback: bool) -> String {
if state.lua.is_none() {
state.lua = Some(mlua::Lua::new());
init(state.lua.as_ref().unwrap());
}
let lua = state.lua.as_ref().unwrap().clone();
if let Err(e) = lua.globals().set("sued_buffer", state.buffer.contents.clone()) {
return format!("failed to set buffer contents as lua global: {}", e);
}
if let Err(e) = lua.globals().set("sued_file_path", state.buffer.file_path.clone()) {
return format!("failed to set file path as lua global: {}", e);
}
let mut is_error: bool = false;
let result = match lua.load(&code).eval::<mlua::Value>() {
Ok(value) => match value {
mlua::Value::Nil => "nil".to_string(),
mlua::Value::Boolean(b) => b.to_string(),
mlua::Value::Integer(n) => n.to_string(),
mlua::Value::Number(n) => n.to_string(),
mlua::Value::String(s) => {
match s.to_str() {
Ok(s) => s.to_string(),
Err(_) => format!("invalid string {:?}", s),
}
}
mlua::Value::Table(t) => {
let table = t.pairs::<String, String>();
let table_output = table.map(|result| match result {
Ok((_k, v)) => v,
Err(e) => format!("invalid table entry: {}", e),
});
format!("[{}]", table_output.collect::<Vec<String>>().join(", "))
},
mlua::Value::Error(e) => {
is_error = true;
format!("error: {}", e)
},
_ => {
let return_type = value.type_name();
format!("unsupported return type: {}", return_type)
}
},
Err(e) => e.to_string(),
};
match lua.globals().get::<Vec<String>>("sued_buffer") {
Ok(buffer) => {
if buffer != state.buffer.contents {
println!("modifications synchronised");
state.buffer.contents = buffer;
}
},
Err(_) => println!("couldn't read back buffer contents"),
}
if !feedback {
if is_error {
return format!("error: {result}");
}
return "eval finished".to_string();
}
format!("=> {result}")
}
pub fn eval() -> Command {
Command {
name: "eval",
arguments: vec!["lua_code"],
description: "evaluate lua",
documentation: "this command evaluates lua code from the arguments \
and displays the result, if any\n\
the buffer contents are added as a lua global, `sued_buffer` \
and like with ~runhere, any modifications made to the buffer \
are synchronised back",
action: CommandAction::new(|args: Vec<&str>, state: &mut EditorState| {
if args.len() < 2 {
return format!(
"eval what? try {}eval [lua_code]\n\
if you want to run the buffer as lua, use {}script this instead\n\
or if you want to run a lua file, {}script [filename]",
state.prefix, state.prefix, state.prefix
);
}
let code = args[1..].join(" ");
evaluate_lua(code, state, true)
}),
..Command::default()
}
}
pub fn script() -> Command {
Command {
name: "script",
arguments: vec!["filename"],
description: "run a lua script",
documentation: "this command imports and runs a lua script at filename\n\
if `this` is specified instead of the filename, it'll \
treat the buffer contents as a lua script\n\
like ~eval, `sued_buffer` is added as a lua global and \
any modifications made to the buffer are synchronised back\n\
unlike ~eval, the return value is not printed unless \
it's an error",
action: CommandAction::new(|args: Vec<&str>, state: &mut EditorState| {
if args.len() < 2 {
return format!(
"what script? try {}script [filename] or {}script this",
state.prefix, state.prefix
);
}
let filename = args[1..].join(" ");
let code = if filename == "this" {
state.buffer.contents.join("\n")
}
else {
match std::fs::read_to_string(&filename) {
Ok(contents) => contents,
Err(e) => {
match e.kind() {
std::io::ErrorKind::NotFound => {
return format!("{} doesn't exist\n\
if you want to run a lua expression, use {}eval [expr] instead",
&filename, state.prefix)
},
_ => {
return format!("failed to read file: {}", e)
}
}
}
}
};
evaluate_lua(code, state, true)
}),
..Command::default()
}
}