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

MitMaro / git-interactive-rebase-tool / 6883077488

15 Nov 2023 09:23PM CUT coverage: 93.248% (-0.4%) from 93.64%
6883077488

Pull #873

github

web-flow
Merge 0ab516642 into d7655157f
Pull Request #873: When editing in the middle of a rebase, dont clear on quit

45 of 72 new or added lines in 14 files covered. (62.5%)

1 existing line in 1 file now uncovered.

4792 of 5139 relevant lines covered (93.25%)

3.67 hits per line

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

99.21
/src/core/src/modules/list/mod.rs
1
#[cfg(all(unix, test))]
2
mod tests;
3
mod utils;
4

5
use std::{cmp::min, sync::Arc};
6

7
use captur::capture;
8
use config::Config;
9
use display::DisplayColor;
10
use if_chain::if_chain;
11
use input::{InputOptions, MouseEventKind, StandardEvent};
12
use parking_lot::Mutex;
13
use todo_file::{Action, EditContext, Line, Search, TodoFile};
14
use view::{LineSegment, RenderContext, ViewData, ViewLine};
15

16
use self::utils::{
17
        get_list_normal_mode_help_lines,
18
        get_list_visual_mode_help_lines,
19
        get_todo_line_segments,
20
        TodoLineSegmentsOptions,
21
};
22
use crate::{
23
        components::{
24
                edit::Edit,
25
                help::Help,
26
                search_bar::{SearchBar, SearchBarAction},
27
        },
28
        events::{Event, KeyBindings, MetaEvent},
29
        module::{ExitStatus, Module, State},
30
        modules::list::utils::get_line_action_maximum_width,
31
        process::Results,
32
        select,
33
};
34

35
// TODO Remove `union` call when bitflags/bitflags#180 is resolved
36
const INPUT_OPTIONS: InputOptions = InputOptions::UNDO_REDO
37
        .union(InputOptions::RESIZE)
38
        .union(InputOptions::HELP)
39
        .union(InputOptions::SEARCH);
40

41
#[derive(Debug, PartialEq, Eq)]
42
enum ListState {
43
        Normal,
44
        Visual,
45
        Edit,
46
}
47

48
#[derive(Debug, Copy, Clone)]
49
enum CursorUpdate {
50
        Down(usize),
51
        Set(usize),
52
        Up(usize),
53
        End,
54
}
55

56
pub(crate) struct List {
57
        auto_select_next: bool,
58
        edit: Edit,
59
        height: usize,
60
        normal_mode_help: Help,
61
        search: Search,
62
        search_bar: SearchBar,
63
        selected_line_action: Option<Action>,
64
        state: ListState,
65
        todo_file: Arc<Mutex<TodoFile>>,
66
        view_data: ViewData,
67
        visual_index_start: Option<usize>,
68
        visual_mode_help: Help,
69
}
70

71
impl Module for List {
72
        fn activate(&mut self, _: State) -> Results {
1✔
73
                self.selected_line_action = self.todo_file.lock().get_selected_line().map(|line| *line.get_action());
3✔
74
                Results::new()
1✔
75
        }
76

77
        fn build_view_data(&mut self, context: &RenderContext) -> &ViewData {
1✔
78
                match self.state {
2✔
79
                        ListState::Normal => self.get_normal_mode_view_data(context),
1✔
80
                        ListState::Visual => self.get_visual_mode_view_data(context),
2✔
81
                        ListState::Edit => {
82
                                if let Some(selected_line) = self.todo_file.lock().get_selected_line() {
2✔
83
                                        if selected_line.is_editable() {
2✔
84
                                                return self.edit.build_view_data(
1✔
85
                                                        |updater| {
2✔
86
                                                                updater.push_leading_line(ViewLine::from(LineSegment::new_with_color(
1✔
87
                                                                        format!("Modifying line: {}", selected_line.to_text()).as_str(),
1✔
88
                                                                        DisplayColor::IndicatorColor,
1✔
89
                                                                )));
90
                                                                updater.push_leading_line(ViewLine::new_empty_line());
1✔
91
                                                        },
92
                                                        |_| {},
2✔
93
                                                );
94
                                        }
95
                                }
96
                                self.edit.get_view_data()
×
97
                        },
98
                }
99
        }
100

101
        fn handle_event(&mut self, event: Event, view_state: &view::State) -> Results {
1✔
102
                select!(
6✔
103
                        default || {
2✔
104
                                match self.state {
1✔
105
                                        ListState::Normal => self.handle_normal_mode_event(event, view_state),
1✔
106
                                        ListState::Visual => self.handle_visual_mode_input(event, view_state),
3✔
107
                                        ListState::Edit => self.handle_edit_mode_input(event),
1✔
108
                                }
109
                        },
110
                        || self.handle_normal_help_input(event, view_state),
3✔
111
                        || self.handle_visual_help_input(event, view_state),
3✔
112
                        || self.handle_search_input(event)
3✔
113
                )
114
        }
115

116
        fn input_options(&self) -> &InputOptions {
1✔
117
                select!(
5✔
118
                        default || &INPUT_OPTIONS,
2✔
119
                        || (self.state == ListState::Edit).then(|| self.edit.input_options()),
5✔
120
                        || self.normal_mode_help.input_options(),
3✔
121
                        || self.visual_mode_help.input_options(),
3✔
122
                        || self.search_bar.input_options()
3✔
123
                )
124
        }
125

126
        fn read_event(&self, event: Event, key_bindings: &KeyBindings) -> Event {
1✔
127
                select!(
6✔
128
                        default || self.read_event_default(event, key_bindings),
3✔
129
                        || (self.state == ListState::Edit).then_some(event),
3✔
130
                        || self.normal_mode_help.read_event(event),
3✔
131
                        || self.visual_mode_help.read_event(event),
3✔
132
                        || self.search_bar.read_event(event)
3✔
133
                )
134
        }
135
}
136

137
impl List {
138
        pub(crate) fn new(config: &Config, todo_file: Arc<Mutex<TodoFile>>) -> Self {
1✔
139
                let view_data = ViewData::new(|updater| {
2✔
140
                        updater.set_show_title(true);
1✔
141
                        updater.set_show_help(true);
1✔
142
                });
143

144
                Self {
145
                        auto_select_next: config.auto_select_next,
1✔
146
                        edit: Edit::new(),
1✔
147
                        height: 0,
148
                        normal_mode_help: Help::new_from_keybindings(&get_list_normal_mode_help_lines(&config.key_bindings)),
3✔
149
                        search: Search::new(),
1✔
150
                        search_bar: SearchBar::new(),
1✔
151
                        selected_line_action: None,
152
                        state: ListState::Normal,
153
                        todo_file,
154
                        view_data,
155
                        visual_index_start: None,
156
                        visual_mode_help: Help::new_from_keybindings(&get_list_visual_mode_help_lines(&config.key_bindings)),
3✔
157
                }
158
        }
159

160
        fn update_cursor(&mut self, cursor_update: CursorUpdate) -> usize {
2✔
161
                let mut todo_file = self.todo_file.lock();
2✔
162
                let new_selected_line_index = match cursor_update {
2✔
163
                        CursorUpdate::Down(amount) => todo_file.get_selected_line_index().saturating_add(amount),
6✔
164
                        CursorUpdate::Up(amount) => todo_file.get_selected_line_index().saturating_sub(amount),
3✔
165
                        CursorUpdate::Set(value) => value,
1✔
166
                        CursorUpdate::End => todo_file.get_max_selected_line_index(),
2✔
167
                };
168
                let selected_line_index = todo_file.set_selected_line_index(new_selected_line_index);
4✔
169
                self.selected_line_action = todo_file.get_selected_line().map(|line| *line.get_action());
6✔
170
                self.search.set_search_start_hint(selected_line_index);
2✔
171
                selected_line_index
172
        }
173

174
        #[allow(clippy::unused_self)]
175
        fn move_cursor_left(&self, view_state: &view::State) {
1✔
176
                view_state.scroll_left();
1✔
177
        }
178

179
        #[allow(clippy::unused_self)]
180
        fn move_cursor_right(&self, view_state: &view::State) {
1✔
181
                view_state.scroll_right();
1✔
182
        }
183

184
        #[allow(clippy::unused_self)]
185
        fn abort(&self, results: &mut Results) {
1✔
186
                results.state(State::ConfirmAbort);
1✔
187
        }
188

189
        #[allow(clippy::unused_self)]
190
        fn force_abort(&self, results: &mut Results) {
1✔
191
                let mut todo_file = self.todo_file.lock();
1✔
192
                todo_file.set_lines(vec![]);
2✔
193
                results.exit_status(ExitStatus::Good);
1✔
194
        }
195

196
        #[allow(clippy::unused_self)]
197
        fn rebase(&self, results: &mut Results) {
1✔
198
                results.state(State::ConfirmRebase);
1✔
199
        }
200

201
        #[allow(clippy::unused_self)]
202
        fn force_rebase(&self, results: &mut Results) {
1✔
203
                results.exit_status(ExitStatus::Good);
1✔
204
        }
205

206
        fn swap_selected_up(&mut self) {
1✔
207
                let mut todo_file = self.todo_file.lock();
1✔
208
                let start_index = todo_file.get_selected_line_index();
2✔
209
                let end_index = self.visual_index_start.unwrap_or(start_index);
1✔
210

211
                let swapped = todo_file.swap_range_up(start_index, end_index);
1✔
212
                drop(todo_file);
1✔
213

214
                if swapped {
1✔
215
                        if let Some(visual_index_start) = self.visual_index_start {
2✔
216
                                self.visual_index_start = Some(visual_index_start - 1);
2✔
217
                        }
218
                        _ = self.update_cursor(CursorUpdate::Up(1));
2✔
219
                }
220
        }
221

222
        fn swap_selected_down(&mut self) {
1✔
223
                let mut todo_file = self.todo_file.lock();
1✔
224
                let start_index = todo_file.get_selected_line_index();
2✔
225
                let end_index = self.visual_index_start.unwrap_or(start_index);
1✔
226

227
                let swapped = todo_file.swap_range_down(start_index, end_index);
1✔
228
                drop(todo_file);
1✔
229

230
                if swapped {
1✔
231
                        if let Some(visual_index_start) = self.visual_index_start {
4✔
232
                                self.visual_index_start = Some(visual_index_start + 1);
4✔
233
                        }
234
                        _ = self.update_cursor(CursorUpdate::Down(1));
2✔
235
                }
236
        }
237

238
        fn set_selected_line_action(&mut self, action: Action) {
3✔
239
                let mut todo_file = self.todo_file.lock();
3✔
240
                let start_index = todo_file.get_selected_line_index();
6✔
241
                let end_index = self.visual_index_start.unwrap_or(start_index);
3✔
242

243
                todo_file.update_range(start_index, end_index, &EditContext::new().action(action));
3✔
244
                drop(todo_file);
4✔
245

246
                if self.state == ListState::Normal && self.auto_select_next {
8✔
247
                        _ = self.update_cursor(CursorUpdate::Down(1));
1✔
248
                }
249
        }
250

251
        fn undo(&mut self) {
3✔
252
                let mut todo_file = self.todo_file.lock();
3✔
253
                let undo_result = todo_file.undo();
8✔
254
                drop(todo_file);
4✔
255

256
                if let Some((start_index, end_index)) = undo_result {
4✔
257
                        let new_start_index = self.update_cursor(CursorUpdate::Set(start_index));
8✔
258
                        if new_start_index == end_index {
8✔
259
                                self.state = ListState::Normal;
2✔
260
                                self.visual_index_start = None;
2✔
261
                        }
262
                        else {
263
                                self.state = ListState::Visual;
2✔
264
                                self.visual_index_start = Some(end_index);
2✔
265
                        }
266
                }
267
        }
268

269
        fn redo(&mut self) {
2✔
270
                let mut todo_file = self.todo_file.lock();
2✔
271
                let redo_result = todo_file.redo();
4✔
272
                drop(todo_file);
2✔
273

274
                if let Some((start_index, end_index)) = redo_result {
2✔
275
                        let new_start_index = self.update_cursor(CursorUpdate::Set(start_index));
5✔
276
                        if new_start_index == end_index {
7✔
277
                                self.state = ListState::Normal;
2✔
278
                                self.visual_index_start = None;
2✔
279
                        }
280
                        else {
281
                                self.state = ListState::Visual;
1✔
282
                                self.visual_index_start = Some(end_index);
1✔
283
                        }
284
                }
285
        }
286

287
        fn delete(&mut self) {
2✔
288
                let mut todo_file = self.todo_file.lock();
2✔
289
                let start_index = todo_file.get_selected_line_index();
4✔
290
                let end_index = self.visual_index_start.unwrap_or(start_index);
2✔
291

292
                todo_file.remove_lines(start_index, end_index);
2✔
293
                drop(todo_file);
4✔
294

295
                let new_index = min(start_index, end_index);
4✔
296
                let selected_line_index = self.update_cursor(CursorUpdate::Set(new_index));
4✔
297

298
                if self.state == ListState::Visual {
6✔
299
                        self.visual_index_start = Some(selected_line_index);
2✔
300
                }
301
        }
302

303
        #[allow(clippy::unused_self)]
304
        fn open_in_editor(&mut self, results: &mut Results) {
1✔
305
                self.search_bar.reset();
1✔
306
                results.state(State::ExternalEditor);
1✔
307
        }
308

309
        fn toggle_visual_mode(&mut self) {
3✔
310
                if self.state == ListState::Visual {
4✔
311
                        self.state = ListState::Normal;
1✔
312
                        self.visual_index_start = None;
1✔
313
                }
314
                else {
315
                        self.state = ListState::Visual;
3✔
316
                        self.visual_index_start = Some(self.todo_file.lock().get_selected_line_index());
3✔
317
                }
318
        }
319

320
        fn search_start(&mut self) {
1✔
321
                self.search_bar.start_search(None);
1✔
322
        }
323

324
        fn help(&mut self) {
1✔
325
                if self.state == ListState::Visual {
1✔
326
                        self.visual_mode_help.set_active();
1✔
327
                }
328
                else {
329
                        self.normal_mode_help.set_active();
2✔
330
                }
331
        }
332

333
        fn resize(&mut self, height: u16) {
1✔
334
                self.height = height as usize;
1✔
335
        }
336

337
        fn show_commit(&mut self, results: &mut Results) {
3✔
338
                let todo_file = self.todo_file.lock();
3✔
339
                if let Some(selected_line) = todo_file.get_selected_line() {
6✔
340
                        if selected_line.has_reference() {
4✔
341
                                results.state(State::ShowCommit);
1✔
342
                        }
343
                }
344
        }
345

346
        fn action_break(&mut self) {
1✔
347
                let mut todo_file = self.todo_file.lock();
2✔
348
                let selected_line_index = todo_file.get_selected_line_index();
4✔
349
                let next_action_is_break = todo_file
6✔
350
                        .get_line(selected_line_index + 1)
2✔
351
                        .map_or(false, |line| line.get_action() == &Action::Break);
2✔
352

353
                // no need to add an additional break when the next line is already a break
354
                if next_action_is_break {
1✔
355
                        return;
356
                }
357

358
                let selected_action_is_break = todo_file
2✔
359
                        .get_line(selected_line_index)
360
                        .map_or(false, |line| line.get_action() == &Action::Break);
2✔
361

362
                let cursor_update = if selected_action_is_break {
2✔
363
                        todo_file.remove_lines(selected_line_index, selected_line_index);
2✔
364
                        CursorUpdate::Up(1)
1✔
365
                }
366
                else {
367
                        todo_file.add_line(selected_line_index + 1, Line::new_break());
2✔
368
                        CursorUpdate::Down(1)
1✔
369
                };
370

371
                drop(todo_file);
1✔
372

373
                _ = self.update_cursor(cursor_update);
1✔
374
        }
375

376
        #[allow(clippy::unused_self)]
377
        fn toggle_option(&mut self, option: &str) {
1✔
378
                let mut todo_file = self.todo_file.lock();
1✔
379
                let selected_line_index = todo_file.get_selected_line_index();
3✔
380
                todo_file.update_range(
2✔
381
                        selected_line_index,
382
                        selected_line_index,
383
                        &EditContext::new().option(option),
2✔
384
                );
385
        }
386

387
        fn edit(&mut self) {
2✔
388
                let todo_file = self.todo_file.lock();
2✔
389
                if let Some(selected_line) = todo_file.get_selected_line() {
4✔
390
                        if selected_line.is_editable() {
4✔
391
                                self.state = ListState::Edit;
1✔
392
                                self.edit.set_content(selected_line.get_content());
1✔
393
                                self.edit.set_label(format!("{} ", selected_line.get_action()).as_str());
2✔
394
                        }
395
                }
396
        }
397

398
        #[allow(clippy::unused_self)]
399
        fn insert_line(&mut self, results: &mut Results) {
1✔
400
                results.state(State::Insert);
1✔
401
        }
402

403
        fn update_list_view_data(&mut self, context: &RenderContext) -> &ViewData {
1✔
404
                let todo_file = self.todo_file.lock();
1✔
405
                let is_visual_mode = self.state == ListState::Visual;
2✔
406
                let selected_index = todo_file.get_selected_line_index();
1✔
407
                let visual_index = self.visual_index_start.unwrap_or(selected_index);
1✔
408
                let search_view_line = self.search_bar.is_editing().then(|| self.search_bar.build_view_line());
3✔
409
                let search_results_total = self.search_bar.is_searching().then(|| self.search.total_results());
4✔
410
                let search_results_current = self.search.current_result_selected();
1✔
411
                let search_term = self.search_bar.search_value();
1✔
412
                let search_index = self.search.current_match();
1✔
413

414
                self.view_data.update_view_data(|updater| {
2✔
415
                        capture!(todo_file);
1✔
416
                        updater.clear();
1✔
417
                        if todo_file.is_empty() {
1✔
418
                                updater.push_leading_line(ViewLine::from(LineSegment::new_with_color(
2✔
419
                                        "Rebase todo file is empty",
420
                                        DisplayColor::IndicatorColor,
1✔
421
                                )));
422
                        }
423
                        else {
424
                                let maximum_action_width = get_line_action_maximum_width(&todo_file);
2✔
425
                                for (index, line) in todo_file.lines_iter().enumerate() {
2✔
426
                                        let selected_line = is_visual_mode
2✔
427
                                                && ((visual_index <= selected_index && index >= visual_index && index <= selected_index)
4✔
428
                                                        || (visual_index > selected_index && index >= selected_index && index <= visual_index));
4✔
429
                                        let mut todo_line_segment_options = TodoLineSegmentsOptions::empty();
2✔
430
                                        if selected_index == index {
1✔
431
                                                todo_line_segment_options.insert(TodoLineSegmentsOptions::CURSOR_LINE);
1✔
432
                                        }
433
                                        if selected_line {
1✔
434
                                                todo_line_segment_options.insert(TodoLineSegmentsOptions::SELECTED);
4✔
435
                                        }
436
                                        if context.is_full_width() {
2✔
437
                                                todo_line_segment_options.insert(TodoLineSegmentsOptions::FULL_WIDTH);
1✔
438
                                        }
439
                                        if search_index.map_or(false, |v| v == index) {
4✔
440
                                                todo_line_segment_options.insert(TodoLineSegmentsOptions::SEARCH_LINE);
1✔
441
                                        }
442
                                        let mut view_line = ViewLine::new_with_pinned_segments(
2✔
443
                                                get_todo_line_segments(line, search_term, todo_line_segment_options, maximum_action_width),
2✔
444
                                                if line.has_reference() { 2 } else { 3 },
2✔
445
                                        )
446
                                        .set_selected(selected_index == index || selected_line);
1✔
447

448
                                        if selected_index == index || selected_line {
2✔
449
                                                view_line = view_line.set_selected(true).set_padding(' ');
2✔
450
                                        }
451

452
                                        updater.push_line(view_line);
2✔
453
                                }
454
                                if let Some(search) = search_view_line {
2✔
455
                                        updater.push_trailing_line(search);
2✔
456
                                }
457
                                else if let Some(s_term) = search_term {
3✔
458
                                        let mut search_line_segments = vec![];
1✔
459
                                        search_line_segments.push(LineSegment::new(format!("[{s_term}]: ").as_str()));
2✔
460
                                        if_chain! {
461
                                                if let Some(s_total) = search_results_total;
1✔
462
                                                if let Some(s_index) = search_results_current;
2✔
463
                                                if s_total != 0;
1✔
464
                                                then {
465
                                                        search_line_segments.push(LineSegment::new(format!("{}/{s_total}", s_index + 1).as_str()));
3✔
466
                                                }
467
                                                else {
468
                                                        search_line_segments.push(LineSegment::new("No Results"));
2✔
469
                                                }
470
                                        }
471
                                        updater.push_trailing_line(ViewLine::from(search_line_segments));
4✔
472
                                }
473
                        }
474
                        if visual_index != selected_index {
1✔
475
                                updater.ensure_line_visible(visual_index);
4✔
476
                        }
477
                        updater.ensure_line_visible(selected_index);
1✔
478
                });
479
                &self.view_data
1✔
480
        }
481

482
        fn get_visual_mode_view_data(&mut self, context: &RenderContext) -> &ViewData {
2✔
483
                if self.visual_mode_help.is_active() {
6✔
484
                        self.visual_mode_help.get_view_data()
1✔
485
                }
486
                else {
487
                        self.update_list_view_data(context)
2✔
488
                }
489
        }
490

491
        fn get_normal_mode_view_data(&mut self, context: &RenderContext) -> &ViewData {
1✔
492
                if self.normal_mode_help.is_active() {
2✔
493
                        self.normal_mode_help.get_view_data()
1✔
494
                }
495
                else {
496
                        self.update_list_view_data(context)
1✔
497
                }
498
        }
499

500
        #[allow(clippy::cognitive_complexity)]
501
        fn read_event_default(&self, event: Event, key_bindings: &KeyBindings) -> Event {
1✔
502
                // handle action level events
503
                if let Some(action) = self.selected_line_action {
1✔
504
                        if action == Action::Fixup {
1✔
505
                                match event {
506
                                        e if key_bindings.custom.fixup_keep_message.contains(&e) => {
2✔
507
                                                return Event::from(MetaEvent::FixupKeepMessage);
1✔
508
                                        },
509
                                        e if key_bindings.custom.fixup_keep_message_with_editor.contains(&e) => {
3✔
510
                                                return Event::from(MetaEvent::FixupKeepMessageWithEditor);
1✔
511
                                        },
512
                                        _ => {},
513
                                }
514
                        }
515
                }
516

517
                match event {
1✔
518
                        e if key_bindings.custom.abort.contains(&e) => Event::from(MetaEvent::Abort),
2✔
519
                        e if key_bindings.custom.action_break.contains(&e) => Event::from(MetaEvent::ActionBreak),
2✔
520
                        e if key_bindings.custom.action_cut.contains(&e) => Event::from(MetaEvent::ActionCut),
2✔
521
                        e if key_bindings.custom.action_drop.contains(&e) => Event::from(MetaEvent::ActionDrop),
2✔
522
                        e if key_bindings.custom.action_edit.contains(&e) => Event::from(MetaEvent::ActionEdit),
2✔
523
                        e if key_bindings.custom.action_fixup.contains(&e) => Event::from(MetaEvent::ActionFixup),
2✔
524
                        e if key_bindings.custom.action_index.contains(&e) => Event::from(MetaEvent::ActionIndex),
2✔
525
                        e if key_bindings.custom.action_pick.contains(&e) => Event::from(MetaEvent::ActionPick),
2✔
526
                        e if key_bindings.custom.action_reword.contains(&e) => Event::from(MetaEvent::ActionReword),
2✔
527
                        e if key_bindings.custom.action_squash.contains(&e) => Event::from(MetaEvent::ActionSquash),
2✔
528
                        e if key_bindings.custom.edit.contains(&e) => Event::from(MetaEvent::Edit),
2✔
529
                        e if key_bindings.custom.force_abort.contains(&e) => Event::from(MetaEvent::ForceAbort),
2✔
530
                        e if key_bindings.custom.force_rebase.contains(&e) => Event::from(MetaEvent::ForceRebase),
2✔
531
                        e if key_bindings.custom.insert_line.contains(&e) => Event::from(MetaEvent::InsertLine),
2✔
532
                        e if key_bindings.custom.move_down.contains(&e) => Event::from(MetaEvent::MoveCursorDown),
3✔
533
                        e if key_bindings.custom.move_down_step.contains(&e) => Event::from(MetaEvent::MoveCursorPageDown),
2✔
534
                        e if key_bindings.custom.move_end.contains(&e) => Event::from(MetaEvent::MoveCursorEnd),
3✔
535
                        e if key_bindings.custom.move_home.contains(&e) => Event::from(MetaEvent::MoveCursorHome),
2✔
536
                        e if key_bindings.custom.move_left.contains(&e) => Event::from(MetaEvent::MoveCursorLeft),
3✔
537
                        e if key_bindings.custom.move_right.contains(&e) => Event::from(MetaEvent::MoveCursorRight),
2✔
538
                        e if key_bindings.custom.move_selection_down.contains(&e) => Event::from(MetaEvent::SwapSelectedDown),
3✔
539
                        e if key_bindings.custom.move_selection_up.contains(&e) => Event::from(MetaEvent::SwapSelectedUp),
2✔
540
                        e if key_bindings.custom.move_up.contains(&e) => Event::from(MetaEvent::MoveCursorUp),
3✔
541
                        e if key_bindings.custom.move_up_step.contains(&e) => Event::from(MetaEvent::MoveCursorPageUp),
2✔
542
                        e if key_bindings.custom.open_in_external_editor.contains(&e) => Event::from(MetaEvent::OpenInEditor),
3✔
543
                        e if key_bindings.custom.rebase.contains(&e) => Event::from(MetaEvent::Rebase),
2✔
544
                        e if key_bindings.custom.remove_line.contains(&e) => Event::from(MetaEvent::Delete),
3✔
545
                        e if key_bindings.custom.show_commit.contains(&e) => Event::from(MetaEvent::ShowCommit),
2✔
546
                        e if key_bindings.custom.toggle_visual_mode.contains(&e) => Event::from(MetaEvent::ToggleVisualMode),
3✔
547
                        Event::Mouse(mouse_event) => {
1✔
548
                                match mouse_event.kind {
1✔
549
                                        MouseEventKind::ScrollDown => Event::from(MetaEvent::MoveCursorDown),
1✔
550
                                        MouseEventKind::ScrollUp => Event::from(MetaEvent::MoveCursorUp),
1✔
551
                                        _ => event,
1✔
552
                                }
553
                        },
554
                        _ => event,
2✔
555
                }
556
        }
557

558
        fn handle_normal_help_input(&mut self, event: Event, view_state: &view::State) -> Option<Results> {
1✔
559
                self.normal_mode_help.is_active().then(|| {
2✔
560
                        self.normal_mode_help.handle_event(event, view_state);
1✔
561
                        Results::new()
1✔
562
                })
563
        }
564

565
        fn handle_visual_help_input(&mut self, event: Event, view_state: &view::State) -> Option<Results> {
1✔
566
                self.visual_mode_help.is_active().then(|| {
2✔
567
                        self.visual_mode_help.handle_event(event, view_state);
1✔
568
                        Results::new()
1✔
569
                })
570
        }
571

572
        fn handle_search_input(&mut self, event: Event) -> Option<Results> {
1✔
573
                if self.search_bar.is_active() {
1✔
574
                        let todo_file = self.todo_file.lock();
1✔
575
                        match self.search_bar.handle_event(event) {
2✔
576
                                SearchBarAction::Start(term) => {
1✔
577
                                        if term.is_empty() {
2✔
578
                                                self.search.cancel();
1✔
579
                                                self.search_bar.reset();
1✔
580
                                        }
581
                                        else {
582
                                                self.search.next(&todo_file, term.as_str());
2✔
583
                                        }
584
                                },
585
                                SearchBarAction::Next(term) => self.search.next(&todo_file, term.as_str()),
1✔
586
                                SearchBarAction::Previous(term) => self.search.previous(&todo_file, term.as_str()),
2✔
587
                                SearchBarAction::Cancel => {
588
                                        self.search.cancel();
1✔
589
                                        return Some(Results::from(event));
1✔
590
                                },
591
                                SearchBarAction::None | SearchBarAction::Update(_) => return None,
1✔
592
                        }
593
                        drop(todo_file);
1✔
594

595
                        if let Some(selected) = self.search.current_match() {
1✔
596
                                _ = self.update_cursor(CursorUpdate::Set(selected));
3✔
597
                        }
598
                        return Some(Results::from(event));
2✔
599
                }
600
                None
1✔
601
        }
602

603
        #[allow(clippy::integer_division)]
604
        fn handle_common_list_input(&mut self, event: Event, view_state: &view::State) -> Option<Results> {
1✔
605
                let mut results = Results::new();
1✔
606
                match event {
1✔
607
                        Event::MetaEvent(meta_event) => {
1✔
608
                                match meta_event {
1✔
609
                                        MetaEvent::Abort => self.abort(&mut results),
2✔
NEW
610
                                        MetaEvent::ActionCut => self.set_selected_line_action(Action::Cut),
×
611
                                        MetaEvent::ActionDrop => self.set_selected_line_action(Action::Drop),
2✔
612
                                        MetaEvent::ActionEdit => self.set_selected_line_action(Action::Edit),
2✔
613
                                        MetaEvent::ActionFixup => self.set_selected_line_action(Action::Fixup),
2✔
NEW
614
                                        MetaEvent::ActionIndex => self.set_selected_line_action(Action::Index),
×
615
                                        MetaEvent::ActionPick => self.set_selected_line_action(Action::Pick),
2✔
616
                                        MetaEvent::ActionReword => self.set_selected_line_action(Action::Reword),
2✔
617
                                        MetaEvent::ActionSquash => self.set_selected_line_action(Action::Squash),
2✔
618
                                        MetaEvent::Delete => self.delete(),
4✔
619
                                        MetaEvent::ForceAbort => self.force_abort(&mut results),
2✔
620
                                        MetaEvent::ForceRebase => self.force_rebase(&mut results),
2✔
621
                                        MetaEvent::MoveCursorDown => {
622
                                                _ = self.update_cursor(CursorUpdate::Down(1));
4✔
623
                                        },
624
                                        MetaEvent::MoveCursorEnd => {
625
                                                _ = self.update_cursor(CursorUpdate::End);
2✔
626
                                        },
627
                                        MetaEvent::MoveCursorHome => {
628
                                                _ = self.update_cursor(CursorUpdate::Set(0));
2✔
629
                                        },
630
                                        MetaEvent::MoveCursorLeft => self.move_cursor_left(view_state),
2✔
631
                                        MetaEvent::MoveCursorPageDown => {
632
                                                _ = self.update_cursor(CursorUpdate::Down(self.height / 2));
6✔
633
                                        },
634
                                        MetaEvent::MoveCursorPageUp => {
635
                                                _ = self.update_cursor(CursorUpdate::Up(self.height / 2));
3✔
636
                                        },
637
                                        MetaEvent::MoveCursorRight => self.move_cursor_right(view_state),
2✔
638
                                        MetaEvent::MoveCursorUp => {
639
                                                _ = self.update_cursor(CursorUpdate::Up(1));
2✔
640
                                        },
641
                                        MetaEvent::OpenInEditor => self.open_in_editor(&mut results),
2✔
642
                                        MetaEvent::Rebase => self.rebase(&mut results),
2✔
643
                                        MetaEvent::SwapSelectedDown => self.swap_selected_down(),
2✔
644
                                        MetaEvent::SwapSelectedUp => self.swap_selected_up(),
2✔
645
                                        MetaEvent::ToggleVisualMode => self.toggle_visual_mode(),
6✔
646
                                        _ => return None,
1✔
647
                                }
648
                        },
649
                        Event::Standard(standard_event) => {
1✔
650
                                match standard_event {
1✔
651
                                        StandardEvent::Help => self.help(),
4✔
652
                                        StandardEvent::Redo => self.redo(),
6✔
653
                                        StandardEvent::Undo => self.undo(),
7✔
654
                                        StandardEvent::SearchStart => self.search_start(),
2✔
655
                                        _ => return None,
1✔
656
                                }
657
                        },
658
                        Event::Resize(_, height) => self.resize(height),
2✔
659
                        _ => {},
660
                }
661

662
                Some(results)
1✔
663
        }
664

665
        fn handle_normal_mode_event(&mut self, event: Event, view_state: &view::State) -> Results {
1✔
666
                if let Some(results) = self.handle_common_list_input(event, view_state) {
3✔
667
                        results
1✔
668
                }
669
                else {
670
                        let mut results = Results::new();
1✔
671
                        if let Event::MetaEvent(meta_event) = event {
1✔
672
                                match meta_event {
1✔
673
                                        MetaEvent::ActionBreak => self.action_break(),
3✔
674
                                        MetaEvent::Edit => self.edit(),
3✔
675
                                        MetaEvent::InsertLine => self.insert_line(&mut results),
2✔
676
                                        MetaEvent::ShowCommit => self.show_commit(&mut results),
4✔
677
                                        MetaEvent::FixupKeepMessage => self.toggle_option("-C"),
2✔
678
                                        MetaEvent::FixupKeepMessageWithEditor => self.toggle_option("-c"),
2✔
679
                                        _ => {},
680
                                }
681
                        }
682
                        results
1✔
683
                }
684
        }
685

686
        fn handle_visual_mode_input(&mut self, event: Event, view_state: &view::State) -> Results {
3✔
687
                self.handle_common_list_input(event, view_state)
3✔
688
                        .unwrap_or_else(Results::new)
689
        }
690

691
        fn handle_edit_mode_input(&mut self, event: Event) -> Results {
1✔
692
                self.edit.handle_event(event);
1✔
693
                if self.edit.is_finished() {
1✔
694
                        let mut todo_file = self.todo_file.lock();
1✔
695
                        let selected_index = todo_file.get_selected_line_index();
2✔
696
                        todo_file.update_range(
2✔
697
                                selected_index,
698
                                selected_index,
699
                                &EditContext::new().content(self.edit.get_content()),
1✔
700
                        );
701
                        self.visual_index_start = None;
1✔
702
                        self.state = ListState::Normal;
1✔
703
                }
704
                Results::new()
1✔
705
        }
706
}
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