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