use crossterm::event::{KeyCode, KeyModifiers};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct BufferMode {
pub name: String,
pub parent: Option<String>,
pub keybindings: HashMap<(KeyCode, KeyModifiers), String>,
pub read_only: bool,
}
impl BufferMode {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
parent: None,
keybindings: HashMap::new(),
read_only: false,
}
}
pub fn with_parent(mut self, parent: impl Into<String>) -> Self {
self.parent = Some(parent.into());
self
}
pub fn with_binding(
mut self,
code: KeyCode,
modifiers: KeyModifiers,
command: impl Into<String>,
) -> Self {
self.keybindings.insert((code, modifiers), command.into());
self
}
pub fn with_read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
pub fn with_bindings(mut self, bindings: Vec<(KeyCode, KeyModifiers, String)>) -> Self {
for (code, modifiers, command) in bindings {
self.keybindings.insert((code, modifiers), command);
}
self
}
}
#[derive(Debug, Clone)]
pub struct ModeRegistry {
modes: HashMap<String, BufferMode>,
}
impl ModeRegistry {
pub fn new() -> Self {
let mut registry = Self {
modes: HashMap::new(),
};
let special_mode = BufferMode::new("special")
.with_read_only(true)
.with_binding(KeyCode::Char('q'), KeyModifiers::NONE, "close-buffer")
.with_binding(KeyCode::Char('g'), KeyModifiers::NONE, "revert-buffer");
registry.register(special_mode);
registry
}
pub fn register(&mut self, mode: BufferMode) {
self.modes.insert(mode.name.clone(), mode);
}
pub fn get(&self, name: &str) -> Option<&BufferMode> {
self.modes.get(name)
}
pub fn resolve_keybinding(
&self,
mode_name: &str,
code: KeyCode,
modifiers: KeyModifiers,
) -> Option<String> {
let mut current_mode_name = Some(mode_name);
while let Some(name) = current_mode_name {
if let Some(mode) = self.modes.get(name) {
if let Some(command) = mode.keybindings.get(&(code, modifiers)) {
return Some(command.clone());
}
current_mode_name = mode.parent.as_deref();
} else {
break;
}
}
None
}
pub fn is_read_only(&self, mode_name: &str) -> bool {
let mut current_mode_name = Some(mode_name);
while let Some(name) = current_mode_name {
if let Some(mode) = self.modes.get(name) {
if mode.read_only {
return true;
}
current_mode_name = mode.parent.as_deref();
} else {
break;
}
}
false
}
pub fn list_modes(&self) -> Vec<String> {
self.modes.keys().cloned().collect()
}
pub fn has_mode(&self, name: &str) -> bool {
self.modes.contains_key(name)
}
pub fn get_all_keybindings(&self, mode_name: &str) -> HashMap<(KeyCode, KeyModifiers), String> {
let mut result = HashMap::new();
let mut chain = Vec::new();
let mut current = Some(mode_name);
while let Some(name) = current {
if let Some(mode) = self.modes.get(name) {
chain.push(mode);
current = mode.parent.as_deref();
} else {
break;
}
}
for mode in chain.into_iter().rev() {
result.extend(mode.keybindings.clone());
}
result
}
}
impl Default for ModeRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_special_mode_exists() {
let registry = ModeRegistry::new();
assert!(registry.has_mode("special"));
}
#[test]
fn test_special_mode_keybindings() {
let registry = ModeRegistry::new();
let mode = registry.get("special").unwrap();
assert_eq!(
mode.keybindings
.get(&(KeyCode::Char('q'), KeyModifiers::NONE)),
Some(&"close-buffer".to_string())
);
assert_eq!(
mode.keybindings
.get(&(KeyCode::Char('g'), KeyModifiers::NONE)),
Some(&"revert-buffer".to_string())
);
}
#[test]
fn test_mode_inheritance() {
let mut registry = ModeRegistry::new();
let diagnostics_mode = BufferMode::new("diagnostics-list")
.with_parent("special")
.with_binding(KeyCode::Enter, KeyModifiers::NONE, "diagnostics:goto")
.with_binding(KeyCode::Char('n'), KeyModifiers::NONE, "next-line");
registry.register(diagnostics_mode);
assert_eq!(
registry.resolve_keybinding("diagnostics-list", KeyCode::Enter, KeyModifiers::NONE),
Some("diagnostics:goto".to_string())
);
assert_eq!(
registry.resolve_keybinding("diagnostics-list", KeyCode::Char('q'), KeyModifiers::NONE),
Some("close-buffer".to_string())
);
assert_eq!(
registry.resolve_keybinding("diagnostics-list", KeyCode::Char('x'), KeyModifiers::NONE),
None
);
}
#[test]
fn test_mode_read_only_inheritance() {
let mut registry = ModeRegistry::new();
assert!(registry.is_read_only("special"));
let child_mode = BufferMode::new("child").with_parent("special");
registry.register(child_mode);
assert!(registry.is_read_only("child"));
let editable_mode = BufferMode::new("editable");
registry.register(editable_mode);
assert!(!registry.is_read_only("editable"));
}
#[test]
fn test_get_all_keybindings() {
let mut registry = ModeRegistry::new();
let child_mode = BufferMode::new("child")
.with_parent("special")
.with_binding(KeyCode::Enter, KeyModifiers::NONE, "child:action")
.with_binding(KeyCode::Char('q'), KeyModifiers::NONE, "child:quit");
registry.register(child_mode);
let all_bindings = registry.get_all_keybindings("child");
assert_eq!(
all_bindings.get(&(KeyCode::Char('q'), KeyModifiers::NONE)),
Some(&"child:quit".to_string())
);
assert_eq!(
all_bindings.get(&(KeyCode::Char('g'), KeyModifiers::NONE)),
Some(&"revert-buffer".to_string())
);
assert_eq!(
all_bindings.get(&(KeyCode::Enter, KeyModifiers::NONE)),
Some(&"child:action".to_string())
);
}
}