• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Blightmud / Blightmud / 23318037125

19 Mar 2026 09:35PM UTC coverage: 73.681% (-0.02%) from 73.704%
23318037125

push

github

web-flow
Adds lua typedefintions file (#1387)

9706 of 13173 relevant lines covered (73.68%)

408.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.43
/src/ui/command.rs
1
use crate::event::QuitMethod;
2
use crate::model::{Completions, Line, PromptMask, Servers};
3
use crate::{event::Event, tts::TTSController};
4
use crate::{lua::LuaScript, lua::UiEvent, session::Session, SaveData};
5
use log::debug;
6
use rs_complete::CompletionTree;
7
use std::collections::HashSet;
8
use std::{
9
    io::stdin,
10
    sync::{mpsc::Sender, Arc, Mutex},
11
};
12
use std::{mem, thread};
13
use termion::{event::Key, input::TermRead};
14

15
#[derive(Default)]
16
struct CompletionStepData {
17
    options: Vec<String>,
18
    index: usize,
19
    base: String,
20
}
21

22
impl CompletionStepData {
23
    fn is_empty(&self) -> bool {
4✔
24
        self.options.is_empty()
4✔
25
    }
4✔
26

27
    fn set_options(&mut self, base: &str, options: Vec<String>) {
1✔
28
        self.options = options;
1✔
29
        self.base = base.to_string();
1✔
30
    }
1✔
31

32
    fn clear(&mut self) {
264✔
33
        self.options.clear();
264✔
34
        self.index = 0;
264✔
35
    }
264✔
36

37
    fn next(&mut self) -> Option<&String> {
2✔
38
        if !self.is_empty() {
2✔
39
            let last_index = self.index;
2✔
40
            self.index = (self.index + 1) % (self.options.len() + 1);
2✔
41
            self.options.get(last_index).or(Some(&self.base))
2✔
42
        } else {
43
            None
×
44
        }
45
    }
2✔
46
}
47

48
pub struct CommandBuffer {
49
    buffer: Vec<char>,
50
    last_buffer: Vec<char>,
51
    last_command_enabled: bool,
52
    cursor_pos: usize,
53
    completion_tree: CompletionTree,
54
    completion: CompletionStepData,
55
    prompt_mask: PromptMask,
56
    script: Arc<Mutex<LuaScript>>,
57
    tts_ctrl: Arc<Mutex<TTSController>>,
58
}
59

60
impl CommandBuffer {
61
    pub fn new(
166✔
62
        tts_ctrl: Arc<Mutex<TTSController>>,
166✔
63
        script: Arc<Mutex<LuaScript>>,
166✔
64
        last_command_enabled: bool,
166✔
65
    ) -> Self {
166✔
66
        let mut completion = CompletionTree::with_inclusions(&['/', '_']);
166✔
67
        completion.set_min_word_len(3);
166✔
68

69
        Self {
166✔
70
            buffer: vec![],
166✔
71
            last_buffer: vec![],
166✔
72
            last_command_enabled,
166✔
73
            cursor_pos: 0,
166✔
74
            completion_tree: completion,
166✔
75
            completion: CompletionStepData::default(),
166✔
76
            prompt_mask: PromptMask::new(),
166✔
77
            script,
166✔
78
            tts_ctrl,
166✔
79
        }
166✔
80
    }
166✔
81

82
    pub fn get_buffer(&mut self) -> String {
42✔
83
        self.buffer.iter().collect::<String>()
42✔
84
    }
42✔
85

86
    pub fn get_masked_buffer(&self) -> String {
×
87
        self.prompt_mask.mask_buffer(&self.buffer)
×
88
    }
×
89

90
    pub fn get_mask(&self) -> &PromptMask {
×
91
        &self.prompt_mask
×
92
    }
×
93

94
    pub fn get_last_command_mask(&self) -> PromptMask {
1✔
95
        let mut mask = PromptMask::new();
1✔
96
        mask.insert(0, "\x1b[44m".to_string()); // BG blue
1✔
97
        mask.insert(self.last_buffer.len() as i32, "\x1b[0m".to_string());
1✔
98
        mask
1✔
99
    }
1✔
100

101
    pub fn enable_last_command(&mut self, enabled: bool) {
2✔
102
        self.last_command_enabled = enabled;
2✔
103
        if !enabled {
2✔
104
            self.last_buffer.clear();
1✔
105
        }
1✔
106
    }
2✔
107

108
    pub fn get_pos(&self) -> usize {
27✔
109
        self.cursor_pos
27✔
110
    }
27✔
111

112
    fn submit(&mut self) -> String {
12✔
113
        // If we have no buffer then swap in the last buffer
114
        if self.last_command_enabled && self.buffer.is_empty() {
12✔
115
            mem::swap(&mut self.last_buffer, &mut self.buffer);
1✔
116
        }
11✔
117

118
        // Insert history
119
        let cmd = if !self.buffer.is_empty() {
12✔
120
            let command = self.get_buffer();
11✔
121
            self.completion_tree.insert(&command);
11✔
122
            command
11✔
123
        } else {
124
            String::new()
1✔
125
        };
126

127
        if self.last_command_enabled {
12✔
128
            // Clear the last buffer and swap them
8✔
129
            self.last_buffer.clear();
8✔
130
            mem::swap(&mut self.last_buffer, &mut self.buffer);
8✔
131
        } else {
8✔
132
            self.buffer.clear();
4✔
133
        }
4✔
134

135
        self.clear_mask();
12✔
136
        self.cursor_pos = 0;
12✔
137

138
        cmd
12✔
139
    }
12✔
140

141
    fn step_left(&mut self) {
10✔
142
        if self.cursor_pos > 0 {
10✔
143
            self.cursor_pos -= 1;
9✔
144
        }
9✔
145
    }
10✔
146

147
    fn step_right(&mut self) {
266✔
148
        // Actualize the last buffer if the current is empty
149
        if self.buffer.is_empty() {
266✔
150
            mem::swap(&mut self.last_buffer, &mut self.buffer);
2✔
151
            self.cursor_pos = self.buffer.len();
2✔
152
        } else if self.cursor_pos < self.buffer.len() {
264✔
153
            self.cursor_pos += 1;
264✔
154
        }
264✔
155
    }
266✔
156

157
    fn move_to_start(&mut self) {
8✔
158
        self.cursor_pos = 0;
8✔
159
    }
8✔
160

161
    fn move_to_end(&mut self) {
5✔
162
        self.cursor_pos = self.buffer.len();
5✔
163
    }
5✔
164

165
    fn step_word_right(&mut self) {
13✔
166
        let origin = (self.cursor_pos + 1).min(self.buffer.len());
13✔
167
        self.cursor_pos = if let Some(pos) = self.buffer[origin..].iter().position(|c| *c == ' ') {
50✔
168
            origin + pos
9✔
169
        } else {
170
            self.buffer.len()
4✔
171
        }
172
    }
13✔
173

174
    fn step_word_left(&mut self) {
8✔
175
        let origin = self.cursor_pos.max(1) - 1;
8✔
176
        self.cursor_pos = if let Some(pos) = self.buffer[0..origin].iter().rposition(|c| *c == ' ')
24✔
177
        {
178
            pos + 1
3✔
179
        } else {
180
            0
5✔
181
        }
182
    }
8✔
183

184
    fn delete_to_end(&mut self) {
2✔
185
        self.buffer.drain(self.cursor_pos..self.buffer.len());
2✔
186
        self.clear_mask();
2✔
187
    }
2✔
188

189
    fn delete_from_start(&mut self) {
2✔
190
        self.buffer.drain(0..self.cursor_pos);
2✔
191
        self.cursor_pos = 0;
2✔
192
    }
2✔
193

194
    fn delete_right(&mut self) {
4✔
195
        if self.cursor_pos < self.buffer.len() {
4✔
196
            self.buffer.remove(self.cursor_pos);
2✔
197
            self.clear_mask();
2✔
198
        }
2✔
199
    }
4✔
200

201
    fn delete_word_right(&mut self) {
3✔
202
        let origin = self.cursor_pos;
3✔
203
        self.step_word_right();
3✔
204
        if origin != self.cursor_pos {
3✔
205
            self.buffer.drain(origin..self.cursor_pos);
2✔
206
            self.clear_mask();
2✔
207
            self.cursor_pos = origin;
2✔
208
        }
2✔
209
    }
3✔
210

211
    fn delete_word_left(&mut self) {
3✔
212
        let origin = self.cursor_pos;
3✔
213
        self.step_word_left();
3✔
214
        if origin != self.cursor_pos {
3✔
215
            self.buffer.drain(self.cursor_pos..origin);
2✔
216
            self.clear_mask();
2✔
217
        }
2✔
218
    }
3✔
219

220
    fn remove(&mut self) -> Option<char> {
6✔
221
        if self.cursor_pos > 0 {
6✔
222
            let removed = if self.cursor_pos < self.buffer.len() {
4✔
223
                Some(self.buffer.remove(self.cursor_pos - 1))
4✔
224
            } else {
225
                self.buffer.pop()
×
226
            };
227
            self.clear_mask();
4✔
228
            self.step_left();
4✔
229
            removed
4✔
230
        } else {
231
            None
2✔
232
        }
233
    }
6✔
234

235
    fn push_key(&mut self, c: char) {
264✔
236
        if self.cursor_pos >= self.buffer.len() {
264✔
237
            self.buffer.push(c);
255✔
238
        } else {
255✔
239
            self.buffer.insert(self.cursor_pos, c);
9✔
240
        }
9✔
241
        self.clear_mask();
264✔
242
        self.completion.clear();
264✔
243
        self.step_right();
264✔
244
    }
264✔
245

246
    fn tab_complete(&mut self) {
2✔
247
        if self.buffer.is_empty() {
2✔
248
            // Actualize the last buffer if the current is empty
×
249
            mem::swap(&mut self.last_buffer, &mut self.buffer);
×
250
            self.cursor_pos = self.buffer.len();
×
251
        } else if self.buffer.len() > 1 {
2✔
252
            // Otherwise attempt a tab-complete
253
            if self.completion.is_empty() {
2✔
254
                let mut completions = Completions::default();
1✔
255
                let strbuf = self.get_buffer();
1✔
256
                completions.merge(self.script.lock().unwrap().tab_complete(&strbuf));
1✔
257
                if let Some(mut options) = self.completion_tree.complete(&strbuf) {
1✔
258
                    completions.add_all(&mut options);
1✔
259
                }
1✔
260

261
                // Remove duplicates but preserve order of occurence
262
                let mut occurences: HashSet<&String> = HashSet::new();
1✔
263
                let completions = completions.iter().fold(vec![], |mut acc, word| {
1✔
264
                    if !occurences.contains(word) {
1✔
265
                        acc.push(word.clone());
1✔
266
                    }
1✔
267
                    occurences.insert(word);
1✔
268
                    acc
1✔
269
                });
1✔
270

271
                self.completion.set_options(&strbuf, completions);
1✔
272
            }
1✔
273
            if let Some(comp) = self.completion.next() {
2✔
274
                self.tts_ctrl.lock().unwrap().speak(comp, true);
2✔
275
                self.buffer = comp.chars().collect();
2✔
276
                self.clear_mask();
2✔
277
                self.cursor_pos = self.buffer.len();
2✔
278
            }
2✔
279
        }
×
280
    }
2✔
281

282
    pub fn clear(&mut self) {
1✔
283
        self.buffer.clear();
1✔
284
        self.clear_mask();
1✔
285
        self.cursor_pos = self.buffer.len();
1✔
286
    }
1✔
287

288
    pub fn set(&mut self, line: String) {
×
289
        self.buffer = line.chars().collect();
×
290
        self.clear_mask();
×
291
        self.cursor_pos = self.buffer.len();
×
292
    }
×
293

294
    pub fn set_pos(&mut self, pos: usize) {
3✔
295
        self.cursor_pos = pos.min(self.buffer.len());
3✔
296
    }
3✔
297

298
    pub fn set_mask(&mut self, mask: PromptMask) -> &PromptMask {
×
299
        self.prompt_mask += mask;
×
300
        &self.prompt_mask
×
301
    }
×
302

303
    pub fn clear_mask(&mut self) {
291✔
304
        self.prompt_mask.clear();
291✔
305
    }
291✔
306
}
307

308
fn parse_mouse_event(event: termion::event::MouseEvent, writer: &Sender<Event>) {
×
309
    use termion::event::{MouseButton, MouseEvent};
310
    match event {
×
311
        MouseEvent::Press(MouseButton::WheelUp, ..) => writer.send(Event::ScrollUp).unwrap(),
×
312
        MouseEvent::Press(MouseButton::WheelDown, ..) => writer.send(Event::ScrollDown).unwrap(),
×
313
        _ => {}
×
314
    }
315
}
×
316

317
fn parse_key_event(
×
318
    key: termion::event::Key,
×
319
    buffer: &mut CommandBuffer,
×
320
    writer: &Sender<Event>,
×
321
    tts_ctrl: &mut Arc<Mutex<TTSController>>,
×
322
    script: &mut Arc<Mutex<LuaScript>>,
×
323
) {
×
324
    match key {
×
325
        Key::Char('\n') => {
326
            let mut line = Line::from(buffer.submit());
×
327
            line.flags.source = Some("user".to_string());
×
328
            writer.send(Event::ServerInput(line)).unwrap();
×
329
            if let Ok(mut script) = script.lock() {
×
330
                script.set_prompt_content(String::new(), 0);
×
331
            }
×
332
        }
333
        Key::Char('\t') => buffer.tab_complete(),
×
334
        Key::Char(c) => {
×
335
            tts_ctrl.lock().unwrap().key_press(c);
×
336
            buffer.push_key(c);
×
337
            if let Ok(mut script) = script.lock() {
×
338
                script.set_prompt_content(buffer.get_buffer(), buffer.get_pos());
×
339
            }
×
340
            buffer.last_buffer.clear();
×
341
        }
342
        Key::Ctrl('l') => writer.send(Event::Redraw).unwrap(),
×
343
        Key::Ctrl('c') => {
×
344
            writer.send(Event::Quit(QuitMethod::CtrlC)).unwrap();
×
345
        }
×
346

347
        // Input navigation
348
        Key::Left => buffer.step_left(),
×
349
        Key::Right => buffer.step_right(),
×
350
        Key::Backspace => {
351
            if let Some(c) = buffer.remove() {
×
352
                if let Ok(mut tts_ctrl) = tts_ctrl.lock() {
×
353
                    tts_ctrl.key_press(c);
×
354
                }
×
355
            }
×
356
            if let Ok(mut script) = script.lock() {
×
357
                script.set_prompt_content(buffer.get_buffer(), buffer.get_pos());
×
358
            }
×
359
            buffer.last_buffer.clear();
×
360
        }
361
        Key::Delete => buffer.delete_right(),
×
362
        _ => {}
×
363
    };
364
}
×
365

366
fn check_command_binds(
16✔
367
    cmd: termion::event::Key,
16✔
368
    buffer: &mut CommandBuffer,
16✔
369
    script: &Arc<Mutex<LuaScript>>,
16✔
370
    writer: &Sender<Event>,
16✔
371
) -> bool {
16✔
372
    let mut ran = false;
16✔
373
    if let Ok(mut script) = script.lock() {
16✔
374
        ran = match cmd {
16✔
375
            Key::Ctrl(c) => script.check_bindings(&human_key("ctrl-", c)),
8✔
376
            Key::CtrlUp => script.check_bindings("ctrl-up"),
×
377
            Key::CtrlDown => script.check_bindings("ctrl-down"),
×
378
            Key::CtrlLeft => script.check_bindings("ctrl-left"),
×
379
            Key::CtrlRight => script.check_bindings("ctrl-right"),
×
380
            Key::AltUp => script.check_bindings("alt-up"),
×
381
            Key::AltDown => script.check_bindings("alt-down"),
×
382
            Key::AltLeft => script.check_bindings("alt-left"),
×
383
            Key::AltRight => script.check_bindings("alt-right"),
×
384
            Key::ShiftUp => script.check_bindings("shift-up"),
×
385
            Key::ShiftDown => script.check_bindings("shift-down"),
×
386
            Key::ShiftLeft => script.check_bindings("shift-left"),
×
387
            Key::ShiftRight => script.check_bindings("shift-right"),
×
388
            Key::Alt(c) => script.check_bindings(&human_key("alt-", c)),
4✔
389
            Key::F(n) => script.check_bindings(&format!("f{n}")),
×
390
            Key::PageUp => script.check_bindings("pageup") || script.check_bindings("page up"),
1✔
391
            Key::PageDown => {
392
                script.check_bindings("pagedown") || script.check_bindings("page down")
1✔
393
            }
394
            Key::Home => script.check_bindings("home"),
1✔
395
            Key::End => script.check_bindings("end"),
1✔
396
            Key::Up => script.check_bindings("up"),
×
397
            Key::Down => script.check_bindings("down"),
×
398
            _ => false,
×
399
        }
400
    }
×
401
    handle_script_ui_io(buffer, script, writer);
16✔
402
    ran
16✔
403
}
16✔
404

405
/// Convert a key combination to a human-readable form.
406
fn human_key(prefix: &str, c: char) -> String {
18✔
407
    let mut out = prefix.to_owned();
18✔
408
    match c {
18✔
409
        '\u{7f}' => out.push_str("backspace"),
3✔
410
        '\u{1b}' => out.push_str("escape"),
2✔
411
        _ => out.push(c),
13✔
412
    }
413
    out
18✔
414
}
18✔
415

416
fn check_escape_bindings(
×
417
    escape: &str,
×
418
    buffer: &mut CommandBuffer,
×
419
    script: &Arc<Mutex<LuaScript>>,
×
420
    writer: &Sender<Event>,
×
421
) {
×
422
    if let Ok(mut script) = script.lock() {
×
423
        if !script.check_bindings(&escape.to_lowercase()) {
×
424
            writer
×
425
                .send(Event::Info(format!("Unknown command: {escape:?}")))
×
426
                .unwrap();
×
427
        }
×
428
    }
×
429
    handle_script_ui_io(buffer, script, writer);
×
430
    writer
×
431
        .send(Event::UserInputBuffer(
×
432
            buffer.get_buffer(),
×
433
            buffer.get_pos(),
×
434
        ))
×
435
        .unwrap();
×
436
}
×
437

438
fn handle_script_ui_io(
16✔
439
    buffer: &mut CommandBuffer,
16✔
440
    script: &Arc<Mutex<LuaScript>>,
16✔
441
    writer: &Sender<Event>,
16✔
442
) {
16✔
443
    if let Ok(mut script) = script.lock() {
16✔
444
        script.get_ui_events().iter().for_each(|event| match event {
16✔
445
            UiEvent::StepLeft => buffer.step_left(),
1✔
446
            UiEvent::StepRight => buffer.step_right(),
1✔
447
            UiEvent::StepToStart => buffer.move_to_start(),
1✔
448
            UiEvent::StepToEnd => buffer.move_to_end(),
1✔
449
            UiEvent::StepWordLeft => buffer.step_word_left(),
1✔
450
            UiEvent::StepWordRight => buffer.step_word_right(),
1✔
451
            UiEvent::Remove => {
1✔
452
                buffer.remove();
1✔
453
            }
1✔
454
            UiEvent::DeleteToEnd => buffer.delete_to_end(),
1✔
455
            UiEvent::DeleteFromStart => buffer.delete_from_start(),
1✔
456
            UiEvent::DeleteWordLeft => buffer.delete_word_left(),
1✔
457
            UiEvent::DeleteWordRight => buffer.delete_word_right(),
1✔
458
            UiEvent::DeleteRight => buffer.delete_right(),
1✔
459
            UiEvent::ScrollDown => writer.send(Event::ScrollDown).unwrap(),
1✔
460
            UiEvent::ScrollUp => writer.send(Event::ScrollUp).unwrap(),
1✔
461
            UiEvent::ScrollTop => writer.send(Event::ScrollTop).unwrap(),
1✔
462
            UiEvent::ScrollBottom => writer.send(Event::ScrollBottom).unwrap(),
1✔
463
            UiEvent::Complete => buffer.tab_complete(),
×
464
            UiEvent::Unknown(_) => {}
×
465
        });
16✔
466
        script.set_prompt_content(buffer.get_buffer(), buffer.get_pos());
16✔
467
        script.get_output_lines().iter().for_each(|l| {
16✔
468
            writer.send(Event::Output(Line::from(l))).unwrap();
×
469
        });
×
470
    }
×
471
}
16✔
472

473
pub fn spawn_input_thread(session: Session) -> thread::JoinHandle<()> {
128✔
474
    thread::Builder::new()
128✔
475
        .name("input-thread".to_string())
128✔
476
        .spawn(move || {
128✔
477
            debug!("Input stream spawned");
128✔
478
            let writer = session.main_writer.clone();
128✔
479
            let mut script = session.lua_script.clone();
128✔
480
            let stdin = stdin();
128✔
481
            let buffer = session.command_buffer.clone();
128✔
482
            let mut tts_ctrl = session.tts_ctrl;
128✔
483

484
            if let Ok(mut buffer) = buffer.lock() {
128✔
485
                for server in Servers::load().keys() {
128✔
486
                    buffer.completion_tree.insert(server);
×
487
                }
×
488
                buffer
128✔
489
                    .completion_tree
128✔
490
                    .insert(include_str!("../../resources/completions.txt"));
128✔
491
            }
×
492

493
            for e in stdin.events() {
128✔
494
                match e.unwrap() {
×
495
                    termion::event::Event::Key(key) => {
×
496
                        if let Ok(mut buffer) = buffer.lock() {
×
497
                            let orig_pos = buffer.get_pos();
×
498
                            let orig_len = buffer.buffer.len();
×
499
                            let bind_ran = check_command_binds(key, &mut buffer, &script, &writer);
×
500
                            if !bind_ran {
×
501
                                parse_key_event(
×
502
                                    key,
×
503
                                    &mut buffer,
×
504
                                    &writer,
×
505
                                    &mut tts_ctrl,
×
506
                                    &mut script,
×
507
                                );
×
508
                            }
×
509
                            if orig_len == buffer.buffer.len() && orig_pos != buffer.get_pos() {
×
510
                                writer
×
511
                                    .send(Event::UserInputCursor(buffer.get_pos()))
×
512
                                    .unwrap();
×
513
                            } else if !bind_ran || orig_len != buffer.buffer.len() {
×
514
                                if let Ok(mut luascript) = script.lock() {
×
515
                                    luascript.set_prompt_mask_content(&buffer.prompt_mask);
×
516
                                    luascript
×
517
                                        .set_prompt_content(buffer.get_buffer(), buffer.get_pos());
×
518
                                }
×
519

520
                                // If 'last command' is applicable then render it.
521
                                if buffer.buffer.is_empty() && !buffer.last_buffer.is_empty() {
×
522
                                    let mask = buffer.get_last_command_mask();
×
523
                                    writer
×
524
                                        .send(Event::UserInputBuffer(
×
525
                                            mask.mask_buffer(&buffer.last_buffer),
×
526
                                            0,
×
527
                                        ))
×
528
                                        .unwrap();
×
529
                                } else {
×
530
                                    writer
×
531
                                        .send(Event::UserInputBuffer(
×
532
                                            buffer.get_buffer(),
×
533
                                            buffer.get_pos(),
×
534
                                        ))
×
535
                                        .unwrap();
×
536
                                }
×
537
                            }
×
538
                        }
×
539
                    }
540
                    termion::event::Event::Mouse(event) => parse_mouse_event(event, &writer),
×
541
                    termion::event::Event::Unsupported(bytes) => {
×
542
                        if let Ok(escape) = String::from_utf8(bytes.clone()) {
×
543
                            if let Ok(mut buffer) = buffer.lock() {
×
544
                                check_escape_bindings(
×
545
                                    &escape.to_lowercase(),
×
546
                                    &mut buffer,
×
547
                                    &script,
×
548
                                    &writer,
×
549
                                );
×
550
                            }
×
551
                        } else {
×
552
                            writer
×
553
                                .send(Event::Info(format!("Unknown command: {bytes:?}")))
×
554
                                .unwrap();
×
555
                        }
×
556
                    }
557
                }
558
            }
559
            debug!("Input stream closing");
128✔
560
        })
128✔
561
        .unwrap()
128✔
562
}
128✔
563

564
#[cfg(test)]
565
mod command_test {
566

567
    use std::sync::mpsc::{channel, Receiver, Sender};
568
    use std::sync::{Arc, Mutex};
569

570
    use termion::event::Key;
571

572
    use super::check_command_binds;
573
    use super::CommandBuffer;
574
    use crate::lua::LuaScriptBuilder;
575
    use crate::tts::TTSController;
576
    use crate::Event;
577

578
    fn push_string(buffer: &mut CommandBuffer, msg: &str) {
24✔
579
        msg.chars().for_each(|c| buffer.push_key(c));
263✔
580
    }
24✔
581

582
    fn get_command() -> (CommandBuffer, Receiver<Event>) {
16✔
583
        let (tx, rx): (Sender<Event>, Receiver<Event>) = channel();
16✔
584
        let buffer = CommandBuffer::new(
16✔
585
            Arc::new(Mutex::new(TTSController::new(false, true))),
16✔
586
            Arc::new(Mutex::new(
16✔
587
                LuaScriptBuilder::new(tx).dimensions((100, 100)).build(),
16✔
588
            )),
589
            false,
590
        );
591
        (buffer, rx)
16✔
592
    }
16✔
593

594
    #[test]
595
    fn test_editing() {
1✔
596
        let mut buffer = get_command().0;
1✔
597

598
        push_string(&mut buffer, "test is test");
1✔
599
        assert_eq!(buffer.get_buffer(), "test is test");
1✔
600
        assert_eq!(buffer.get_pos(), 12);
1✔
601
        buffer.step_left();
1✔
602
        buffer.step_left();
1✔
603
        buffer.step_left();
1✔
604
        buffer.step_left();
1✔
605
        buffer.remove();
1✔
606
        buffer.remove();
1✔
607
        buffer.remove();
1✔
608
        buffer.remove();
1✔
609
        assert_eq!(buffer.get_buffer(), "testtest");
1✔
610
        assert_eq!(buffer.get_pos(), 4);
1✔
611
        push_string(&mut buffer, " confirm ");
1✔
612
        assert_eq!(buffer.get_buffer(), "test confirm test");
1✔
613
        assert_eq!(buffer.get_pos(), 13);
1✔
614
    }
1✔
615

616
    #[test]
617
    fn test_no_zero_index_remove_crash() {
1✔
618
        let mut buffer = get_command().0;
1✔
619
        buffer.push_key('t');
1✔
620
        buffer.step_left();
1✔
621
        assert_eq!(buffer.get_pos(), 0);
1✔
622
        buffer.remove();
1✔
623
        assert_eq!(buffer.get_pos(), 0);
1✔
624
    }
1✔
625

626
    #[test]
627
    fn test_input_navigation() {
1✔
628
        let mut buffer = get_command().0;
1✔
629
        push_string(&mut buffer, "some random words");
1✔
630
        buffer.step_word_left();
1✔
631
        assert_eq!(buffer.cursor_pos, 12);
1✔
632
        buffer.step_word_left();
1✔
633
        assert_eq!(buffer.cursor_pos, 5);
1✔
634
        buffer.step_word_left();
1✔
635
        assert_eq!(buffer.cursor_pos, 0);
1✔
636
        buffer.step_word_left();
1✔
637
        assert_eq!(buffer.cursor_pos, 0);
1✔
638
        buffer.step_word_right();
1✔
639
        assert_eq!(buffer.cursor_pos, 4);
1✔
640
        buffer.step_word_right();
1✔
641
        assert_eq!(buffer.cursor_pos, 11);
1✔
642
        buffer.step_word_right();
1✔
643
        assert_eq!(buffer.cursor_pos, 17);
1✔
644
        buffer.step_word_right();
1✔
645
        assert_eq!(buffer.cursor_pos, 17);
1✔
646
    }
1✔
647

648
    #[test]
649
    fn test_end_start_navigation() {
1✔
650
        let mut buffer = get_command().0;
1✔
651
        push_string(&mut buffer, "some random words");
1✔
652
        buffer.move_to_start();
1✔
653
        assert_eq!(buffer.cursor_pos, 0);
1✔
654
        buffer.move_to_start();
1✔
655
        assert_eq!(buffer.cursor_pos, 0);
1✔
656
        buffer.move_to_end();
1✔
657
        assert_eq!(buffer.cursor_pos, 17);
1✔
658
        buffer.move_to_end();
1✔
659
        assert_eq!(buffer.cursor_pos, 17);
1✔
660
    }
1✔
661

662
    #[test]
663
    fn test_delete_rest_of_line() {
1✔
664
        let mut buffer = get_command().0;
1✔
665
        push_string(&mut buffer, "some random words");
1✔
666
        buffer.move_to_start();
1✔
667
        buffer.step_word_right();
1✔
668
        buffer.delete_from_start();
1✔
669
        assert_eq!(buffer.get_buffer(), " random words");
1✔
670
    }
1✔
671

672
    #[test]
673
    fn test_delete_from_start_of_line() {
1✔
674
        let mut buffer = get_command().0;
1✔
675
        push_string(&mut buffer, "some random words");
1✔
676
        buffer.move_to_start();
1✔
677
        buffer.step_word_right();
1✔
678
        buffer.step_word_right();
1✔
679
        buffer.delete_to_end();
1✔
680
        assert_eq!(buffer.get_buffer(), "some random");
1✔
681
    }
1✔
682

683
    #[test]
684
    fn test_delete_right() {
1✔
685
        let mut buffer = get_command().0;
1✔
686
        push_string(&mut buffer, "some random words");
1✔
687
        buffer.move_to_start();
1✔
688
        buffer.step_word_right();
1✔
689
        buffer.delete_right();
1✔
690
        assert_eq!(buffer.get_buffer(), "somerandom words");
1✔
691
        buffer.delete_right();
1✔
692
        assert_eq!(buffer.get_buffer(), "someandom words");
1✔
693
        buffer.move_to_end();
1✔
694
        buffer.delete_right();
1✔
695
        assert_eq!(buffer.get_buffer(), "someandom words");
1✔
696
    }
1✔
697

698
    #[test]
699
    fn test_delete_word_left() {
1✔
700
        let mut buffer = get_command().0;
1✔
701
        push_string(&mut buffer, "some random words");
1✔
702
        buffer.move_to_end();
1✔
703
        buffer.delete_word_left();
1✔
704
        assert_eq!(buffer.get_buffer(), "some random ");
1✔
705
        buffer.move_to_start();
1✔
706
        buffer.step_word_right();
1✔
707
        buffer.delete_word_left();
1✔
708
        assert_eq!(buffer.get_buffer(), " random ");
1✔
709
    }
1✔
710

711
    #[test]
712
    fn test_delete_word_right() {
1✔
713
        let mut buffer = get_command().0;
1✔
714
        push_string(&mut buffer, "some random words");
1✔
715
        buffer.move_to_start();
1✔
716
        buffer.delete_word_right();
1✔
717
        assert_eq!(buffer.get_buffer(), " random words");
1✔
718
        buffer.delete_word_right();
1✔
719
        assert_eq!(buffer.get_buffer(), " words");
1✔
720
    }
1✔
721

722
    #[test]
723
    fn test_fancy_chars() {
1✔
724
        let mut buffer = get_command().0;
1✔
725
        let input = "some weird chars: ÅÖÄø æĸœ→ €ßðߪ“";
1✔
726
        push_string(&mut buffer, input);
1✔
727
        assert_eq!(input.chars().count(), buffer.buffer.len());
1✔
728
        assert_ne!(input.len(), buffer.buffer.len());
1✔
729
        assert_eq!(buffer.get_buffer().len(), input.len());
1✔
730
    }
1✔
731

732
    #[test]
733
    fn test_human_key() {
1✔
734
        use super::human_key;
735

736
        assert_eq!(human_key("alt-", '\u{7f}'), "alt-backspace");
1✔
737
        assert_eq!(human_key("ctrl-", '\u{7f}'), "ctrl-backspace");
1✔
738
        assert_eq!(human_key("alt-", '\u{1b}'), "alt-escape");
1✔
739
        assert_eq!(human_key("ctrl-", '\u{1b}'), "ctrl-escape");
1✔
740
        assert_eq!(human_key("ctrl-", 'd'), "ctrl-d");
1✔
741
        assert_eq!(human_key("f", 'x'), "fx");
1✔
742
    }
1✔
743

744
    #[test]
745
    fn test_completions() {
1✔
746
        let mut buffer = get_command().0;
1✔
747
        push_string(&mut buffer, "batman");
1✔
748
        buffer.submit();
1✔
749
        push_string(&mut buffer, "bat");
1✔
750
        buffer.tab_complete();
1✔
751
        assert_eq!(buffer.completion.options, vec!["batman".to_string()]);
1✔
752
    }
1✔
753

754
    #[test]
755
    fn test_completion_with_big_chars() {
1✔
756
        // Issue #522
757
        let mut buffer = get_command().0;
1✔
758
        push_string(&mut buffer, "fend");
1✔
759
        buffer.completion.options = vec!["fender🎸".to_string()];
1✔
760
        buffer.tab_complete();
1✔
761
        assert_eq!(buffer.completion.options, vec!["fender🎸".to_string()]);
1✔
762
        assert_eq!(buffer.buffer, vec!['f', 'e', 'n', 'd', 'e', 'r', '🎸']);
1✔
763
        assert_eq!(buffer.cursor_pos, 7);
1✔
764
    }
1✔
765

766
    #[test]
767
    fn test_pos_cursor() {
1✔
768
        let mut buffer = get_command().0;
1✔
769
        let input = "Gibson Les Paul";
1✔
770
        push_string(&mut buffer, input);
1✔
771
        assert_eq!(buffer.get_pos(), input.len());
1✔
772
        buffer.set_pos(1000);
1✔
773
        assert_eq!(buffer.get_pos(), input.len());
1✔
774
        buffer.set_pos(0);
1✔
775
        assert_eq!(buffer.get_pos(), 0);
1✔
776
        buffer.set_pos(2);
1✔
777
        assert_eq!(buffer.get_pos(), 2);
1✔
778
        buffer.clear();
1✔
779
        assert_eq!(buffer.get_pos(), 0);
1✔
780
        push_string(&mut buffer, input);
1✔
781
        assert_eq!(buffer.get_pos(), input.len());
1✔
782
    }
1✔
783

784
    #[test]
785
    fn test_lua_key_binds() {
1✔
786
        let tts = Arc::new(Mutex::new(TTSController::new(false, false)));
1✔
787

788
        let (tx, _rx): (Sender<Event>, Receiver<Event>) = channel();
1✔
789
        let script = Arc::new(Mutex::new(
1✔
790
            LuaScriptBuilder::new(tx.clone())
1✔
791
                .dimensions((100, 100))
1✔
792
                .build(),
1✔
793
        ));
794
        let mut buffer = CommandBuffer::new(tts, script.clone(), false);
1✔
795

796
        assert!(check_command_binds(
1✔
797
            Key::Alt('b'),
1✔
798
            &mut buffer,
1✔
799
            &script,
1✔
800
            &tx
1✔
801
        ));
802
        assert!(check_command_binds(
1✔
803
            Key::Alt('f'),
1✔
804
            &mut buffer,
1✔
805
            &script,
1✔
806
            &tx
1✔
807
        ));
808
        assert!(check_command_binds(
1✔
809
            Key::Alt('d'),
1✔
810
            &mut buffer,
1✔
811
            &script,
1✔
812
            &tx
1✔
813
        ));
814
        assert!(check_command_binds(
1✔
815
            Key::Alt('\u{7f}'),
1✔
816
            &mut buffer,
1✔
817
            &script,
1✔
818
            &tx
1✔
819
        ));
820
        assert!(check_command_binds(
1✔
821
            Key::Ctrl('a'),
1✔
822
            &mut buffer,
1✔
823
            &script,
1✔
824
            &tx
1✔
825
        ));
826
        assert!(check_command_binds(
1✔
827
            Key::Ctrl('b'),
1✔
828
            &mut buffer,
1✔
829
            &script,
1✔
830
            &tx
1✔
831
        ));
832
        assert!(check_command_binds(
1✔
833
            Key::Ctrl('e'),
1✔
834
            &mut buffer,
1✔
835
            &script,
1✔
836
            &tx
1✔
837
        ));
838
        assert!(check_command_binds(
1✔
839
            Key::Ctrl('f'),
1✔
840
            &mut buffer,
1✔
841
            &script,
1✔
842
            &tx
1✔
843
        ));
844
        assert!(check_command_binds(
1✔
845
            Key::Ctrl('d'),
1✔
846
            &mut buffer,
1✔
847
            &script,
1✔
848
            &tx
1✔
849
        ));
850
        assert!(check_command_binds(
1✔
851
            Key::Ctrl('h'),
1✔
852
            &mut buffer,
1✔
853
            &script,
1✔
854
            &tx
1✔
855
        ));
856
        assert!(check_command_binds(
1✔
857
            Key::Ctrl('k'),
1✔
858
            &mut buffer,
1✔
859
            &script,
1✔
860
            &tx
1✔
861
        ));
862
        assert!(check_command_binds(
1✔
863
            Key::Ctrl('u'),
1✔
864
            &mut buffer,
1✔
865
            &script,
1✔
866
            &tx
1✔
867
        ));
868

869
        assert!(check_command_binds(Key::Home, &mut buffer, &script, &tx));
1✔
870
        assert!(check_command_binds(Key::End, &mut buffer, &script, &tx));
1✔
871
        assert!(check_command_binds(Key::PageUp, &mut buffer, &script, &tx));
1✔
872
        assert!(check_command_binds(
1✔
873
            Key::PageDown,
1✔
874
            &mut buffer,
1✔
875
            &script,
1✔
876
            &tx
1✔
877
        ));
878
    }
1✔
879

880
    fn get_command_with_last() -> (CommandBuffer, Receiver<Event>) {
6✔
881
        let (tx, rx): (Sender<Event>, Receiver<Event>) = channel();
6✔
882
        let buffer = CommandBuffer::new(
6✔
883
            Arc::new(Mutex::new(TTSController::new(false, true))),
6✔
884
            Arc::new(Mutex::new(
6✔
885
                LuaScriptBuilder::new(tx).dimensions((100, 100)).build(),
6✔
886
            )),
887
            true,
888
        );
889
        (buffer, rx)
6✔
890
    }
6✔
891

892
    #[test]
893
    fn test_last_command_stored_after_submit() {
1✔
894
        let mut buffer = get_command_with_last().0;
1✔
895
        push_string(&mut buffer, "hello");
1✔
896
        buffer.submit();
1✔
897
        // buffer should be empty, last_buffer should hold the command
898
        assert!(buffer.buffer.is_empty());
1✔
899
        assert_eq!(buffer.last_buffer.iter().collect::<String>(), "hello");
1✔
900
    }
1✔
901

902
    #[test]
903
    fn test_last_command_replayed_on_empty_submit() {
1✔
904
        let mut buffer = get_command_with_last().0;
1✔
905
        push_string(&mut buffer, "hello");
1✔
906
        buffer.submit();
1✔
907
        // submit again with empty buffer — should replay last command
908
        let replayed = buffer.submit();
1✔
909
        assert_eq!(replayed, "hello");
1✔
910
    }
1✔
911

912
    #[test]
913
    fn test_last_command_updates_on_new_submit() {
1✔
914
        let mut buffer = get_command_with_last().0;
1✔
915
        push_string(&mut buffer, "first");
1✔
916
        buffer.submit();
1✔
917
        push_string(&mut buffer, "second");
1✔
918
        buffer.submit();
1✔
919
        assert_eq!(buffer.last_buffer.iter().collect::<String>(), "second");
1✔
920
    }
1✔
921

922
    #[test]
923
    fn test_last_command_disabled_clears_last_buffer() {
1✔
924
        let mut buffer = get_command_with_last().0;
1✔
925
        push_string(&mut buffer, "hello");
1✔
926
        buffer.submit();
1✔
927
        assert!(!buffer.last_buffer.is_empty());
1✔
928
        buffer.enable_last_command(false);
1✔
929
        assert!(buffer.last_buffer.is_empty());
1✔
930
        assert!(!buffer.last_command_enabled);
1✔
931
    }
1✔
932

933
    #[test]
934
    fn test_last_command_enabled_sets_flag() {
1✔
935
        let mut buffer = get_command().0;
1✔
936
        assert!(!buffer.last_command_enabled);
1✔
937
        buffer.enable_last_command(true);
1✔
938
        assert!(buffer.last_command_enabled);
1✔
939
    }
1✔
940

941
    #[test]
942
    fn test_step_right_expands_last_command() {
1✔
943
        let mut buffer = get_command_with_last().0;
1✔
944
        push_string(&mut buffer, "hello");
1✔
945
        buffer.submit();
1✔
946
        assert!(buffer.buffer.is_empty());
1✔
947
        // step_right on empty buffer should swap in last_buffer
948
        buffer.step_right();
1✔
949
        assert_eq!(buffer.get_buffer(), "hello");
1✔
950
        assert_eq!(buffer.cursor_pos, "hello".len());
1✔
951
    }
1✔
952

953
    #[test]
954
    fn test_last_command_mask_covers_full_buffer() {
1✔
955
        let mut buffer = get_command_with_last().0;
1✔
956
        push_string(&mut buffer, "hello");
1✔
957
        buffer.submit();
1✔
958
        let mask = buffer.get_last_command_mask();
1✔
959
        // mask should have entries at 0 (open) and len of last_buffer (close)
960
        assert!(mask.contains_key(&0));
1✔
961
        assert!(mask.contains_key(&(buffer.last_buffer.len() as i32)));
1✔
962
    }
1✔
963

964
    #[test]
965
    fn test_last_command_not_stored_when_disabled() {
1✔
966
        let mut buffer = get_command().0; // last_command_enabled = false
1✔
967
        push_string(&mut buffer, "hello");
1✔
968
        buffer.submit();
1✔
969
        assert!(buffer.last_buffer.is_empty());
1✔
970
        assert!(buffer.buffer.is_empty());
1✔
971
    }
1✔
972

973
    #[test]
974
    fn test_last_command_empty_submit_when_disabled() {
1✔
975
        let mut buffer = get_command().0; // last_command_enabled = false
1✔
976
        push_string(&mut buffer, "hello");
1✔
977
        buffer.submit();
1✔
978
        // empty submit should return empty string, not replay last
979
        let result = buffer.submit();
1✔
980
        assert_eq!(result, String::new());
1✔
981
    }
1✔
982
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc