use crossterm::event::{Event, KeyCode};
use ratatui::prelude::*;
use ratatui_textarea::TextArea;
use crate::github::GitHubRepo;
use crate::tui::widgets::{self, KeyResult};
use super::{HandleResult, Screen, WizardState, bordered_textarea};
pub(super) fn make_edit_github_screen(state: &WizardState) -> Screen {
let mut textarea = bordered_textarea();
if let Some(ref gh) = state.detected_github {
textarea.insert_str(format!("{}/{}", gh.owner, gh.repo));
}
Screen::EditGitHub {
textarea,
error: false,
}
}
fn handle_enter(mut state: WizardState, textarea: TextArea<'static>) -> HandleResult {
let text = textarea.lines().first().cloned().unwrap_or_default();
let trimmed = text.trim().to_string();
let matches_detected = state
.detected_github
.as_ref()
.is_some_and(|gh| format!("{}/{}", gh.owner, gh.repo) == trimmed);
if trimmed.is_empty() || matches_detected {
state.github_owner = None;
state.github_repo = None;
return Ok(KeyResult::Continue((state, Screen::OpenEditor(false))));
}
let Some((owner, repo)) = trimmed.split_once('/') else {
return Ok(KeyResult::Continue((
state,
Screen::EditGitHub {
textarea,
error: true,
},
)));
};
match GitHubRepo::new(owner, repo) {
Ok(gh) => {
state.github_owner = Some(gh.owner);
state.github_repo = Some(gh.repo);
Ok(KeyResult::Continue((state, Screen::OpenEditor(false))))
}
Err(_) => Ok(KeyResult::Continue((
state,
Screen::EditGitHub {
textarea,
error: true,
},
))),
}
}
pub(super) fn handle_edit_github(
state: WizardState,
mut textarea: TextArea<'static>,
error: bool,
event: Event,
) -> HandleResult {
match event {
Event::Key(key) => match key.code {
KeyCode::Enter => handle_enter(state, textarea),
KeyCode::Esc => Ok(KeyResult::Cancelled),
_ => {
textarea.input(key);
Ok(KeyResult::Continue((
state,
Screen::EditGitHub {
textarea,
error: false,
},
)))
}
},
_ => Ok(KeyResult::Continue((
state,
Screen::EditGitHub { textarea, error },
))),
}
}
pub(super) fn render_edit_github(
frame: &mut Frame,
area: Rect,
textarea: &TextArea<'static>,
error: bool,
) {
let question = if error {
crate::t!("edit-github-invalid-question")
} else {
crate::t!("edit-github-question")
};
let color = if error { Color::Red } else { Color::Yellow };
let chunks = widgets::wizard_layout(
area,
&[
Constraint::Length(widgets::paragraph_height(&question, area.width, 2)),
Constraint::Length(3),
Constraint::Min(1),
],
);
widgets::render_question(frame, chunks[0], &question, color);
frame.render_widget(textarea, chunks[1]);
widgets::render_help(frame, chunks[2], &crate::t!("edit-github-help"));
}
#[cfg(test)]
mod tests {
use crossterm::event::KeyCode;
use ratatui_textarea::TextArea;
use crate::github::GitHubRepo;
use super::super::test_helpers::*;
use super::super::{Screen, WizardState, handle_key};
#[test]
fn edit_github_empty_advances_with_no_owner_repo() {
let dir = temp_dir();
let state = make_state(&dir);
let screen = Screen::EditGitHub {
textarea: TextArea::default(),
error: false,
};
let (new_state, s) = unwrap_continue(handle_key(state, screen, key(KeyCode::Enter)));
assert_eq!(new_state.github_owner, None);
assert_eq!(new_state.github_repo, None);
assert!(matches!(s, Screen::OpenEditor(_)));
}
#[test]
fn edit_github_valid_owner_repo_advances() {
let dir = temp_dir();
let state = make_state(&dir);
let mut textarea = TextArea::default();
textarea.insert_str("acme/my-app");
let screen = Screen::EditGitHub {
textarea,
error: false,
};
let (new_state, s) = unwrap_continue(handle_key(state, screen, key(KeyCode::Enter)));
assert_eq!(new_state.github_owner, Some("acme".to_string()));
assert_eq!(new_state.github_repo, Some("my-app".to_string()));
assert!(matches!(s, Screen::OpenEditor(_)));
}
#[test]
fn edit_github_unmodified_detected_value_leaves_owner_repo_none() {
let dir = temp_dir();
let mut state = make_state(&dir);
state.detected_github = Some(GitHubRepo {
owner: "acme".to_string(),
repo: "my-app".to_string(),
});
let mut textarea = TextArea::default();
textarea.insert_str("acme/my-app");
let screen = Screen::EditGitHub {
textarea,
error: false,
};
let (new_state, s) = unwrap_continue(handle_key(state, screen, key(KeyCode::Enter)));
assert_eq!(new_state.github_owner, None);
assert_eq!(new_state.github_repo, None);
assert!(matches!(s, Screen::OpenEditor(_)));
}
#[test]
fn edit_github_modified_detected_value_sets_explicit_owner_repo() {
let dir = temp_dir();
let mut state: WizardState = make_state(&dir);
state.detected_github = Some(GitHubRepo {
owner: "acme".to_string(),
repo: "my-app".to_string(),
});
let mut textarea = TextArea::default();
textarea.insert_str("acme/other-repo");
let screen = Screen::EditGitHub {
textarea,
error: false,
};
let (new_state, _) = unwrap_continue(handle_key(state, screen, key(KeyCode::Enter)));
assert_eq!(new_state.github_owner, Some("acme".to_string()));
assert_eq!(new_state.github_repo, Some("other-repo".to_string()));
}
#[test]
fn edit_github_invalid_no_slash_shows_error() {
let dir = temp_dir();
let state = make_state(&dir);
let mut textarea = TextArea::default();
textarea.insert_str("notvalid");
let screen = Screen::EditGitHub {
textarea,
error: false,
};
let (_, s) = unwrap_continue(handle_key(state, screen, key(KeyCode::Enter)));
assert!(matches!(s, Screen::EditGitHub { error: true, .. }));
}
#[test]
fn edit_github_invalid_chars_shows_error() {
let dir = temp_dir();
let state = make_state(&dir);
let mut textarea = TextArea::default();
textarea.insert_str("bad owner/repo");
let screen = Screen::EditGitHub {
textarea,
error: false,
};
let (_, s) = unwrap_continue(handle_key(state, screen, key(KeyCode::Enter)));
assert!(matches!(s, Screen::EditGitHub { error: true, .. }));
}
#[test]
fn edit_github_esc_cancels() {
let dir = temp_dir();
let state = make_state(&dir);
let screen = Screen::EditGitHub {
textarea: TextArea::default(),
error: false,
};
assert_cancelled(handle_key(state, screen, key(KeyCode::Esc)));
}
#[test]
fn edit_github_q_key_types_character_not_cancel() {
let dir = temp_dir();
let state = make_state(&dir);
let screen = Screen::EditGitHub {
textarea: TextArea::default(),
error: false,
};
let (_, next) = unwrap_continue(handle_key(state, screen, key(KeyCode::Char('q'))));
assert!(matches!(next, Screen::EditGitHub { .. }));
}
#[test]
fn ui_renders_edit_github() {
crate::locale::set_locale("en");
use crate::tui::test_utils::{buffer_to_string, create_test_terminal};
let mut terminal = create_test_terminal();
let dir = temp_dir();
let state = make_state(&dir);
terminal
.draw(|frame| {
super::super::ui(
frame,
&state,
&Screen::EditGitHub {
textarea: TextArea::default(),
error: false,
},
)
})
.unwrap();
let content = buffer_to_string(terminal.backend().buffer());
assert!(content.contains("owner/repo"));
}
#[test]
fn ui_renders_edit_github_error() {
crate::locale::set_locale("en");
use crate::tui::test_utils::{buffer_to_string, create_test_terminal};
let mut terminal = create_test_terminal();
let dir = temp_dir();
let state = make_state(&dir);
terminal
.draw(|frame| {
super::super::ui(
frame,
&state,
&Screen::EditGitHub {
textarea: TextArea::default(),
error: true,
},
)
})
.unwrap();
let content = buffer_to_string(terminal.backend().buffer());
assert!(content.contains("Invalid"));
}
}