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

Blightmud / Blightmud / 21505902101

30 Jan 2026 05:48AM UTC coverage: 73.891% (-0.09%) from 73.98%
21505902101

push

github

LiquidityC
Added a .cargo/config.toml

This allows for the use of binary modules,
for example those written in rust using mlua.

This is per the mlua faq to fix getting an
undefined symbol when loading a lua binary module.

6631 of 8974 relevant lines covered (73.89%)

345.25 hits per line

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

75.31
/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::thread;
9
use std::{
10
    io::stdin,
11
    sync::{mpsc::Sender, Arc, Mutex},
12
};
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) {
218✔
33
        self.options.clear();
218✔
34
        self.index = 0;
218✔
35
    }
218✔
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
    cursor_pos: usize,
51
    completion_tree: CompletionTree,
52
    completion: CompletionStepData,
53
    prompt_mask: PromptMask,
54
    script: Arc<Mutex<LuaScript>>,
55
    tts_ctrl: Arc<Mutex<TTSController>>,
56
}
57

58
impl CommandBuffer {
59
    pub fn new(tts_ctrl: Arc<Mutex<TTSController>>, script: Arc<Mutex<LuaScript>>) -> Self {
116✔
60
        let mut completion = CompletionTree::with_inclusions(&['/', '_']);
116✔
61
        completion.set_min_word_len(3);
116✔
62

63
        Self {
116✔
64
            buffer: vec![],
116✔
65
            cursor_pos: 0,
116✔
66
            completion_tree: completion,
116✔
67
            completion: CompletionStepData::default(),
116✔
68
            prompt_mask: PromptMask::new(),
116✔
69
            script,
116✔
70
            tts_ctrl,
116✔
71
        }
116✔
72
    }
116✔
73

74
    pub fn get_buffer(&mut self) -> String {
31✔
75
        self.buffer.iter().collect::<String>()
31✔
76
    }
31✔
77

78
    pub fn get_masked_buffer(&self) -> String {
×
79
        self.prompt_mask.mask_buffer(&self.buffer)
×
80
    }
×
81

82
    pub fn get_mask(&self) -> &PromptMask {
×
83
        &self.prompt_mask
×
84
    }
×
85

86
    pub fn get_pos(&self) -> usize {
27✔
87
        self.cursor_pos
27✔
88
    }
27✔
89

90
    fn submit(&mut self) -> String {
1✔
91
        // Insert history
92
        let cmd = if !self.buffer.is_empty() {
1✔
93
            let command = self.get_buffer();
1✔
94
            self.completion_tree.insert(&command);
1✔
95
            command
1✔
96
        } else {
97
            String::new()
×
98
        };
99

100
        self.buffer.clear();
1✔
101
        self.clear_mask();
1✔
102
        self.cursor_pos = 0;
1✔
103

104
        cmd
1✔
105
    }
1✔
106

107
    fn step_left(&mut self) {
10✔
108
        if self.cursor_pos > 0 {
10✔
109
            self.cursor_pos -= 1;
9✔
110
        }
9✔
111
    }
10✔
112

113
    fn step_right(&mut self) {
219✔
114
        if self.cursor_pos < self.buffer.len() {
219✔
115
            self.cursor_pos += 1;
218✔
116
        }
218✔
117
    }
219✔
118

119
    fn move_to_start(&mut self) {
8✔
120
        self.cursor_pos = 0;
8✔
121
    }
8✔
122

123
    fn move_to_end(&mut self) {
5✔
124
        self.cursor_pos = self.buffer.len();
5✔
125
    }
5✔
126

127
    fn step_word_right(&mut self) {
13✔
128
        let origin = (self.cursor_pos + 1).min(self.buffer.len());
13✔
129
        self.cursor_pos = if let Some(pos) = self.buffer[origin..].iter().position(|c| *c == ' ') {
50✔
130
            origin + pos
9✔
131
        } else {
132
            self.buffer.len()
4✔
133
        }
134
    }
13✔
135

136
    fn step_word_left(&mut self) {
8✔
137
        let origin = self.cursor_pos.max(1) - 1;
8✔
138
        self.cursor_pos = if let Some(pos) = self.buffer[0..origin].iter().rposition(|c| *c == ' ')
24✔
139
        {
140
            pos + 1
3✔
141
        } else {
142
            0
5✔
143
        }
144
    }
8✔
145

146
    fn delete_to_end(&mut self) {
2✔
147
        self.buffer.drain(self.cursor_pos..self.buffer.len());
2✔
148
        self.clear_mask();
2✔
149
    }
2✔
150

151
    fn delete_from_start(&mut self) {
2✔
152
        self.buffer.drain(0..self.cursor_pos);
2✔
153
        self.cursor_pos = 0;
2✔
154
    }
2✔
155

156
    fn delete_right(&mut self) {
4✔
157
        if self.cursor_pos < self.buffer.len() {
4✔
158
            self.buffer.remove(self.cursor_pos);
2✔
159
            self.clear_mask();
2✔
160
        }
2✔
161
    }
4✔
162

163
    fn delete_word_right(&mut self) {
3✔
164
        let origin = self.cursor_pos;
3✔
165
        self.step_word_right();
3✔
166
        if origin != self.cursor_pos {
3✔
167
            self.buffer.drain(origin..self.cursor_pos);
2✔
168
            self.clear_mask();
2✔
169
            self.cursor_pos = origin;
2✔
170
        }
2✔
171
    }
3✔
172

173
    fn delete_word_left(&mut self) {
3✔
174
        let origin = self.cursor_pos;
3✔
175
        self.step_word_left();
3✔
176
        if origin != self.cursor_pos {
3✔
177
            self.buffer.drain(self.cursor_pos..origin);
2✔
178
            self.clear_mask();
2✔
179
        }
2✔
180
    }
3✔
181

182
    fn remove(&mut self) -> Option<char> {
6✔
183
        if self.cursor_pos > 0 {
6✔
184
            let removed = if self.cursor_pos < self.buffer.len() {
4✔
185
                Some(self.buffer.remove(self.cursor_pos - 1))
4✔
186
            } else {
187
                self.buffer.pop()
×
188
            };
189
            self.clear_mask();
4✔
190
            self.step_left();
4✔
191
            removed
4✔
192
        } else {
193
            None
2✔
194
        }
195
    }
6✔
196

197
    fn push_key(&mut self, c: char) {
218✔
198
        if self.cursor_pos >= self.buffer.len() {
218✔
199
            self.buffer.push(c);
209✔
200
        } else {
209✔
201
            self.buffer.insert(self.cursor_pos, c);
9✔
202
        }
9✔
203
        self.clear_mask();
218✔
204
        self.completion.clear();
218✔
205
        self.step_right();
218✔
206
    }
218✔
207

208
    fn tab_complete(&mut self) {
2✔
209
        if self.buffer.len() > 1 {
2✔
210
            if self.completion.is_empty() {
2✔
211
                let mut completions = Completions::default();
1✔
212
                let strbuf = self.get_buffer();
1✔
213
                completions.merge(self.script.lock().unwrap().tab_complete(&strbuf));
1✔
214
                if let Some(mut options) = self.completion_tree.complete(&strbuf) {
1✔
215
                    completions.add_all(&mut options);
1✔
216
                }
1✔
217

218
                // Remove duplicates but preserve order of occurence
219
                let mut occurences: HashSet<&String> = HashSet::new();
1✔
220
                let completions = completions.iter().fold(vec![], |mut acc, word| {
1✔
221
                    if !occurences.contains(word) {
1✔
222
                        acc.push(word.clone());
1✔
223
                    }
1✔
224
                    occurences.insert(word);
1✔
225
                    acc
1✔
226
                });
1✔
227

228
                self.completion.set_options(&strbuf, completions);
1✔
229
            }
1✔
230
            if let Some(comp) = self.completion.next() {
2✔
231
                self.tts_ctrl.lock().unwrap().speak(comp, true);
2✔
232
                self.buffer = comp.chars().collect();
2✔
233
                self.clear_mask();
2✔
234
                self.cursor_pos = self.buffer.len();
2✔
235
            }
2✔
236
        }
×
237
    }
2✔
238

239
    pub fn clear(&mut self) {
1✔
240
        self.buffer.clear();
1✔
241
        self.clear_mask();
1✔
242
        self.cursor_pos = self.buffer.len();
1✔
243
    }
1✔
244

245
    pub fn set(&mut self, line: String) {
×
246
        self.buffer = line.chars().collect();
×
247
        self.clear_mask();
×
248
        self.cursor_pos = self.buffer.len();
×
249
    }
×
250

251
    pub fn set_pos(&mut self, pos: usize) {
3✔
252
        self.cursor_pos = pos.min(self.buffer.len());
3✔
253
    }
3✔
254

255
    pub fn set_mask(&mut self, mask: PromptMask) -> &PromptMask {
×
256
        self.prompt_mask += mask;
×
257
        &self.prompt_mask
×
258
    }
×
259

260
    pub fn clear_mask(&mut self) {
234✔
261
        self.prompt_mask.clear();
234✔
262
    }
234✔
263
}
264

265
fn parse_mouse_event(event: termion::event::MouseEvent, writer: &Sender<Event>) {
×
266
    use termion::event::{MouseButton, MouseEvent};
267
    match event {
×
268
        MouseEvent::Press(MouseButton::WheelUp, ..) => writer.send(Event::ScrollUp).unwrap(),
×
269
        MouseEvent::Press(MouseButton::WheelDown, ..) => writer.send(Event::ScrollDown).unwrap(),
×
270
        _ => {}
×
271
    }
272
}
×
273

274
fn parse_key_event(
×
275
    key: termion::event::Key,
×
276
    buffer: &mut CommandBuffer,
×
277
    writer: &Sender<Event>,
×
278
    tts_ctrl: &mut Arc<Mutex<TTSController>>,
×
279
    script: &mut Arc<Mutex<LuaScript>>,
×
280
) {
×
281
    match key {
×
282
        Key::Char('\n') => {
283
            let mut line = Line::from(buffer.submit());
×
284
            line.flags.source = Some("user".to_string());
×
285
            writer.send(Event::ServerInput(line)).unwrap();
×
286
            if let Ok(mut script) = script.lock() {
×
287
                script.set_prompt_content(String::new(), 0);
×
288
            }
×
289
        }
290
        Key::Char('\t') => buffer.tab_complete(),
×
291
        Key::Char(c) => {
×
292
            tts_ctrl.lock().unwrap().key_press(c);
×
293
            buffer.push_key(c);
×
294
            if let Ok(mut script) = script.lock() {
×
295
                script.set_prompt_content(buffer.get_buffer(), buffer.get_pos());
×
296
            }
×
297
        }
298
        Key::Ctrl('l') => writer.send(Event::Redraw).unwrap(),
×
299
        Key::Ctrl('c') => {
×
300
            writer.send(Event::Quit(QuitMethod::CtrlC)).unwrap();
×
301
        }
×
302

303
        // Input navigation
304
        Key::Left => buffer.step_left(),
×
305
        Key::Right => buffer.step_right(),
×
306
        Key::Backspace => {
307
            if let Some(c) = buffer.remove() {
×
308
                if let Ok(mut tts_ctrl) = tts_ctrl.lock() {
×
309
                    tts_ctrl.key_press(c);
×
310
                }
×
311
            }
×
312
            if let Ok(mut script) = script.lock() {
×
313
                script.set_prompt_content(buffer.get_buffer(), buffer.get_pos());
×
314
            }
×
315
        }
316
        Key::Delete => buffer.delete_right(),
×
317
        _ => {}
×
318
    };
319
}
×
320

321
fn check_command_binds(
16✔
322
    cmd: termion::event::Key,
16✔
323
    buffer: &mut CommandBuffer,
16✔
324
    script: &Arc<Mutex<LuaScript>>,
16✔
325
    writer: &Sender<Event>,
16✔
326
) -> bool {
16✔
327
    let mut ran = false;
16✔
328
    if let Ok(mut script) = script.lock() {
16✔
329
        ran = match cmd {
16✔
330
            Key::Ctrl(c) => script.check_bindings(&human_key("ctrl-", c)),
8✔
331
            Key::CtrlUp => script.check_bindings("ctrl-up"),
×
332
            Key::CtrlDown => script.check_bindings("ctrl-down"),
×
333
            Key::CtrlLeft => script.check_bindings("ctrl-left"),
×
334
            Key::CtrlRight => script.check_bindings("ctrl-right"),
×
335
            Key::AltUp => script.check_bindings("alt-up"),
×
336
            Key::AltDown => script.check_bindings("alt-down"),
×
337
            Key::AltLeft => script.check_bindings("alt-left"),
×
338
            Key::AltRight => script.check_bindings("alt-right"),
×
339
            Key::ShiftUp => script.check_bindings("shift-up"),
×
340
            Key::ShiftDown => script.check_bindings("shift-down"),
×
341
            Key::ShiftLeft => script.check_bindings("shift-left"),
×
342
            Key::ShiftRight => script.check_bindings("shift-right"),
×
343
            Key::Alt(c) => script.check_bindings(&human_key("alt-", c)),
4✔
344
            Key::F(n) => script.check_bindings(&format!("f{n}")),
×
345
            Key::PageUp => script.check_bindings("pageup") || script.check_bindings("page up"),
1✔
346
            Key::PageDown => {
347
                script.check_bindings("pagedown") || script.check_bindings("page down")
1✔
348
            }
349
            Key::Home => script.check_bindings("home"),
1✔
350
            Key::End => script.check_bindings("end"),
1✔
351
            Key::Up => script.check_bindings("up"),
×
352
            Key::Down => script.check_bindings("down"),
×
353
            _ => false,
×
354
        }
355
    }
×
356
    handle_script_ui_io(buffer, script, writer);
16✔
357
    ran
16✔
358
}
16✔
359

360
/// Convert a key combination to a human-readable form.
361
fn human_key(prefix: &str, c: char) -> String {
18✔
362
    let mut out = prefix.to_owned();
18✔
363
    match c {
18✔
364
        '\u{7f}' => out.push_str("backspace"),
3✔
365
        '\u{1b}' => out.push_str("escape"),
2✔
366
        _ => out.push(c),
13✔
367
    }
368
    out
18✔
369
}
18✔
370

371
fn check_escape_bindings(
×
372
    escape: &str,
×
373
    buffer: &mut CommandBuffer,
×
374
    script: &Arc<Mutex<LuaScript>>,
×
375
    writer: &Sender<Event>,
×
376
) {
×
377
    if let Ok(mut script) = script.lock() {
×
378
        if !script.check_bindings(&escape.to_lowercase()) {
×
379
            writer
×
380
                .send(Event::Info(format!("Unknown command: {escape:?}")))
×
381
                .unwrap();
×
382
        }
×
383
    }
×
384
    handle_script_ui_io(buffer, script, writer);
×
385
    writer
×
386
        .send(Event::UserInputBuffer(
×
387
            buffer.get_buffer(),
×
388
            buffer.get_pos(),
×
389
        ))
×
390
        .unwrap();
×
391
}
×
392

393
fn handle_script_ui_io(
16✔
394
    buffer: &mut CommandBuffer,
16✔
395
    script: &Arc<Mutex<LuaScript>>,
16✔
396
    writer: &Sender<Event>,
16✔
397
) {
16✔
398
    if let Ok(mut script) = script.lock() {
16✔
399
        script.get_ui_events().iter().for_each(|event| match event {
16✔
400
            UiEvent::StepLeft => buffer.step_left(),
1✔
401
            UiEvent::StepRight => buffer.step_right(),
1✔
402
            UiEvent::StepToStart => buffer.move_to_start(),
1✔
403
            UiEvent::StepToEnd => buffer.move_to_end(),
1✔
404
            UiEvent::StepWordLeft => buffer.step_word_left(),
1✔
405
            UiEvent::StepWordRight => buffer.step_word_right(),
1✔
406
            UiEvent::Remove => {
1✔
407
                buffer.remove();
1✔
408
            }
1✔
409
            UiEvent::DeleteToEnd => buffer.delete_to_end(),
1✔
410
            UiEvent::DeleteFromStart => buffer.delete_from_start(),
1✔
411
            UiEvent::DeleteWordLeft => buffer.delete_word_left(),
1✔
412
            UiEvent::DeleteWordRight => buffer.delete_word_right(),
1✔
413
            UiEvent::DeleteRight => buffer.delete_right(),
1✔
414
            UiEvent::ScrollDown => writer.send(Event::ScrollDown).unwrap(),
1✔
415
            UiEvent::ScrollUp => writer.send(Event::ScrollUp).unwrap(),
1✔
416
            UiEvent::ScrollTop => writer.send(Event::ScrollTop).unwrap(),
1✔
417
            UiEvent::ScrollBottom => writer.send(Event::ScrollBottom).unwrap(),
1✔
418
            UiEvent::Complete => buffer.tab_complete(),
×
419
            UiEvent::Unknown(_) => {}
×
420
        });
16✔
421
        script.set_prompt_content(buffer.get_buffer(), buffer.get_pos());
16✔
422
        script.get_output_lines().iter().for_each(|l| {
16✔
423
            writer.send(Event::Output(Line::from(l))).unwrap();
×
424
        });
×
425
    }
×
426
}
16✔
427

428
pub fn spawn_input_thread(session: Session) -> thread::JoinHandle<()> {
91✔
429
    thread::Builder::new()
91✔
430
        .name("input-thread".to_string())
91✔
431
        .spawn(move || {
91✔
432
            debug!("Input stream spawned");
91✔
433
            let writer = session.main_writer.clone();
91✔
434
            let mut script = session.lua_script.clone();
91✔
435
            let stdin = stdin();
91✔
436
            let buffer = session.command_buffer.clone();
91✔
437
            let mut tts_ctrl = session.tts_ctrl;
91✔
438

439
            if let Ok(mut buffer) = buffer.lock() {
91✔
440
                for server in Servers::load().keys() {
91✔
441
                    buffer.completion_tree.insert(server);
×
442
                }
×
443
                buffer
91✔
444
                    .completion_tree
91✔
445
                    .insert(include_str!("../../resources/completions.txt"));
91✔
446
            }
×
447

448
            for e in stdin.events() {
91✔
449
                match e.unwrap() {
×
450
                    termion::event::Event::Key(key) => {
×
451
                        if let Ok(mut buffer) = buffer.lock() {
×
452
                            let orig_pos = buffer.get_pos();
×
453
                            let orig_len = buffer.buffer.len();
×
454
                            let bind_ran = check_command_binds(key, &mut buffer, &script, &writer);
×
455
                            if !bind_ran {
×
456
                                parse_key_event(
×
457
                                    key,
×
458
                                    &mut buffer,
×
459
                                    &writer,
×
460
                                    &mut tts_ctrl,
×
461
                                    &mut script,
×
462
                                );
×
463
                            }
×
464
                            if orig_len == buffer.buffer.len() && orig_pos != buffer.get_pos() {
×
465
                                writer
×
466
                                    .send(Event::UserInputCursor(buffer.get_pos()))
×
467
                                    .unwrap();
×
468
                            } else if !bind_ran || orig_len != buffer.buffer.len() {
×
469
                                if let Ok(mut luascript) = script.lock() {
×
470
                                    luascript.set_prompt_mask_content(&buffer.prompt_mask);
×
471
                                    luascript
×
472
                                        .set_prompt_content(buffer.get_buffer(), buffer.get_pos());
×
473
                                }
×
474
                                writer
×
475
                                    .send(Event::UserInputBuffer(
×
476
                                        buffer.get_buffer(),
×
477
                                        buffer.get_pos(),
×
478
                                    ))
×
479
                                    .unwrap();
×
480
                            }
×
481
                        }
×
482
                    }
483
                    termion::event::Event::Mouse(event) => parse_mouse_event(event, &writer),
×
484
                    termion::event::Event::Unsupported(bytes) => {
×
485
                        if let Ok(escape) = String::from_utf8(bytes.clone()) {
×
486
                            if let Ok(mut buffer) = buffer.lock() {
×
487
                                check_escape_bindings(
×
488
                                    &escape.to_lowercase(),
×
489
                                    &mut buffer,
×
490
                                    &script,
×
491
                                    &writer,
×
492
                                );
×
493
                            }
×
494
                        } else {
×
495
                            writer
×
496
                                .send(Event::Info(format!("Unknown command: {bytes:?}")))
×
497
                                .unwrap();
×
498
                        }
×
499
                    }
500
                }
501
            }
502
            debug!("Input stream closing");
91✔
503
        })
91✔
504
        .unwrap()
91✔
505
}
91✔
506

507
#[cfg(test)]
508
mod command_test {
509

510
    use std::sync::mpsc::{channel, Receiver, Sender};
511
    use std::sync::{Arc, Mutex};
512

513
    use termion::event::Key;
514

515
    use super::check_command_binds;
516
    use super::CommandBuffer;
517
    use crate::lua::LuaScriptBuilder;
518
    use crate::tts::TTSController;
519
    use crate::Event;
520

521
    fn push_string(buffer: &mut CommandBuffer, msg: &str) {
15✔
522
        msg.chars().for_each(|c| buffer.push_key(c));
217✔
523
    }
15✔
524

525
    fn get_command() -> (CommandBuffer, Receiver<Event>) {
13✔
526
        let (tx, rx): (Sender<Event>, Receiver<Event>) = channel();
13✔
527
        let buffer = CommandBuffer::new(
13✔
528
            Arc::new(Mutex::new(TTSController::new(false, true))),
13✔
529
            Arc::new(Mutex::new(
13✔
530
                LuaScriptBuilder::new(tx).dimensions((100, 100)).build(),
13✔
531
            )),
532
        );
533
        (buffer, rx)
13✔
534
    }
13✔
535

536
    #[test]
537
    fn test_editing() {
1✔
538
        let mut buffer = get_command().0;
1✔
539

540
        push_string(&mut buffer, "test is test");
1✔
541
        assert_eq!(buffer.get_buffer(), "test is test");
1✔
542
        assert_eq!(buffer.get_pos(), 12);
1✔
543
        buffer.step_left();
1✔
544
        buffer.step_left();
1✔
545
        buffer.step_left();
1✔
546
        buffer.step_left();
1✔
547
        buffer.remove();
1✔
548
        buffer.remove();
1✔
549
        buffer.remove();
1✔
550
        buffer.remove();
1✔
551
        assert_eq!(buffer.get_buffer(), "testtest");
1✔
552
        assert_eq!(buffer.get_pos(), 4);
1✔
553
        push_string(&mut buffer, " confirm ");
1✔
554
        assert_eq!(buffer.get_buffer(), "test confirm test");
1✔
555
        assert_eq!(buffer.get_pos(), 13);
1✔
556
    }
1✔
557

558
    #[test]
559
    fn test_no_zero_index_remove_crash() {
1✔
560
        let mut buffer = get_command().0;
1✔
561
        buffer.push_key('t');
1✔
562
        buffer.step_left();
1✔
563
        assert_eq!(buffer.get_pos(), 0);
1✔
564
        buffer.remove();
1✔
565
        assert_eq!(buffer.get_pos(), 0);
1✔
566
    }
1✔
567

568
    #[test]
569
    fn test_input_navigation() {
1✔
570
        let mut buffer = get_command().0;
1✔
571
        push_string(&mut buffer, "some random words");
1✔
572
        buffer.step_word_left();
1✔
573
        assert_eq!(buffer.cursor_pos, 12);
1✔
574
        buffer.step_word_left();
1✔
575
        assert_eq!(buffer.cursor_pos, 5);
1✔
576
        buffer.step_word_left();
1✔
577
        assert_eq!(buffer.cursor_pos, 0);
1✔
578
        buffer.step_word_left();
1✔
579
        assert_eq!(buffer.cursor_pos, 0);
1✔
580
        buffer.step_word_right();
1✔
581
        assert_eq!(buffer.cursor_pos, 4);
1✔
582
        buffer.step_word_right();
1✔
583
        assert_eq!(buffer.cursor_pos, 11);
1✔
584
        buffer.step_word_right();
1✔
585
        assert_eq!(buffer.cursor_pos, 17);
1✔
586
        buffer.step_word_right();
1✔
587
        assert_eq!(buffer.cursor_pos, 17);
1✔
588
    }
1✔
589

590
    #[test]
591
    fn test_end_start_navigation() {
1✔
592
        let mut buffer = get_command().0;
1✔
593
        push_string(&mut buffer, "some random words");
1✔
594
        buffer.move_to_start();
1✔
595
        assert_eq!(buffer.cursor_pos, 0);
1✔
596
        buffer.move_to_start();
1✔
597
        assert_eq!(buffer.cursor_pos, 0);
1✔
598
        buffer.move_to_end();
1✔
599
        assert_eq!(buffer.cursor_pos, 17);
1✔
600
        buffer.move_to_end();
1✔
601
        assert_eq!(buffer.cursor_pos, 17);
1✔
602
    }
1✔
603

604
    #[test]
605
    fn test_delete_rest_of_line() {
1✔
606
        let mut buffer = get_command().0;
1✔
607
        push_string(&mut buffer, "some random words");
1✔
608
        buffer.move_to_start();
1✔
609
        buffer.step_word_right();
1✔
610
        buffer.delete_from_start();
1✔
611
        assert_eq!(buffer.get_buffer(), " random words");
1✔
612
    }
1✔
613

614
    #[test]
615
    fn test_delete_from_start_of_line() {
1✔
616
        let mut buffer = get_command().0;
1✔
617
        push_string(&mut buffer, "some random words");
1✔
618
        buffer.move_to_start();
1✔
619
        buffer.step_word_right();
1✔
620
        buffer.step_word_right();
1✔
621
        buffer.delete_to_end();
1✔
622
        assert_eq!(buffer.get_buffer(), "some random");
1✔
623
    }
1✔
624

625
    #[test]
626
    fn test_delete_right() {
1✔
627
        let mut buffer = get_command().0;
1✔
628
        push_string(&mut buffer, "some random words");
1✔
629
        buffer.move_to_start();
1✔
630
        buffer.step_word_right();
1✔
631
        buffer.delete_right();
1✔
632
        assert_eq!(buffer.get_buffer(), "somerandom words");
1✔
633
        buffer.delete_right();
1✔
634
        assert_eq!(buffer.get_buffer(), "someandom words");
1✔
635
        buffer.move_to_end();
1✔
636
        buffer.delete_right();
1✔
637
        assert_eq!(buffer.get_buffer(), "someandom words");
1✔
638
    }
1✔
639

640
    #[test]
641
    fn test_delete_word_left() {
1✔
642
        let mut buffer = get_command().0;
1✔
643
        push_string(&mut buffer, "some random words");
1✔
644
        buffer.move_to_end();
1✔
645
        buffer.delete_word_left();
1✔
646
        assert_eq!(buffer.get_buffer(), "some random ");
1✔
647
        buffer.move_to_start();
1✔
648
        buffer.step_word_right();
1✔
649
        buffer.delete_word_left();
1✔
650
        assert_eq!(buffer.get_buffer(), " random ");
1✔
651
    }
1✔
652

653
    #[test]
654
    fn test_delete_word_right() {
1✔
655
        let mut buffer = get_command().0;
1✔
656
        push_string(&mut buffer, "some random words");
1✔
657
        buffer.move_to_start();
1✔
658
        buffer.delete_word_right();
1✔
659
        assert_eq!(buffer.get_buffer(), " random words");
1✔
660
        buffer.delete_word_right();
1✔
661
        assert_eq!(buffer.get_buffer(), " words");
1✔
662
    }
1✔
663

664
    #[test]
665
    fn test_fancy_chars() {
1✔
666
        let mut buffer = get_command().0;
1✔
667
        let input = "some weird chars: ÅÖÄø æĸœ→ €ßðߪ“";
1✔
668
        push_string(&mut buffer, input);
1✔
669
        assert_eq!(input.chars().count(), buffer.buffer.len());
1✔
670
        assert_ne!(input.len(), buffer.buffer.len());
1✔
671
        assert_eq!(buffer.get_buffer().len(), input.len());
1✔
672
    }
1✔
673

674
    #[test]
675
    fn test_human_key() {
1✔
676
        use super::human_key;
677

678
        assert_eq!(human_key("alt-", '\u{7f}'), "alt-backspace");
1✔
679
        assert_eq!(human_key("ctrl-", '\u{7f}'), "ctrl-backspace");
1✔
680
        assert_eq!(human_key("alt-", '\u{1b}'), "alt-escape");
1✔
681
        assert_eq!(human_key("ctrl-", '\u{1b}'), "ctrl-escape");
1✔
682
        assert_eq!(human_key("ctrl-", 'd'), "ctrl-d");
1✔
683
        assert_eq!(human_key("f", 'x'), "fx");
1✔
684
    }
1✔
685

686
    #[test]
687
    fn test_completions() {
1✔
688
        let mut buffer = get_command().0;
1✔
689
        push_string(&mut buffer, "batman");
1✔
690
        buffer.submit();
1✔
691
        push_string(&mut buffer, "bat");
1✔
692
        buffer.tab_complete();
1✔
693
        assert_eq!(buffer.completion.options, vec!["batman".to_string()]);
1✔
694
    }
1✔
695

696
    #[test]
697
    fn test_completion_with_big_chars() {
1✔
698
        // Issue #522
699
        let mut buffer = get_command().0;
1✔
700
        push_string(&mut buffer, "fend");
1✔
701
        buffer.completion.options = vec!["fender🎸".to_string()];
1✔
702
        buffer.tab_complete();
1✔
703
        assert_eq!(buffer.completion.options, vec!["fender🎸".to_string()]);
1✔
704
        assert_eq!(buffer.buffer, vec!['f', 'e', 'n', 'd', 'e', 'r', '🎸']);
1✔
705
        assert_eq!(buffer.cursor_pos, 7);
1✔
706
    }
1✔
707

708
    #[test]
709
    fn test_pos_cursor() {
1✔
710
        let mut buffer = get_command().0;
1✔
711
        let input = "Gibson Les Paul";
1✔
712
        push_string(&mut buffer, input);
1✔
713
        assert_eq!(buffer.get_pos(), input.len());
1✔
714
        buffer.set_pos(1000);
1✔
715
        assert_eq!(buffer.get_pos(), input.len());
1✔
716
        buffer.set_pos(0);
1✔
717
        assert_eq!(buffer.get_pos(), 0);
1✔
718
        buffer.set_pos(2);
1✔
719
        assert_eq!(buffer.get_pos(), 2);
1✔
720
        buffer.clear();
1✔
721
        assert_eq!(buffer.get_pos(), 0);
1✔
722
        push_string(&mut buffer, input);
1✔
723
        assert_eq!(buffer.get_pos(), input.len());
1✔
724
    }
1✔
725

726
    #[test]
727
    fn test_lua_key_binds() {
1✔
728
        let tts = Arc::new(Mutex::new(TTSController::new(false, false)));
1✔
729

730
        let (tx, _rx): (Sender<Event>, Receiver<Event>) = channel();
1✔
731
        let script = Arc::new(Mutex::new(
1✔
732
            LuaScriptBuilder::new(tx.clone())
1✔
733
                .dimensions((100, 100))
1✔
734
                .build(),
1✔
735
        ));
736
        let mut buffer = CommandBuffer::new(tts, script.clone());
1✔
737

738
        assert!(check_command_binds(
1✔
739
            Key::Alt('b'),
1✔
740
            &mut buffer,
1✔
741
            &script,
1✔
742
            &tx
1✔
743
        ));
744
        assert!(check_command_binds(
1✔
745
            Key::Alt('f'),
1✔
746
            &mut buffer,
1✔
747
            &script,
1✔
748
            &tx
1✔
749
        ));
750
        assert!(check_command_binds(
1✔
751
            Key::Alt('d'),
1✔
752
            &mut buffer,
1✔
753
            &script,
1✔
754
            &tx
1✔
755
        ));
756
        assert!(check_command_binds(
1✔
757
            Key::Alt('\u{7f}'),
1✔
758
            &mut buffer,
1✔
759
            &script,
1✔
760
            &tx
1✔
761
        ));
762
        assert!(check_command_binds(
1✔
763
            Key::Ctrl('a'),
1✔
764
            &mut buffer,
1✔
765
            &script,
1✔
766
            &tx
1✔
767
        ));
768
        assert!(check_command_binds(
1✔
769
            Key::Ctrl('b'),
1✔
770
            &mut buffer,
1✔
771
            &script,
1✔
772
            &tx
1✔
773
        ));
774
        assert!(check_command_binds(
1✔
775
            Key::Ctrl('e'),
1✔
776
            &mut buffer,
1✔
777
            &script,
1✔
778
            &tx
1✔
779
        ));
780
        assert!(check_command_binds(
1✔
781
            Key::Ctrl('f'),
1✔
782
            &mut buffer,
1✔
783
            &script,
1✔
784
            &tx
1✔
785
        ));
786
        assert!(check_command_binds(
1✔
787
            Key::Ctrl('d'),
1✔
788
            &mut buffer,
1✔
789
            &script,
1✔
790
            &tx
1✔
791
        ));
792
        assert!(check_command_binds(
1✔
793
            Key::Ctrl('h'),
1✔
794
            &mut buffer,
1✔
795
            &script,
1✔
796
            &tx
1✔
797
        ));
798
        assert!(check_command_binds(
1✔
799
            Key::Ctrl('k'),
1✔
800
            &mut buffer,
1✔
801
            &script,
1✔
802
            &tx
1✔
803
        ));
804
        assert!(check_command_binds(
1✔
805
            Key::Ctrl('u'),
1✔
806
            &mut buffer,
1✔
807
            &script,
1✔
808
            &tx
1✔
809
        ));
810

811
        assert!(check_command_binds(Key::Home, &mut buffer, &script, &tx));
1✔
812
        assert!(check_command_binds(Key::End, &mut buffer, &script, &tx));
1✔
813
        assert!(check_command_binds(Key::PageUp, &mut buffer, &script, &tx));
1✔
814
        assert!(check_command_binds(
1✔
815
            Key::PageDown,
1✔
816
            &mut buffer,
1✔
817
            &script,
1✔
818
            &tx
1✔
819
        ));
820
    }
1✔
821
}
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