feat: Add support for action modifier keys like ctrl,shift,alt.
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled

This commit is contained in:
2026-03-15 11:51:17 -04:00
parent 0d241841bc
commit 47437f536b
18 changed files with 1055 additions and 244 deletions

View File

@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::PiklError;
use crate::event::Modifiers;
/// A lifecycle event emitted by the menu engine. Handler
/// hooks receive these as JSON lines on stdin. The `event`
@@ -16,11 +17,33 @@ use crate::error::PiklError;
pub enum HookEvent {
Open,
Close,
Hover { item: Value, index: usize },
Select { item: Value, index: usize },
Cancel,
Filter { text: String },
Quicklist { items: Vec<Value>, count: usize },
Hover {
item: Value,
index: usize,
#[serde(skip_serializing_if = "Modifiers::is_empty")]
modifiers: Modifiers,
},
Select {
item: Value,
index: usize,
#[serde(skip_serializing_if = "Modifiers::is_empty")]
modifiers: Modifiers,
},
Cancel {
#[serde(skip_serializing_if = "Modifiers::is_empty")]
modifiers: Modifiers,
},
Filter {
text: String,
#[serde(skip_serializing_if = "Modifiers::is_empty")]
modifiers: Modifiers,
},
Quicklist {
items: Vec<Value>,
count: usize,
#[serde(skip_serializing_if = "Modifiers::is_empty")]
modifiers: Modifiers,
},
}
/// Discriminant for [`HookEvent`], used as a key for
@@ -44,7 +67,7 @@ impl HookEvent {
HookEvent::Close => HookEventKind::Close,
HookEvent::Hover { .. } => HookEventKind::Hover,
HookEvent::Select { .. } => HookEventKind::Select,
HookEvent::Cancel => HookEventKind::Cancel,
HookEvent::Cancel { .. } => HookEventKind::Cancel,
HookEvent::Filter { .. } => HookEventKind::Filter,
HookEvent::Quicklist { .. } => HookEventKind::Quicklist,
}
@@ -116,11 +139,14 @@ mod tests {
let event = HookEvent::Hover {
item: json!({"label": "test"}),
index: 5,
modifiers: Modifiers::default(),
};
let json = serde_json::to_value(&event).unwrap_or_default();
assert_eq!(json["event"], "hover");
assert_eq!(json["item"]["label"], "test");
assert_eq!(json["index"], 5);
// Empty modifiers should be omitted
assert!(json.get("modifiers").is_none());
}
#[test]
@@ -128,6 +154,7 @@ mod tests {
let event = HookEvent::Select {
item: json!("hello"),
index: 0,
modifiers: Modifiers::default(),
};
let json = serde_json::to_value(&event).unwrap_or_default();
assert_eq!(json["event"], "select");
@@ -137,14 +164,19 @@ mod tests {
#[test]
fn event_cancel_serializes() {
let json = serde_json::to_value(&HookEvent::Cancel).unwrap_or_default();
let json = serde_json::to_value(&HookEvent::Cancel {
modifiers: Modifiers::default(),
})
.unwrap_or_default();
assert_eq!(json["event"], "cancel");
assert!(json.get("modifiers").is_none());
}
#[test]
fn event_filter_serializes() {
let event = HookEvent::Filter {
text: "foo".to_string(),
modifiers: Modifiers::default(),
};
let json = serde_json::to_value(&event).unwrap_or_default();
assert_eq!(json["event"], "filter");
@@ -160,7 +192,8 @@ mod tests {
assert_eq!(
HookEvent::Hover {
item: json!(null),
index: 0
index: 0,
modifiers: Modifiers::default(),
}
.kind(),
HookEventKind::Hover
@@ -168,15 +201,23 @@ mod tests {
assert_eq!(
HookEvent::Select {
item: json!(null),
index: 0
index: 0,
modifiers: Modifiers::default(),
}
.kind(),
HookEventKind::Select
);
assert_eq!(HookEvent::Cancel.kind(), HookEventKind::Cancel);
assert_eq!(
HookEvent::Cancel {
modifiers: Modifiers::default()
}
.kind(),
HookEventKind::Cancel
);
assert_eq!(
HookEvent::Filter {
text: String::new()
text: String::new(),
modifiers: Modifiers::default(),
}
.kind(),
HookEventKind::Filter
@@ -291,6 +332,7 @@ mod tests {
let event = HookEvent::Quicklist {
items: vec![json!("alpha"), json!("beta")],
count: 2,
modifiers: Modifiers::default(),
};
let json = serde_json::to_value(&event).unwrap_or_default();
assert_eq!(json["event"], "quicklist");
@@ -303,6 +345,7 @@ mod tests {
let event = HookEvent::Quicklist {
items: vec![],
count: 0,
modifiers: Modifiers::default(),
};
assert_eq!(event.kind(), HookEventKind::Quicklist);
}
@@ -314,6 +357,7 @@ mod tests {
let event = HookEvent::Hover {
item: json!({"label": "Firefox", "url": "https://firefox.com"}),
index: 2,
modifiers: Modifiers::default(),
};
let serialized = serde_json::to_string(&event).unwrap_or_default();
let parsed: Value = serde_json::from_str(&serialized).unwrap_or_default();
@@ -321,4 +365,35 @@ mod tests {
assert_eq!(parsed["item"]["label"], "Firefox");
assert_eq!(parsed["index"], 2);
}
// -- Modifier serialization in hook events --
#[test]
fn event_select_with_modifiers() {
let event = HookEvent::Select {
item: json!("test"),
index: 0,
modifiers: Modifiers {
shift: true,
ctrl: false,
alt: false,
},
};
let json = serde_json::to_value(&event).unwrap_or_default();
assert_eq!(json["modifiers"], json!(["shift"]));
}
#[test]
fn event_cancel_with_modifiers() {
let event = HookEvent::Cancel {
modifiers: Modifiers {
shift: true,
ctrl: true,
alt: false,
},
};
let json = serde_json::to_value(&event).unwrap_or_default();
assert_eq!(json["event"], "cancel");
assert_eq!(json["modifiers"], json!(["shift", "ctrl"]));
}
}