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 }