feat: add configurable Space leader bindings

This commit is contained in:
Valdemar Lorenzen
2026-10-08 13:54:48 +02:00
parent 5d2d67ab09
commit 5a67bf64a3
8 changed files with 958 additions and 28 deletions
+53 -2
View File
@@ -13,10 +13,21 @@ const MAX_FRAME: usize = 4 * 1024 * 1024;
const STARTUP_TIMEOUT: Duration = Duration::from_secs(2);
const INVOKE_TIMEOUT: Duration = Duration::from_millis(500);
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct Config {
pub plugins: Vec<PathBuf>,
pub bindings: Vec<Binding>,
pub leader: String,
}
impl Default for Config {
fn default() -> Self {
Self {
plugins: Vec::new(),
bindings: Vec::new(),
leader: "space".into(),
}
}
}
/// Relative plugin paths are resolved against the configuration file's directory.
@@ -27,7 +38,10 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
let response = host.receive(deadline)?;
match response {
Response::Config {
plugins, bindings, ..
plugins,
bindings,
leader,
..
} => {
let mut config = Config::default();
let directory = path.parent().expect("absolute file has a parent");
@@ -38,6 +52,7 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
config.plugins.push(directory.join(plugin));
}
config.bindings = bindings;
config.leader = leader;
Ok(config)
}
Response::Error {
@@ -182,6 +197,7 @@ enum Response {
version: u8,
plugins: Vec<String>,
bindings: Vec<Binding>,
leader: String,
},
#[serde(rename = "result")]
Result {
@@ -550,6 +566,41 @@ end
assert!(RubyPlugin::start(&invalid).is_err());
}
#[test]
fn config_leader_defaults_custom_keys_validation_and_required_wire_field() {
let fixture = Fixture::new();
let defaults = Config::default();
assert_eq!(defaults.leader, "space");
assert!(defaults.plugins.is_empty() && defaults.bindings.is_empty());
let path = fixture.file("config.rb", "Vamp.configure { |_| }");
assert_eq!(load_config(&path).unwrap().leader, "space");
for (source, key) in [
(r#"Vamp.configure { |config| config.leader = "," }"#, ","),
(r#"Vamp.configure { |config| config.leader = "\\" }"#, "\\"),
] {
let path = fixture.file("config.rb", source);
assert_eq!(load_config(&path).unwrap().leader, key);
}
for (value, error) in [
("7", "leader key must be a String"),
("nil", "leader key must be a String"),
(r#""""#, "leader key must not be empty"),
] {
let path = fixture.file(
"config.rb",
&format!("Vamp.configure {{ |config| config.leader = {value} }}"),
);
assert!(load_config(&path).unwrap_err().contains(error), "{value}");
}
for frame in [
r#"{"type":"config","version":1,"plugins":[],"bindings":[]}"#,
r#"{"type":"config","version":1,"plugins":[],"bindings":[],"leader":null}"#,
r#"{"type":"config","version":1,"plugins":[],"bindings":[],"leader":7}"#,
] {
assert!(serde_json::from_str::<Response>(frame).is_err(), "{frame}");
}
}
#[test]
fn crash_hang_callback_failure_and_fresh_host_recovery() {
let fixture = Fixture::new();