diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 35 | ||||
| -rw-r--r-- | src/config/keymap/event_from_string.rs | 78 | ||||
| -rw-r--r-- | src/config/keymap/keymap_store.rs | 81 | ||||
| -rw-r--r-- | src/config/keymap/mod.rs | 41 | ||||
| -rw-r--r-- | src/config/keymap/template.rs | 57 | ||||
| -rw-r--r-- | src/config/mod.rs | 13 | ||||
| -rw-r--r-- | src/state/mod.rs | 9 | ||||
| -rw-r--r-- | src/state/view/mod.rs | 2 | ||||
| -rw-r--r-- | src/widgets/sheetview/mod.rs | 116 | ||||
| -rw-r--r-- | src/widgets/statusbar.rs | 12 |
10 files changed, 310 insertions, 134 deletions
@@ -1,7 +1,8 @@ use std::{io, time::Duration}; use crate::{ - config, lua, + config::{self, keymap::GlobalKeyMap}, + lua, sheet::register::Register, state::{window::Window, GlobalState}, tui, @@ -11,7 +12,7 @@ use crate::{ use ratatui::{ crossterm::{ - event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, + event::{self, Event, KeyEvent, KeyEventKind}, ExecutableCommand, }, prelude::*, @@ -19,7 +20,6 @@ use ratatui::{ #[derive(Default)] pub struct App { - exit: bool, view: SheetView, editor: LuaEditor, logview: LogView, @@ -34,7 +34,6 @@ impl App { .set_active_sheet(Some(sheet_id)); Self { - exit: false, view: SheetView::new(), editor: LuaEditor::new(), logview: LogView::new(), @@ -44,12 +43,12 @@ impl App { pub fn run(mut self, terminal: &mut tui::Tui) -> io::Result<()> { if let Err(e) = lua::source(&config::constants::USER_RC_PATH) { - self.exit = true; tui::restore()?; println!("{}", e); + return Ok(()); } - while !self.exit { + while !{ GlobalState::instance().exit } { terminal.draw(|frame| { self.render_frame(frame); @@ -84,27 +83,19 @@ impl App { Ok(()) } - fn handle_key_event(&mut self, key_event: KeyEvent) { + fn handle_key_event(&mut self, event: KeyEvent) { let focus = { GlobalState::instance().active_window }; - match key_event.code { - KeyCode::Char('q') if key_event.modifiers == KeyModifiers::CONTROL => self.exit(), - KeyCode::Char('l') if key_event.modifiers == KeyModifiers::CONTROL => { - let mut state = GlobalState::instance_mut(); - state.log.visible = !state.log.visible; - } - _ => match focus { - Window::View => self.view.handle_key_event(key_event), - Window::Editor => self.editor.handle_key_event(key_event), - Window::Log => self.logview.handle_key_event(key_event), + + if !GlobalKeyMap::handle(event) { + match focus { + Window::View => self.view.handle_key_event(event), + Window::Editor => self.editor.handle_key_event(event), + Window::Log => self.logview.handle_key_event(event), Window::Error => {} - }, + } } } - fn exit(&mut self) { - self.exit = true; - } - fn area(&self, area: Rect) -> (Rect, Option<Rect>, Option<Rect>) { let state = GlobalState::instance(); let mut view = area; diff --git a/src/config/keymap/event_from_string.rs b/src/config/keymap/event_from_string.rs new file mode 100644 index 0000000..00179c5 --- /dev/null +++ b/src/config/keymap/event_from_string.rs @@ -0,0 +1,78 @@ +use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + +fn parse_key_names(s: &str) -> Option<KeyCode> { + if s.chars().count() == 1 { + return Some(KeyCode::Char(s.chars().next().unwrap())); + } + + match s { + "esc" => Some(KeyCode::Esc), + "up" => Some(KeyCode::Up), + "down" => Some(KeyCode::Down), + "left" => Some(KeyCode::Left), + "right" => Some(KeyCode::Right), + "backspace" => Some(KeyCode::Backspace), + "enter" => Some(KeyCode::Enter), + "home" => Some(KeyCode::Home), + "end" => Some(KeyCode::End), + "pageup" => Some(KeyCode::PageUp), + "pagedown" => Some(KeyCode::PageDown), + "tab" => Some(KeyCode::Tab), + "delete" => Some(KeyCode::Delete), + "insert" => Some(KeyCode::Insert), + "f1" => Some(KeyCode::F(1)), + "f2" => Some(KeyCode::F(2)), + "f3" => Some(KeyCode::F(3)), + "f4" => Some(KeyCode::F(4)), + "f5" => Some(KeyCode::F(5)), + "f6" => Some(KeyCode::F(6)), + "f7" => Some(KeyCode::F(7)), + "f8" => Some(KeyCode::F(8)), + "f9" => Some(KeyCode::F(9)), + "f10" => Some(KeyCode::F(10)), + "f11" => Some(KeyCode::F(11)), + "f12" => Some(KeyCode::F(12)), + "capslock" => Some(KeyCode::CapsLock), + "scrolllock" => Some(KeyCode::ScrollLock), + "numlock" => Some(KeyCode::NumLock), + "printscreen" => Some(KeyCode::PrintScreen), + "pause" => Some(KeyCode::Pause), + "menu" => Some(KeyCode::Menu), + "keypadbegin" => Some(KeyCode::KeypadBegin), + _ => None + } +} + +pub fn event_from_string(s: String) -> Result<KeyEvent, String> { + let mut modifiers = KeyModifiers::NONE; + let s = s.to_lowercase(); + + let code = if s.chars().count() == 1 { + KeyCode::Char(s.chars().next().unwrap()) + } else if s.starts_with('<') && s.ends_with('>') { + let mut inner = &s[1..s.len() - 1]; + + loop { + if inner.starts_with("c-") { + inner = &inner[2..]; + modifiers |= KeyModifiers::CONTROL; + } else if inner.starts_with("a-") { + inner = &inner[2..]; + modifiers |= KeyModifiers::ALT; + } else { + break; + } + } + + match parse_key_names(inner) { + Some(keycode) => keycode, + None => return Err(format!("{:?} is not a valid keyname", inner)) + } + } else { + return Err(format!("{:?} is not a valid keyname", s)); + }; + + println!("{:?}", KeyEvent::new(code, modifiers)); + + Ok(KeyEvent::new(code, modifiers)) +} diff --git a/src/config/keymap/keymap_store.rs b/src/config/keymap/keymap_store.rs new file mode 100644 index 0000000..c782219 --- /dev/null +++ b/src/config/keymap/keymap_store.rs @@ -0,0 +1,81 @@ +use core::fmt; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use mlua::{Function, RegistryKey, Result}; +use ratatui::crossterm::event::KeyEvent; + +use crate::lua; + +#[derive(Default, Clone)] +pub struct KeyMapStore { + store: Option<HashMap<KeyEvent, Arc<Mutex<dyn Runnable>>>>, +} + +impl KeyMapStore { + pub const fn new() -> Self { + Self { store: None } + } + + pub fn get(&mut self, event: KeyEvent) -> Option<Arc<Mutex<dyn Runnable>>> { + match &self.store { + Some(store) => match store.get(&event) { + Some(func) => Some(Arc::clone(func)), + None => None, + }, + None => { + self.store = Some(HashMap::new()); + None + } + } + } + + pub fn map(&mut self, event: KeyEvent, func: impl Runnable + 'static) { + match &self.store { + Some(_) => {} + None => self.store = Some(HashMap::new()), + } + + if let Some(store) = &mut self.store { + store.insert(event, Arc::new(Mutex::new(func))); + } + } +} + +pub trait Runnable +where + Self: Send, +{ + fn run(&self) -> Result<()>; +} + +impl<T> Runnable for T +where + T: Fn(), + Self: Send, +{ + fn run(&self) -> Result<()> { + self(); + Ok(()) + } +} + +impl Runnable for RegistryKey +where + Self: Send, +{ + fn run(&self) -> Result<()> { + let lua = lua::get(); + let func: Function = lua.registry_value(self)?; + func.call::<(), ()>(())?; + Ok(()) + } +} + +impl fmt::Debug for KeyMapStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "KeyMapStore") + } +} diff --git a/src/config/keymap/mod.rs b/src/config/keymap/mod.rs new file mode 100644 index 0000000..e9995f2 --- /dev/null +++ b/src/config/keymap/mod.rs @@ -0,0 +1,41 @@ +use mlua::{Function, UserData}; +use ratatui::crossterm::event::KeyEvent; + +use self::{ + event_from_string::event_from_string, + keymap_store::{KeyMapStore, Runnable}, +}; + +use super::{GlobalConfig, DUMMY_CONFIG}; +use template::KeyMapSections; + +KeyMapSections!( + ViewKeyMap => view, + GlobalKeyMap => global, +); + +#[derive(Debug, Default, Clone)] +pub struct KeyMap { + pub view: ViewKeyMap, + pub global: GlobalKeyMap, +} + +pub mod event_from_string; +pub mod keymap_store; +pub mod template; + +impl KeyMap { + pub const fn new() -> Self { + Self { + view: ViewKeyMap::new(), + global: GlobalKeyMap::new(), + } + } +} + +impl UserData for KeyMap { + fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_function_get("view", |_, _| Ok(DUMMY_CONFIG.keymap.view.clone())); + fields.add_field_function_get("global", |_, _| Ok(DUMMY_CONFIG.keymap.global.clone())); + } +} diff --git a/src/config/keymap/template.rs b/src/config/keymap/template.rs new file mode 100644 index 0000000..bebcdc3 --- /dev/null +++ b/src/config/keymap/template.rs @@ -0,0 +1,57 @@ +macro_rules! KeyMapSections { + ($($name:ident => $key:ident),+ $(,)?) => { + $( + #[derive(Debug, Default, Clone)] + pub struct $name { + store: KeyMapStore, + } + + impl $name { + pub const fn new() -> Self { + Self { + store: KeyMapStore::new(), + } + } + + pub fn handle(event: KeyEvent) -> bool { + let func = { GlobalConfig::instance_mut().keymap.$key.store.get(event) }; + + if let Some(func) = func { + let func = func.lock().unwrap(); + if let Err(_error) = func.run() { + // TODO: add error buffer + } + true + } else { + false + } + } + + pub fn map(event: KeyEvent, func: impl Runnable + 'static) { + GlobalConfig::instance_mut() + .keymap + .$key + .store + .map(event, func) + } + } + + impl UserData for $name { + fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>( + methods: &mut M, + ) { + methods.add_function("map", |lua, (event, func): (String, Function<'_>)| { + let key = lua.create_registry_value(func)?; + + if let Ok(event) = event_from_string(event) { + $name::map(event, key); + } + Ok(()) + }); + } + } + )* + }; +} + +pub(super) use KeyMapSections; diff --git a/src/config/mod.rs b/src/config/mod.rs index 4f8ad7d..cc4154c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,8 +1,9 @@ use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use lazy_static::lazy_static; use mlua::{UserData, UserDataFields}; -use self::theme::Theme; +use self::{keymap::KeyMap, theme::Theme}; pub mod constants; pub mod theme; @@ -11,15 +12,20 @@ pub mod keymap; #[derive(Debug, Default)] pub struct GlobalConfig { pub theme: Theme, + pub keymap: KeyMap, +} + +lazy_static! { + static ref GLOBAL_CONFIG: RwLock<GlobalConfig> = RwLock::new(GlobalConfig::new()); } -static GLOBAL_CONFIG: RwLock<GlobalConfig> = RwLock::new(GlobalConfig::new()); const DUMMY_CONFIG: GlobalConfig = GlobalConfig::new(); impl GlobalConfig { const fn new() -> Self { Self { theme: Theme::new(), + keymap: KeyMap::new(), } } @@ -34,6 +40,7 @@ impl GlobalConfig { impl UserData for GlobalConfig { fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_function_get("theme", |_, _| Ok(DUMMY_CONFIG.theme)) + fields.add_field_function_get("theme", |_, _| Ok(DUMMY_CONFIG.theme)); + fields.add_field_function_get("keymap", |_, _| Ok(DUMMY_CONFIG.keymap)); } } diff --git a/src/state/mod.rs b/src/state/mod.rs index 3b5d3c5..a5528e3 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -16,6 +16,7 @@ pub struct GlobalState { pub editor: editor::EditorState, pub active_window: window::Window, pub log: log::LogState, + pub exit: bool, } static GLOBAL_STATE: RwLock<GlobalState> = RwLock::new(GlobalState::new()); @@ -28,6 +29,7 @@ impl GlobalState { editor: editor::EditorState::new(), log: LogState::new(), active_window: window::Window::View, + exit: false, } } @@ -62,4 +64,11 @@ impl UserData for GlobalState { Ok(()) }); } + + fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("quit", |_, _: ()| { + GlobalState::instance_mut().exit = true; + Ok(()) + }) + } } diff --git a/src/state/view/mod.rs b/src/state/view/mod.rs index a63b6ed..b142227 100644 --- a/src/state/view/mod.rs +++ b/src/state/view/mod.rs @@ -181,7 +181,7 @@ impl UserData for SheetViewState { methods.add_function("move_cursor", |_, (row, column): (usize, usize)| { cfg_mut!() .cursor - .move_checked(CursorMove::Jump((row, column))); + .move_checked(CursorMove::Jump((column, row))); Ok(()) }); diff --git a/src/widgets/sheetview/mod.rs b/src/widgets/sheetview/mod.rs index 6bd8408..fe2aa5a 100644 --- a/src/widgets/sheetview/mod.rs +++ b/src/widgets/sheetview/mod.rs @@ -6,11 +6,12 @@ use ratatui::{ }; use crate::{ - config::{theme::view::bar::SheetViewBarTheme, GlobalConfig}, - cursor::CursorMove, + config::{keymap::ViewKeyMap, theme::view::bar::SheetViewBarTheme, GlobalConfig}, lua, - sheet::cell::Cell, - state::{view::{bar::SheetViewBarState, mode::Mode}, window::Window, GlobalState}, + state::{ + view::{bar::SheetViewBarState, mode::Mode}, + GlobalState, + }, }; use super::statusbar::StatusBar; @@ -31,108 +32,9 @@ impl SheetView { } } - fn set_mode(&self, mode: Mode) { - GlobalState::instance_mut().view.mode = mode; - } - - fn move_cursor(&self, cm: CursorMove) { - GlobalState::instance_mut() - .view - .cursor - .move_checked(cm) - } - - fn start_selection(&self) { - let mut state = GlobalState::instance_mut(); - state.view.selection_anchor = Some((state.view.cursor.y(), state.view.cursor.x())); - state.view.mode = Mode::Visual; - } - - fn stop_selection(&self) { - let mut state = GlobalState::instance_mut(); - state.view.selection_anchor = None; - state.view.mode = Mode::Normal; - } - - fn open_editor(&self) { - let mut state = GlobalState::instance_mut(); - state.editor.buffer.set_lines_from_string( - r#"require('neosheet') - .state - .view - .active:map(function(cell) - return "" -end)"#, - ); - state.set_focus(Window::Editor) - } - pub fn handle_key_event(&mut self, event: KeyEvent) { let mode = { GlobalState::instance().view.mode }; match mode { - Mode::Normal => match event.code { - KeyCode::Char('j') => self.move_cursor(CursorMove::Down(1)), - KeyCode::Char('k') => self.move_cursor(CursorMove::Up(1)), - KeyCode::Char('h') => self.move_cursor(CursorMove::Left(1)), - KeyCode::Char('l') => self.move_cursor(CursorMove::Right(1)), - KeyCode::Char('v') => self.start_selection(), - KeyCode::Char('s') => self.open_editor(), - KeyCode::Char(':') => { - self.set_mode(Mode::Command); - self.bar.set_input_mode(true); - } - KeyCode::Enter => { - self.set_mode(Mode::Insert); - self.bar.set_input_mode(true); - } - _ => {} - }, - Mode::Insert => match event.code { - KeyCode::Enter => { - let mut state = GlobalState::instance_mut(); - let lock = state.view.active_sheet().unwrap(); - let mut sheet = lock.write().unwrap(); - - state - .view - .selection_or_cursor() - .into_iter() - .for_each(|(r, c)| { - sheet.set_cell(r, c, { - let s = self.bar.input().unwrap(); - match s.parse() { - Ok(n) => Cell::Number(n), - Err(_) => Cell::String(s.to_string()), - } - }) - }); - - state.view.cancel_mode(); - self.bar.set_input_mode(false); - } - KeyCode::Esc => { - GlobalState::instance_mut().view.cancel_mode(); - self.bar.set_input_mode(false); - } - _ => self.bar.handle_keyevent(event), - }, - Mode::Visual => match event.code { - KeyCode::Char('j') => self.move_cursor(CursorMove::Down(1)), - KeyCode::Char('k') => self.move_cursor(CursorMove::Up(1)), - KeyCode::Char('h') => self.move_cursor(CursorMove::Left(1)), - KeyCode::Char('l') => self.move_cursor(CursorMove::Right(1)), - KeyCode::Char('v') | KeyCode::Esc => self.stop_selection(), - KeyCode::Char('s') => self.open_editor(), - KeyCode::Char(':') => { - self.set_mode(Mode::Command); - self.bar.set_input_mode(true); - } - KeyCode::Enter => { - self.set_mode(Mode::Insert); - self.bar.set_input_mode(true); - } - _ => {} - }, Mode::Command => match event.code { KeyCode::Enter => { if let Err(_error) = lua::get().load(self.bar.input().unwrap_or("")).exec() { @@ -148,6 +50,14 @@ end)"#, } _ => self.bar.handle_keyevent(event), }, + _ => { + ViewKeyMap::handle(event); + + let mode = { GlobalState::instance().view.mode }; + if let Mode::Command = mode { + self.bar.set_input_mode(true) + } + } } } } diff --git a/src/widgets/statusbar.rs b/src/widgets/statusbar.rs index 9375d95..baa19a7 100644 --- a/src/widgets/statusbar.rs +++ b/src/widgets/statusbar.rs @@ -76,11 +76,13 @@ impl StatusBar { } pub fn set_input_mode(&mut self, input: bool) { - if input { - self.input = Some(String::new()); - self.cursor = 0; - } else { - self.input = None; + if self.input.is_some() != input { + if input { + self.input = Some(String::new()); + self.cursor = 0; + } else { + self.input = None; + } } } |