Skip to content

Instantly share code, notes, and snippets.

@ek0
Last active February 4, 2025 02:05
Show Gist options
  • Save ek0/d2c48b88fa05751c1131342b88c3a169 to your computer and use it in GitHub Desktop.
Save ek0/d2c48b88fa05751c1131342b88c3a169 to your computer and use it in GitHub Desktop.
use mlua::Lua;
use rustyline::{
CompletionType, Config, EditMode, Editor,
completion::{Completer, Pair, extract_word},
error::ReadlineError,
history::FileHistory,
};
use rustyline_derive::{Helper, Highlighter, Hinter, Validator};
struct MyObject {
value: u32,
}
impl MyObject {
fn new(value: u32) -> Self {
Self { value }
}
}
struct LuaContext {
lua: Lua,
}
impl LuaContext {
fn new() -> Self {
let lua = Lua::new();
Self { lua }
}
fn exec(&self, line: String) -> mlua::Result<()> {
match self.lua.load(line).exec() {
Ok(_) => {}
Err(err) => {
println!("Error: {}", err);
}
}
Ok(())
}
fn get_lua(&self) -> &Lua {
&self.lua
}
}
const DEFAULT_BREAK_CHARS: [u8; 3] = [b' ', b'\t', b'\n'];
const LUA_STATEMENTS: [&'static str; 11] = [
"local", "then", "if", "do", "end", "while", "repeat", "until", "for", "break", "function",
];
#[derive(Helper, Hinter, Validator, Highlighter)]
struct LuaInterpreterHelper<'a> {
lua: &'a Lua,
}
impl<'a> LuaInterpreterHelper<'a> {
// We need a reference to the Lua context.
fn new(lua: &'a Lua) -> Self {
Self { lua }
}
}
impl<'a> Completer for LuaInterpreterHelper<'a> {
type Candidate = Pair;
fn complete(
&self, // FIXME should be `&mut self`
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let mut is_nested_context = false;
let (start, word) = extract_word(line, pos, None, |c| {
let result = if c == ' ' {
true
} else if c == '.' {
true
} else if c == '\t' {
true
} else if c == '\n' {
true
} else if c == '(' {
true
} else {
false
};
result
});
// TODO(_eko): For nested contexts (e.g: table members). We need to rebuild the whole dependency chain from the global context.
if start > 0 {
if line.chars().nth(start-1).unwrap() == '.' {
// We are calling an object method. We gotta find last global symbol in the chain.
// This should be the first word encountered that isn't preceeded by a '.', a ')' or a ']'
}
}
let mut matches: Vec<Pair> = LUA_STATEMENTS
.iter()
.filter_map(|hint| {
if hint.starts_with(word) {
let replacement = hint;
Some(Pair {
display: hint.to_string(),
replacement: String::from(line[..start].trim()) + replacement,
})
} else {
None
}
})
.collect();
// Iterate over every global symbol
let globals = self.lua.globals();
for pair in globals.pairs::<mlua::Value, mlua::Value>() {
let (value, object) = match pair {
Ok((value, object)) => (value, object),
Err(e) => panic!("Error: {}", e),
};
let name = match value.to_string() {
Ok(name) => name,
Err(e) => panic!("Error: {}", e),
};
if name.starts_with(word) {
let mut replacement = String::from(line[..start].trim()) + name.as_str();
if object.is_function() {
replacement.push('(');
}
matches.push(Pair {
display: name,
replacement: replacement,
});
}
}
Ok((0, matches))
}
}
fn main() -> rustyline::Result<()> {
let ctx = LuaContext::new();
let config = Config::builder()
.history_ignore_space(true)
.auto_add_history(true)
.completion_type(CompletionType::List)
.edit_mode(EditMode::Emacs)
.build();
let mut rl: Editor<LuaInterpreterHelper, FileHistory> = Editor::with_config(config)?;
let helper = LuaInterpreterHelper::new(ctx.get_lua());
rl.set_helper(Some(helper));
loop {
let readline = rl.readline(">>> ");
match readline {
Ok(line) => {
ctx.exec(line).unwrap();
}
Err(err) => match err {
ReadlineError::Interrupted => break,
_ => {
println!("{}", err);
}
},
}
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment