Skip to main content

wisp/screens/git_diff/
mod.rs

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