use ratatui::{
crossterm::event::{KeyCode, KeyModifiers},
layout::Constraint,
style::{Modifier, Style},
text::Text,
widgets::Padding,
};
use ratzgo::{
core::*,
scroll::ScrollAction,
widget::{BorderType, ListState, MountPoint, block, list},
};
use crate::{
ui::{
LogMsg, Message,
view::log::{LogFocus, LogLayout, bookmark_list, tag_list},
widgets::{LogHistory, LogHistoryState, Modal, TextAreaState},
},
utils::{
jj::{Abandon, Duplicate, LogMode, Rebase, Split, SplitMode, Squash},
tui::{BoxText, LogText},
},
};
#[derive(Debug)]
pub struct VState<'a> {
pub state: &'a mut LogHistoryState,
pub view: &'a LogText,
pub log_focus: &'a LogFocus,
pub log_layout: &'a LogLayout,
pub log_mode: &'a LogMode,
pub mount_point: &'a MountPoint<Message>,
pub modal_abandon: Option<&'a Abandon>,
pub modal_squash: Option<&'a Squash>,
pub modal_rebase: Option<&'a Rebase>,
pub modal_split: Option<&'a Split>,
pub modal_duplicate: Option<&'a Duplicate>,
pub modal_bookmark_list: Option<&'a Text<'static>>,
pub modal_bookmark_list_state: &'a mut ListState,
pub modal_bookmark_list_input: Option<&'a mut TextAreaState>,
pub modal_tag_list: Option<&'a Text<'static>>,
pub modal_tag_list_state: &'a mut ListState,
pub modal_tag_list_input: Option<&'a mut TextAreaState>,
pub modal_undo: bool,
pub modal_redo: bool,
pub modal_unsync: Option<(&'a BoxText, &'a mut ListState)>,
}
pub fn view<'a>(
VState {
state,
view,
log_focus,
log_layout,
log_mode,
mount_point,
modal_abandon,
modal_squash,
modal_rebase,
modal_split,
modal_duplicate,
modal_bookmark_list,
modal_bookmark_list_state,
modal_bookmark_list_input,
modal_tag_list,
modal_tag_list_state,
modal_tag_list_input,
modal_undo,
modal_redo,
modal_unsync,
}: VState<'a>,
) -> impl Into<Element<'a, LogMsg>> {
let hover = state.hovered();
if let Some(v) = modal_abandon {
mount_point.mount(
Modal::new("Abandon", v.msg())
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::AbandonConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::AbandonConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if let Some(squash) = modal_squash {
mount_point.mount(
Modal::new("Squash", squash.msg())
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::SquashConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::SquashConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if let Some(rebase) = modal_rebase {
mount_point.mount(
Modal::new("Rebase", rebase.msg())
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::RebaseConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::RebaseConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if let Some(dup) = modal_duplicate {
mount_point.mount(
Modal::new("Duplicate", dup.msg())
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::DuplicateConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::DuplicateConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if let Some(Split { id, mode }) = modal_split {
let content = match mode {
SplitMode::ParentChild => format!("split {id}"),
SplitMode::Parallel => format!("split {id} parallelly"),
};
mount_point.mount(
Modal::new("Split", content)
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::SplitConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::SplitConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if let Some(text) = modal_bookmark_list {
let list = bookmark_list::view(bookmark_list::VState {
view: text,
state: modal_bookmark_list_state,
input: modal_bookmark_list_input,
});
mount_point.mount(list, |area| {
area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3))
});
} else if let Some(text) = modal_tag_list {
let list = tag_list::view(tag_list::VState {
view: text,
state: modal_tag_list_state,
input: modal_tag_list_input,
});
mount_point.mount(list, |area| {
area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3))
});
} else if modal_undo {
mount_point.mount(
Modal::new("Undo", "undo an operation?")
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::UndoConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::UndoConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if modal_redo {
mount_point.mount(
Modal::new("Redo", "redo an operation?")
.on_key(
|k| k.code == KeyCode::Char('y'),
LogMsg::RedoConfirm(true).into(),
)
.on_key(
|k| k.code == KeyCode::Char('n') || k.code == KeyCode::Esc,
LogMsg::RedoConfirm(false).into(),
)
.on_key(|k| k.code == KeyCode::Char('?'), LogMsg::Help.into()),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
} else if let Some((bookmarks, state)) = modal_unsync {
let inner = list(state)
.items(bookmarks.get())
.decorate(|v| v.highlight_style(Style::default().add_modifier(Modifier::REVERSED)))
.active(true)
.on_key_with(|k| {
let msg = match k.code {
KeyCode::Char('k') => LogMsg::ScrollUnsync(ScrollAction::Fixed(-1)),
KeyCode::Char('j') => LogMsg::ScrollUnsync(ScrollAction::Fixed(1)),
KeyCode::Char('d') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollUnsync(ScrollAction::Viewport(50))
}
KeyCode::Char('u') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollUnsync(ScrollAction::Viewport(-50))
}
KeyCode::Char('f') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollUnsync(ScrollAction::Viewport(100))
}
KeyCode::Char('b') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollUnsync(ScrollAction::Viewport(-100))
}
KeyCode::Enter => LogMsg::PushConfirm(true),
KeyCode::Esc => LogMsg::PushConfirm(false),
KeyCode::Char('?') => LogMsg::Help,
_ => return None,
};
Some(msg.into())
});
mount_point.mount(
block(inner).bordered().border_type(BorderType::Rounded),
|area| area.centered(Constraint::Ratio(1, 2), Constraint::Ratio(1, 3)),
);
}
let yanking = state.yanking().is_some();
let inner = LogHistory::new(view, state)
.active(log_focus.is_history())
.on_key_with(move |k| {
let msg = match k.code {
KeyCode::Char('k') => LogMsg::ScrollHistory(ScrollAction::Fixed(-1)),
KeyCode::Char('j') => LogMsg::ScrollHistory(ScrollAction::Fixed(1)),
KeyCode::Char('d') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollHistory(ScrollAction::Viewport(50))
}
KeyCode::Char('d') if yanking && let Some(change) = view.beacons().get(hover) => {
LogMsg::Duplicate {
id: change.id.clone(),
}
}
KeyCode::Char('d') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Desc {
id: change.id.clone(),
}
}
KeyCode::Char('u') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollHistory(ScrollAction::Viewport(-50))
}
KeyCode::Char('u') => LogMsg::Undo,
KeyCode::Char('r') if k.modifiers == KeyModifiers::CONTROL => LogMsg::Redo,
KeyCode::Char('r') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Rebase {
id: change.id.clone(),
}
}
KeyCode::Char('f') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollHistory(ScrollAction::Viewport(100))
}
KeyCode::Char('b') if k.modifiers == KeyModifiers::CONTROL => {
LogMsg::ScrollHistory(ScrollAction::Viewport(-100))
}
KeyCode::Enter if *log_layout == LogLayout::HISTORY => {
LogMsg::Layout(LogLayout::HISTORY_FILES)
}
KeyCode::Enter if *log_layout == LogLayout::HISTORY_FILES => {
LogMsg::Layout(LogLayout::FILES_DIFF)
}
KeyCode::Esc if yanking => LogMsg::Unyank,
KeyCode::Esc if *log_layout == LogLayout::HISTORY_FILES => {
LogMsg::Layout(LogLayout::HISTORY)
}
KeyCode::Esc
if *log_mode != LogMode::Default && *log_layout == LogLayout::HISTORY =>
{
LogMsg::ResetMode
}
KeyCode::Char('K')
if k.modifiers == KeyModifiers::SHIFT
&& *log_layout == LogLayout::HISTORY_FILES =>
{
LogMsg::ScrollShow(ScrollAction::Fixed(-1))
}
KeyCode::Char('J')
if k.modifiers == KeyModifiers::SHIFT
&& *log_layout == LogLayout::HISTORY_FILES =>
{
LogMsg::ScrollShow(ScrollAction::Fixed(1))
}
KeyCode::Char('n') if let Some(change) = view.beacons().get(hover) => LogMsg::New {
parent: change.id.clone(),
},
KeyCode::Char('e') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Edit {
id: change.id.clone(),
}
}
KeyCode::Char('a') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Abandon {
id: change.id.clone(),
}
}
KeyCode::Char('s') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Squash {
id: change.id.clone(),
}
}
KeyCode::Char('x') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Split(Split {
id: change.id.clone(),
mode: SplitMode::ParentChild,
})
}
KeyCode::Char('X') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Split(Split {
id: change.id.clone(),
mode: SplitMode::Parallel,
})
}
KeyCode::Char('f') => LogMsg::Fetch,
KeyCode::Char('b') => LogMsg::BookmarkListOpen,
KeyCode::Char('t') => LogMsg::TagListOpen,
KeyCode::Char('p') => LogMsg::Push,
KeyCode::Char(' ') if let Some(change) = view.beacons().get(hover) => {
LogMsg::Yank {
id: change.id.clone(),
}
}
KeyCode::Char('?') => LogMsg::Help,
_ => return None,
};
Some(msg)
});
block(inner)
.bordered()
.border_type(BorderType::Rounded)
.decorate(|v| v.padding(Padding::horizontal(1)))
}