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