manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

editor.rs (4168B)


      1 use std::{
      2     process,
      3     sync::{
      4         Arc,
      5         atomic::{AtomicBool, Ordering},
      6     },
      7 };
      8 
      9 use directories::ProjectDirs;
     10 use mlua::prelude::*;
     11 use reedline::{
     12     DefaultPrompt, DefaultPromptSegment, EditCommand, Emacs, FileBackedHistory, IdeMenu, KeyCode,
     13     KeyModifiers, MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu, Signal,
     14     default_emacs_keybindings,
     15 };
     16 
     17 use crate::{
     18     completion::LuaCompleter, config::Config, hinter::LuaHinter, inspect::display_basic,
     19     lua::LuaExecutor, parse::LuaHighlighter, validator::LuaValidator,
     20 };
     21 
     22 pub struct Editor {
     23     prompt: DefaultPrompt,
     24     editor: Reedline,
     25     lua_executor: Arc<dyn LuaExecutor>,
     26     config: Config,
     27 }
     28 
     29 impl Editor {
     30     pub fn new() -> LuaResult<Self> {
     31         let config = Config::load()?;
     32         let lua_executor = config.get_executor().map_err(LuaError::external)?;
     33 
     34         let version: String = lua_executor.globals()?.get("_VERSION")?;
     35 
     36         let prompt = DefaultPrompt::new(
     37             DefaultPromptSegment::Basic(version),
     38             DefaultPromptSegment::Empty,
     39         );
     40 
     41         let mut keybindings = default_emacs_keybindings();
     42         keybindings.add_binding(
     43             KeyModifiers::NONE,
     44             KeyCode::Tab,
     45             ReedlineEvent::UntilFound(vec![
     46                 ReedlineEvent::Menu(String::from("completion_menu")),
     47                 ReedlineEvent::MenuNext,
     48             ]),
     49         );
     50         keybindings.add_binding(
     51             KeyModifiers::ALT,
     52             KeyCode::Enter,
     53             ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
     54         );
     55 
     56         let ide_menu = IdeMenu::default().with_name("completion_menu");
     57 
     58         let mut editor = Reedline::create()
     59             .with_validator(Box::new(LuaValidator::new()))
     60             .with_completer(Box::new(LuaCompleter::new(
     61                 lua_executor.clone() as Arc<dyn LuaExecutor>
     62             )))
     63             .with_highlighter(Box::new(LuaHighlighter))
     64             .with_hinter(Box::new(LuaHinter))
     65             .with_edit_mode(Box::new(Emacs::new(keybindings)))
     66             .with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu)))
     67             .with_ansi_colors(config.color_output);
     68 
     69         if let Some(proj_dirs) = ProjectDirs::from("net.sivory", "", "Manen") {
     70             let history = FileBackedHistory::with_file(256, proj_dirs.data_dir().join("history"));
     71                 config.history_size,
     72                 proj_dirs.data_dir().join("history"),
     73             );
     74 
     75             if let Ok(history) = history {
     76                 editor = editor.with_history(Box::new(history))
     77             }
     78         }
     79 
     80         Ok(Self {
     81             prompt,
     82             editor,
     83             lua_executor,
     84             config,
     85         })
     86     }
     87 
     88     fn register_ctrl_c(&self, is_running_lua: Arc<AtomicBool>) {
     89         let executor = self.lua_executor.clone();
     90 
     91         ctrlc::set_handler(move || {
     92             if is_running_lua.load(Ordering::Relaxed) {
     93                 executor.cancel();
     94             } else {
     95                 process::exit(0)
     96             }
     97         })
     98         .unwrap();
     99     }
    100 
    101     pub fn run(mut self) {
    102         let is_running_lua = Arc::new(AtomicBool::new(false));
    103 
    104         self.register_ctrl_c(is_running_lua.clone());
    105 
    106         loop {
    107             let signal = self.editor.read_line(&self.prompt);
    108 
    109             match signal {
    110                 Ok(Signal::Success(line)) => {
    111                     is_running_lua.store(true, Ordering::Relaxed);
    112 
    113                     if let Err(e) = self.eval(&line) {
    114                         eprintln!("{e}")
    115                     }
    116 
    117                     is_running_lua.store(false, Ordering::Relaxed);
    118                 }
    119                 Ok(Signal::CtrlC) | Ok(Signal::CtrlD) => break,
    120                 _ => {}
    121             }
    122         }
    123     }
    124 
    125     fn eval(&self, line: &str) -> LuaResult<()> {
    126         let value: LuaValue = self.lua_executor.exec(line)?;
    127         let config = &self.config;
    128 
    129         let stringify = match value {
    130             LuaValue::Table(tbl) => config.table_format.format(&tbl, config.color_output)?,
    131             value => display_basic(&value, config.color_output),
    132         };
    133 
    134         println!("{stringify}");
    135 
    136         Ok(())
    137     }
    138 }