use bmux_text_edit::{SelectionMode, TextEditBuffer, TextMotion};
use bmux_tui::geometry::Rect;
use bmux_tui_components::form::{Form, FormFieldItem, FormOutcome, FormPolicy, FormState};
use bmux_tui_components::text_input::{TextInputPolicy, TextInputState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RalphStartDialogField {
LoopName,
WorkAreaPath,
Branch,
ValidationCommands,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RalphStartDialog {
loop_name: TextInputState,
work_area_path: TextInputState,
branch: TextInputState,
validation_commands: TextInputState,
focused_field: RalphStartDialogField,
status: String,
form: FormState,
}
impl RalphStartDialog {
#[must_use]
pub fn new(default_loop_name: &str, default_validation_commands: &[String]) -> Self {
let mut loop_name = TextEditBuffer::from_text(default_loop_name);
loop_name.move_cursor_with_selection(TextMotion::Start, SelectionMode::Extend);
let validation_commands = default_validation_commands.join("; ");
Self {
loop_name: TextInputState::new(loop_name),
work_area_path: TextInputState::new(TextEditBuffer::default()),
branch: TextInputState::new(TextEditBuffer::default()),
validation_commands: TextInputState::new(TextEditBuffer::from_text(
&validation_commands,
)),
focused_field: RalphStartDialogField::LoopName,
status: "Review the optional fields, then confirm to start or cancel.".to_owned(),
form: FormState::new(Some(0)),
}
}
#[must_use]
pub const fn loop_name(&self) -> &TextInputState {
&self.loop_name
}
#[must_use]
pub const fn work_area_path(&self) -> &TextInputState {
&self.work_area_path
}
#[must_use]
pub const fn branch(&self) -> &TextInputState {
&self.branch
}
#[must_use]
pub const fn validation_commands(&self) -> &TextInputState {
&self.validation_commands
}
#[must_use]
pub const fn focused_field(&self) -> RalphStartDialogField {
self.focused_field
}
pub const fn focused_input_mut(&mut self) -> &mut TextInputState {
match self.focused_field {
RalphStartDialogField::LoopName => &mut self.loop_name,
RalphStartDialogField::WorkAreaPath => &mut self.work_area_path,
RalphStartDialogField::Branch => &mut self.branch,
RalphStartDialogField::ValidationCommands => &mut self.validation_commands,
}
}
pub fn focus_next(&mut self) {
let fields = form_fields();
let loop_name = self.loop_name.buffer().text().to_owned();
let work_area_path = self.work_area_path.buffer().text().to_owned();
let branch = self.branch.buffer().text().to_owned();
let validation_commands = self.validation_commands.buffer().text().to_owned();
let values = [
Some(loop_name.as_str()),
Some(work_area_path.as_str()),
Some(branch.as_str()),
Some(validation_commands.as_str()),
];
if let FormOutcome::Focused(index) = Form::new(&fields, &values)
.policy(FormPolicy::wrapping())
.handle_event(&mut self.form, &bmux_tui::event::Event::Key(tab_stroke()))
{
self.set_focused_index(index);
}
}
const fn set_focused_index(&mut self, index: usize) {
self.focused_field = match index {
1 => RalphStartDialogField::WorkAreaPath,
2 => RalphStartDialogField::Branch,
3 => RalphStartDialogField::ValidationCommands,
_ => RalphStartDialogField::LoopName,
};
}
pub fn set_loop_name_content_area(&mut self, area: Rect) {
self.loop_name.set_content_area(area, &input_policy());
}
pub fn set_work_area_path_content_area(&mut self, area: Rect) {
self.work_area_path.set_content_area(area, &input_policy());
}
pub fn set_branch_content_area(&mut self, area: Rect) {
self.branch.set_content_area(area, &input_policy());
}
pub fn set_validation_commands_content_area(&mut self, area: Rect) {
self.validation_commands
.set_content_area(area, &input_policy());
}
#[must_use]
pub fn loop_name_text(&self) -> String {
self.loop_name.buffer().text().trim().to_owned()
}
#[must_use]
pub fn work_area_path_text(&self) -> Option<String> {
let text = self.work_area_path.buffer().text().trim().to_owned();
(!text.is_empty()).then_some(text)
}
#[must_use]
pub fn branch_text(&self) -> Option<String> {
let text = self.branch.buffer().text().trim().to_owned();
(!text.is_empty()).then_some(text)
}
#[must_use]
pub fn validation_command_texts(&self) -> Vec<String> {
self.validation_commands
.buffer()
.text()
.split(';')
.map(str::trim)
.filter(|command| !command.is_empty())
.map(ToOwned::to_owned)
.collect()
}
pub fn handle_event(
&mut self,
event: &bmux_tui::event::Event,
keymap: &super::keymap::BmuxKeyMap,
) -> RalphStartDialogOutcome {
use bmux_tui::event::Event;
use bmux_tui_components::text_input::TextInputControl;
match event {
Event::Paste(text) => {
let _ = TextInputControl::new(&input_policy())
.handle_paste(self.focused_input_mut(), text);
}
Event::Key(stroke) => match keymap.action("ralph_start", *stroke) {
Some("tui.control.cancel") => return RalphStartDialogOutcome::Canceled,
Some("tui.control.focus_next") => self.focus_next(),
Some("tui.control.submit") => return RalphStartDialogOutcome::Submit,
_ => {
let _ = super::text_input_flow::handle_key_in_scope(
self.focused_input_mut(),
keymap,
"ralph_start",
*stroke,
);
}
},
Event::Mouse(mouse) => {
let _ = TextInputControl::new(&input_policy())
.handle_mouse(self.focused_input_mut(), *mouse);
}
Event::Resize(_) | Event::Focus(_) | Event::Tick | Event::User(_) => {}
}
RalphStartDialogOutcome::Handled
}
#[must_use]
pub fn status(&self) -> &str {
&self.status
}
pub fn set_status(&mut self, status: impl Into<String>) {
self.status = status.into();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RalphStartDialogOutcome {
Handled,
Submit,
Canceled,
}
#[must_use]
pub const fn input_policy() -> TextInputPolicy {
TextInputPolicy::chat_composer()
}
fn form_fields() -> [FormFieldItem; 4] {
[
FormFieldItem::new("loop-name").required(true),
FormFieldItem::new("work-area-path"),
FormFieldItem::new("branch"),
FormFieldItem::new("validation-commands"),
]
}
const fn tab_stroke() -> bmux_keyboard::KeyStroke {
bmux_keyboard::KeyStroke::simple(bmux_keyboard::KeyCode::Tab)
}
#[cfg(test)]
mod tests {
use super::{RalphStartDialog, RalphStartDialogField};
#[test]
fn form_state_drives_wrapping_tab_focus_order() {
let mut dialog = RalphStartDialog::new("loop", &[]);
assert_eq!(dialog.focused_field(), RalphStartDialogField::LoopName);
dialog.focus_next();
assert_eq!(dialog.focused_field(), RalphStartDialogField::WorkAreaPath);
dialog.focus_next();
assert_eq!(dialog.focused_field(), RalphStartDialogField::Branch);
dialog.focus_next();
assert_eq!(
dialog.focused_field(),
RalphStartDialogField::ValidationCommands
);
dialog.focus_next();
assert_eq!(dialog.focused_field(), RalphStartDialogField::LoopName);
}
}