use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use std::path::Path;
use super::{App, BranchAction, Flow, InputOp, Modal, PendingOp, byte_index};
impl App {
pub(super) fn handle_modal_key(&mut self, key: &KeyEvent) -> Flow {
match &self.modal {
Some(Modal::Confirm { .. }) => match key.code {
KeyCode::Char('y') => self.confirm_modal(),
KeyCode::Char('n') | KeyCode::Esc => self.modal = None,
_ => {}
},
Some(Modal::Input { .. }) => self.handle_input_key(key),
Some(Modal::BranchList { .. }) => self.handle_branch_list_key(key),
Some(Modal::Help) => match key.code {
KeyCode::Esc | KeyCode::Char('q' | '?') => self.modal = None,
_ => {}
},
None => {}
}
Flow::Continue
}
pub(super) fn confirm_modal(&mut self) {
let Some(Modal::Confirm { on_confirm, .. }) = self.modal.take() else {
return;
};
match on_confirm {
PendingOp::Discard { path } => {
self.vcs_op(move |vcs| vcs.discard(Path::new(&path)));
}
PendingOp::DeleteBranch(name) => {
self.message = None;
self.vcs_op(|vcs| vcs.delete_branch(&name));
if self.message.is_none() {
self.info(format!("deleted branch {name}"));
}
}
}
}
pub(super) fn handle_input_key(&mut self, key: &KeyEvent) {
let newline = (key.code == KeyCode::Enter && key.modifiers.contains(KeyModifiers::ALT))
|| (key.code == KeyCode::Char('j') && key.modifiers.contains(KeyModifiers::CONTROL));
match key.code {
_ if newline => {
let Some(Modal::Input { buffer, cursor, .. }) = self.modal.as_mut() else {
return;
};
buffer.insert(byte_index(buffer, *cursor), '\n');
*cursor += 1;
}
KeyCode::Esc => self.modal = None,
KeyCode::Enter => self.submit_input(),
code => {
let Some(Modal::Input { buffer, cursor, .. }) = self.modal.as_mut() else {
return;
};
match code {
KeyCode::Char(c) => {
buffer.insert(byte_index(buffer, *cursor), c);
*cursor += 1;
}
KeyCode::Backspace => {
if *cursor > 0 {
*cursor -= 1;
buffer.remove(byte_index(buffer, *cursor));
}
}
KeyCode::Left => *cursor = cursor.saturating_sub(1),
KeyCode::Right => *cursor = (*cursor + 1).min(buffer.chars().count()),
KeyCode::Home => *cursor = 0,
KeyCode::End => *cursor = buffer.chars().count(),
_ => {}
}
}
}
}
pub(crate) fn open_input(&mut self, title: String, buffer: String, on_submit: InputOp) {
self.modal = Some(Modal::Input {
cursor: buffer.chars().count(),
buffer,
title,
on_submit,
});
}
pub(super) fn submit_input(&mut self) {
let Some(Modal::Input {
buffer, on_submit, ..
}) = self.modal.take()
else {
return;
};
let body = buffer.trim();
if body.is_empty() {
return;
}
let source = self.active_review_source();
match on_submit {
InputOp::Comment { anchor } => {
self.review
.session_for_mut(&source)
.add_comment(&self.author, anchor, body);
if let Some(diff) = self.diff.as_mut() {
diff.visual_anchor = None;
}
self.after_session_change();
}
InputOp::Reply { comment_id } => {
if self
.review
.session_for_mut(&source)
.reply(&comment_id, &self.author, body)
{
self.after_session_change();
} else {
self.error("comment is gone; reply dropped");
}
}
InputOp::EditComment { comment_id } => {
if self
.review
.session_for_mut(&source)
.edit_comment(&comment_id, body)
{
self.after_session_change();
} else {
self.error("comment is gone; edit dropped");
}
}
InputOp::CreateBranch { checkout } => {
let name = body.to_owned();
self.message = None;
self.vcs_op(|vcs| vcs.create_branch(&name, checkout));
if self.message.is_none() {
if checkout {
self.info(format!("switched to new branch {name}"));
} else {
self.info(format!("created branch {name}"));
}
}
}
}
}
pub(super) fn branch_name_input(&mut self, checkout: bool) {
let title = if checkout {
"New branch (checkout)"
} else {
"New branch"
};
self.open_input(
title.to_owned(),
String::new(),
InputOp::CreateBranch { checkout },
);
}
pub(super) fn open_branch_list(&mut self, action: BranchAction) {
match self.review.vcs.branches() {
Ok(branches) if branches.is_empty() => {
self.modal = None;
self.info("no branches");
}
Ok(branches) => {
self.modal = Some(Modal::BranchList {
branches,
cursor: 0,
action,
});
}
Err(err) => {
self.modal = None;
self.error(err.to_string());
}
}
}
pub(super) fn handle_branch_list_key(&mut self, key: &KeyEvent) {
match key.code {
KeyCode::Esc | KeyCode::Char('q') => self.modal = None,
KeyCode::Char('j') | KeyCode::Down => {
if let Some(Modal::BranchList {
branches, cursor, ..
}) = self.modal.as_mut()
{
*cursor = (*cursor + 1).min(branches.len().saturating_sub(1));
}
}
KeyCode::Char('k') | KeyCode::Up => {
if let Some(Modal::BranchList { cursor, .. }) = self.modal.as_mut() {
*cursor = cursor.saturating_sub(1);
}
}
KeyCode::Enter => self.submit_branch_list(),
_ => {}
}
}
pub(super) fn submit_branch_list(&mut self) {
let Some(Modal::BranchList {
branches,
cursor,
action,
}) = self.modal.take()
else {
return;
};
let Some(name) = branches.get(cursor).map(|b| b.name.clone()) else {
return;
};
self.message = None;
match action {
BranchAction::Checkout => {
self.vcs_op(|vcs| vcs.checkout(&name));
if self.message.is_none() {
self.info(format!("checked out {name}"));
}
}
BranchAction::Delete => {
self.modal = Some(Modal::Confirm {
message: format!("Delete branch {name}?"),
on_confirm: PendingOp::DeleteBranch(name),
});
}
}
}
}