window management library
git clone https://git.lucas.co/cce-window-manager.git
src/query.rs (3.1K)
1 // Window-query resolution: how a user-supplied query string (ccectl
2 // focus-window / center-window, window-stream subscriptions) picks a window.
3 //
4 // The candidate list is the mechanism's job: it passes only MAPPED windows,
5 // in window order. This module owns just the matching rules.
6
7 /// A mapped window's identity, as query resolution sees it.
8 #[derive(Debug, Clone)]
9 pub struct QueryCandidate {
10 /// The numeric window id users see (the slotmap key index).
11 pub index: u32,
12 pub app_id: Option<String>,
13 }
14
15 /// Resolve a query to a position in `candidates`, or None.
16 ///
17 /// An all-numeric query is tried as an exact window id first. Failing that
18 /// (non-numeric, or no window has that id), it is matched case-insensitively
19 /// against app_ids: an exact match beats a substring match, and the first
20 /// window at the best score wins.
21 pub fn find_window(candidates: &[QueryCandidate], query: &str) -> Option<usize> {
22 let query = query.to_lowercase();
23
24 if let Ok(id) = query.parse::<u32>() {
25 if let Some(pos) = candidates.iter().position(|c| c.index == id) {
26 return Some(pos);
27 }
28 }
29
30 let mut best: Option<usize> = None;
31 let mut best_score = 0;
32 for (pos, c) in candidates.iter().enumerate() {
33 let Some(aid) = &c.app_id else { continue };
34 let aid = aid.to_lowercase();
35 let score = if aid == query {
36 100
37 } else if aid.contains(&query) {
38 50
39 } else {
40 0
41 };
42 if score > best_score {
43 best_score = score;
44 best = Some(pos);
45 }
46 }
47 best
48 }
49
50 #[cfg(test)]
51 mod tests {
52 use super::*;
53
54 fn cand(index: u32, app_id: Option<&str>) -> QueryCandidate {
55 QueryCandidate { index, app_id: app_id.map(str::to_string) }
56 }
57
58 #[test]
59 fn numeric_query_matches_window_id_first() {
60 let c = [cand(3, Some("2048-game")), cand(7, None)];
61 // "7" is window id 7, even though nothing app_id-matches it.
62 assert_eq!(find_window(&c, "7"), Some(1));
63 // "3" is window id 3, beating the app_id containing "3"... (none here)
64 assert_eq!(find_window(&c, "3"), Some(0));
65 }
66
67 #[test]
68 fn numeric_query_without_id_match_falls_to_app_ids() {
69 // No window has id 2048, but an app_id contains "2048".
70 let c = [cand(1, Some("2048-game"))];
71 assert_eq!(find_window(&c, "2048"), Some(0));
72 }
73
74 #[test]
75 fn exact_beats_substring_and_first_best_wins() {
76 let c = [
77 cand(1, Some("cce-mail-helper")),
78 cand(2, Some("cce-mail")),
79 cand(3, Some("cce-mail")),
80 ];
81 // Exact match at position 1 outranks the earlier substring match;
82 // the later equal-score exact match doesn't displace it.
83 assert_eq!(find_window(&c, "CCE-Mail"), Some(1));
84 // Pure substring query: first container wins.
85 assert_eq!(find_window(&c, "mail"), Some(0));
86 }
87
88 #[test]
89 fn no_match_is_none() {
90 let c = [cand(1, Some("cce-files")), cand(2, None)];
91 assert_eq!(find_window(&c, "firefox"), None);
92 assert_eq!(find_window(&[], "anything"), None);
93 }
94 }