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, Instant};
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 theme: Option<String>,
75 pub context: Option<Rc<str>>,
78 pub feature_keys: BTreeMap<String, char>,
80 pub clipboard: Option<Clipboard>,
81 pub read_only: bool,
84 needs_redraw: bool,
85 pub(crate) terminal_width: Option<(u16, Instant)>,
89 file_watcher: Option<FileWatcher>,
90 inhibit_close_menu: bool,
91}
92
93pub struct App {
94 pub(crate) state: State,
95}
96
97impl App {
98 pub fn create(
99 repo: Rc<Repository>,
100 size: (u16, u16),
101 args: &cli::Args,
102 config: Arc<Config>,
103 enable_async_cmds: bool,
104 piped: Option<Paged>,
105 ) -> Res<Self> {
106 let params = RenderParams {
107 size,
108 features: overlay(&[], config.general.diff_renderer.theme.as_deref()),
109 context: None,
110 streaming: enable_async_cmds,
111 };
112 let screens = match args.command {
113 _ if let Some(paged) = piped => {
115 vec![screen::pager::create(
116 Arc::clone(&config),
117 Rc::clone(&repo),
118 params,
119 paged,
120 )?]
121 }
122 Some(cli::Commands::Show { ref reference }) => {
123 vec![screen::pager::show(
124 Arc::clone(&config),
125 Rc::clone(&repo),
126 params.clone(),
127 reference,
128 )?]
129 }
130 Some(cli::Commands::Blame { ref rev, ref file }) => {
131 vec![screen::blame::create(
132 Arc::clone(&config),
133 Rc::clone(&repo),
134 params.clone(),
135 file.clone(),
136 rev.clone(),
137 None,
138 )?]
139 }
140 Some(cli::Commands::Rebase { ref upstream }) => {
141 let todo = RebaseTodo::capture(&repo, OsStr::new(upstream), &[])?;
142 vec![rebase_todo_screen(&config, &repo, params.clone(), todo)?]
143 }
144 Some(cli::Commands::SequenceEditor { ref file }) => {
145 let todo = RebaseTodo::read(&repo, file)?;
146 vec![rebase_todo_screen(&config, &repo, params.clone(), todo)?]
147 }
148 None => vec![screen::status::create(
149 Arc::clone(&config),
150 Rc::clone(&repo),
151 params,
152 )?],
153 };
154
155 let clipboard = Clipboard::new()
156 .inspect_err(|e| log::warn!("Couldn't initialize clipboard: {e}"))
157 .ok();
158 let read_only = config.general.read_only.is_on(&repo);
159 let prompt = prompt::Prompt::with_history(repo.path().join("gitu"), read_only);
160
161 let mut app = Self {
162 state: State {
163 repo,
164 feature_keys: config.general.diff_renderer.feature_keys.clone(),
165 theme: config.general.diff_renderer.theme.clone(),
166 config,
167 pending_keys: vec![],
168 enable_async_cmds,
169 debug: args.debug,
170 quit: false,
171 exit_code: match args.command {
172 Some(cli::Commands::SequenceEditor { .. }) => 1,
173 _ => 0,
174 },
175 screens,
176 pending_cmd: None,
177 pending_menu: None,
178 current_cmd_log: CmdLog::new(),
179 prompt,
180 picker: None,
181 picked_commit: None,
182 features: Rc::from([]),
183 context: None,
184 clipboard,
185 read_only,
186 file_watcher: None,
187 needs_redraw: true,
188 terminal_width: None,
189 inhibit_close_menu: false,
190 },
191 };
192
193 app.close_menu();
196 app.state.file_watcher = app.init_file_watcher()?;
197 Ok(app)
198 }
199
200 fn init_file_watcher(&mut self) -> Res<Option<FileWatcher>> {
201 if !self.state.config.general.refresh_on_file_change.enabled {
202 return Ok(None);
203 }
204
205 let hiding_untracked_files = !self
206 .state
207 .repo
208 .config()
209 .map_err(Error::ReadGitConfig)?
210 .get_bool("status.showUntrackedFiles")
211 .unwrap_or(true);
212
213 if hiding_untracked_files {
214 self.display_info("File watcher disabled (status.showUntrackedFiles is off)");
215
216 return Ok(None);
217 }
218
219 Ok(
220 FileWatcher::new(self.state.repo.workdir().expect("Bare repos unhandled"))
221 .inspect_err(|err| {
222 self.display_error(err.to_string());
223 self.display_info("File watcher disabled");
224 })
225 .ok(),
226 )
227 }
228
229 pub fn run(&mut self, term: &mut Term, max_tick_delay: Duration) -> Res<()> {
230 while !self.state.quit {
231 let event = if term.poll_event(self.poll_timeout(max_tick_delay, Instant::now()))? {
232 Some(term.read_event()?)
233 } else if term.hung_up() {
234 return Ok(());
235 } else {
236 None
237 };
238
239 self.update(term, event)?;
240 }
241
242 Ok(())
243 }
244
245 pub fn update(&mut self, term: &mut Term, event: Option<Event>) -> Res<()> {
246 if let Some(e) = event {
247 self.handle_event(term, e)?;
248 }
249
250 self.rerender_at_terminal_width(term)?;
251
252 if let Some(file_watcher) = &mut self.state.file_watcher
253 && file_watcher.pending_updates()
254 {
255 self.update_screens()?;
256 }
257
258 let finished = self.finish_screens();
259 self.handle_result(finished)?;
260
261 let handle_pending_cmd_result = self.handle_pending_cmd();
262 self.handle_result(handle_pending_cmd_result)?;
263
264 if self.state.needs_redraw {
265 self.redraw_now(term)?;
266 }
267
268 Ok(())
269 }
270
271 fn finish_screens(&mut self) -> Res<()> {
274 let mut finished = false;
275 for screen in &mut self.state.screens {
276 finished |= screen.finish()?;
277 }
278 if finished {
279 self.stage_redraw();
280 }
281 Ok(())
282 }
283
284 pub(crate) fn poll_timeout(&self, max_tick_delay: Duration, now: Instant) -> Duration {
287 match self.resize_render_due() {
288 Some(due) => max_tick_delay.min(due.saturating_duration_since(now)),
289 None => max_tick_delay,
290 }
291 }
292
293 fn resize_render_due(&self) -> Option<Instant> {
297 let (width, since) = self.state.terminal_width?;
298 let stale = self
299 .state
300 .screens
301 .iter()
302 .any(|screen| screen.rendered_width != width);
303 let delay = if self.state.enable_async_cmds {
304 RESIZE_RENDER_DELAY
305 } else {
306 Duration::ZERO
307 };
308 stale.then_some(since + delay)
309 }
310
311 fn rerender_at_terminal_width(&mut self, term: &mut Term) -> Res<()> {
315 let width = term.size().map_err(Error::Term)?.0;
316 let now = Instant::now();
317 if self
318 .state
319 .terminal_width
320 .is_none_or(|(seen, _)| seen != width)
321 {
322 self.state.terminal_width = Some((width, now));
323 }
324 match self.resize_render_due() {
325 Some(due) if now >= due && !term.has_pending_event()? => {}
326 _ => return Ok(()),
327 }
328
329 for screen in &mut self.state.screens {
330 let height = screen.size.1;
331 screen.resize(width, height)?;
332 }
333 self.rerender_screens()
334 }
335
336 pub fn update_screens(&mut self) -> Res<()> {
337 self.rebuild_screens(Screen::refresh)
338 }
339
340 pub fn rerender_screens(&mut self) -> Res<()> {
345 self.rebuild_screens(Screen::refresh_keeping_position)
346 }
347
348 pub(crate) fn rerender_screen(&mut self) -> Res<()> {
351 let features = self.overlay();
352 let screen = self.state.screens.last_mut().unwrap();
353 screen.features = features;
354 screen.refresh_keeping_position()?;
355 self.stage_redraw();
356 Ok(())
357 }
358
359 fn overlay(&self) -> Rc<[String]> {
361 overlay(&self.state.features, self.state.theme.as_deref())
362 }
363
364 fn rebuild_screens(&mut self, rebuild: impl Fn(&mut Screen) -> Res<()>) -> Res<()> {
365 let features = self.overlay();
366 for screen in &mut self.state.screens {
367 screen.features = Rc::clone(&features);
368 screen.context = self.state.context.clone();
369 rebuild(screen)?;
370 }
371
372 self.stage_redraw();
373 Ok(())
374 }
375
376 pub fn handle_event(&mut self, term: &mut Term, event: Event) -> Res<()> {
377 match event {
378 Event::Resize(w, h) => {
379 for screen in self.state.screens.iter_mut() {
383 screen.resize(w, h)?;
384 }
385
386 self.stage_redraw();
387 Ok(())
388 }
389 Event::Mouse(ref mouse) => {
390 if self.state.config.general.mouse_support
391 && self.handle_mouse_input(term, mouse)?
392 {
393 self.stage_redraw();
394 }
395 Ok(())
396 }
397 Event::Key(mut key) => {
398 if self.state.pending_cmd.is_none() {
399 self.state.current_cmd_log.clear();
400 }
401
402 if self.state.pending_cmd.is_none() {
403 self.state.current_cmd_log.clear();
404 }
405
406 if matches!(key.code, KeyCode::Char(_) | KeyCode::BackTab) {
411 key.modifiers = key.modifiers.difference(KeyModifiers::SHIFT);
412 }
413
414 if self.state.picker.is_some() {
415 self.handle_picker_input(key)?;
416 } else if self.state.prompt.state.focused {
417 self.state.prompt.handle_key(key);
418 } else {
419 self.handle_key_input(term, key)?;
420 }
421
422 self.stage_redraw();
423 Ok(())
424 }
425 _ => Ok(()),
426 }
427 }
428
429 pub fn redraw_now(&mut self, term: &mut Term) -> Res<()> {
430 if self.state.screens.last_mut().is_some() {
431 ui::ui(term, &mut self.state)?;
432 self.state.needs_redraw = false;
433 };
434
435 Ok(())
436 }
437
438 pub fn print_if_one_screen(&mut self, term: &mut Term) -> Res<bool> {
442 let screen = self.state.screens.last_mut().unwrap();
443 let (_, rows) = term.size().map_err(Error::Term)?;
444 let rows = rows.saturating_sub(1).into();
445 loop {
446 if !screen.fits_in(rows) {
447 return Ok(false);
448 }
449 if !screen.is_drawing() {
450 break;
451 }
452 if !screen.finish()? {
453 std::thread::sleep(Duration::from_millis(10));
454 }
455 }
456
457 screen::print_screen(term, screen)?;
458 Ok(true)
459 }
460
461 pub fn stage_redraw(&mut self) {
462 self.state.needs_redraw = true;
463 }
464
465 fn handle_key_input(&mut self, term: &mut Term, key: event::KeyEvent) -> Res<()> {
466 if key.kind != event::KeyEventKind::Press {
467 return Ok(());
468 }
469
470 let menu = match &self.state.pending_menu {
471 None => self.base_menu().unwrap_or(Menu::Root),
472 Some(menu) if menu.menu == Menu::Help => Menu::Root,
473 Some(menu) => menu.menu,
474 };
475
476 self.state.pending_keys.push((key.modifiers, key.code));
477
478 let matching_bindings = self
479 .state
480 .config
481 .bindings
482 .match_bindings(&menu, &self.state.pending_keys)
483 .collect::<Vec<_>>();
484
485 match matching_bindings[..] {
486 [binding] => {
487 if binding.keys == self.state.pending_keys {
488 let op = binding.op.clone();
489 self.state.pending_keys.clear();
490 self.handle_op(op, term)?;
491 }
492 }
493 [] => {
494 let feature = match self.state.pending_keys[..] {
495 [(KeyModifiers::NONE, KeyCode::Char(key))] if menu == Menu::Root => {
496 self.feature_with_key(key)
497 }
498 _ => None,
499 };
500 self.state.pending_keys.clear();
501 if let Some(name) = feature {
502 if self.state.config.general.commands.contains_key(&name) {
503 crate::ops::command::run(self, term, &name)?;
504 } else if self.state.config.general.views.contains_key(&name) {
505 crate::ops::command::open_view(self, term, &name)?;
506 } else {
507 crate::ops::editor::toggle_feature(self, &name)?;
508 }
509 }
510 }
511 [_, ..] => (),
512 }
513
514 Ok(())
515 }
516
517 fn handle_mouse_input(&mut self, term: &mut Term, mouse: &MouseEvent) -> Res<bool> {
518 match mouse.kind {
519 MouseEventKind::Down(MouseButton::Left) => {
520 let click_y = mouse.row as usize;
521 if self.screen().is_valid_screen_line(click_y) {
522 let old_selected_item_id = self.screen().get_selected_item().id;
523 self.handle_op(Op::MoveToScreenLine(click_y), term)?;
524 let new_selected_item = self.screen().get_selected_item();
525
526 if old_selected_item_id == new_selected_item.id {
527 if new_selected_item.data.is_section() {
530 self.handle_op(Op::ToggleSection, term)?;
531 } else {
532 self.handle_op(Op::Show, term)?;
533 }
534 }
535 }
536 }
537 MouseEventKind::Down(MouseButton::Right) => {
538 let click_y = mouse.row as usize;
539 if self.screen().is_valid_screen_line(click_y) {
540 self.handle_op(Op::MoveToScreenLine(click_y), term)?;
541 self.handle_op(Op::Show, term)?;
542 }
543 }
544 MouseEventKind::ScrollUp => {
545 let scroll_lines = self.state.config.general.mouse_scroll_lines;
546 if scroll_lines > 0 {
547 self.screen_mut().scroll_view_up(scroll_lines);
548 }
549 }
550 MouseEventKind::ScrollDown => {
551 let scroll_lines = self.state.config.general.mouse_scroll_lines;
552 if scroll_lines > 0 {
553 self.screen_mut().scroll_view_down(scroll_lines);
554 }
555 }
556 _ => return Ok(false),
557 }
558
559 Ok(true)
560 }
561
562 pub(crate) fn handle_op(&mut self, op: Op, term: &mut Term) -> Res<()> {
563 if self.state.read_only && op.writes() {
564 return Ok(());
565 }
566 let item_data = self.screen().selected_target();
567 let implementation = op.clone().implementation();
568
569 if let Some(mut action) = implementation.get_action(&item_data) {
570 let result = Rc::get_mut(&mut action).unwrap()(self, term);
571 self.handle_result(result)?;
572 if !self.state.inhibit_close_menu {
573 self.close_menu();
574 }
575 self.state.inhibit_close_menu = false;
576 }
577
578 Ok(())
579 }
580
581 fn handle_result<T>(&mut self, result: Res<T>) -> Res<()> {
582 match result {
583 Ok(_) => Ok(()),
584 Err(Error::NoMoreEvents) => Err(Error::NoMoreEvents),
585 Err(Error::PromptAborted) => Ok(()),
586 Err(error) => {
587 self.state
588 .current_cmd_log
589 .push(CmdLogEntry::Error(error.to_string()));
590
591 Ok(())
592 }
593 }
594 }
595
596 pub(crate) fn render_params(&self, size: (u16, u16)) -> RenderParams {
599 RenderParams {
600 size,
601 features: self.overlay(),
602 context: self.state.context.clone(),
603 streaming: self.state.enable_async_cmds,
604 }
605 }
606
607 pub fn close_menu(&mut self) {
608 let hide = self.screen().menu.is_some() && !self.screen().show_menu;
609 self.state.pending_menu = self.base_menu().map(|menu| PendingMenu {
610 is_hidden: hide,
611 ..PendingMenu::init(menu)
612 });
613 }
614
615 pub(crate) fn base_menu(&self) -> Option<Menu> {
618 self.screen().menu.or_else(|| root_menu(&self.state.config))
619 }
620
621 pub fn inhibit_close_menu(&mut self) {
622 self.state.inhibit_close_menu = true;
623 }
624
625 pub(crate) fn screen_mut(&mut self) -> &mut Screen {
626 self.state.screens.last_mut().expect("No screen")
627 }
628
629 pub(crate) fn screen(&self) -> &Screen {
630 self.state.screens.last().expect("No screen")
631 }
632
633 pub fn display_info<S: Into<Cow<'static, str>>>(&mut self, message: S) {
635 self.state
636 .current_cmd_log
637 .push(CmdLogEntry::Info(message.into().into_owned()));
638 }
639
640 pub fn display_error<S: Into<Cow<'static, str>>>(&mut self, message: S) {
642 self.state
643 .current_cmd_log
644 .push(CmdLogEntry::Error(message.into().into_owned()));
645 }
646
647 pub fn run_cmd(&mut self, term: &mut Term, input: &[u8], cmd: Command) -> Res<()> {
650 self.run_cmd_async(term, input, cmd)?;
651 self.await_pending_cmd()?;
652 self.handle_pending_cmd()?;
653 Ok(())
654 }
655
656 pub fn run_cmd_async(&mut self, term: &mut Term, input: &[u8], cmd: Command) -> Res<()> {
659 let shown = crate::cmd_log::command_args(&cmd);
660 self.run_cmd_async_shown(term, input, cmd, shown)
661 }
662
663 pub(crate) fn run_shell_async(
666 &mut self,
667 term: &mut Term,
668 command: &str,
669 env: Vec<(&'static str, String)>,
670 ) -> Res<()> {
671 let mut cmd = Command::new("sh");
672 cmd.args(["-c", command]).envs(env);
673 self.run_cmd_async_shown(term, &[], cmd, command.to_owned().into())
674 }
675
676 fn run_cmd_async_shown(
677 &mut self,
678 term: &mut Term,
679 input: &[u8],
680 mut cmd: Command,
681 shown: Cow<'static, str>,
682 ) -> Res<()> {
683 cmd.env("CLICOLOR_FORCE", "1"); if self.state.pending_cmd.is_some() {
686 return Err(Error::CmdAlreadyRunning);
687 }
688
689 cmd.current_dir(self.state.repo.workdir().expect("No workdir"));
690
691 cmd.stdin(Stdio::piped());
692 cmd.stdout(Stdio::piped());
693 cmd.stderr(Stdio::piped());
694
695 let log_entry = self.state.current_cmd_log.push_cmd(shown);
696
697 ui::ui(term, &mut self.state)?;
698
699 let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
700
701 use std::io::Write;
702 child
703 .stdin
704 .take()
705 .unwrap()
706 .write_all(input)
707 .map_err(Error::Term)?;
708
709 self.state.pending_cmd = Some((child, log_entry));
710
711 if !self.state.enable_async_cmds {
712 self.await_pending_cmd()?;
713 }
714
715 Ok(())
716 }
717
718 fn await_pending_cmd(&mut self) -> Res<()> {
719 if let Some((child, _)) = &mut self.state.pending_cmd {
720 child.wait().map_err(Error::CouldntAwaitCmd)?;
721 }
722 Ok(())
723 }
724
725 fn handle_pending_cmd(&mut self) -> Res<()> {
727 let Some((ref mut child, ref mut log_rwlock)) = self.state.pending_cmd else {
728 return Ok(());
729 };
730
731 let Some(status) = child.try_wait().map_err(Error::CouldntAwaitCmd)? else {
732 return Ok(());
733 };
734
735 log::debug!("pending cmd finished with {status:?}");
736
737 let result = write_child_output_to_log(log_rwlock, child, status);
738 self.state.pending_cmd = None;
739 self.update_screens()?;
740 result?;
741
742 Ok(())
743 }
744
745 pub fn run_cmd_interactive(&mut self, term: &mut Term, mut cmd: Command) -> Res<()> {
746 cmd.env("CLICOLOR_FORCE", "1"); if self.state.pending_cmd.is_some() {
749 return Err(Error::CmdAlreadyRunning);
750 }
751
752 cmd.current_dir(self.state.repo.workdir().ok_or(Error::NoRepoWorkdir)?);
753
754 self.state
755 .current_cmd_log
756 .push_cmd_with_output(&cmd, "\n".into());
757 self.redraw_now(term)?;
758
759 eprint!("\r");
760
761 cmd.stderr(Stdio::piped());
763
764 term.reset_term_stay_on_alt_screeen(&self.state.config)
765 .map_err(Error::Term)?;
766
767 let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
768
769 drop(child.stdin.take());
771
772 let mut stderr = Vec::new();
773
774 tee(
775 child.stderr.as_mut(),
776 &mut [&mut std::io::stderr(), &mut stderr],
777 )
778 .map_err(Error::Term)?;
779
780 let status = child.wait().map_err(Error::CouldntAwaitCmd)?;
781
782 term.setup_term(&self.state.config).map_err(Error::Term)?;
783
784 let out_utf8 = String::from_utf8(strip_ansi_escapes::strip(stderr.clone()))
785 .expect("Error turning command output to String")
786 .into();
787
788 self.state.current_cmd_log.clear();
789 self.state
790 .current_cmd_log
791 .push_cmd_with_output(&cmd, out_utf8);
792
793 term.clear().map_err(Error::Term)?;
794 self.update_screens()?;
795
796 if !status.success() {
797 return Err(Error::CmdBadExit(
799 crate::cmd_log::command_args(&cmd).into_owned(),
800 status.code(),
801 String::new(),
802 ));
803 }
804
805 Ok(())
806 }
807
808 pub fn hide_menu(&mut self) {
809 if let Some(ref mut menu) = self.state.pending_menu {
810 menu.is_hidden = true;
811 }
812 }
813
814 fn unhide_menu(&mut self) {
815 if let Some(ref mut menu) = self.state.pending_menu {
816 menu.is_hidden = false;
817 }
818 }
819
820 pub(crate) fn selected_rev(&self) -> Option<Rev> {
821 self.screen().get_selected_item().data.rev()
822 }
823
824 pub(crate) fn prompt(&mut self, term: &mut Term, params: &PromptParams) -> Res<String> {
825 let default = (params.create_default_value)(self);
826
827 let prompt_text = match (params.prompt, &default) {
832 ("", _) => Cow::Borrowed(""),
833 (prompt, _) if params.bare => Cow::Borrowed(prompt),
834 (prompt, Some(default)) if !params.prefill => {
835 format!("{prompt} (default {default}):").into()
836 }
837 (prompt, _) => format!("{prompt}:").into(),
838 };
839
840 if params.hide_menu {
841 self.hide_menu();
842 }
843
844 self.state.prompt.set(prompt::PromptData {
845 prompt_text,
846 bare: params.bare,
847 });
848
849 if params.prefill
850 && let Some(default) = default
851 {
852 self.state.prompt.state.value = default;
853 self.state.prompt.state.move_to(usize::MAX);
854 }
855 self.state.prompt.start_history(params.history)?;
856
857 let result = self.handle_prompt(term, params);
858
859 self.unhide_menu();
860 self.state.prompt.reset();
861
862 result
863 }
864
865 fn handle_prompt(&mut self, term: &mut Term, params: &PromptParams) -> Res<String> {
866 self.redraw_now(term)?;
867
868 loop {
869 let event = term.read_event()?;
870 let was = self.state.prompt.state.value.clone();
871 self.handle_event(term, event)?;
872
873 if self.state.prompt.state.status == text_input::Status::Done {
874 let value = get_prompt_result(params, self)?;
875 if let Some(kind) = params.history {
876 self.state.prompt.remember(kind, &value)?;
877 }
878 return Ok(value);
879 } else if self.state.prompt.state.status == text_input::Status::Aborted {
880 return Err(Error::PromptAborted);
881 }
882 if let Some(on_edit) = ¶ms.on_edit
883 && self.state.prompt.state.value != was
884 {
885 let value = self.state.prompt.state.value.clone();
886 on_edit(self, &value);
887 }
888
889 self.redraw_now(term)?;
890 }
891 }
892
893 pub fn confirm(&mut self, term: &mut Term, prompt: &'static str) -> Res<()> {
894 self.hide_menu();
895 self.state.prompt.set(prompt::PromptData {
896 prompt_text: prompt.into(),
897 bare: false,
898 });
899
900 let result = self.handle_confirm(term);
901
902 self.unhide_menu();
903 self.state.prompt.reset();
904
905 result
906 }
907
908 fn handle_confirm(&mut self, term: &mut Term) -> Res<()> {
909 self.redraw_now(term)?;
910 loop {
911 let event = term.read_event()?;
912 self.handle_event(term, event)?;
913
914 match self.state.prompt.state.value.as_str() {
915 "y" => {
916 return Ok(());
917 }
918 "" => (),
919 _ => {
920 return Err(Error::PromptAborted);
921 }
922 }
923
924 self.redraw_now(term)?;
925 }
926 }
927
928 pub fn pick(&mut self, term: &mut Term, picker_state: PickerState) -> Res<Option<PickerData>> {
958 Ok(self
959 .pick_or_set_key(term, picker_state)?
960 .map(|picked| match picked {
961 Picked::Item(data) => data,
962 Picked::KeySet(..) => unreachable!("only a picker assigning keys sets them"),
963 }))
964 }
965
966 pub(crate) fn pick_or_set_key(
968 &mut self,
969 term: &mut Term,
970 picker_state: PickerState,
971 ) -> Res<Option<Picked>> {
972 self.state.picker = Some(picker_state);
973 let result = self.handle_picker(term, &mut |_, _| Ok(()));
974
975 self.state.picker = None;
976
977 result
978 }
979
980 pub(crate) fn pick_previewing(
982 &mut self,
983 term: &mut Term,
984 picker_state: PickerState,
985 preview: &mut dyn FnMut(&mut App, &PickerData) -> Res<()>,
986 ) -> Res<Option<PickerData>> {
987 self.state.picker = Some(picker_state);
988 let result = self.handle_picker(term, preview);
989
990 self.state.picker = None;
991
992 Ok(result?.map(|picked| match picked {
993 Picked::Item(data) => data,
994 Picked::KeySet(..) => unreachable!("only a picker assigning keys sets them"),
995 }))
996 }
997
998 pub fn pick_commit(&mut self, term: &mut Term) -> Res<Option<String>> {
1002 let params = self.render_params(self.screen().size);
1003 let selected = match &self.screen().get_selected_item().data {
1004 crate::item_data::ItemData::Commit { oid, .. } => Some(oid.clone()),
1005 _ => None,
1006 };
1007 let params = RenderParams {
1010 streaming: params.streaming && selected.is_none(),
1011 ..params
1012 };
1013 self.state.screens.push(screen::pager::log(
1014 Arc::clone(&self.state.config),
1015 Rc::clone(&self.state.repo),
1016 params,
1017 COMMIT_PICK_LIMIT,
1018 None,
1019 None,
1020 )?);
1021 self.screen_mut().menu = Some(Menu::CommitPicker);
1022 self.close_menu();
1023
1024 if let Some(selected) = selected {
1026 self.screen_mut().select_matching(|data| {
1027 matches!(data, crate::item_data::ItemData::Commit { oid, .. } if *oid == selected)
1028 });
1029 }
1030
1031 let depth = self.state.screens.len();
1032 self.redraw_now(term)?;
1033
1034 loop {
1035 let event = term.read_event()?;
1036 self.handle_event(term, event)?;
1037
1038 if let Some(oid) = self.state.picked_commit.take() {
1039 self.state.screens.truncate(depth - 1);
1040 self.close_menu();
1041 return Ok(Some(oid));
1042 }
1043 if self.state.screens.len() < depth {
1044 return Ok(None); }
1046
1047 self.redraw_now(term)?;
1048 }
1049 }
1050
1051 fn handle_picker(
1052 &mut self,
1053 term: &mut Term,
1054 preview: &mut dyn FnMut(&mut App, &PickerData) -> Res<()>,
1055 ) -> Res<Option<Picked>> {
1056 self.redraw_now(term)?;
1057 let selected = |app: &App| {
1058 app.state
1059 .picker
1060 .as_ref()
1061 .and_then(|picker| picker.selected())
1062 .map(|item| item.data.clone())
1063 };
1064 let mut previewed = selected(self);
1065
1066 loop {
1067 let event = term.read_event()?;
1068 self.handle_event(term, event)?;
1069
1070 if let Some(ref picker) = self.state.picker {
1071 let selected = || picker.selected().map(|item| item.data.clone());
1072 match *picker.status() {
1073 PickerStatus::Done => return Ok(selected().map(Picked::Item)),
1074 PickerStatus::KeySet(key) => {
1075 return Ok(selected().map(|data| Picked::KeySet(key, data)));
1076 }
1077 PickerStatus::Cancelled => return Ok(None),
1079 PickerStatus::Active => (),
1080 }
1081 }
1082
1083 let now = selected(self);
1086 if now != previewed && !matches!(term.poll_event(Duration::ZERO), Ok(true)) {
1087 if let Some(data) = &now {
1088 preview(self, data)?;
1089 }
1090 previewed = now;
1091 }
1092
1093 self.redraw_now(term)?;
1094 }
1095 }
1096
1097 fn feature_with_key_in_one_row(&self, key: event::KeyEvent) -> Option<String> {
1100 let picker = self.state.picker.as_ref()?;
1101 let quit = &self.state.config.picker_bindings.quit;
1102 match (key.code, key.modifiers) {
1103 (event::KeyCode::Char(c), event::KeyModifiers::NONE)
1104 if picker.is_one_row()
1105 && !c.is_ascii_digit()
1106 && !quit.iter().any(|b| b == &vec![(key.modifiers, key.code)]) =>
1107 {
1108 self.feature_with_key(c).filter(|name| {
1109 let general = &self.state.config.general;
1110 !general.commands.contains_key(name) && !general.views.contains_key(name)
1111 })
1112 }
1113 _ => None,
1114 }
1115 }
1116
1117 fn feature_with_key(&self, key: char) -> Option<String> {
1120 self.state
1121 .feature_keys
1122 .iter()
1123 .find(|(_, held)| **held == key)
1124 .map(|(feature, _)| feature.clone())
1125 }
1126
1127 fn handle_picker_input(&mut self, key: event::KeyEvent) -> Res<()> {
1128 if key.kind != event::KeyEventKind::Press {
1129 return Ok(());
1130 }
1131
1132 if let Some(feature) = self.feature_with_key_in_one_row(key) {
1136 return crate::ops::editor::toggle_feature(self, &feature);
1137 }
1138
1139 if let Some(ref mut picker) = self.state.picker {
1140 let bindings = &self.state.config.picker_bindings;
1141 let key_combo = vec![(key.modifiers, key.code)];
1142 let typed = match (key.code, key.modifiers) {
1143 (
1144 event::KeyCode::Char(c),
1145 event::KeyModifiers::NONE | event::KeyModifiers::SHIFT,
1146 ) => Some(c),
1147 _ => None,
1148 };
1149
1150 if bindings.next.iter().any(|b| b == &key_combo) {
1151 picker.next();
1152 } else if bindings.previous.iter().any(|b| b == &key_combo) {
1153 picker.previous();
1154 } else if bindings.done.iter().any(|b| b == &key_combo) {
1155 picker.done();
1156 } else if bindings.cancel.iter().any(|b| b == &key_combo) {
1157 picker.cancel();
1158 } else if bindings.toggle_list.iter().any(|b| b == &key_combo) {
1159 picker.toggle_one_row();
1160 } else if picker.is_one_row() {
1161 if bindings.quit.iter().any(|b| b == &key_combo) {
1163 picker.cancel();
1164 } else if let Some(digit) = typed.and_then(|c| c.to_digit(10)) {
1165 picker.select_number(digit as usize);
1166 }
1167 } else if picker.assigns_keys() {
1168 if picker.grid().is_some() && key.code == event::KeyCode::Left {
1172 picker.move_column(-1);
1173 } else if picker.grid().is_some() && key.code == event::KeyCode::Right {
1174 picker.move_column(1);
1175 } else if bindings.quit.iter().any(|b| b == &key_combo) {
1176 picker.cancel();
1177 } else if let Some(c) = typed {
1178 picker.set_key(c);
1179 }
1180 } else {
1181 picker.input_state.handle_key_event(key);
1183 picker.update_filter();
1184 }
1185 }
1186 Ok(())
1187 }
1188}
1189
1190fn overlay(features: &[String], theme: Option<&str>) -> Rc<[String]> {
1194 features
1195 .iter()
1196 .cloned()
1197 .chain(theme.map(str::to_owned))
1198 .collect()
1199}
1200
1201fn get_prompt_result(params: &PromptParams, app: &mut App) -> Res<String> {
1202 let input = app.state.prompt.state.value.as_str();
1203 if params.prefill {
1204 return Ok(input.to_owned());
1205 }
1206
1207 let default_value = (params.create_default_value)(app);
1208
1209 let value = match (input, &default_value) {
1210 ("", None) => "",
1211 ("", Some(selected)) => selected,
1212 (value, _) => value,
1213 };
1214
1215 Ok(value.to_string())
1216}
1217
1218fn tee(maybe_input: Option<&mut impl Read>, outputs: &mut [&mut dyn Write]) -> std::io::Result<()> {
1219 let Some(input) = maybe_input else {
1220 return Ok(());
1221 };
1222
1223 let mut buf = [0u8; 1024];
1224
1225 loop {
1226 let num_read = input.read(&mut buf)?;
1227 if num_read == 0 {
1228 break;
1229 }
1230
1231 let buf = &buf[..num_read];
1232 for output in &mut *outputs {
1233 output.write_all(buf)?;
1234 }
1235 }
1236
1237 Ok(())
1238}
1239
1240fn rebase_todo_screen(
1241 config: &Arc<Config>,
1242 repo: &Rc<Repository>,
1243 params: RenderParams,
1244 todo: RebaseTodo,
1245) -> Res<Screen> {
1246 screen::rebase_todo::create(
1247 Arc::clone(config),
1248 Rc::clone(repo),
1249 params,
1250 Rc::new(RefCell::new(todo)),
1251 )
1252}
1253
1254const COMMIT_PICK_LIMIT: usize = 256;
1256
1257const RESIZE_RENDER_DELAY: Duration = Duration::from_millis(50);
1261
1262pub(crate) fn root_menu(config: &Config) -> Option<Menu> {
1263 if config.general.always_show_help.enabled {
1264 Some(Menu::Help)
1265 } else {
1266 None
1267 }
1268}
1269
1270fn write_child_output_to_log(
1271 log_rwlock: &mut Arc<RwLock<CmdLogEntry>>,
1272 child: &mut Child,
1273 status: std::process::ExitStatus,
1274) -> Res<()> {
1275 let mut log = log_rwlock.write().unwrap();
1276
1277 let CmdLogEntry::Cmd { args, out: out_log } = log.deref_mut() else {
1278 unreachable!("pending_cmd is always CmdLogEntry::Cmd variant");
1279 };
1280
1281 drop(child.stdin.take());
1282
1283 let mut err_bytes = vec![];
1284 log::debug!("Reading stderr");
1285
1286 child
1287 .stderr
1288 .take()
1289 .unwrap()
1290 .read_to_end(&mut err_bytes)
1291 .map_err(Error::CouldntReadCmdOutput)?;
1292
1293 let mut out_bytes = err_bytes.clone();
1294 child
1295 .stdout
1296 .take()
1297 .unwrap()
1298 .read_to_end(&mut out_bytes)
1299 .map_err(Error::CouldntReadCmdOutput)?;
1300
1301 let out_string = String::from_utf8_lossy(&out_bytes).to_string();
1302 *out_log = Some(out_string.into());
1303
1304 if !status.success() {
1305 return Err(Error::CmdBadExit(
1306 args.to_string(),
1307 status.code(),
1308 String::from_utf8_lossy(&err_bytes).into_owned(),
1309 ));
1310 }
1311
1312 Ok(())
1313}
1314
1315type DefaultFn = Box<dyn Fn(&App) -> Option<String>>;
1316type EditFn = Box<dyn Fn(&mut App, &str)>;
1317
1318pub(crate) struct PromptParams {
1319 pub prompt: &'static str,
1320 pub create_default_value: DefaultFn,
1321 pub hide_menu: bool,
1322 pub prefill: bool,
1325 pub history: Option<prompt::HistoryKind>,
1326 pub bare: bool,
1329 pub on_edit: Option<EditFn>,
1331}
1332
1333impl Default for PromptParams {
1334 fn default() -> Self {
1335 Self {
1336 prompt: "",
1337 create_default_value: Box::new(|_| None),
1338 hide_menu: true,
1339 prefill: false,
1340 history: None,
1341 bare: false,
1342 on_edit: None,
1343 }
1344 }
1345}