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