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

MitMaro / git-interactive-rebase-tool / 14340401715

08 Apr 2025 06:00PM UTC coverage: 95.488% (-1.9%) from 97.339%
14340401715

Pull #959

github

web-flow
Merge 755a26246 into aa2157af9
Pull Request #959: WIP: Move Diff to Thread

372 of 483 new or added lines in 31 files covered. (77.02%)

4 existing lines in 2 files now uncovered.

4741 of 4965 relevant lines covered (95.49%)

2.74 hits per line

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

88.02
/src/process.rs
1
mod artifact;
2
mod results;
3
#[cfg(test)]
4
mod tests;
5
mod thread;
6

7
use std::{
8
        io::ErrorKind,
9
        process::Command,
10
        sync::{
11
                Arc,
12
                atomic::{AtomicBool, Ordering},
13
        },
14
};
15

16
use anyhow::{Error, Result, anyhow};
17
use parking_lot::Mutex;
18

19
pub(crate) use self::{artifact::Artifact, results::Results, thread::Thread};
20
use crate::{
21
        application::AppData,
22
        display::Size,
23
        input::{Event, StandardEvent},
24
        module::{self, ExitStatus, ModuleHandler, State},
25
        runtime::ThreadStatuses,
26
        search::{self, Action, Searchable},
27
        todo_file::TodoFile,
28
        view::RenderContext,
29
};
30

31
pub(crate) struct Process<ModuleProvider: module::ModuleProvider> {
32
        ended: Arc<AtomicBool>,
33
        exit_status: Arc<Mutex<ExitStatus>>,
34
        input_state: crate::input::State,
35
        module_handler: Arc<Mutex<ModuleHandler<ModuleProvider>>>,
36
        paused: Arc<AtomicBool>,
37
        render_context: Arc<Mutex<RenderContext>>,
38
        state: Arc<Mutex<State>>,
39
        thread_statuses: ThreadStatuses,
40
        todo_file: Arc<Mutex<TodoFile>>,
41
        view_state: crate::view::State,
42
        diff_state: crate::diff::State,
43
        search_state: search::State,
44
}
45

46
impl<ModuleProvider: module::ModuleProvider> Clone for Process<ModuleProvider> {
47
        fn clone(&self) -> Self {
4✔
48
                Self {
49
                        ended: Arc::clone(&self.ended),
4✔
50
                        exit_status: Arc::clone(&self.exit_status),
8✔
51
                        input_state: self.input_state.clone(),
4✔
52
                        module_handler: Arc::clone(&self.module_handler),
8✔
53
                        paused: Arc::clone(&self.paused),
8✔
54
                        render_context: Arc::clone(&self.render_context),
8✔
55
                        state: Arc::clone(&self.state),
8✔
56
                        thread_statuses: self.thread_statuses.clone(),
8✔
57
                        todo_file: Arc::clone(&self.todo_file),
8✔
58
                        view_state: self.view_state.clone(),
4✔
59
                        diff_state: self.diff_state.clone(),
4✔
60
                        search_state: self.search_state.clone(),
4✔
61
                }
62
        }
63
}
64

65
impl<ModuleProvider: module::ModuleProvider> Process<ModuleProvider> {
66
        pub(crate) fn new(
7✔
67
                app_data: &AppData,
68
                initial_display_size: Size,
69
                module_handler: ModuleHandler<ModuleProvider>,
70
                thread_statuses: ThreadStatuses,
71
        ) -> Self {
72
                Self {
73
                        ended: Arc::new(AtomicBool::from(false)),
16✔
74
                        exit_status: Arc::new(Mutex::new(ExitStatus::None)),
16✔
75
                        input_state: app_data.input_state(),
8✔
76
                        module_handler: Arc::new(Mutex::new(module_handler)),
16✔
77
                        paused: Arc::new(AtomicBool::from(false)),
16✔
78
                        render_context: Arc::new(Mutex::new(RenderContext::new(
8✔
79
                                initial_display_size.width(),
80
                                initial_display_size.height(),
81
                        ))),
82
                        search_state: app_data.search_state(),
8✔
83
                        state: app_data.active_module(),
16✔
84
                        thread_statuses,
85
                        todo_file: app_data.todo_file(),
16✔
86
                        view_state: app_data.view_state(),
8✔
87
                        diff_state: app_data.diff_state(),
8✔
88
                }
89
        }
90

91
        pub(crate) fn is_ended(&self) -> bool {
5✔
92
                self.ended.load(Ordering::Acquire)
5✔
93
        }
94

95
        /// Permanently End the event read thread.
96
        pub(crate) fn end(&self) {
2✔
97
                self.ended.store(true, Ordering::Release);
2✔
98
        }
99

100
        pub(crate) fn state(&self) -> State {
6✔
101
                *self.state.lock()
13✔
102
        }
103

104
        pub(crate) fn set_state(&self, state: State) {
6✔
105
                *self.state.lock() = state;
6✔
106
        }
107

108
        pub(crate) fn exit_status(&self) -> ExitStatus {
6✔
109
                *self.exit_status.lock()
12✔
110
        }
111

112
        pub(crate) fn set_exit_status(&self, exit_status: ExitStatus) {
6✔
113
                *self.exit_status.lock() = exit_status;
6✔
114
        }
115

116
        pub(crate) fn should_exit(&self) -> bool {
5✔
117
                self.exit_status() != ExitStatus::None || self.is_ended()
5✔
118
        }
119

120
        pub(crate) fn is_exit_status_kill(&self) -> bool {
5✔
121
                self.exit_status() == ExitStatus::Kill
5✔
122
        }
123

124
        fn activate(&self, previous_state: State) -> Results {
6✔
125
                let mut module_handler = self.module_handler.lock();
6✔
126
                let mut results = module_handler.activate(self.state(), previous_state);
12✔
127
                // always trigger a resize on activate, for modules that track size
128
                results.enqueue_resize();
6✔
129
                results
8✔
130
        }
131

132
        pub(crate) fn render(&self) {
5✔
133
                let render_context = *self.render_context.lock();
5✔
134
                let mut module_handler = self.module_handler.lock();
5✔
135
                let view_data = module_handler.build_view_data(self.state(), &render_context);
10✔
136
                // TODO It is not possible for this to fail. crate::view::State should be updated to not return an error
137
                self.view_state.render(view_data);
5✔
138
        }
139

140
        pub(crate) fn write_todo_file(&self) -> Result<()> {
4✔
141
                self.todo_file.lock().write_file().map_err(Error::from)
8✔
142
        }
143

144
        fn deactivate(&self, state: State) -> Results {
6✔
145
                let mut module_handler = self.module_handler.lock();
6✔
146
                module_handler.deactivate(state)
12✔
147
        }
148

149
        pub(crate) fn handle_event(&self) -> Option<Results> {
6✔
150
                self.module_handler
12✔
151
                        .lock()
152
                        .handle_event(self.state(), self.input_state.read_event())
6✔
153
        }
154

155
        fn handle_event_artifact(&self, event: Event) -> Results {
6✔
156
                let mut results = Results::new();
6✔
157
                match event {
9✔
158
                        Event::Standard(StandardEvent::Exit) => {
×
159
                                results.exit_status(ExitStatus::Abort);
2✔
160
                        },
161
                        Event::Standard(StandardEvent::Kill) => {
×
162
                                results.exit_status(ExitStatus::Kill);
6✔
163
                        },
164
                        Event::Resize(width, height) => {
2✔
165
                                self.view_state.resize(width, height);
2✔
166

167
                                let mut render_context = self.render_context.lock();
2✔
168
                                render_context.update(width, height);
5✔
169
                                if self.state() != State::WindowSizeError && render_context.is_window_too_small() {
5✔
170
                                        results.state(State::WindowSizeError);
1✔
171
                                }
172
                        },
173
                        _ => {},
×
174
                }
175
                results
6✔
176
        }
177

178
        fn handle_state(&self, state: State) -> Results {
6✔
179
                let mut results = Results::new();
6✔
180
                let previous_state = self.state();
12✔
181
                if previous_state != state {
6✔
182
                        self.set_state(state);
6✔
183
                        results.append(self.deactivate(previous_state));
6✔
184
                        results.append(self.activate(previous_state));
6✔
185
                }
186
                results
6✔
187
        }
188

189
        fn handle_error(&self, error: &Error, previous_state: Option<State>) -> Results {
1✔
190
                let mut results = Results::new();
2✔
191
                let return_state = previous_state.unwrap_or_else(|| self.state());
5✔
192
                self.set_state(State::Error);
1✔
193
                results.append(self.activate(return_state));
2✔
194
                let mut module_handler = self.module_handler.lock();
2✔
195
                results.append(module_handler.error(State::Error, error));
4✔
196
                results
2✔
197
        }
198

199
        fn handle_exit_status(&self, exit_status: ExitStatus) -> Results {
6✔
200
                self.set_exit_status(exit_status);
6✔
201
                Results::new()
6✔
202
        }
203

204
        #[expect(clippy::cast_possible_truncation, reason = "Resize events are safe to cast to u16")]
205
        fn handle_enqueue_resize(&self) -> Results {
6✔
206
                let render_context = self.render_context.lock();
6✔
207
                self.input_state.enqueue_event(Event::Resize(
6✔
208
                        render_context.width() as u16,
12✔
209
                        render_context.height() as u16,
6✔
210
                ));
211
                Results::new()
6✔
212
        }
213

214
        fn handle_external_command(&self, external_command: &(String, Vec<String>)) -> Results {
1✔
215
                let mut results = Results::new();
1✔
216

217
                match self.run_command(external_command) {
2✔
218
                        Ok(meta_event) => {
1✔
219
                                self.input_state.enqueue_event(Event::from(meta_event));
2✔
220
                        },
221
                        Err(err) => {
1✔
222
                                results.error_with_return(
1✔
223
                                        err.context(format!(
2✔
224
                                                "Unable to run {} {}",
×
225
                                                external_command.0,
×
226
                                                external_command.1.join(" ")
1✔
227
                                        )),
228
                                        State::List,
1✔
229
                                );
230
                        },
231
                }
232
                results
1✔
233
        }
234

235
        fn run_command(&self, external_command: &(String, Vec<String>)) -> Result<StandardEvent> {
1✔
236
                self.view_state.stop();
1✔
237
                self.input_state.pause();
1✔
238

239
                self.thread_statuses
1✔
240
                        .wait_for_status(crate::view::REFRESH_THREAD_NAME, &crate::runtime::Status::Waiting)?;
×
241
                self.thread_statuses
1✔
242
                        .wait_for_status(crate::input::THREAD_NAME, &crate::runtime::Status::Waiting)?;
×
243

244
                let mut cmd = Command::new(external_command.0.clone());
1✔
245
                _ = cmd.args(external_command.1.clone());
2✔
246

247
                let result = cmd
1✔
248
                        .status()
249
                        .map(|status| {
1✔
250
                                if status.success() {
2✔
251
                                        StandardEvent::ExternalCommandSuccess
1✔
252
                                }
253
                                else {
×
254
                                        StandardEvent::ExternalCommandError
1✔
255
                                }
256
                        })
257
                        .map_err(|err| {
1✔
258
                                match err.kind() {
2✔
259
                                        ErrorKind::NotFound => {
×
260
                                                anyhow!("File does not exist: {}", external_command.0)
2✔
261
                                        },
262
                                        ErrorKind::PermissionDenied => {
×
263
                                                anyhow!("File not executable: {}", external_command.0)
2✔
264
                                        },
265
                                        _ => Error::from(err),
×
266
                                }
267
                        });
268

269
                self.input_state.resume();
1✔
270
                self.view_state.start();
1✔
271
                result
1✔
272
        }
273

274
        fn handle_search_cancel(&self) -> Results {
1✔
275
                self.search_state.send_update(Action::Cancel);
1✔
276
                Results::new()
1✔
277
        }
278

279
        fn handle_search_term(&self, term: String) -> Results {
1✔
280
                self.search_state.send_update(Action::Start(term));
1✔
281
                Results::new()
1✔
282
        }
283

284
        fn handle_searchable(&self, searchable: Box<dyn Searchable>) -> Results {
2✔
285
                self.search_state.send_update(Action::SetSearchable(searchable));
2✔
286
                Results::new()
2✔
287
        }
288

NEW
289
        fn handle_diff_load(&self, hash: String) -> Results {
×
NEW
290
                self.diff_state.cancel();
×
NEW
291
                self.diff_state.send_update(crate::diff::Action::Load(hash));
×
NEW
292
                Results::new()
×
293
        }
294

NEW
295
        fn handle_diff_cancel(&self) -> Results {
×
NEW
296
                self.diff_state.cancel();
×
NEW
297
                Results::new()
×
298
        }
299

300
        fn handle_results(&self, mut results: Results) {
6✔
301
                while let Some(artifact) = results.artifact() {
18✔
302
                        results.append(match artifact {
12✔
303
                                Artifact::ChangeState(state) => self.handle_state(state),
12✔
304
                                Artifact::EnqueueResize => self.handle_enqueue_resize(),
12✔
305
                                Artifact::Error(err, previous_state) => self.handle_error(&err, previous_state),
1✔
306
                                Artifact::Event(event) => self.handle_event_artifact(event),
12✔
307
                                Artifact::ExitStatus(exit_status) => self.handle_exit_status(exit_status),
12✔
308
                                Artifact::ExternalCommand(command) => self.handle_external_command(&command),
1✔
309
                                Artifact::SearchCancel => self.handle_search_cancel(),
2✔
310
                                Artifact::SearchTerm(search_term) => self.handle_search_term(search_term),
2✔
311
                                Artifact::Searchable(searchable) => self.handle_searchable(searchable),
4✔
NEW
312
                                Artifact::LoadDiff(hash) => self.handle_diff_load(hash),
×
NEW
313
                                Artifact::CancelDiff => self.handle_diff_cancel(),
×
314
                        });
315
                }
316
        }
317
}
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