rmux_core/
command_queue.rs1use std::collections::VecDeque;
9
10use crate::command_parser::{CommandGrouping, ParsedCommand, ParsedCommands};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct CommandGroup(u64);
18
19impl CommandGroup {
20 #[must_use]
22 pub const fn get(self) -> u64 {
23 self.0
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct QueuedCommand {
30 command: ParsedCommand,
31 group: CommandGroup,
32}
33
34impl QueuedCommand {
35 #[must_use]
37 pub const fn command(&self) -> &ParsedCommand {
38 &self.command
39 }
40
41 #[must_use]
43 pub fn into_command(self) -> ParsedCommand {
44 self.command
45 }
46
47 #[must_use]
49 pub const fn group(&self) -> CommandGroup {
50 self.group
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum CommandQueueResult {
57 Normal,
59 Error,
61 Wait,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct CommandQueue {
69 items: VecDeque<QueuedCommand>,
70 next_group: u64,
71}
72
73impl Default for CommandQueue {
74 fn default() -> Self {
75 Self {
76 items: VecDeque::new(),
77 next_group: 1,
78 }
79 }
80}
81
82impl CommandQueue {
83 #[must_use]
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 #[must_use]
91 pub fn from_parsed(commands: ParsedCommands) -> Self {
92 let mut queue = Self::new();
93 queue.append_parsed(commands);
94 queue
95 }
96
97 #[must_use]
99 pub fn is_empty(&self) -> bool {
100 self.items.is_empty()
101 }
102
103 #[must_use]
105 pub fn len(&self) -> usize {
106 self.items.len()
107 }
108
109 #[must_use]
111 pub fn items(&self) -> &VecDeque<QueuedCommand> {
112 &self.items
113 }
114
115 pub fn append_parsed(&mut self, commands: ParsedCommands) {
117 let items = self.assign_groups(commands);
118 self.items.extend(items);
119 }
120
121 pub fn insert_after_current(&mut self, commands: ParsedCommands) {
128 let items = self.assign_groups(commands);
129 for item in items.into_iter().rev() {
130 self.items.push_front(item);
131 }
132 }
133
134 pub fn pop_front(&mut self) -> Option<QueuedCommand> {
136 self.items.pop_front()
137 }
138
139 pub fn remove_group(&mut self, group: CommandGroup) -> usize {
144 let before = self.items.len();
145 self.items.retain(|item| item.group != group);
146 before - self.items.len()
147 }
148
149 fn assign_groups(&mut self, commands: ParsedCommands) -> Vec<QueuedCommand> {
150 let grouping = commands.grouping();
151 let mut assigned = Vec::new();
152 let mut current_line = None;
153 let mut current_group = None;
154
155 for command in commands.into_commands() {
156 let group = match grouping {
157 CommandGrouping::OneGroup => {
158 *current_group.get_or_insert_with(|| self.next_group())
159 }
160 CommandGrouping::ByLine if current_line == Some(command.line()) => {
161 current_group.expect("line reuse requires an existing command group")
162 }
163 CommandGrouping::ByLine => {
164 current_line = Some(command.line());
165 let group = self.next_group();
166 current_group = Some(group);
167 group
168 }
169 };
170
171 assigned.push(QueuedCommand { command, group });
172 }
173
174 assigned
175 }
176
177 fn next_group(&mut self) -> CommandGroup {
178 let group = CommandGroup(self.next_group);
179 self.next_group += 1;
180 group
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use crate::command_parser::{CommandArgument, CommandParser};
187
188 use super::CommandQueue;
189
190 fn queue_groups(input: &str) -> Vec<u64> {
191 let parsed = CommandParser::new().parse(input).expect("commands parse");
192 CommandQueue::from_parsed(parsed)
193 .items()
194 .iter()
195 .map(|item| item.group().get())
196 .collect()
197 }
198
199 #[test]
200 fn same_source_line_commands_share_a_group() {
201 assert_eq!(
202 queue_groups("display-message first ; display-message second"),
203 [1, 1]
204 );
205 }
206
207 #[test]
208 fn newline_separated_commands_get_distinct_groups() {
209 assert_eq!(
210 queue_groups("display-message first\ndisplay-message second"),
211 [1, 2]
212 );
213 }
214
215 #[test]
216 fn argv_trailing_semicolon_commands_share_the_default_line_group() {
217 let parsed = CommandParser::new()
218 .parse_arguments(["display-message;", "display-message", "ok"])
219 .expect("argv commands parse");
220 let queue = CommandQueue::from_parsed(parsed);
221 let groups = queue
222 .items()
223 .iter()
224 .map(|item| item.group().get())
225 .collect::<Vec<_>>();
226
227 assert_eq!(groups, [1, 1]);
228 }
229
230 #[test]
231 fn one_group_string_mode_collapses_multiline_commands() {
232 let parsed = CommandParser::new()
233 .parse_one_group("display-message first\ndisplay-message second")
234 .expect("commands parse");
235 let groups = CommandQueue::from_parsed(parsed)
236 .items()
237 .iter()
238 .map(|item| item.group().get())
239 .collect::<Vec<_>>();
240
241 assert_eq!(groups, [1, 1]);
242 }
243
244 #[test]
245 fn brace_arguments_remain_preparsed_command_lists() {
246 let parsed = CommandParser::new()
247 .parse("if-shell -F 1 { display-message yes ; list-sessions }")
248 .expect("commands parse");
249 let queue = CommandQueue::from_parsed(parsed);
250 let arguments = queue.items()[0].command().arguments();
251 let nested = match &arguments[2] {
252 CommandArgument::Commands(nested) => nested,
253 CommandArgument::String(value) => panic!("expected parsed command list, got {value}"),
254 };
255 let names = nested
256 .commands()
257 .iter()
258 .map(|command| command.name())
259 .collect::<Vec<_>>();
260
261 assert_eq!(names, ["display-message", "list-sessions"]);
262 }
263
264 #[test]
265 fn one_group_mode_propagates_into_brace_command_lists() {
266 let parsed = CommandParser::new()
267 .parse_one_group("if-shell -F 1 { display-message yes\nlist-sessions }")
268 .expect("commands parse");
269 let queue = CommandQueue::from_parsed(parsed);
270 let arguments = queue.items()[0].command().arguments();
271 let nested = match &arguments[2] {
272 CommandArgument::Commands(nested) => nested.clone(),
273 CommandArgument::String(value) => panic!("expected parsed command list, got {value}"),
274 };
275 let groups = CommandQueue::from_parsed(nested)
276 .items()
277 .iter()
278 .map(|item| item.group().get())
279 .collect::<Vec<_>>();
280
281 assert_eq!(groups, [1, 1]);
282 }
283
284 #[test]
285 fn insert_after_current_preserves_inserted_order_and_fresh_groups() {
286 let parser = CommandParser::new();
287 let parsed = parser
288 .parse("display-message parent ; display-message tail")
289 .expect("commands parse");
290 let mut queue = CommandQueue::from_parsed(parsed);
291 let current = queue.pop_front().expect("current command");
292
293 queue.insert_after_current(
294 parser
295 .parse("display-message inserted-one\ndisplay-message inserted-two")
296 .expect("inserted commands parse"),
297 );
298
299 let queued = queue
300 .items()
301 .iter()
302 .map(|item| {
303 (
304 item.command().arguments()[0]
305 .as_string()
306 .expect("display-message argument")
307 .to_owned(),
308 item.group().get(),
309 )
310 })
311 .collect::<Vec<_>>();
312
313 assert_eq!(
314 queued,
315 [
316 ("inserted-one".to_owned(), 2),
317 ("inserted-two".to_owned(), 3),
318 ("tail".to_owned(), current.group().get()),
319 ]
320 );
321 }
322
323 #[test]
324 fn remove_group_removes_noncontiguous_later_members() {
325 let parser = CommandParser::new();
326 let parsed = parser
327 .parse("display-message first ; display-message skipped")
328 .expect("commands parse");
329 let mut queue = CommandQueue::from_parsed(parsed);
330 let failed = queue.pop_front().expect("failed command");
331
332 queue.insert_after_current(
333 parser
334 .parse("display-message inserted")
335 .expect("inserted command parses"),
336 );
337
338 assert_eq!(queue.remove_group(failed.group()), 1);
339 assert_eq!(queue.len(), 1);
340 assert_eq!(
341 queue.items()[0].command().arguments()[0].as_string(),
342 Some("inserted")
343 );
344 }
345
346 #[test]
347 fn alias_expansion_uses_string_mode_and_inherits_source_line() {
348 let parsed = CommandParser::new()
349 .with_command_alias("q=display-message one\ndisplay-message two")
350 .expect("valid alias")
351 .parse("q\ndisplay-message kept")
352 .expect("commands parse");
353 let queue = CommandQueue::from_parsed(parsed);
354 let groups = queue
355 .items()
356 .iter()
357 .map(|item| item.group().get())
358 .collect::<Vec<_>>();
359
360 assert_eq!(groups, [1, 1, 2]);
361 }
362
363 #[test]
364 fn remove_group_preserves_commands_from_later_lines() {
365 let parsed = CommandParser::new()
366 .parse("display-message first ; display-message skipped\ndisplay-message kept")
367 .expect("commands parse");
368 let mut queue = CommandQueue::from_parsed(parsed);
369 let failed = queue.pop_front().expect("first command");
370
371 assert_eq!(queue.remove_group(failed.group()), 1);
372 assert_eq!(queue.len(), 1);
373 assert_eq!(
374 queue.items()[0].command().arguments()[0].as_string(),
375 Some("kept")
376 );
377 }
378}