Skip to main content

git_tailor/views/
split_select.rs

1// Copyright 2026 Thomas Johannesson
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Split strategy selection dialog
16
17use super::dialog::render_centered_dialog;
18use crate::app::{AppAction, AppMode, AppState, KeyCommand, SplitStrategy};
19use ratatui::{
20    Frame,
21    layout::Alignment,
22    style::{Color, Modifier, Style},
23    text::{Line, Span},
24};
25
26/// Handle an action while in SplitSelect mode.
27pub fn handle_key(action: KeyCommand, app: &mut AppState) -> AppAction {
28    match action {
29        KeyCommand::MoveUp => {
30            app.split_select_up();
31            AppAction::Handled
32        }
33        KeyCommand::MoveDown => {
34            app.split_select_down();
35            AppAction::Handled
36        }
37        KeyCommand::Confirm => {
38            let strategy = app.selected_split_strategy();
39            let commit_oid = app.commits[app.selection_index].oid.clone();
40            app.mode = AppMode::CommitList;
41            AppAction::PrepareSplit {
42                strategy,
43                commit_oid,
44            }
45        }
46        KeyCommand::ShowHelp => {
47            app.toggle_help();
48            AppAction::Handled
49        }
50        KeyCommand::Quit => {
51            app.mode = AppMode::CommitList;
52            AppAction::Handled
53        }
54        _ => AppAction::Handled,
55    }
56}
57
58/// Handle an action while in SplitConfirm mode.
59pub fn handle_confirm_key(action: KeyCommand, app: &mut AppState) -> AppAction {
60    match action {
61        KeyCommand::Confirm => {
62            if let AppMode::SplitConfirm(pending) =
63                std::mem::replace(&mut app.mode, AppMode::CommitList)
64            {
65                AppAction::ExecuteSplit {
66                    strategy: pending.strategy,
67                    commit_oid: pending.commit_oid,
68                    head_oid: pending.head_oid,
69                }
70            } else {
71                AppAction::Handled
72            }
73        }
74        KeyCommand::ShowHelp => {
75            app.toggle_help();
76            AppAction::Handled
77        }
78        KeyCommand::Quit => {
79            app.cancel_split_confirm();
80            AppAction::Handled
81        }
82        _ => AppAction::Handled,
83    }
84}
85
86/// Render the split strategy selection dialog as a centered overlay.
87pub fn render(app: &AppState, frame: &mut Frame) {
88    let commit_summary = app
89        .commits
90        .get(app.selection_index)
91        .map(|c| {
92            let short_oid = if c.oid.len() > 10 {
93                &c.oid[..10]
94            } else {
95                &c.oid
96            };
97            format!("{} {}", short_oid, c.summary)
98        })
99        .unwrap_or_default();
100
101    // Truncate summary if too long for dialog
102    let max_summary_len = 44;
103    let display_summary = if commit_summary.len() > max_summary_len {
104        format!("{}…", &commit_summary[..max_summary_len - 1])
105    } else {
106        commit_summary
107    };
108
109    let mut lines = vec![
110        Line::from(""),
111        Line::from(Span::styled(
112            format!(" {}", display_summary),
113            Style::default()
114                .fg(Color::White)
115                .add_modifier(Modifier::DIM),
116        )),
117        Line::from(""),
118        Line::from(Span::styled(
119            " Choose split strategy:",
120            Style::default()
121                .fg(Color::Yellow)
122                .add_modifier(Modifier::BOLD),
123        )),
124        Line::from(""),
125    ];
126
127    let strategy_index = match app.mode {
128        AppMode::SplitSelect { strategy_index } => strategy_index,
129        _ => 0,
130    };
131    for (i, strategy) in SplitStrategy::ALL.iter().enumerate() {
132        let selected = i == strategy_index;
133        let marker = if selected { "▸ " } else { "  " };
134        let style = if selected {
135            Style::default()
136                .fg(Color::Cyan)
137                .add_modifier(Modifier::BOLD)
138        } else {
139            Style::default().fg(Color::White)
140        };
141
142        lines.push(Line::from(Span::styled(
143            format!(" {}  {}", marker, strategy.label()),
144            style,
145        )));
146
147        let desc_style = Style::default().fg(Color::DarkGray);
148        lines.push(Line::from(Span::styled(
149            format!("        {}", strategy.description()),
150            desc_style,
151        )));
152        lines.push(Line::from(""));
153    }
154
155    lines.push(
156        Line::from(vec![
157            Span::styled("Enter ", Style::default().fg(Color::Cyan)),
158            Span::raw("Select   "),
159            Span::styled("Esc ", Style::default().fg(Color::Cyan)),
160            Span::raw("Cancel"),
161        ])
162        .alignment(Alignment::Center),
163    );
164    lines.push(Line::from(""));
165
166    let content_width = 50;
167
168    render_centered_dialog(frame, " Split Commit ", Color::Cyan, content_width, lines);
169}
170
171/// Render the large-split confirmation dialog as a centered overlay.
172pub fn render_split_confirm(app: &AppState, frame: &mut Frame) {
173    let pending = match &app.mode {
174        AppMode::SplitConfirm(p) => p,
175        _ => return,
176    };
177    let strategy_name = match pending.strategy {
178        crate::app::SplitStrategy::PerFile => "per file",
179        crate::app::SplitStrategy::PerHunk => "per hunk",
180        crate::app::SplitStrategy::PerHunkGroup => "per hunk group",
181    };
182
183    let lines = vec![
184        Line::from(""),
185        Line::from(Span::styled(
186            format!(
187                " This will create {} commits ({}).",
188                pending.count, strategy_name
189            ),
190            Style::default()
191                .fg(Color::Yellow)
192                .add_modifier(Modifier::BOLD),
193        )),
194        Line::from(""),
195        Line::from(Span::raw(" Do you want to proceed?")),
196        Line::from(""),
197        Line::from(vec![
198            Span::styled("Enter ", Style::default().fg(Color::Cyan)),
199            Span::raw("Confirm   "),
200            Span::styled("Esc ", Style::default().fg(Color::Cyan)),
201            Span::raw("Cancel"),
202        ])
203        .alignment(Alignment::Center),
204        Line::from(""),
205    ];
206
207    render_centered_dialog(frame, " Confirm Split ", Color::Yellow, 52, lines);
208}