1use std::borrow::Cow;
2use std::cell::RefCell;
3use std::collections::BTreeMap;
4use std::ffi::OsStr;
5use std::io::Read;
6use std::io::Write;
7use std::ops::DerefMut;
8use std::process::Child;
9use std::process::Command;
10use std::process::Stdio;
11use std::rc::Rc;
12use std::sync::Arc;
13use std::sync::RwLock;
14use std::time::Duration;
15
16use arboard::Clipboard;
17use crossterm::event;
18use crossterm::event::Event;
19use crossterm::event::KeyCode;
20use crossterm::event::KeyModifiers;
21use crossterm::event::MouseButton;
22use crossterm::event::MouseEvent;
23use crossterm::event::MouseEventKind;
24use git2::Repository;
25
26use crate::cli;
27use crate::cmd_log::CmdLog;
28use crate::cmd_log::CmdLogEntry;
29use crate::config::Config;
30use crate::error::Error;
31use crate::file_watcher::FileWatcher;
32use crate::item_data::Rev;
33use crate::items::RenderParams;
34use crate::menu::Menu;
35use crate::menu::PendingMenu;
36use crate::ops::Op;
37use crate::picker::PickerData;
38use crate::picker::PickerState;
39use crate::picker::{Picked, PickerStatus};
40use crate::prompt;
41use crate::rebase_todo::RebaseTodo;
42use crate::screen;
43use crate::screen::Screen;
44use crate::screen::pager::Paged;
45use crate::term::Term;
46use crate::text_input;
47use crate::ui;
48
49use super::Res;
50
51pub(crate) struct State {
52 pub repo: Rc<Repository>,
53 pub config: Arc<Config>,
54 pub pending_keys: Vec<(KeyModifiers, KeyCode)>,
55 pub quit: bool,
56 pub exit_code: i32,
60 pub screens: Vec<Screen>,
61 pub pending_menu: Option<PendingMenu>,
62 pending_cmd: Option<(Child, Arc<RwLock<CmdLogEntry>>)>,
63 enable_async_cmds: bool,
64 pub debug: bool,
65 pub current_cmd_log: CmdLog,
66 pub prompt: prompt::Prompt,
67 pub picker: Option<PickerState>,
68 pub picked_commit: Option<String>,
70 pub features: Rc<[String]>,
73 pub context: Option<Rc<str>>,
76 pub feature_keys: BTreeMap<String, char>,
78 pub clipboard: Option<Clipboard>,
79 needs_redraw: bool,
80 file_watcher: Option<FileWatcher>,
81 inhibit_close_menu: bool,
82}
83
84pub struct App {
85 pub(crate) state: State,
86}
87
88impl App {
89 pub fn create(
90 repo: Rc<Repository>,
91 size: (u16, u16),
92 args: &cli::Args,
93 config: Arc<Config>,
94 enable_async_cmds: bool,
95 piped: Option<Paged>,
96 ) -> Res<Self> {
97 let params = RenderParams {
98 size,
99 features: Rc::from([]),
100 context: None,
101 streaming: enable_async_cmds,
102 };
103 let screens = match args.command {
104 _ if let Some(paged) = piped => {
106 vec![screen::pager::create(
107 Arc::clone(&config),
108 Rc::clone(&repo),
109 params,
110 paged,
111 )?]
112 }
113 Some(cli::Commands::Show { ref reference }) => {
114 vec![screen::pager::show(
115 Arc::clone(&config),
116 Rc::clone(&repo),
117 params.clone(),
118 reference,
119 )?]
120 }
121 Some(cli::Commands::Blame { ref rev, ref file }) => {
122 vec![screen::blame::create(
123 Arc::clone(&config),
124 Rc::clone(&repo),
125 params.clone(),
126 file.clone(),
127 rev.clone(),
128 None,
129 )?]
130 }
131 Some(cli::Commands::Rebase { ref upstream }) => {
132 let todo = RebaseTodo::capture(&repo, OsStr::new(upstream), &[])?;
133 vec![rebase_todo_screen(&config, &repo, params.clone(), todo)?]
134 }
135 Some(cli::Commands::SequenceEditor { ref file }) => {
136 let todo = RebaseTodo::read(&repo, file)?;
137 vec![rebase_todo_screen(&config, &repo, params.clone(), todo)?]
138 }
139 Some(cli::Commands::Completion { .. }) => {
141 unreachable!("completion is handled before the app starts")
142 }
143 None => vec![screen::status::create(
144 Arc::clone(&config),
145 Rc::clone(&repo),
146 params,
147 )?],
148 };
149
150 let clipboard = Clipboard::new()
151 .inspect_err(|e| log::warn!("Couldn't initialize clipboard: {e}"))
152 .ok();
153 let prompt = prompt::Prompt::with_history(repo.path().join("gitu"));
154
155 let mut app = Self {
156 state: State {
157 repo,
158 feature_keys: config.general.diff_renderer.feature_keys.clone(),
159 config,
160 pending_keys: vec![],
161 enable_async_cmds,
162 debug: args.debug,
163 quit: false,
164 exit_code: match args.command {
165 Some(cli::Commands::SequenceEditor { .. }) => 1,
166 _ => 0,
167 },
168 screens,
169 pending_cmd: None,
170 pending_menu: None,
171 current_cmd_log: CmdLog::new(),
172 prompt,
173 picker: None,
174 picked_commit: None,
175 features: Rc::from([]),
176 context: None,
177 clipboard,
178 file_watcher: None,
179 needs_redraw: true,
180 inhibit_close_menu: false,
181 },
182 };
183
184 app.close_menu();
187 app.state.file_watcher = app.init_file_watcher()?;
188 Ok(app)
189 }
190
191 fn init_file_watcher(&mut self) -> Res<Option<FileWatcher>> {
192 if !self.state.config.general.refresh_on_file_change.enabled {
193 return Ok(None);
194 }
195
196 let hiding_untracked_files = !self
197 .state
198 .repo
199 .config()
200 .map_err(Error::ReadGitConfig)?
201 .get_bool("status.showUntrackedFiles")
202 .unwrap_or(true);
203
204 if hiding_untracked_files {
205 self.display_info("File watcher disabled (status.showUntrackedFiles is off)");
206
207 return Ok(None);
208 }
209
210 Ok(
211 FileWatcher::new(self.state.repo.workdir().expect("Bare repos unhandled"))
212 .inspect_err(|err| {
213 self.display_error(err.to_string());
214 self.display_info("File watcher disabled");
215 })
216 .ok(),
217 )
218 }
219
220 pub fn run(&mut self, term: &mut Term, max_tick_delay: Duration) -> Res<()> {
221 while !self.state.quit {
222 let event = if term.poll_event(max_tick_delay)? {
223 Some(term.read_event()?)
224 } else if term.hung_up() {
225 return Ok(());
226 } else {
227 None
228 };
229
230 self.update(term, event)?;
231 }
232
233 Ok(())
234 }
235
236 pub fn update(&mut self, term: &mut Term, event: Option<Event>) -> Res<()> {
237 if let Some(e) = event {
238 self.handle_event(term, e)?;
239 }
240
241 if let Some(file_watcher) = &mut self.state.file_watcher
242 && file_watcher.pending_updates()
243 {
244 self.update_screens()?;
245 }
246
247 let finished = self.finish_screens();
248 self.handle_result(finished)?;
249
250 let handle_pending_cmd_result = self.handle_pending_cmd();
251 self.handle_result(handle_pending_cmd_result)?;
252
253 if self.state.needs_redraw {
254 self.redraw_now(term)?;
255 }
256
257 Ok(())
258 }
259
260 fn finish_screens(&mut self) -> Res<()> {
263 let mut finished = false;
264 for screen in &mut self.state.screens {
265 finished |= screen.finish()?;
266 }
267 if finished {
268 self.stage_redraw();
269 }
270 Ok(())
271 }
272
273 pub fn update_screens(&mut self) -> Res<()> {
274 self.rebuild_screens(Screen::refresh)
275 }
276
277 pub fn rerender_screens(&mut self) -> Res<()> {
282 self.rebuild_screens(Screen::refresh_keeping_position)
283 }
284
285 fn rebuild_screens(&mut self, rebuild: impl Fn(&mut Screen) -> Res<()>) -> Res<()> {
286 let features = Rc::clone(&self.state.features);
287 for screen in &mut self.state.screens {
288 screen.features = Rc::clone(&features);
289 screen.context = self.state.context.clone();
290 rebuild(screen)?;
291 }
292
293 self.stage_redraw();
294 Ok(())
295 }
296
297 pub fn handle_event(&mut self, term: &mut Term, event: Event) -> Res<()> {
298 match event {
299 Event::Resize(w, h) => {
300 for screen in self.state.screens.iter_mut() {
301 screen.resize(w, h)?;
302 }
303
304 self.stage_redraw();
305 Ok(())
306 }
307 Event::Mouse(ref mouse) => {
308 if self.state.config.general.mouse_support
309 && self.handle_mouse_input(term, mouse)?
310 {
311 self.stage_redraw();
312 }
313 Ok(())
314 }
315 Event::Key(mut key) => {
316 if self.state.pending_cmd.is_none() {
317 self.state.current_cmd_log.clear();
318 }
319
320 if self.state.pending_cmd.is_none() {
321 self.state.current_cmd_log.clear();
322 }
323
324 if matches!(key.code, KeyCode::Char(_) | KeyCode::BackTab) {
329 key.modifiers = key.modifiers.difference(KeyModifiers::SHIFT);
330 }
331
332 if self.state.picker.is_some() {
333 self.handle_picker_input(key);
334 } else if self.state.prompt.state.focused {
335 self.state.prompt.handle_key(key);
336 } else {
337 self.handle_key_input(term, key)?;
338 }
339
340 self.stage_redraw();
341 Ok(())
342 }
343 _ => Ok(()),
344 }
345 }
346
347 pub fn redraw_now(&mut self, term: &mut Term) -> Res<()> {
348 if self.state.screens.last_mut().is_some() {
349 ui::ui(term, &mut self.state)?;
350 self.state.needs_redraw = false;
351 };
352
353 Ok(())
354 }
355
356 pub fn print_if_one_screen(&mut self, term: &mut Term) -> Res<bool> {
360 let screen = self.state.screens.last_mut().unwrap();
361 let (_, rows) = term.size().map_err(Error::Term)?;
362 let rows = rows.saturating_sub(1).into();
363 loop {
364 if !screen.fits_in(rows) {
365 return Ok(false);
366 }
367 if !screen.is_drawing() {
368 break;
369 }
370 if !screen.finish()? {
371 std::thread::sleep(Duration::from_millis(10));
372 }
373 }
374
375 screen::print_screen(term, screen)?;
376 Ok(true)
377 }
378
379 pub fn stage_redraw(&mut self) {
380 self.state.needs_redraw = true;
381 }
382
383 fn handle_key_input(&mut self, term: &mut Term, key: event::KeyEvent) -> Res<()> {
384 if key.kind != event::KeyEventKind::Press {
385 return Ok(());
386 }
387
388 let menu = match &self.state.pending_menu {
389 None => self.base_menu().unwrap_or(Menu::Root),
390 Some(menu) if menu.menu == Menu::Help => Menu::Root,
391 Some(menu) => menu.menu,
392 };
393
394 self.state.pending_keys.push((key.modifiers, key.code));
395
396 let matching_bindings = self
397 .state
398 .config
399 .bindings
400 .match_bindings(&menu, &self.state.pending_keys)
401 .collect::<Vec<_>>();
402
403 match matching_bindings[..] {
404 [binding] => {
405 if binding.keys == self.state.pending_keys {
406 let op = binding.op.clone();
407 self.state.pending_keys.clear();
408 self.handle_op(op, term)?;
409 }
410 }
411 [] => {
412 let feature = match self.state.pending_keys[..] {
413 [(KeyModifiers::NONE, KeyCode::Char(key))] if menu == Menu::Root => self
414 .state
415 .feature_keys
416 .iter()
417 .find(|(_, held)| **held == key)
418 .map(|(feature, _)| feature.clone()),
419 _ => None,
420 };
421 self.state.pending_keys.clear();
422 if let Some(feature) = feature {
423 crate::ops::editor::toggle_feature(self, &feature)?;
424 }
425 }
426 [_, ..] => (),
427 }
428
429 Ok(())
430 }
431
432 fn handle_mouse_input(&mut self, term: &mut Term, mouse: &MouseEvent) -> Res<bool> {
433 match mouse.kind {
434 MouseEventKind::Down(MouseButton::Left) => {
435 let click_y = mouse.row as usize;
436 if self.screen().is_valid_screen_line(click_y) {
437 let old_selected_item_id = self.screen().get_selected_item().id;
438 self.handle_op(Op::MoveToScreenLine(click_y), term)?;
439 let new_selected_item = self.screen().get_selected_item();
440
441 if old_selected_item_id == new_selected_item.id {
442 if new_selected_item.data.is_section() {
445 self.handle_op(Op::ToggleSection, term)?;
446 } else {
447 self.handle_op(Op::Show, term)?;
448 }
449 }
450 }
451 }
452 MouseEventKind::Down(MouseButton::Right) => {
453 let click_y = mouse.row as usize;
454 if self.screen().is_valid_screen_line(click_y) {
455 self.handle_op(Op::MoveToScreenLine(click_y), term)?;
456 self.handle_op(Op::Show, term)?;
457 }
458 }
459 MouseEventKind::ScrollUp => {
460 let scroll_lines = self.state.config.general.mouse_scroll_lines;
461 if scroll_lines > 0 {
462 self.screen_mut().scroll_view_up(scroll_lines);
463 }
464 }
465 MouseEventKind::ScrollDown => {
466 let scroll_lines = self.state.config.general.mouse_scroll_lines;
467 if scroll_lines > 0 {
468 self.screen_mut().scroll_view_down(scroll_lines);
469 }
470 }
471 _ => return Ok(false),
472 }
473
474 Ok(true)
475 }
476
477 pub(crate) fn handle_op(&mut self, op: Op, term: &mut Term) -> Res<()> {
478 let item_data = self.screen().selected_target();
479 let implementation = op.clone().implementation();
480
481 if let Some(mut action) = implementation.get_action(&item_data) {
482 let result = Rc::get_mut(&mut action).unwrap()(self, term);
483 self.handle_result(result)?;
484 if !self.state.inhibit_close_menu {
485 self.close_menu();
486 }
487 self.state.inhibit_close_menu = false;
488 }
489
490 Ok(())
491 }
492
493 fn handle_result<T>(&mut self, result: Res<T>) -> Res<()> {
494 match result {
495 Ok(_) => Ok(()),
496 Err(Error::NoMoreEvents) => Err(Error::NoMoreEvents),
497 Err(Error::PromptAborted) => Ok(()),
498 Err(error) => {
499 self.state
500 .current_cmd_log
501 .push(CmdLogEntry::Error(error.to_string()));
502
503 Ok(())
504 }
505 }
506 }
507
508 pub(crate) fn render_params(&self, size: (u16, u16)) -> RenderParams {
511 RenderParams {
512 size,
513 features: Rc::clone(&self.state.features),
514 context: self.state.context.clone(),
515 streaming: self.state.enable_async_cmds,
516 }
517 }
518
519 pub fn close_menu(&mut self) {
520 let hide = self.screen().menu.is_some() && !self.screen().show_menu;
521 self.state.pending_menu = self.base_menu().map(|menu| PendingMenu {
522 is_hidden: hide,
523 ..PendingMenu::init(menu)
524 });
525 }
526
527 pub(crate) fn base_menu(&self) -> Option<Menu> {
530 self.screen().menu.or_else(|| root_menu(&self.state.config))
531 }
532
533 pub fn inhibit_close_menu(&mut self) {
534 self.state.inhibit_close_menu = true;
535 }
536
537 pub(crate) fn screen_mut(&mut self) -> &mut Screen {
538 self.state.screens.last_mut().expect("No screen")
539 }
540
541 pub(crate) fn screen(&self) -> &Screen {
542 self.state.screens.last().expect("No screen")
543 }
544
545 pub fn display_info<S: Into<Cow<'static, str>>>(&mut self, message: S) {
547 self.state
548 .current_cmd_log
549 .push(CmdLogEntry::Info(message.into().into_owned()));
550 }
551
552 pub fn display_error<S: Into<Cow<'static, str>>>(&mut self, message: S) {
554 self.state
555 .current_cmd_log
556 .push(CmdLogEntry::Error(message.into().into_owned()));
557 }
558
559 pub fn run_cmd(&mut self, term: &mut Term, input: &[u8], cmd: Command) -> Res<()> {
562 self.run_cmd_async(term, input, cmd)?;
563 self.await_pending_cmd()?;
564 self.handle_pending_cmd()?;
565 Ok(())
566 }
567
568 pub fn run_cmd_async(&mut self, term: &mut Term, input: &[u8], mut cmd: Command) -> Res<()> {
571 cmd.env("CLICOLOR_FORCE", "1"); if self.state.pending_cmd.is_some() {
574 return Err(Error::CmdAlreadyRunning);
575 }
576
577 cmd.current_dir(self.state.repo.workdir().expect("No workdir"));
578
579 cmd.stdin(Stdio::piped());
580 cmd.stdout(Stdio::piped());
581 cmd.stderr(Stdio::piped());
582
583 let log_entry = self.state.current_cmd_log.push_cmd(&cmd);
584
585 ui::ui(term, &mut self.state)?;
586
587 let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
588
589 use std::io::Write;
590 child
591 .stdin
592 .take()
593 .unwrap()
594 .write_all(input)
595 .map_err(Error::Term)?;
596
597 self.state.pending_cmd = Some((child, log_entry));
598
599 if !self.state.enable_async_cmds {
600 self.await_pending_cmd()?;
601 }
602
603 Ok(())
604 }
605
606 fn await_pending_cmd(&mut self) -> Res<()> {
607 if let Some((child, _)) = &mut self.state.pending_cmd {
608 child.wait().map_err(Error::CouldntAwaitCmd)?;
609 }
610 Ok(())
611 }
612
613 fn handle_pending_cmd(&mut self) -> Res<()> {
615 let Some((ref mut child, ref mut log_rwlock)) = self.state.pending_cmd else {
616 return Ok(());
617 };
618
619 let Some(status) = child.try_wait().map_err(Error::CouldntAwaitCmd)? else {
620 return Ok(());
621 };
622
623 log::debug!("pending cmd finished with {status:?}");
624
625 let result = write_child_output_to_log(log_rwlock, child, status);
626 self.state.pending_cmd = None;
627 self.update_screens()?;
628 result?;
629
630 Ok(())
631 }
632
633 pub fn run_cmd_interactive(&mut self, term: &mut Term, mut cmd: Command) -> Res<()> {
634 cmd.env("CLICOLOR_FORCE", "1"); if self.state.pending_cmd.is_some() {
637 return Err(Error::CmdAlreadyRunning);
638 }
639
640 cmd.current_dir(self.state.repo.workdir().ok_or(Error::NoRepoWorkdir)?);
641
642 self.state
643 .current_cmd_log
644 .push_cmd_with_output(&cmd, "\n".into());
645 self.redraw_now(term)?;
646
647 eprint!("\r");
648
649 cmd.stderr(Stdio::piped());
651
652 term.reset_term_stay_on_alt_screeen(&self.state.config)
653 .map_err(Error::Term)?;
654
655 let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
656
657 drop(child.stdin.take());
659
660 let mut stderr = Vec::new();
661
662 tee(
663 child.stderr.as_mut(),
664 &mut [&mut std::io::stderr(), &mut stderr],
665 )
666 .map_err(Error::Term)?;
667
668 let status = child.wait().map_err(Error::CouldntAwaitCmd)?;
669
670 term.setup_term(&self.state.config).map_err(Error::Term)?;
671
672 let out_utf8 = String::from_utf8(strip_ansi_escapes::strip(stderr.clone()))
673 .expect("Error turning command output to String")
674 .into();
675
676 self.state.current_cmd_log.clear();
677 self.state
678 .current_cmd_log
679 .push_cmd_with_output(&cmd, out_utf8);
680
681 term.clear().map_err(Error::Term)?;
682 self.update_screens()?;
683
684 if !status.success() {
685 return Err(Error::CmdBadExit(
686 format!(
687 "{} {}",
688 cmd.get_program().to_string_lossy(),
689 cmd.get_args()
690 .map(|arg| arg.to_string_lossy())
691 .collect::<String>()
692 ),
693 status.code(),
694 ));
695 }
696
697 Ok(())
698 }
699
700 pub fn hide_menu(&mut self) {
701 if let Some(ref mut menu) = self.state.pending_menu {
702 menu.is_hidden = true;
703 }
704 }
705
706 fn unhide_menu(&mut self) {
707 if let Some(ref mut menu) = self.state.pending_menu {
708 menu.is_hidden = false;
709 }
710 }
711
712 pub(crate) fn selected_rev(&self) -> Option<Rev> {
713 self.screen().get_selected_item().data.rev()
714 }
715
716 pub(crate) fn prompt(&mut self, term: &mut Term, params: &PromptParams) -> Res<String> {
717 let default = (params.create_default_value)(self);
718
719 let prompt_text = match (params.prompt, &default) {
724 ("", _) => Cow::Borrowed(""),
725 (prompt, Some(default)) if !params.prefill => {
726 format!("{prompt} (default {default}):").into()
727 }
728 (prompt, _) => format!("{prompt}:").into(),
729 };
730
731 if params.hide_menu {
732 self.hide_menu();
733 }
734
735 self.state.prompt.set(prompt::PromptData { prompt_text });
736
737 if params.prefill
738 && let Some(default) = default
739 {
740 self.state.prompt.state.value = default;
741 self.state.prompt.state.move_to(usize::MAX);
742 }
743 self.state.prompt.start_history(params.history)?;
744
745 let result = self.handle_prompt(term, params);
746
747 self.unhide_menu();
748 self.state.prompt.reset();
749
750 result
751 }
752
753 fn handle_prompt(&mut self, term: &mut Term, params: &PromptParams) -> Res<String> {
754 self.redraw_now(term)?;
755
756 loop {
757 let event = term.read_event()?;
758 self.handle_event(term, event)?;
759
760 if self.state.prompt.state.status == text_input::Status::Done {
761 let value = get_prompt_result(params, self)?;
762 if let Some(kind) = params.history {
763 self.state.prompt.remember(kind, &value)?;
764 }
765 return Ok(value);
766 } else if self.state.prompt.state.status == text_input::Status::Aborted {
767 return Err(Error::PromptAborted);
768 }
769
770 self.redraw_now(term)?;
771 }
772 }
773
774 pub fn confirm(&mut self, term: &mut Term, prompt: &'static str) -> Res<()> {
775 self.hide_menu();
776 self.state.prompt.set(prompt::PromptData {
777 prompt_text: prompt.into(),
778 });
779
780 let result = self.handle_confirm(term);
781
782 self.unhide_menu();
783 self.state.prompt.reset();
784
785 result
786 }
787
788 fn handle_confirm(&mut self, term: &mut Term) -> Res<()> {
789 self.redraw_now(term)?;
790 loop {
791 let event = term.read_event()?;
792 self.handle_event(term, event)?;
793
794 match self.state.prompt.state.value.as_str() {
795 "y" => {
796 return Ok(());
797 }
798 "" => (),
799 _ => {
800 return Err(Error::PromptAborted);
801 }
802 }
803
804 self.redraw_now(term)?;
805 }
806 }
807
808 pub fn pick(&mut self, term: &mut Term, picker_state: PickerState) -> Res<Option<PickerData>> {
838 Ok(self
839 .pick_or_set_key(term, picker_state)?
840 .map(|picked| match picked {
841 Picked::Item(data) => data,
842 Picked::KeySet(..) => unreachable!("only a picker assigning keys sets them"),
843 }))
844 }
845
846 pub(crate) fn pick_or_set_key(
848 &mut self,
849 term: &mut Term,
850 picker_state: PickerState,
851 ) -> Res<Option<Picked>> {
852 self.state.picker = Some(picker_state);
853 let result = self.handle_picker(term);
854
855 self.state.picker = None;
856
857 result
858 }
859
860 pub fn pick_commit(&mut self, term: &mut Term) -> Res<Option<String>> {
864 let params = self.render_params(self.screen().size);
865 let selected = match &self.screen().get_selected_item().data {
866 crate::item_data::ItemData::Commit { oid, .. } => Some(oid.clone()),
867 _ => None,
868 };
869 let params = RenderParams {
872 streaming: params.streaming && selected.is_none(),
873 ..params
874 };
875 self.state.screens.push(screen::pager::log(
876 Arc::clone(&self.state.config),
877 Rc::clone(&self.state.repo),
878 params,
879 COMMIT_PICK_LIMIT,
880 None,
881 None,
882 )?);
883 self.screen_mut().menu = Some(Menu::CommitPicker);
884 self.close_menu();
885
886 if let Some(selected) = selected {
888 self.screen_mut().select_matching(|data| {
889 matches!(data, crate::item_data::ItemData::Commit { oid, .. } if *oid == selected)
890 });
891 }
892
893 let depth = self.state.screens.len();
894 self.redraw_now(term)?;
895
896 loop {
897 let event = term.read_event()?;
898 self.handle_event(term, event)?;
899
900 if let Some(oid) = self.state.picked_commit.take() {
901 self.state.screens.truncate(depth - 1);
902 self.close_menu();
903 return Ok(Some(oid));
904 }
905 if self.state.screens.len() < depth {
906 return Ok(None); }
908
909 self.redraw_now(term)?;
910 }
911 }
912
913 fn handle_picker(&mut self, term: &mut Term) -> Res<Option<Picked>> {
914 self.redraw_now(term)?;
915
916 loop {
917 let event = term.read_event()?;
918 self.handle_event(term, event)?;
919
920 if let Some(ref picker) = self.state.picker {
921 let selected = || picker.selected().map(|item| item.data.clone());
922 match *picker.status() {
923 PickerStatus::Done => return Ok(selected().map(Picked::Item)),
924 PickerStatus::KeySet(key) => {
925 return Ok(selected().map(|data| Picked::KeySet(key, data)));
926 }
927 PickerStatus::Cancelled => return Ok(None),
929 PickerStatus::Active => (),
930 }
931 }
932
933 self.redraw_now(term)?;
934 }
935 }
936
937 fn handle_picker_input(&mut self, key: event::KeyEvent) {
938 if key.kind != event::KeyEventKind::Press {
939 return;
940 }
941
942 if let Some(ref mut picker) = self.state.picker {
943 let bindings = &self.state.config.picker_bindings;
944 let key_combo = vec![(key.modifiers, key.code)];
945 let typed = match (key.code, key.modifiers) {
946 (
947 event::KeyCode::Char(c),
948 event::KeyModifiers::NONE | event::KeyModifiers::SHIFT,
949 ) => Some(c),
950 _ => None,
951 };
952
953 if bindings.next.iter().any(|b| b == &key_combo) {
954 picker.next();
955 } else if bindings.previous.iter().any(|b| b == &key_combo) {
956 picker.previous();
957 } else if bindings.done.iter().any(|b| b == &key_combo) {
958 picker.done();
959 } else if bindings.cancel.iter().any(|b| b == &key_combo) {
960 picker.cancel();
961 } else if picker.assigns_keys() {
962 if let Some(c) = typed {
963 picker.set_key(c);
964 }
965 } else {
966 picker.input_state.handle_key_event(key);
968 picker.update_filter();
969 }
970 }
971 }
972}
973
974fn get_prompt_result(params: &PromptParams, app: &mut App) -> Res<String> {
975 let input = app.state.prompt.state.value.as_str();
976 if params.prefill {
977 return Ok(input.to_owned());
978 }
979
980 let default_value = (params.create_default_value)(app);
981
982 let value = match (input, &default_value) {
983 ("", None) => "",
984 ("", Some(selected)) => selected,
985 (value, _) => value,
986 };
987
988 Ok(value.to_string())
989}
990
991fn tee(maybe_input: Option<&mut impl Read>, outputs: &mut [&mut dyn Write]) -> std::io::Result<()> {
992 let Some(input) = maybe_input else {
993 return Ok(());
994 };
995
996 let mut buf = [0u8; 1024];
997
998 loop {
999 let num_read = input.read(&mut buf)?;
1000 if num_read == 0 {
1001 break;
1002 }
1003
1004 let buf = &buf[..num_read];
1005 for output in &mut *outputs {
1006 output.write_all(buf)?;
1007 }
1008 }
1009
1010 Ok(())
1011}
1012
1013fn rebase_todo_screen(
1014 config: &Arc<Config>,
1015 repo: &Rc<Repository>,
1016 params: RenderParams,
1017 todo: RebaseTodo,
1018) -> Res<Screen> {
1019 screen::rebase_todo::create(
1020 Arc::clone(config),
1021 Rc::clone(repo),
1022 params,
1023 Rc::new(RefCell::new(todo)),
1024 )
1025}
1026
1027const COMMIT_PICK_LIMIT: usize = 256;
1029
1030pub(crate) fn root_menu(config: &Config) -> Option<Menu> {
1031 if config.general.always_show_help.enabled {
1032 Some(Menu::Help)
1033 } else {
1034 None
1035 }
1036}
1037
1038fn write_child_output_to_log(
1039 log_rwlock: &mut Arc<RwLock<CmdLogEntry>>,
1040 child: &mut Child,
1041 status: std::process::ExitStatus,
1042) -> Res<()> {
1043 let mut log = log_rwlock.write().unwrap();
1044
1045 let CmdLogEntry::Cmd { args, out: out_log } = log.deref_mut() else {
1046 unreachable!("pending_cmd is always CmdLogEntry::Cmd variant");
1047 };
1048
1049 drop(child.stdin.take());
1050
1051 let mut out_bytes = vec![];
1052 log::debug!("Reading stderr");
1053
1054 child
1055 .stderr
1056 .take()
1057 .unwrap()
1058 .read_to_end(&mut out_bytes)
1059 .map_err(Error::CouldntReadCmdOutput)?;
1060
1061 child
1062 .stdout
1063 .take()
1064 .unwrap()
1065 .read_to_end(&mut out_bytes)
1066 .map_err(Error::CouldntReadCmdOutput)?;
1067
1068 let out_string = String::from_utf8_lossy(&out_bytes).to_string();
1069 *out_log = Some(out_string.into());
1070
1071 if !status.success() {
1072 return Err(Error::CmdBadExit(args.to_string(), status.code()));
1073 }
1074
1075 Ok(())
1076}
1077
1078type DefaultFn = Box<dyn Fn(&App) -> Option<String>>;
1079
1080pub(crate) struct PromptParams {
1081 pub prompt: &'static str,
1082 pub create_default_value: DefaultFn,
1083 pub hide_menu: bool,
1084 pub prefill: bool,
1087 pub history: Option<prompt::HistoryKind>,
1088}
1089
1090impl Default for PromptParams {
1091 fn default() -> Self {
1092 Self {
1093 prompt: "",
1094 create_default_value: Box::new(|_| None),
1095 hide_menu: true,
1096 prefill: false,
1097 history: None,
1098 }
1099 }
1100}