use bytestring::ByteString;
use ratatui::macros::constraints;
use ratzgo::{core::*, event::DefaultContext, widget::row};
use crate::{
ui::{
BookmarkTrack, BookmarksMsg, HelpMsg, LogRelocate, MainState, Message, State,
view::{
bookmarks::{history, tree},
log::LogLayout,
nav::Tab,
},
},
utils::{
jj::LogMode,
tui::{LogText, TreeText},
},
};
pub fn view<'a>(state: &'a mut MainState) -> Element<'a, BookmarksMsg> {
let css = constraints![==1/3, ==2/3];
row! {
css;
[
tree::view(tree::VState {
view: &state.bookmarks,
state: &mut state.bookmarks_state,
mount_point: &state.mount_point,
modal_delete: state.bookmarks_modal_delete.as_ref(),
modal_remotes: state
.bookmarks_modal_remotes
.as_ref()
.map(|v| (v.remotes.as_slice(), &mut state.bookmarks_modal_remotes_state)),
}),
history::view(history::VState {
view: &state.bookmarks_history_view,
state: &mut state.bookmarks_history_state,
})
]
}
.into()
}
pub async fn update(
state: &mut MainState,
msg: BookmarksMsg,
ctx: &mut DefaultContext<Message, State>,
) {
match msg {
BookmarksMsg::UpdateTree(v) => {
state.bookmarks = v;
if state.bookmarks.get().is_empty() {
state.bookmarks_state.select(vec![]);
state.bookmarks_history_view = LogText::default();
state.bookmarks_history_state.reset();
state.bookmarks_history_debounce_mut().cancel();
return;
}
let reset = match state.bookmarks_state.selected() {
[bookmark] => state
.bookmarks
.get()
.iter()
.all(|bm| bm.identifier() != bookmark),
[bookmark, remote] => state
.bookmarks
.get()
.iter()
.find(|v| v.identifier() == bookmark)
.is_none_or(|v| v.children().iter().all(|v| v.identifier() != remote)),
[] => true,
_ => unreachable!(),
};
if reset {
state.bookmarks.get().iter().for_each(|v| {
state.bookmarks_state.open(vec![v.identifier().clone()]);
});
let bookmark = state.bookmarks.get()[0].identifier().clone();
state.bookmarks_state.select(vec![bookmark]);
}
if let Some((bookmark, remote)) = selected_bookmark(state) {
debounce_history(state, bookmark, remote);
}
}
BookmarksMsg::ScrollTree(v) => {
state.bookmarks_state.scroll_vertical(v);
if let Some((bookmark, remote)) = selected_bookmark(state) {
state.bookmarks_history_state.reset();
debounce_history(state, bookmark, remote);
}
}
BookmarksMsg::ScrollHistory(v) => {
state
.bookmarks_history_state
.scroll_vertical(&state.log_history, v);
}
BookmarksMsg::BookmarkOpen => {
let path = state.bookmarks_state.selected().to_vec();
state.bookmarks_state.open(path);
}
BookmarksMsg::BookmarkClose => {
let path = state.bookmarks_state.selected().to_vec();
state.bookmarks_state.close(&path);
}
BookmarksMsg::UpdateHistory { text, version } => {
if state.bookmarks_history_debounce().version() != version
&& let Some((bookmark, remote)) = selected_bookmark(state)
{
debounce_history(state, bookmark, remote);
} else {
state.bookmarks_history_view = text;
}
}
BookmarksMsg::ViewHistory => {
let bookmark = match selected_bookmark(state) {
Some((bookmark, Some(remote)))
if let Ok(true) = state
.jj_handle
.bookmark_remote_present(&bookmark, &remote)
.await =>
{
format!("{bookmark}@{remote}").into()
}
Some((name, None)) => name,
_ => return,
};
state.nav_tab = Tab::Log;
state.log_mode = LogMode::Bookmark(bookmark);
state.log_history_state.reset();
state.log_reloc = LogRelocate::Index {
index: 0,
file: None,
};
state.log_layout = LogLayout::HISTORY_FILES;
state.log_focus = state.log_layout.into();
state.bookmarks_history_state.reset();
ctx.queue().push(Message::Refresh);
}
BookmarksMsg::Track => {
if let [bm] = state.bookmarks_state.selected() {
match bm.split_once('@') {
Some((name, remote)) => {
match state.jj_handle.bookmark_track(name, remote).await {
Ok(_) => {
let name = bm.slice_ref(name);
state.bookmarks_state.select(vec![name]);
state.bookmarks_history_state.reset();
}
Err(e) => {
ratzgo::log::error("`bookmark track`", e.into_text());
}
}
}
None => {
let remotes_untrack =
match state.jj_handle.bookmark_remotes_untrack(bm).await {
Ok(v) => v,
Err(e) => {
ratzgo::log::error("`bookmark remotes untrack`", e.into_text());
return;
}
};
if remotes_untrack.is_empty() {
return;
}
state.bookmarks_modal_remotes = Some(BookmarkTrack {
bookmark: bm.clone(),
remotes: remotes_untrack,
});
state.bookmarks_modal_remotes_state.reset();
}
}
}
}
BookmarksMsg::TrackConfirm(yes) => {
if let Some(v) = state.bookmarks_modal_remotes.take()
&& yes
&& let Some(i) = state.bookmarks_modal_remotes_state.selected()
&& let Some(remote) = v.remotes.get(i)
{
match state.jj_handle.bookmark_track(&v.bookmark, remote).await {
Ok(_) => {
state
.bookmarks_state
.select(vec![v.bookmark, remote.as_str().into()]);
state.bookmarks_history_state.reset();
}
Err(e) => {
ratzgo::log::error("`bookmark track`", e.into_text());
}
}
}
}
BookmarksMsg::Untrack => {
if let [name, remote] = state.bookmarks_state.selected()
&& remote != "git"
{
match state.jj_handle.bookmark_untrack(name, remote).await {
Ok(_) => {
let bookmark = format!("{name}@{remote}");
state.bookmarks_state.select(vec![bookmark.into()]);
state.bookmarks_history_state.reset();
}
Err(e) => {
ratzgo::log::error("`bookmark untrack`", e.into_text());
}
}
}
}
BookmarksMsg::Delete => {
if let [name] = state.bookmarks_state.selected()
&& !name.contains('@')
{
state.bookmarks_modal_delete = Some(name.clone());
}
}
BookmarksMsg::DeleteConfirm(yes) => {
if let Some(name) = state.bookmarks_modal_delete.take()
&& yes
{
match state.jj_handle.bookmark_delete(&name).await {
Ok(_) => state.bookmarks_history_state.reset(),
Err(e) => ratzgo::log::error("`bookmark delete`", e.into_text()),
}
}
}
BookmarksMsg::ScrollRemotes(action) => {
if let Some(v) = &state.bookmarks_modal_remotes {
state
.bookmarks_modal_remotes_state
.scroll_vertical(action, v.remotes.len());
}
}
BookmarksMsg::Help => {
let page = if state.bookmarks_modal_delete.is_some() {
"confirm-modal"
} else if state.bookmarks_modal_remotes.is_some() {
"bookmarks-remotes"
} else {
"bookmarks-tree"
};
ctx.queue().push(HelpMsg::Page(page));
}
}
}
pub fn refresh(state: &mut MainState, ctx: &mut DefaultContext<Message, State>) {
let jj_handle = state.jj_handle.clone();
ctx.queue().spawn_try(async move {
jj_handle
.bookmark_tree()
.await
.map(|v| BookmarksMsg::UpdateTree(TreeText::new(v)))
});
}
fn debounce_history(state: &mut MainState, bookmark: ByteString, remote: Option<ByteString>) {
let jj = state.jj_handle.clone();
state
.bookmarks_history_debounce_mut()
.spawn_try(|version| async move {
let bookmark = match remote {
Some(remote)
if let Ok(true) = jj.bookmark_remote_present(&bookmark, &remote).await =>
{
format!("{bookmark}@{remote}").into()
}
None => bookmark,
_ => {
return Ok(BookmarksMsg::UpdateHistory {
text: LogText::default(),
version,
});
}
};
jj.log(&LogMode::Bookmark(bookmark))
.await
.map(|v| BookmarksMsg::UpdateHistory {
text: LogText::new(v),
version,
})
});
}
fn selected_bookmark(state: &MainState) -> Option<(ByteString, Option<ByteString>)> {
match state.bookmarks_state.selected() {
[bookmark] => Some((bookmark.clone(), None)),
[bookmark, remote] => Some((bookmark.clone(), Some(remote.clone()))),
_ => None,
}
}