summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.rs35
-rw-r--r--src/config/keymap/event_from_string.rs78
-rw-r--r--src/config/keymap/keymap_store.rs81
-rw-r--r--src/config/keymap/mod.rs41
-rw-r--r--src/config/keymap/template.rs57
-rw-r--r--src/config/mod.rs13
-rw-r--r--src/state/mod.rs9
-rw-r--r--src/state/view/mod.rs2
-rw-r--r--src/widgets/sheetview/mod.rs116
-rw-r--r--src/widgets/statusbar.rs12
10 files changed, 310 insertions, 134 deletions
diff --git a/src/app.rs b/src/app.rs
index 2e956eb..476d4c0 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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;
+ }
}
}