wisp/screens/git_diff/
mod.rs1use std::sync::Arc;
2
3use clankerdiff_ratatui::diff::InteractionPhase;
4use clankerdiff_ratatui::theme::ThemeChoice;
5use clankerdiff_ratatui::{DiffReviewState, DiffReviewWidget, InputOutcome, default_diff_keybindings};
6use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
7use ratatui::{
8 buffer::Buffer,
9 layout::{Position, Rect},
10 widgets::{Clear, StatefulWidget, Widget},
11};
12
13use crate::command::GitReviewCommand;
14use crate::git_review::{ClientState, DiffReviewEvent, DiffSnapshot, ReviewCapabilities};
15use crate::renderer::DrawContext;
16use crate::screens::reviewer::{crossterm_event, theme_selection};
17use crate::settings::builtin_review_theme_choices;
18use crate::surfaces::input::{GitReviewOutput, ReviewOutcome, UiEvent};
19use crate::view::generation::Generation;
20
21pub struct GitDiffScreen {
22 installed: Option<Arc<DiffSnapshot>>,
23 state: DiffReviewState,
24 theme_generation: Option<Generation>,
25}
26
27impl GitDiffScreen {
28 pub fn new() -> Self {
29 let mut state = DiffReviewState::loading();
30 let mut bindings = default_diff_keybindings();
31 bindings.retain(|binding| binding.key != KeyEvent::new(KeyCode::Char('g'), KeyModifiers::CONTROL).into());
32 state.set_keybindings(bindings);
33 state.set_theme_choices(builtin_review_theme_choices());
34 state.set_capabilities(ReviewCapabilities { clipboard: false, ..ReviewCapabilities::default() });
35
36 Self { installed: None, state, theme_generation: None }
37 }
38
39 pub fn set_theme_choices(&mut self, choices: Vec<ThemeChoice>) {
40 self.state.set_theme_choices(choices);
41 }
42
43 pub(crate) fn is_browsing(&self) -> bool {
44 self.state.interaction_phase() == InteractionPhase::Browse
45 }
46
47 pub fn on_ui_event(&mut self, event: UiEvent) -> Vec<GitReviewOutput> {
48 self.handle_event(crossterm_event(event))
49 }
50
51 pub fn install(&mut self, current: &ClientState) {
52 self.state.apply_client_state(current, &mut self.installed);
53 }
54
55 pub fn on_action_result(&mut self, result: Result<(), String>) {
56 match result {
57 Ok(()) => self.state.clear_repository_pending(),
58 Err(error) => self.state.set_repository_error(error),
59 }
60 }
61
62 pub fn render(&mut self, area: Rect, buf: &mut Buffer, cx: &mut DrawContext<'_>) -> Option<Position> {
63 if self.theme_generation != Some(cx.theme_generation) {
64 self.state.set_theme(cx.theme.review().clone());
65 self.theme_generation = Some(cx.theme_generation);
66 }
67 Clear.render(area, buf);
68 let title = format!("Git Diff ยท {:?}", self.state.scope());
69 DiffReviewWidget::new().title(title).render(area, buf, &mut self.state);
70 self.state.cursor_position()
71 }
72
73 fn handle_event(&mut self, event: Event) -> Vec<GitReviewOutput> {
74 let outcome = clankerdiff_ratatui::handle_crossterm_event(&mut self.state, event);
75 match outcome {
76 InputOutcome::Ignored
77 | InputOutcome::Consumed
78 | InputOutcome::Emitted(DiffReviewEvent::CopyFormattedReview(_)) => Vec::new(),
79 InputOutcome::ThemeSelected(id) => vec![GitReviewOutput::SetTheme(theme_selection(id))],
80 InputOutcome::Emitted(DiffReviewEvent::Cancel) => {
81 vec![GitReviewOutput::Outcome(ReviewOutcome::Cancelled)]
82 }
83 InputOutcome::Emitted(DiffReviewEvent::SubmitReview(submission)) => {
84 vec![GitReviewOutput::Outcome(ReviewOutcome::Submitted(submission.formatted))]
85 }
86 InputOutcome::Emitted(event) => {
87 if let DiffReviewEvent::SetScope(scope) = &event {
88 self.state.set_scope(*scope);
89 }
90 self.state.set_repository_pending();
91 vec![GitReviewOutput::Command(GitReviewCommand::Event(event))]
92 }
93 }
94 }
95}
96
97impl Default for GitDiffScreen {
98 fn default() -> Self {
99 Self::new()
100 }
101}