feat: add configurable Space leader bindings
This commit is contained in:
+207
-1
@@ -33,6 +33,9 @@ impl EditorSession {
|
||||
}),
|
||||
None => Config::default(),
|
||||
};
|
||||
if let Err(error) = editor.set_leader(&config.leader) {
|
||||
notices.push(format!("leader {:?}: {error}", config.leader));
|
||||
}
|
||||
let mut plugins = BTreeMap::new();
|
||||
for path in config.plugins {
|
||||
match RubyPlugin::start(&path) {
|
||||
@@ -155,6 +158,195 @@ Vamp.command("healthy") { |editor, args| editor.replace "healthy #{args}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_config_uses_space_leader_for_palette_and_write() {
|
||||
let fixture = Fixture::new();
|
||||
let buffer = fixture.file("buffer.txt", "base");
|
||||
let mut session =
|
||||
EditorSession::open(Some(buffer.clone()), vec!["text".into()], None).unwrap();
|
||||
session.handle(Input::Char(' '));
|
||||
assert_eq!(session.editor().leader_pending(), Some("Space"));
|
||||
session.handle(Input::Char(' '));
|
||||
assert!(session.editor().palette().is_some());
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('!'));
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Char(' '));
|
||||
session.handle(Input::Char('w'));
|
||||
assert_eq!(fs::read_to_string(buffer).unwrap(), "!base");
|
||||
assert!(!session.editor().dirty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_leader_sequences_share_one_config_and_plugin_host_across_buffers() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "alpha");
|
||||
let second = fixture.file("second.txt", "beta");
|
||||
fixture.file(
|
||||
"shared.rb",
|
||||
r##"
|
||||
Vamp.plugin "shared", requires: ["text"]
|
||||
calls = 0
|
||||
Vamp.command("stamp") do |editor, _|
|
||||
calls += 1
|
||||
editor.insert "#{calls}:#{File.basename(editor.path)}:"
|
||||
end
|
||||
Vamp.command("fail") { |_, _| raise "shared host failure" }
|
||||
"##,
|
||||
);
|
||||
let mut session = fixture.open(
|
||||
r#"
|
||||
File.open("loads.txt", "a") { |file| file.puts "loaded" }
|
||||
Vamp.configure do |config|
|
||||
config.leader = ","
|
||||
config.plugin "shared.rb"
|
||||
config.bind "leader z n", "stamp"
|
||||
config.bind "leader z f", "fail"
|
||||
end
|
||||
"#,
|
||||
);
|
||||
session.handle(Input::Char(','));
|
||||
session.handle(Input::Char('z'));
|
||||
assert_eq!(session.editor().leader_pending(), Some(", z"));
|
||||
assert_eq!(session.editor().lines(), &["alpha"]);
|
||||
session.handle(Input::Char('n'));
|
||||
assert_eq!(session.editor().lines(), &["1:buffer.txt:alpha"]);
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
session.handle(Input::Char(','));
|
||||
session.handle(Input::Char('z'));
|
||||
session.execute(&format!("edit {}", second.display()));
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
for key in [',', 'z', 'n'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert_eq!(session.editor().lines(), &["2:second.txt:beta"]);
|
||||
for key in [',', 'b', 'p'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert_eq!(session.editor().buffer_id(), 1);
|
||||
session.handle(Input::Home);
|
||||
for key in [',', 'z', 'n'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert_eq!(
|
||||
session.editor().lines(),
|
||||
&["3:buffer.txt:1:buffer.txt:alpha"]
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(fixture.0.join("loads.txt")).unwrap(),
|
||||
"loaded\n"
|
||||
);
|
||||
for key in [',', 'z', 'f'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert!(
|
||||
session
|
||||
.editor()
|
||||
.message()
|
||||
.contains("Plugin shared disabled:")
|
||||
);
|
||||
for key in [',', 'z', 'n'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert_eq!(
|
||||
session.editor().lines(),
|
||||
&["3:buffer.txt:1:buffer.txt:alpha"]
|
||||
);
|
||||
session.handle(Input::Ctrl('p'));
|
||||
assert!(
|
||||
!session
|
||||
.editor()
|
||||
.palette()
|
||||
.unwrap()
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| entry.owner == "shared")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_disabled_leader_suffix_and_cancellation_keep_counts_and_selections_safe() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "abcdef");
|
||||
fixture.healthy();
|
||||
let mut session = fixture.open(
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.leader = ","
|
||||
config.plugin "healthy.rb"
|
||||
config.bind "leader z n", "healthy"
|
||||
config.bind "leader z x", nil
|
||||
config.bind "leader z h", config.core.left, mode: config.modes.visual
|
||||
end
|
||||
"#,
|
||||
);
|
||||
for last in ['x', 'd'] {
|
||||
for key in ['3', ',', 'z', last] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
assert_eq!(session.editor().lines(), &["abcdef"]);
|
||||
session.handle(Input::Char('l'));
|
||||
}
|
||||
assert_eq!(session.editor().cursor().column, 2);
|
||||
for key in ['3', ',', 'z'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
session.handle(Input::Paste("n:q!\n".into()));
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
session.handle(Input::Char('l'));
|
||||
assert_eq!(session.editor().cursor().column, 3);
|
||||
assert_eq!(session.editor().lines(), &["abcdef"]);
|
||||
session.handle(Input::Char('v'));
|
||||
let selected = session.editor().selections();
|
||||
for key in ['3', ',', 'z'] {
|
||||
session.handle(Input::Char(key));
|
||||
}
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Visual);
|
||||
assert_eq!(session.editor().selections(), selected);
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
session.handle(Input::Char('h'));
|
||||
assert_eq!(session.editor().cursor().column, 2);
|
||||
assert_eq!(session.editor().lines(), &["abcdef"]);
|
||||
assert!(!session.editor().should_quit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_backend_leader_retains_space_and_installs_valid_config_bindings() {
|
||||
for key in ["not-a-key", "esc", "7", "i"] {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "base");
|
||||
fixture.healthy();
|
||||
let mut session = fixture.open(&format!(
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.leader = "{key}"
|
||||
config.plugin "healthy.rb"
|
||||
config.bind "ctrl+f", config.core.right
|
||||
config.bind "leader z n", "healthy"
|
||||
end
|
||||
"#,
|
||||
));
|
||||
assert!(
|
||||
session
|
||||
.editor()
|
||||
.message()
|
||||
.contains(&format!("leader {key:?}:"))
|
||||
);
|
||||
assert!(!session.editor().message().contains("config disabled"));
|
||||
session.handle(Input::Ctrl('f'));
|
||||
assert_eq!(session.editor().cursor().column, 1);
|
||||
session.handle(Input::Char(' '));
|
||||
assert_eq!(session.editor().leader_pending(), Some("Space"));
|
||||
session.handle(Input::Char('z'));
|
||||
session.handle(Input::Char('n'));
|
||||
assert_eq!(session.editor().lines(), &["healthy "]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffers_share_config_and_plugin_host_but_keep_history_and_protect_hidden_edits() {
|
||||
let fixture = Fixture::new();
|
||||
@@ -546,6 +738,8 @@ end
|
||||
session.handle(Input::Char(':'));
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Normal);
|
||||
session.handle(Input::Char(' '));
|
||||
assert_eq!(session.editor().mode(), crate::editor::Mode::Command);
|
||||
assert!(session.editor().leader_pending().is_none());
|
||||
for ch in "w界".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
@@ -765,6 +959,9 @@ end
|
||||
for (expression, error) in [
|
||||
("config.core.reod", "NoMethodError"),
|
||||
("config.modes.insret", "NoMethodError"),
|
||||
("config.leader = 7", "leader key must be a String"),
|
||||
("config.leader = nil", "leader key must be a String"),
|
||||
(r#"config.leader = """#, "leader key must not be empty"),
|
||||
(r#"config.bind "n", "core.insert""#, "TypeError"),
|
||||
(r#"config.bind "n", nil, mode: "insert""#, "TypeError"),
|
||||
(r#"config.bind "n", config.modes.insert"#, "TypeError"),
|
||||
@@ -784,7 +981,7 @@ end
|
||||
),
|
||||
] {
|
||||
let mut session = fixture.open(&format!(
|
||||
"Vamp.configure do |config|\n config.bind \"i\", nil\n {expression}\nend"
|
||||
"Vamp.configure do |config|\n config.leader = \",\"\n config.bind \"i\", nil\n {expression}\nend"
|
||||
));
|
||||
assert!(session.editor().message().contains(error), "{expression}");
|
||||
session.handle(Input::Char('i'));
|
||||
@@ -794,6 +991,15 @@ end
|
||||
assert_eq!(session.editor().lines(), &["界"], "{expression}");
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().lines(), &[String::new()], "{expression}");
|
||||
session.handle(Input::Char(' '));
|
||||
assert_eq!(
|
||||
session.editor().leader_pending(),
|
||||
Some("Space"),
|
||||
"{expression}"
|
||||
);
|
||||
session.handle(Input::Char(' '));
|
||||
assert!(session.editor().palette().is_some(), "{expression}");
|
||||
session.handle(Input::Escape);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user