1use std::borrow::Cow;
2use std::cell::RefCell;
3use std::ffi::OsStr;
4use std::io::Read;
5use std::io::Write;
6use std::ops::DerefMut;
7use std::process::Child;
8use std::process::Command;
9use std::process::Stdio;
10use std::rc::Rc;
11use std::sync::Arc;
12use std::sync::RwLock;
13use std::time::Duration;
14
15use arboard::Clipboard;
16use crossterm::event;
17use crossterm::event::Event;
18use crossterm::event::KeyCode;
19use crossterm::event::KeyModifiers;
20use crossterm::event::MouseButton;
21use crossterm::event::MouseEvent;
22use crossterm::event::MouseEventKind;
23use git2::Repository;
24
25use crate::cli;
26use crate::cmd_log::CmdLog;
27use crate::cmd_log::CmdLogEntry;
28use crate::config::Config;
29use crate::error::Error;
30use crate::file_watcher::FileWatcher;
31use crate::item_data::Rev;
32use crate::items::RenderParams;
33use crate::menu::Menu;
34use crate::menu::PendingMenu;
35use crate::ops::Op;
36use crate::picker::PickerData;
37use crate::picker::PickerState;
38use crate::prompt;
39use crate::rebase_todo::RebaseTodo;
40use crate::screen;
41use crate::screen::Screen;
42use crate::screen::pager::Paged;
43use crate::term::Term;
44use crate::text_input;
45use crate::ui;
46
47use super::Res;
48
49pub(crate) struct State {
50 pub repo: Rc<Repository>,
51 pub config: Arc<Config>,
52 pub pending_keys: Vec<(KeyModifiers, KeyCode)>,
53 pub quit: bool,
54 pub exit_code: i32,
57 pub screens: Vec<Screen>,
58 pub pending_menu: Option<PendingMenu>,
59 pending_cmd: Option<(Child, Arc<RwLock<CmdLogEntry>>)>,
60 enable_async_cmds: bool,
61 pub current_cmd_log: CmdLog,
62 pub prompt: prompt::Prompt,
63 pub picker: Option<PickerState>,
64 pub picked_commit: Option<String>,
66 pub features: Rc<[String]>,
69 pub context: Option<Rc<str>>,
72 pub clipboard: Option<Clipboard>,
73 needs_redraw: bool,
74 file_watcher: Option<FileWatcher>,
75 inhibit_close_menu: bool,
76}
77
78pub struct App {
79 pub(crate) state: State,
80}
81
82impl App {
83 pub fn create(
84 repo: Rc<Repository>,
85 size: (u16, u16),
86 args: &cli::Args,
87 config: Arc<Config>,
88 enable_async_cmds: bool,
89 piped: Option<Paged>,
90 ) -> Res<Self> {
91 let params = RenderParams {
92 size,
93 features: Rc::from([]),
94 context: None,
95 };
96 let screens = match args.command {
97 _ if let Some(paged) = piped => {
99 vec![screen::pager::create(
100 Arc::clone(&config),
101 Rc::clone(&repo),
102 params,
103 paged,
104 )?]
105 }
106 Some(cli::Commands::Show { ref reference }) => {
107 vec![screen::show::create(
108 Arc::clone(&config),
109 Rc::clone(&repo),
110 params.clone(),
111 reference.clone(),
112 None,
113 )?]
114 }
115 Some(cli::Commands::Blame { ref rev, ref file }) => {
116 vec![screen::blame::create(
117 Arc::clone(&config),
118 Rc::clone(&repo),
119 params.clone(),
120 file.clone(),
121 rev.clone(),
122 None,
123 )?]
124 }
125 Some(cli::Commands::Rebase { ref upstream }) => {
126 let todo = RebaseTodo::capture(&repo, OsStr::new(upstream), &[])?;
127 vec![rebase_todo_screen(&config, &repo, params.clone(), todo)?]
128 }
129 Some(cli::Commands::SequenceEditor { ref file }) => {
130 let todo = RebaseTodo::read(&repo, file)?;
131 vec![rebase_todo_screen(&config, &repo, params.clone(), todo)?]
132 }
133 Some(cli::Commands::Completion { .. }) => {
135 unreachable!("completion is handled before the app starts")
136 }
137 None => vec![screen::status::create(
138 Arc::clone(&config),
139 Rc::clone(&repo),
140 params,
141 )?],
142 };
143
144 let clipboard = Clipboard::new()
145 .inspect_err(|e| log::warn!("Couldn't initialize clipboard: {e}"))
146 .ok();
147 let prompt = prompt::Prompt::with_history(repo.path().join("gitu"));
148
149 let mut app = Self {
150 state: State {
151 repo,
152 config,
153 pending_keys: vec![],
154 enable_async_cmds,
155 quit: false,
156 exit_code: 0,
157 screens,
158 pending_cmd: None,
159 pending_menu: None,
160 current_cmd_log: CmdLog::new(),
161 prompt,
162 picker: None,
163 picked_commit: None,
164 features: Rc::from([]),
165 context: None,
166 clipboard,
167 file_watcher: None,
168 needs_redraw: true,
169 inhibit_close_menu: false,
170 },
171 };
172
173 app.close_menu();
176 app.state.file_watcher = app.init_file_watcher()?;
177 Ok(app)
178 }
179
180 fn init_file_watcher(&mut self) -> Res<Option<FileWatcher>> {
181 if !self.state.config.general.refresh_on_file_change.enabled {
182 return Ok(None);
183 }
184
185 let hiding_untracked_files = !self
186 .state
187 .repo
188 .config()
189 .map_err(Error::ReadGitConfig)?
190 .get_bool("status.showUntrackedFiles")
191 .unwrap_or(true);
192
193 if hiding_untracked_files {
194 self.display_info("File watcher disabled (status.showUntrackedFiles is off)");
195
196 return Ok(None);
197 }
198
199 Ok(
200 FileWatcher::new(self.state.repo.workdir().expect("Bare repos unhandled"))
201 .inspect_err(|err| {
202 self.display_error(err.to_string());
203 self.display_info("File watcher disabled");
204 })
205 .ok(),
206 )
207 }
208
209 pub fn run(&mut self, term: &mut Term, max_tick_delay: Duration) -> Res<()> {
210 while !self.state.quit {
211 let event = if term.poll_event(max_tick_delay)? {
212 Some(term.read_event()?)
213 } else {
214 None
215 };
216
217 self.update(term, event)?;
218 }
219
220 Ok(())
221 }
222
223 pub fn update(&mut self, term: &mut Term, event: Option<Event>) -> Res<()> {
224 if let Some(e) = event {
225 self.handle_event(term, e)?;
226 }
227
228 if let Some(file_watcher) = &mut self.state.file_watcher
229 && file_watcher.pending_updates()
230 {
231 self.update_screens()?;
232 }
233
234 let handle_pending_cmd_result = self.handle_pending_cmd();
235 self.handle_result(handle_pending_cmd_result)?;
236
237 if self.state.needs_redraw {
238 self.redraw_now(term)?;
239 }
240
241 Ok(())
242 }
243
244 pub fn update_screens(&mut self) -> Res<()> {
245 self.rebuild_screens(Screen::refresh)
246 }
247
248 pub fn rerender_screens(&mut self) -> Res<()> {
253 self.rebuild_screens(Screen::refresh_keeping_position)
254 }
255
256 fn rebuild_screens(&mut self, rebuild: impl Fn(&mut Screen) -> Res<()>) -> Res<()> {
257 let features = Rc::clone(&self.state.features);
258 for screen in &mut self.state.screens {
259 screen.features = Rc::clone(&features);
260 screen.context = self.state.context.clone();
261 rebuild(screen)?;
262 }
263
264 self.stage_redraw();
265 Ok(())
266 }
267
268 pub fn handle_event(&mut self, term: &mut Term, event: Event) -> Res<()> {
269 match event {
270 Event::Resize(w, h) => {
271 for screen in self.state.screens.iter_mut() {
272 screen.resize(w, h)?;
273 }
274
275 self.stage_redraw();
276 Ok(())
277 }
278 Event::Mouse(ref mouse) => {
279 if self.state.config.general.mouse_support
280 && self.handle_mouse_input(term, mouse)?
281 {
282 self.stage_redraw();
283 }
284 Ok(())
285 }
286 Event::Key(mut key) => {
287 if self.state.pending_cmd.is_none() {
288 self.state.current_cmd_log.clear();
289 }
290
291 if self.state.pending_cmd.is_none() {
292 self.state.current_cmd_log.clear();
293 }
294
295 if matches!(key.code, KeyCode::Char(_) | KeyCode::BackTab) {
300 key.modifiers = key.modifiers.difference(KeyModifiers::SHIFT);
301 }
302
303 if self.state.picker.is_some() {
304 self.handle_picker_input(key);
305 } else if self.state.prompt.state.focused {
306 self.state.prompt.handle_key(key);
307 } else {
308 self.handle_key_input(term, key)?;
309 }
310
311 self.stage_redraw();
312 Ok(())
313 }
314 _ => Ok(()),
315 }
316 }
317
318 pub fn redraw_now(&mut self, term: &mut Term) -> Res<()> {
319 if self.state.screens.last_mut().is_some() {
320 ui::ui(term, &mut self.state)?;
321 self.state.needs_redraw = false;
322 };
323
324 Ok(())
325 }
326
327 pub fn print_if_one_screen(&self, term: &mut Term) -> Res<bool> {
330 let screen = self.state.screens.last().unwrap();
331 let (_, rows) = term.size().map_err(Error::Term)?;
332 if !screen.fits_in(rows.saturating_sub(1).into()) {
333 return Ok(false);
334 }
335
336 screen::print_screen(term, screen)?;
337 Ok(true)
338 }
339
340 pub fn stage_redraw(&mut self) {
341 self.state.needs_redraw = true;
342 }
343
344 fn handle_key_input(&mut self, term: &mut Term, key: event::KeyEvent) -> Res<()> {
345 if key.kind != event::KeyEventKind::Press {
346 return Ok(());
347 }
348
349 let menu = match &self.state.pending_menu {
350 None => self.base_menu().unwrap_or(Menu::Root),
351 Some(menu) if menu.menu == Menu::Help => Menu::Root,
352 Some(menu) => menu.menu,
353 };
354
355 self.state.pending_keys.push((key.modifiers, key.code));
356
357 let matching_bindings = self
358 .state
359 .config
360 .bindings
361 .match_bindings(&menu, &self.state.pending_keys)
362 .collect::<Vec<_>>();
363
364 match matching_bindings[..] {
365 [binding] => {
366 if binding.keys == self.state.pending_keys {
367 let op = binding.op.clone();
368 self.state.pending_keys.clear();
369 self.handle_op(op, term)?;
370 }
371 }
372 [] => self.state.pending_keys.clear(),
373 [_, ..] => (),
374 }
375
376 Ok(())
377 }
378
379 fn handle_mouse_input(&mut self, term: &mut Term, mouse: &MouseEvent) -> Res<bool> {
380 match mouse.kind {
381 MouseEventKind::Down(MouseButton::Left) => {
382 let click_y = mouse.row as usize;
383 if self.screen().is_valid_screen_line(click_y) {
384 let old_selected_item_id = self.screen().get_selected_item().id;
385 self.handle_op(Op::MoveToScreenLine(click_y), term)?;
386 let new_selected_item = self.screen().get_selected_item();
387
388 if old_selected_item_id == new_selected_item.id {
389 if new_selected_item.data.is_section() {
392 self.handle_op(Op::ToggleSection, term)?;
393 } else {
394 self.handle_op(Op::Show, term)?;
395 }
396 }
397 }
398 }
399 MouseEventKind::Down(MouseButton::Right) => {
400 let click_y = mouse.row as usize;
401 if self.screen().is_valid_screen_line(click_y) {
402 self.handle_op(Op::MoveToScreenLine(click_y), term)?;
403 self.handle_op(Op::Show, term)?;
404 }
405 }
406 MouseEventKind::ScrollUp => {
407 let scroll_lines = self.state.config.general.mouse_scroll_lines;
408 if scroll_lines > 0 {
409 self.screen_mut().scroll_view_up(scroll_lines);
410 }
411 }
412 MouseEventKind::ScrollDown => {
413 let scroll_lines = self.state.config.general.mouse_scroll_lines;
414 if scroll_lines > 0 {
415 self.screen_mut().scroll_view_down(scroll_lines);
416 }
417 }
418 _ => return Ok(false),
419 }
420
421 Ok(true)
422 }
423
424 pub(crate) fn handle_op(&mut self, op: Op, term: &mut Term) -> Res<()> {
425 let item_data = self.screen().selected_target();
426 let implementation = op.clone().implementation();
427
428 if let Some(mut action) = implementation.get_action(&item_data) {
429 let result = Rc::get_mut(&mut action).unwrap()(self, term);
430 self.handle_result(result)?;
431 if !self.state.inhibit_close_menu {
432 self.close_menu();
433 }
434 self.state.inhibit_close_menu = false;
435 }
436
437 Ok(())
438 }
439
440 fn handle_result<T>(&mut self, result: Res<T>) -> Res<()> {
441 match result {
442 Ok(_) => Ok(()),
443 Err(Error::NoMoreEvents) => Err(Error::NoMoreEvents),
444 Err(Error::PromptAborted) => Ok(()),
445 Err(error) => {
446 self.state
447 .current_cmd_log
448 .push(CmdLogEntry::Error(error.to_string()));
449
450 Ok(())
451 }
452 }
453 }
454
455 pub(crate) fn render_params(&self, size: (u16, u16)) -> RenderParams {
458 RenderParams {
459 size,
460 features: Rc::clone(&self.state.features),
461 context: self.state.context.clone(),
462 }
463 }
464
465 pub fn close_menu(&mut self) {
466 let hide = self.screen().menu.is_some() && !self.screen().show_menu;
467 self.state.pending_menu = self.base_menu().map(|menu| PendingMenu {
468 is_hidden: hide,
469 ..PendingMenu::init(menu)
470 });
471 }
472
473 pub(crate) fn base_menu(&self) -> Option<Menu> {
476 self.screen().menu.or_else(|| root_menu(&self.state.config))
477 }
478
479 pub fn inhibit_close_menu(&mut self) {
480 self.state.inhibit_close_menu = true;
481 }
482
483 pub(crate) fn screen_mut(&mut self) -> &mut Screen {
484 self.state.screens.last_mut().expect("No screen")
485 }
486
487 pub(crate) fn screen(&self) -> &Screen {
488 self.state.screens.last().expect("No screen")
489 }
490
491 pub fn display_info<S: Into<Cow<'static, str>>>(&mut self, message: S) {
493 self.state
494 .current_cmd_log
495 .push(CmdLogEntry::Info(message.into().into_owned()));
496 }
497
498 pub fn display_error<S: Into<Cow<'static, str>>>(&mut self, message: S) {
500 self.state
501 .current_cmd_log
502 .push(CmdLogEntry::Error(message.into().into_owned()));
503 }
504
505 pub fn run_cmd(&mut self, term: &mut Term, input: &[u8], cmd: Command) -> Res<()> {
508 self.run_cmd_async(term, input, cmd)?;
509 self.await_pending_cmd()?;
510 self.handle_pending_cmd()?;
511 Ok(())
512 }
513
514 pub fn run_cmd_async(&mut self, term: &mut Term, input: &[u8], mut cmd: Command) -> Res<()> {
517 cmd.env("CLICOLOR_FORCE", "1"); if self.state.pending_cmd.is_some() {
520 return Err(Error::CmdAlreadyRunning);
521 }
522
523 cmd.current_dir(self.state.repo.workdir().expect("No workdir"));
524
525 cmd.stdin(Stdio::piped());
526 cmd.stdout(Stdio::piped());
527 cmd.stderr(Stdio::piped());
528
529 let log_entry = self.state.current_cmd_log.push_cmd(&cmd);
530
531 ui::ui(term, &mut self.state)?;
532
533 let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
534
535 use std::io::Write;
536 child
537 .stdin
538 .take()
539 .unwrap()
540 .write_all(input)
541 .map_err(Error::Term)?;
542
543 self.state.pending_cmd = Some((child, log_entry));
544
545 if !self.state.enable_async_cmds {
546 self.await_pending_cmd()?;
547 }
548
549 Ok(())
550 }
551
552 fn await_pending_cmd(&mut self) -> Res<()> {
553 if let Some((child, _)) = &mut self.state.pending_cmd {
554 child.wait().map_err(Error::CouldntAwaitCmd)?;
555 }
556 Ok(())
557 }
558
559 fn handle_pending_cmd(&mut self) -> Res<()> {
561 let Some((ref mut child, ref mut log_rwlock)) = self.state.pending_cmd else {
562 return Ok(());
563 };
564
565 let Some(status) = child.try_wait().map_err(Error::CouldntAwaitCmd)? else {
566 return Ok(());
567 };
568
569 log::debug!("pending cmd finished with {status:?}");
570
571 let result = write_child_output_to_log(log_rwlock, child, status);
572 self.state.pending_cmd = None;
573 self.update_screens()?;
574 result?;
575
576 Ok(())
577 }
578
579 pub fn run_cmd_interactive(&mut self, term: &mut Term, mut cmd: Command) -> Res<()> {
580 cmd.env("CLICOLOR_FORCE", "1"); if self.state.pending_cmd.is_some() {
583 return Err(Error::CmdAlreadyRunning);
584 }
585
586 cmd.current_dir(self.state.repo.workdir().ok_or(Error::NoRepoWorkdir)?);
587
588 self.state
589 .current_cmd_log
590 .push_cmd_with_output(&cmd, "\n".into());
591 self.redraw_now(term)?;
592
593 eprint!("\r");
594
595 cmd.stderr(Stdio::piped());
597
598 term.reset_term_stay_on_alt_screeen(&self.state.config)
599 .map_err(Error::Term)?;
600
601 let mut child = cmd.spawn().map_err(Error::SpawnCmd)?;
602
603 drop(child.stdin.take());
605
606 let mut stderr = Vec::new();
607
608 tee(
609 child.stderr.as_mut(),
610 &mut [&mut std::io::stderr(), &mut stderr],
611 )
612 .map_err(Error::Term)?;
613
614 let status = child.wait().map_err(Error::CouldntAwaitCmd)?;
615
616 term.setup_term(&self.state.config).map_err(Error::Term)?;
617
618 let out_utf8 = String::from_utf8(strip_ansi_escapes::strip(stderr.clone()))
619 .expect("Error turning command output to String")
620 .into();
621
622 self.state.current_cmd_log.clear();
623 self.state
624 .current_cmd_log
625 .push_cmd_with_output(&cmd, out_utf8);
626
627 term.clear().map_err(Error::Term)?;
628 self.update_screens()?;
629
630 if !status.success() {
631 return Err(Error::CmdBadExit(
632 format!(
633 "{} {}",
634 cmd.get_program().to_string_lossy(),
635 cmd.get_args()
636 .map(|arg| arg.to_string_lossy())
637 .collect::<String>()
638 ),
639 status.code(),
640 ));
641 }
642
643 Ok(())
644 }
645
646 pub fn hide_menu(&mut self) {
647 if let Some(ref mut menu) = self.state.pending_menu {
648 menu.is_hidden = true;
649 }
650 }
651
652 fn unhide_menu(&mut self) {
653 if let Some(ref mut menu) = self.state.pending_menu {
654 menu.is_hidden = false;
655 }
656 }
657
658 pub(crate) fn selected_rev(&self) -> Option<Rev> {
659 self.screen().get_selected_item().data.rev()
660 }
661
662 pub(crate) fn prompt(&mut self, term: &mut Term, params: &PromptParams) -> Res<String> {
663 let default = (params.create_default_value)(self);
664
665 let prompt_text = match (params.prompt, &default) {
670 ("", _) => Cow::Borrowed(""),
671 (prompt, Some(default)) if !params.prefill => {
672 format!("{prompt} (default {default}):").into()
673 }
674 (prompt, _) => format!("{prompt}:").into(),
675 };
676
677 if params.hide_menu {
678 self.hide_menu();
679 }
680
681 self.state.prompt.set(prompt::PromptData { prompt_text });
682
683 if params.prefill
684 && let Some(default) = default
685 {
686 self.state.prompt.state.value = default;
687 self.state.prompt.state.move_to(usize::MAX);
688 }
689 self.state.prompt.start_history(params.history)?;
690
691 let result = self.handle_prompt(term, params);
692
693 self.unhide_menu();
694 self.state.prompt.reset();
695
696 result
697 }
698
699 fn handle_prompt(&mut self, term: &mut Term, params: &PromptParams) -> Res<String> {
700 self.redraw_now(term)?;
701
702 loop {
703 let event = term.read_event()?;
704 self.handle_event(term, event)?;
705
706 if self.state.prompt.state.status == text_input::Status::Done {
707 let value = get_prompt_result(params, self)?;
708 self.state.prompt.remember(&value)?;
709 return Ok(value);
710 } else if self.state.prompt.state.status == text_input::Status::Aborted {
711 return Err(Error::PromptAborted);
712 }
713
714 self.redraw_now(term)?;
715 }
716 }
717
718 pub fn confirm(&mut self, term: &mut Term, prompt: &'static str) -> Res<()> {
719 self.hide_menu();
720 self.state.prompt.set(prompt::PromptData {
721 prompt_text: prompt.into(),
722 });
723
724 let result = self.handle_confirm(term);
725
726 self.unhide_menu();
727 self.state.prompt.reset();
728
729 result
730 }
731
732 fn handle_confirm(&mut self, term: &mut Term) -> Res<()> {
733 self.redraw_now(term)?;
734 loop {
735 let event = term.read_event()?;
736 self.handle_event(term, event)?;
737
738 match self.state.prompt.state.value.as_str() {
739 "y" => {
740 return Ok(());
741 }
742 "" => (),
743 _ => {
744 return Err(Error::PromptAborted);
745 }
746 }
747
748 self.redraw_now(term)?;
749 }
750 }
751
752 pub fn pick(&mut self, term: &mut Term, picker_state: PickerState) -> Res<Option<PickerData>> {
782 self.state.picker = Some(picker_state);
783 let result = self.handle_picker(term);
784
785 self.state.picker = None;
786
787 result
788 }
789
790 pub fn pick_commit(&mut self, term: &mut Term) -> Res<Option<String>> {
794 let params = self.render_params(self.screen().size);
795 let selected = match &self.screen().get_selected_item().data {
796 crate::item_data::ItemData::Commit { oid, .. } => Some(oid.clone()),
797 _ => None,
798 };
799 self.state.screens.push(screen::log::create(
800 Arc::clone(&self.state.config),
801 Rc::clone(&self.state.repo),
802 params,
803 COMMIT_PICK_LIMIT,
804 None,
805 None,
806 )?);
807 self.screen_mut().menu = Some(Menu::CommitPicker);
808 self.close_menu();
809
810 if let Some(selected) = selected {
812 self.screen_mut().select_matching(|data| {
813 matches!(data, crate::item_data::ItemData::Commit { oid, .. } if *oid == selected)
814 });
815 }
816
817 let depth = self.state.screens.len();
818 self.redraw_now(term)?;
819
820 loop {
821 let event = term.read_event()?;
822 self.handle_event(term, event)?;
823
824 if let Some(oid) = self.state.picked_commit.take() {
825 self.state.screens.truncate(depth - 1);
826 self.close_menu();
827 return Ok(Some(oid));
828 }
829 if self.state.screens.len() < depth {
830 return Ok(None); }
832
833 self.redraw_now(term)?;
834 }
835 }
836
837 fn handle_picker(&mut self, term: &mut Term) -> Res<Option<PickerData>> {
838 self.redraw_now(term)?;
839
840 loop {
841 let event = term.read_event()?;
842 self.handle_event(term, event)?;
843
844 if let Some(ref picker) = self.state.picker {
845 if picker.is_done() {
846 return Ok(picker.selected().map(|item| item.data.clone()));
848 } else if picker.is_cancelled() {
849 return Ok(None);
851 }
852 }
853
854 self.redraw_now(term)?;
855 }
856 }
857
858 fn handle_picker_input(&mut self, key: event::KeyEvent) {
859 if key.kind != event::KeyEventKind::Press {
860 return;
861 }
862
863 if let Some(ref mut picker) = self.state.picker {
864 let bindings = &self.state.config.picker_bindings;
865 let key_combo = vec![(key.modifiers, key.code)];
866
867 if bindings.next.iter().any(|b| b == &key_combo) {
868 picker.next();
869 } else if bindings.previous.iter().any(|b| b == &key_combo) {
870 picker.previous();
871 } else if bindings.done.iter().any(|b| b == &key_combo) {
872 picker.done();
873 } else if bindings.cancel.iter().any(|b| b == &key_combo) {
874 picker.cancel();
875 } else {
876 picker.input_state.handle_key_event(key);
878 picker.update_filter();
879 }
880 }
881 }
882}
883
884fn get_prompt_result(params: &PromptParams, app: &mut App) -> Res<String> {
885 let input = app.state.prompt.state.value.as_str();
886 if params.prefill {
887 return Ok(input.to_owned());
888 }
889
890 let default_value = (params.create_default_value)(app);
891
892 let value = match (input, &default_value) {
893 ("", None) => "",
894 ("", Some(selected)) => selected,
895 (value, _) => value,
896 };
897
898 Ok(value.to_string())
899}
900
901fn tee(maybe_input: Option<&mut impl Read>, outputs: &mut [&mut dyn Write]) -> std::io::Result<()> {
902 let Some(input) = maybe_input else {
903 return Ok(());
904 };
905
906 let mut buf = [0u8; 1024];
907
908 loop {
909 let num_read = input.read(&mut buf)?;
910 if num_read == 0 {
911 break;
912 }
913
914 let buf = &buf[..num_read];
915 for output in &mut *outputs {
916 output.write_all(buf)?;
917 }
918 }
919
920 Ok(())
921}
922
923fn rebase_todo_screen(
924 config: &Arc<Config>,
925 repo: &Rc<Repository>,
926 params: RenderParams,
927 todo: RebaseTodo,
928) -> Res<Screen> {
929 screen::rebase_todo::create(
930 Arc::clone(config),
931 Rc::clone(repo),
932 params,
933 Rc::new(RefCell::new(todo)),
934 )
935}
936
937const COMMIT_PICK_LIMIT: usize = 256;
939
940pub(crate) fn root_menu(config: &Config) -> Option<Menu> {
941 if config.general.always_show_help.enabled {
942 Some(Menu::Help)
943 } else {
944 None
945 }
946}
947
948fn write_child_output_to_log(
949 log_rwlock: &mut Arc<RwLock<CmdLogEntry>>,
950 child: &mut Child,
951 status: std::process::ExitStatus,
952) -> Res<()> {
953 let mut log = log_rwlock.write().unwrap();
954
955 let CmdLogEntry::Cmd { args, out: out_log } = log.deref_mut() else {
956 unreachable!("pending_cmd is always CmdLogEntry::Cmd variant");
957 };
958
959 drop(child.stdin.take());
960
961 let mut out_bytes = vec![];
962 log::debug!("Reading stderr");
963
964 child
965 .stderr
966 .take()
967 .unwrap()
968 .read_to_end(&mut out_bytes)
969 .map_err(Error::CouldntReadCmdOutput)?;
970
971 child
972 .stdout
973 .take()
974 .unwrap()
975 .read_to_end(&mut out_bytes)
976 .map_err(Error::CouldntReadCmdOutput)?;
977
978 let out_string = String::from_utf8_lossy(&out_bytes).to_string();
979 *out_log = Some(out_string.into());
980
981 if !status.success() {
982 return Err(Error::CmdBadExit(args.to_string(), status.code()));
983 }
984
985 Ok(())
986}
987
988type DefaultFn = Box<dyn Fn(&App) -> Option<String>>;
989
990pub(crate) struct PromptParams {
991 pub prompt: &'static str,
992 pub create_default_value: DefaultFn,
993 pub hide_menu: bool,
994 pub prefill: bool,
997 pub history: Option<prompt::HistoryKind>,
998}
999
1000impl Default for PromptParams {
1001 fn default() -> Self {
1002 Self {
1003 prompt: "",
1004 create_default_value: Box::new(|_| None),
1005 hide_menu: true,
1006 prefill: false,
1007 history: None,
1008 }
1009 }
1010}