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

Blightmud / Blightmud / 18934991473

30 Oct 2025 08:49AM UTC coverage: 73.795% (-0.09%) from 73.884%
18934991473

push

github

web-flow
build(deps): bump actions/upload-artifact in /.github/workflows (#1291)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

6646 of 9006 relevant lines covered (73.8%)

343.55 hits per line

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

76.75
/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::Alt(c) => script.check_bindings(&human_key("alt-", c)),
4✔
332
            Key::F(n) => script.check_bindings(&format!("f{n}")),
×
333
            Key::PageUp => script.check_bindings("pageup") || script.check_bindings("page up"),
1✔
334
            Key::PageDown => {
335
                script.check_bindings("pagedown") || script.check_bindings("page down")
1✔
336
            }
337
            Key::Home => script.check_bindings("home"),
1✔
338
            Key::End => script.check_bindings("end"),
1✔
339
            Key::Up => script.check_bindings("up"),
×
340
            Key::Down => script.check_bindings("down"),
×
341
            _ => false,
×
342
        }
343
    }
×
344
    handle_script_ui_io(buffer, script, writer);
16✔
345
    ran
16✔
346
}
16✔
347

348
/// Convert a key combination to a human-readable form.
349
fn human_key(prefix: &str, c: char) -> String {
18✔
350
    let mut out = prefix.to_owned();
18✔
351
    match c {
18✔
352
        '\u{7f}' => out.push_str("backspace"),
3✔
353
        '\u{1b}' => out.push_str("escape"),
2✔
354
        _ => out.push(c),
13✔
355
    }
356
    out
18✔
357
}
18✔
358

359
fn check_escape_bindings(
×
360
    escape: &str,
×
361
    buffer: &mut CommandBuffer,
×
362
    script: &Arc<Mutex<LuaScript>>,
×
363
    writer: &Sender<Event>,
×
364
) {
×
365
    if let Ok(mut script) = script.lock() {
×
366
        if !script.check_bindings(&escape.to_lowercase()) {
×
367
            writer
×
368
                .send(Event::Info(format!("Unknown command: {escape:?}")))
×
369
                .unwrap();
×
370
        }
×
371
    }
×
372
    handle_script_ui_io(buffer, script, writer);
×
373
    writer
×
374
        .send(Event::UserInputBuffer(
×
375
            buffer.get_buffer(),
×
376
            buffer.get_pos(),
×
377
        ))
×
378
        .unwrap();
×
379
}
×
380

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

416
pub fn spawn_input_thread(session: Session) -> thread::JoinHandle<()> {
91✔
417
    thread::Builder::new()
91✔
418
        .name("input-thread".to_string())
91✔
419
        .spawn(move || {
91✔
420
            debug!("Input stream spawned");
91✔
421
            let writer = session.main_writer.clone();
91✔
422
            let mut script = session.lua_script.clone();
91✔
423
            let stdin = stdin();
91✔
424
            let buffer = session.command_buffer.clone();
91✔
425
            let mut tts_ctrl = session.tts_ctrl;
91✔
426

427
            if let Ok(mut buffer) = buffer.lock() {
91✔
428
                for server in Servers::load().keys() {
91✔
429
                    buffer.completion_tree.insert(server);
×
430
                }
×
431
                buffer
91✔
432
                    .completion_tree
91✔
433
                    .insert(include_str!("../../resources/completions.txt"));
91✔
434
            }
×
435

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

495
#[cfg(test)]
496
mod command_test {
497

498
    use std::sync::mpsc::{channel, Receiver, Sender};
499
    use std::sync::{Arc, Mutex};
500

501
    use termion::event::Key;
502

503
    use super::check_command_binds;
504
    use super::CommandBuffer;
505
    use crate::lua::LuaScriptBuilder;
506
    use crate::tts::TTSController;
507
    use crate::Event;
508

509
    fn push_string(buffer: &mut CommandBuffer, msg: &str) {
15✔
510
        msg.chars().for_each(|c| buffer.push_key(c));
217✔
511
    }
15✔
512

513
    fn get_command() -> (CommandBuffer, Receiver<Event>) {
13✔
514
        let (tx, rx): (Sender<Event>, Receiver<Event>) = channel();
13✔
515
        let buffer = CommandBuffer::new(
13✔
516
            Arc::new(Mutex::new(TTSController::new(false, true))),
13✔
517
            Arc::new(Mutex::new(
13✔
518
                LuaScriptBuilder::new(tx).dimensions((100, 100)).build(),
13✔
519
            )),
520
        );
521
        (buffer, rx)
13✔
522
    }
13✔
523

524
    #[test]
525
    fn test_editing() {
1✔
526
        let mut buffer = get_command().0;
1✔
527

528
        push_string(&mut buffer, "test is test");
1✔
529
        assert_eq!(buffer.get_buffer(), "test is test");
1✔
530
        assert_eq!(buffer.get_pos(), 12);
1✔
531
        buffer.step_left();
1✔
532
        buffer.step_left();
1✔
533
        buffer.step_left();
1✔
534
        buffer.step_left();
1✔
535
        buffer.remove();
1✔
536
        buffer.remove();
1✔
537
        buffer.remove();
1✔
538
        buffer.remove();
1✔
539
        assert_eq!(buffer.get_buffer(), "testtest");
1✔
540
        assert_eq!(buffer.get_pos(), 4);
1✔
541
        push_string(&mut buffer, " confirm ");
1✔
542
        assert_eq!(buffer.get_buffer(), "test confirm test");
1✔
543
        assert_eq!(buffer.get_pos(), 13);
1✔
544
    }
1✔
545

546
    #[test]
547
    fn test_no_zero_index_remove_crash() {
1✔
548
        let mut buffer = get_command().0;
1✔
549
        buffer.push_key('t');
1✔
550
        buffer.step_left();
1✔
551
        assert_eq!(buffer.get_pos(), 0);
1✔
552
        buffer.remove();
1✔
553
        assert_eq!(buffer.get_pos(), 0);
1✔
554
    }
1✔
555

556
    #[test]
557
    fn test_input_navigation() {
1✔
558
        let mut buffer = get_command().0;
1✔
559
        push_string(&mut buffer, "some random words");
1✔
560
        buffer.step_word_left();
1✔
561
        assert_eq!(buffer.cursor_pos, 12);
1✔
562
        buffer.step_word_left();
1✔
563
        assert_eq!(buffer.cursor_pos, 5);
1✔
564
        buffer.step_word_left();
1✔
565
        assert_eq!(buffer.cursor_pos, 0);
1✔
566
        buffer.step_word_left();
1✔
567
        assert_eq!(buffer.cursor_pos, 0);
1✔
568
        buffer.step_word_right();
1✔
569
        assert_eq!(buffer.cursor_pos, 4);
1✔
570
        buffer.step_word_right();
1✔
571
        assert_eq!(buffer.cursor_pos, 11);
1✔
572
        buffer.step_word_right();
1✔
573
        assert_eq!(buffer.cursor_pos, 17);
1✔
574
        buffer.step_word_right();
1✔
575
        assert_eq!(buffer.cursor_pos, 17);
1✔
576
    }
1✔
577

578
    #[test]
579
    fn test_end_start_navigation() {
1✔
580
        let mut buffer = get_command().0;
1✔
581
        push_string(&mut buffer, "some random words");
1✔
582
        buffer.move_to_start();
1✔
583
        assert_eq!(buffer.cursor_pos, 0);
1✔
584
        buffer.move_to_start();
1✔
585
        assert_eq!(buffer.cursor_pos, 0);
1✔
586
        buffer.move_to_end();
1✔
587
        assert_eq!(buffer.cursor_pos, 17);
1✔
588
        buffer.move_to_end();
1✔
589
        assert_eq!(buffer.cursor_pos, 17);
1✔
590
    }
1✔
591

592
    #[test]
593
    fn test_delete_rest_of_line() {
1✔
594
        let mut buffer = get_command().0;
1✔
595
        push_string(&mut buffer, "some random words");
1✔
596
        buffer.move_to_start();
1✔
597
        buffer.step_word_right();
1✔
598
        buffer.delete_from_start();
1✔
599
        assert_eq!(buffer.get_buffer(), " random words");
1✔
600
    }
1✔
601

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

613
    #[test]
614
    fn test_delete_right() {
1✔
615
        let mut buffer = get_command().0;
1✔
616
        push_string(&mut buffer, "some random words");
1✔
617
        buffer.move_to_start();
1✔
618
        buffer.step_word_right();
1✔
619
        buffer.delete_right();
1✔
620
        assert_eq!(buffer.get_buffer(), "somerandom words");
1✔
621
        buffer.delete_right();
1✔
622
        assert_eq!(buffer.get_buffer(), "someandom words");
1✔
623
        buffer.move_to_end();
1✔
624
        buffer.delete_right();
1✔
625
        assert_eq!(buffer.get_buffer(), "someandom words");
1✔
626
    }
1✔
627

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

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

652
    #[test]
653
    fn test_fancy_chars() {
1✔
654
        let mut buffer = get_command().0;
1✔
655
        let input = "some weird chars: ÅÖÄø æĸœ→ €ßðߪ“";
1✔
656
        push_string(&mut buffer, input);
1✔
657
        assert_eq!(input.chars().count(), buffer.buffer.len());
1✔
658
        assert_ne!(input.len(), buffer.buffer.len());
1✔
659
        assert_eq!(buffer.get_buffer().len(), input.len());
1✔
660
    }
1✔
661

662
    #[test]
663
    fn test_human_key() {
1✔
664
        use super::human_key;
665

666
        assert_eq!(human_key("alt-", '\u{7f}'), "alt-backspace");
1✔
667
        assert_eq!(human_key("ctrl-", '\u{7f}'), "ctrl-backspace");
1✔
668
        assert_eq!(human_key("alt-", '\u{1b}'), "alt-escape");
1✔
669
        assert_eq!(human_key("ctrl-", '\u{1b}'), "ctrl-escape");
1✔
670
        assert_eq!(human_key("ctrl-", 'd'), "ctrl-d");
1✔
671
        assert_eq!(human_key("f", 'x'), "fx");
1✔
672
    }
1✔
673

674
    #[test]
675
    fn test_completions() {
1✔
676
        let mut buffer = get_command().0;
1✔
677
        push_string(&mut buffer, "batman");
1✔
678
        buffer.submit();
1✔
679
        push_string(&mut buffer, "bat");
1✔
680
        buffer.tab_complete();
1✔
681
        assert_eq!(buffer.completion.options, vec!["batman".to_string()]);
1✔
682
    }
1✔
683

684
    #[test]
685
    fn test_completion_with_big_chars() {
1✔
686
        // Issue #522
687
        let mut buffer = get_command().0;
1✔
688
        push_string(&mut buffer, "fend");
1✔
689
        buffer.completion.options = vec!["fender🎸".to_string()];
1✔
690
        buffer.tab_complete();
1✔
691
        assert_eq!(buffer.completion.options, vec!["fender🎸".to_string()]);
1✔
692
        assert_eq!(buffer.buffer, vec!['f', 'e', 'n', 'd', 'e', 'r', '🎸']);
1✔
693
        assert_eq!(buffer.cursor_pos, 7);
1✔
694
    }
1✔
695

696
    #[test]
697
    fn test_pos_cursor() {
1✔
698
        let mut buffer = get_command().0;
1✔
699
        let input = "Gibson Les Paul";
1✔
700
        push_string(&mut buffer, input);
1✔
701
        assert_eq!(buffer.get_pos(), input.len());
1✔
702
        buffer.set_pos(1000);
1✔
703
        assert_eq!(buffer.get_pos(), input.len());
1✔
704
        buffer.set_pos(0);
1✔
705
        assert_eq!(buffer.get_pos(), 0);
1✔
706
        buffer.set_pos(2);
1✔
707
        assert_eq!(buffer.get_pos(), 2);
1✔
708
        buffer.clear();
1✔
709
        assert_eq!(buffer.get_pos(), 0);
1✔
710
        push_string(&mut buffer, input);
1✔
711
        assert_eq!(buffer.get_pos(), input.len());
1✔
712
    }
1✔
713

714
    #[test]
715
    fn test_lua_key_binds() {
1✔
716
        let tts = Arc::new(Mutex::new(TTSController::new(false, false)));
1✔
717

718
        let (tx, _rx): (Sender<Event>, Receiver<Event>) = channel();
1✔
719
        let script = Arc::new(Mutex::new(
1✔
720
            LuaScriptBuilder::new(tx.clone())
1✔
721
                .dimensions((100, 100))
1✔
722
                .build(),
1✔
723
        ));
724
        let mut buffer = CommandBuffer::new(tts, script.clone());
1✔
725

726
        assert!(check_command_binds(
1✔
727
            Key::Alt('b'),
1✔
728
            &mut buffer,
1✔
729
            &script,
1✔
730
            &tx
1✔
731
        ));
732
        assert!(check_command_binds(
1✔
733
            Key::Alt('f'),
1✔
734
            &mut buffer,
1✔
735
            &script,
1✔
736
            &tx
1✔
737
        ));
738
        assert!(check_command_binds(
1✔
739
            Key::Alt('d'),
1✔
740
            &mut buffer,
1✔
741
            &script,
1✔
742
            &tx
1✔
743
        ));
744
        assert!(check_command_binds(
1✔
745
            Key::Alt('\u{7f}'),
1✔
746
            &mut buffer,
1✔
747
            &script,
1✔
748
            &tx
1✔
749
        ));
750
        assert!(check_command_binds(
1✔
751
            Key::Ctrl('a'),
1✔
752
            &mut buffer,
1✔
753
            &script,
1✔
754
            &tx
1✔
755
        ));
756
        assert!(check_command_binds(
1✔
757
            Key::Ctrl('b'),
1✔
758
            &mut buffer,
1✔
759
            &script,
1✔
760
            &tx
1✔
761
        ));
762
        assert!(check_command_binds(
1✔
763
            Key::Ctrl('e'),
1✔
764
            &mut buffer,
1✔
765
            &script,
1✔
766
            &tx
1✔
767
        ));
768
        assert!(check_command_binds(
1✔
769
            Key::Ctrl('f'),
1✔
770
            &mut buffer,
1✔
771
            &script,
1✔
772
            &tx
1✔
773
        ));
774
        assert!(check_command_binds(
1✔
775
            Key::Ctrl('d'),
1✔
776
            &mut buffer,
1✔
777
            &script,
1✔
778
            &tx
1✔
779
        ));
780
        assert!(check_command_binds(
1✔
781
            Key::Ctrl('h'),
1✔
782
            &mut buffer,
1✔
783
            &script,
1✔
784
            &tx
1✔
785
        ));
786
        assert!(check_command_binds(
1✔
787
            Key::Ctrl('k'),
1✔
788
            &mut buffer,
1✔
789
            &script,
1✔
790
            &tx
1✔
791
        ));
792
        assert!(check_command_binds(
1✔
793
            Key::Ctrl('u'),
1✔
794
            &mut buffer,
1✔
795
            &script,
1✔
796
            &tx
1✔
797
        ));
798

799
        assert!(check_command_binds(Key::Home, &mut buffer, &script, &tx));
1✔
800
        assert!(check_command_binds(Key::End, &mut buffer, &script, &tx));
1✔
801
        assert!(check_command_binds(Key::PageUp, &mut buffer, &script, &tx));
1✔
802
        assert!(check_command_binds(
1✔
803
            Key::PageDown,
1✔
804
            &mut buffer,
1✔
805
            &script,
1✔
806
            &tx
1✔
807
        ));
808
    }
1✔
809
}
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

© 2025 Coveralls, Inc