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