mod confirm;
mod input;
mod select;
#[cfg(test)]
mod tests;
use crossterm::event::KeyEvent;
use ratatui::{
Frame,
layout::{Constraint, Layout, Rect},
};
use crate::jj::PushBulkMode;
use crate::keys;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DialogCallback {
DeleteBookmarks,
MoveBookmark {
name: String,
revision: String,
},
#[allow(dead_code)]
OpRestore,
GitPush,
Track,
BookmarkJump,
BookmarkForget,
GitFetch,
GitFetchBranch,
GitPushChange {
change_id: String,
},
GitPushRemoteSelect,
GitPushModeSelect { change_id: String },
GitPushBulkConfirm {
mode: PushBulkMode,
remote: Option<String>,
},
BookmarkMoveToWc { name: String },
BookmarkMoveBackwards { name: String },
RestoreFile { file_path: String },
RestoreAll,
Revert { revision: String },
SimplifyParents { revision: String },
Parallelize { from: String, to: String },
Fix { revision: String, change_id: String },
GitPushRevisions {
change_id: String,
bookmarks: Vec<String>,
},
GitPushMultiBookmarkMode {
change_id: String,
bookmarks: Vec<String>,
},
TagCreate,
TagDelete { name: String },
WorkspaceAdd,
WorkspaceForget { name: String },
WorkspaceRename { old_name: String },
BisectRun { good: String, bad: String },
MetaeditSelect {
commit_id: String,
change_id: String,
},
MetaeditSetAuthor {
commit_id: String,
change_id: String,
},
MetaeditNewChangeId {
commit_id: String,
change_id: String,
},
}
#[derive(Debug, Clone)]
pub struct SelectItem {
pub label: String,
pub value: String,
pub selected: bool,
}
#[derive(Debug, Clone)]
pub enum DialogKind {
Confirm {
title: String,
message: String,
detail: Option<String>,
},
Select {
title: String,
message: String,
items: Vec<SelectItem>,
detail: Option<String>,
single_select: bool,
},
Input {
title: String,
message: String,
buffer: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DialogResult {
Confirmed(Vec<String>),
Cancelled,
}
#[derive(Debug, Clone)]
pub struct Dialog {
pub kind: DialogKind,
pub cursor: usize,
pub callback_id: DialogCallback,
}
impl Dialog {
pub fn confirm(
title: impl Into<String>,
message: impl Into<String>,
detail: Option<String>,
callback_id: DialogCallback,
) -> Self {
Self {
kind: DialogKind::Confirm {
title: title.into(),
message: message.into(),
detail,
},
cursor: 0,
callback_id,
}
}
pub fn select(
title: impl Into<String>,
message: impl Into<String>,
items: Vec<SelectItem>,
detail: Option<String>,
callback_id: DialogCallback,
) -> Self {
Self {
kind: DialogKind::Select {
title: title.into(),
message: message.into(),
items,
detail,
single_select: false,
},
cursor: 0,
callback_id,
}
}
pub fn select_single(
title: impl Into<String>,
message: impl Into<String>,
items: Vec<SelectItem>,
detail: Option<String>,
callback_id: DialogCallback,
) -> Self {
Self {
kind: DialogKind::Select {
title: title.into(),
message: message.into(),
items,
detail,
single_select: true,
},
cursor: 0,
callback_id,
}
}
pub fn input(
title: impl Into<String>,
message: impl Into<String>,
callback_id: DialogCallback,
) -> Self {
Self {
kind: DialogKind::Input {
title: title.into(),
message: message.into(),
buffer: String::new(),
},
cursor: 0,
callback_id,
}
}
pub fn handle_key(&mut self, key: KeyEvent) -> Option<DialogResult> {
match &self.kind {
DialogKind::Confirm { .. } => self.handle_confirm_key(key),
DialogKind::Select { .. } => self.handle_select_key(key),
DialogKind::Input { .. } => self.handle_input_key(key),
}
}
pub fn render(&self, frame: &mut Frame, area: Rect) {
match &self.kind {
DialogKind::Confirm {
title,
message,
detail,
} => self.render_confirm(frame, area, title, message, detail.as_deref()),
DialogKind::Select {
title,
message,
items,
detail,
single_select,
} => self.render_select(
frame,
area,
title,
message,
items,
detail.as_deref(),
*single_select,
),
DialogKind::Input {
title,
message,
buffer,
} => self.render_input(frame, area, title, message, buffer),
}
}
}
pub(super) fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
let vertical_margin = area.height.saturating_sub(height) / 2;
let horizontal_margin = area.width.saturating_sub(width) / 2;
let vertical_layout = Layout::vertical([
Constraint::Length(vertical_margin),
Constraint::Length(height),
Constraint::Length(vertical_margin),
])
.split(area);
let horizontal_layout = Layout::horizontal([
Constraint::Length(horizontal_margin),
Constraint::Length(width),
Constraint::Length(horizontal_margin),
])
.split(vertical_layout[1]);
horizontal_layout[1]
}