feat: expose typed action and mode configuration values
This commit is contained in:
+47
-4
@@ -18,6 +18,30 @@ module Vamp
|
||||
value.dup.force_encoding(Encoding::UTF_8)
|
||||
end
|
||||
|
||||
Action = Struct.new(:name)
|
||||
Mode = Struct.new(:name)
|
||||
|
||||
module Core
|
||||
%w[
|
||||
insert insert_first_nonblank append append_line_end open_below open_above
|
||||
left right down up join_lines line_start line_end delete undo redo command
|
||||
normal newline backspace submit
|
||||
].each do |name|
|
||||
action = Action.new("core.#{name}".freeze).freeze
|
||||
define_singleton_method(name) { action }
|
||||
end
|
||||
end
|
||||
|
||||
module Modes
|
||||
%w[normal insert command].each do |name|
|
||||
mode = Mode.new(name.freeze).freeze
|
||||
define_singleton_method(name) { mode }
|
||||
end
|
||||
end
|
||||
|
||||
Action.private_class_method :new
|
||||
Mode.private_class_method :new
|
||||
|
||||
class Config
|
||||
def initialize
|
||||
@plugins = []
|
||||
@@ -31,13 +55,32 @@ module Vamp
|
||||
nil
|
||||
end
|
||||
|
||||
def bind(key, command, mode: "normal")
|
||||
def core
|
||||
Core
|
||||
end
|
||||
|
||||
def modes
|
||||
Modes
|
||||
end
|
||||
|
||||
def bind(key, command, mode: Modes.normal)
|
||||
key = Vamp.string!(key, "binding key")
|
||||
mode = Vamp.string!(mode, "binding mode")
|
||||
command = Vamp.string!(command, "binding command") unless command.nil?
|
||||
raise TypeError, "binding mode must be a value from config.modes" unless mode.is_a?(Mode)
|
||||
command = case command
|
||||
when Action
|
||||
command.name
|
||||
when String
|
||||
name = Vamp.string!(command, "binding command")
|
||||
raise TypeError, "use config.core for built-in actions" if name.start_with?("core.")
|
||||
name
|
||||
when nil
|
||||
nil
|
||||
else
|
||||
raise TypeError, "binding command must be an Action, String, or nil"
|
||||
end
|
||||
raise ArgumentError, "binding key must not be empty" if key.empty?
|
||||
raise ArgumentError, "binding command must not be empty" if command == ""
|
||||
@bindings << { "mode" => mode, "key" => key, "command" => command }
|
||||
@bindings << { "mode" => mode.name, "key" => key, "command" => command }
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
+53
-25
@@ -329,37 +329,27 @@ end
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.plugin "healthy.rb"
|
||||
config.bind "n", "core.insert_first_nonblank"
|
||||
config.bind "n", config.core.insert_first_nonblank
|
||||
config.bind "i", nil
|
||||
config.bind "ctrl+r", "core.redo"
|
||||
config.bind "ctrl+c", "core.normal", mode: "insert"
|
||||
config.bind "esc", nil, mode: "insert"
|
||||
config.bind "!", "core.newline", mode: "insert"
|
||||
config.bind "shift+x", nil, mode: "insert"
|
||||
config.bind "ctrl+r", config.core.redo
|
||||
config.bind "ctrl+c", config.core.normal, mode: config.modes.insert
|
||||
config.bind "esc", nil, mode: config.modes.insert
|
||||
config.bind "!", config.core.newline, mode: config.modes.insert
|
||||
config.bind "shift+x", nil, mode: config.modes.insert
|
||||
config.bind ":", nil
|
||||
config.bind "space", "core.command"
|
||||
config.bind "enter", nil, mode: "command"
|
||||
config.bind "ctrl+x", "core.submit", mode: "command"
|
||||
config.bind "ctrl+c", "core.normal", mode: "command"
|
||||
config.bind "ctrl+h", "core.backspace", mode: "command"
|
||||
config.bind "g", "core.undo"
|
||||
config.bind "space", config.core.command
|
||||
config.bind "enter", nil, mode: config.modes.command
|
||||
config.bind "ctrl+x", config.core.submit, mode: config.modes.command
|
||||
config.bind "ctrl+c", config.core.normal, mode: config.modes.command
|
||||
config.bind "ctrl+h", config.core.backspace, mode: config.modes.command
|
||||
config.bind "g", config.core.undo
|
||||
config.bind "g", "healthy"
|
||||
config.bind "n", "core.missing"
|
||||
config.bind "n", "core.undo", mode: "missing"
|
||||
config.bind "unknown", "core.undo"
|
||||
config.bind "ctrl+f", "healthy", mode: "insert"
|
||||
config.bind "n", config.core.newline
|
||||
config.bind "unknown", config.core.undo
|
||||
config.bind "ctrl+f", "healthy", mode: config.modes.insert
|
||||
end
|
||||
"#,
|
||||
);
|
||||
let message = session.editor().message();
|
||||
for error in [
|
||||
"Unknown action for NORMAL: core.missing",
|
||||
"Unknown binding mode: missing",
|
||||
"Invalid binding key",
|
||||
"Ex/plugin commands can only be bound in Normal mode",
|
||||
] {
|
||||
assert!(message.contains(error), "{message}");
|
||||
}
|
||||
session.handle(Input::Char('i'));
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
session.handle(Input::Char('n'));
|
||||
@@ -414,6 +404,44 @@ end
|
||||
assert!(session.editor().should_quit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_config_errors_discard_partial_bindings_and_leave_default_editing_available() {
|
||||
let fixture = Fixture::new();
|
||||
for (expression, error) in [
|
||||
("config.core.reod", "NoMethodError"),
|
||||
("config.modes.insret", "NoMethodError"),
|
||||
(r#"config.bind "n", "core.insert""#, "TypeError"),
|
||||
(r#"config.bind "n", nil, mode: "insert""#, "TypeError"),
|
||||
(r#"config.bind "n", config.modes.insert"#, "TypeError"),
|
||||
(
|
||||
r#"config.bind "n", nil, mode: config.core.insert"#,
|
||||
"TypeError",
|
||||
),
|
||||
(r#"config.core.redo.name = "core.undo""#, "FrozenError"),
|
||||
(
|
||||
r#"config.core.redo.name.replace("core.undo")"#,
|
||||
"FrozenError",
|
||||
),
|
||||
(r#"config.modes.insert.name = "normal""#, "FrozenError"),
|
||||
(
|
||||
r#"config.modes.insert.name.replace("normal")"#,
|
||||
"FrozenError",
|
||||
),
|
||||
] {
|
||||
let mut session = fixture.open(&format!(
|
||||
"Vamp.configure do |config|\n config.bind \"i\", nil\n {expression}\nend"
|
||||
));
|
||||
assert!(session.editor().message().contains(error), "{expression}");
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('界'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
assert_eq!(session.editor().lines(), &["界"], "{expression}");
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &[String::new()], "{expression}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_config_falls_back_to_core_without_failing_the_session() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
Reference in New Issue
Block a user