Files
ElyOffice/apps/ely-tree/src/side_log.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

172 lines
5.7 KiB
Rust

//! The commit box, and history in the left panel.
use ely_gpui_component::{
buttons::{Button, ButtonVariant},
feedback::{EmptyState, ErrorView},
forms::{Choice, SearchInput, Select},
git::{CommitInput, CommitList},
primitives::IconName,
theme::{ActiveTheme as _, ControlSize},
};
use gpui::{
AnyElement, Context, Entity, IntoElement, ParentElement as _, SharedString, Styled as _, div,
prelude::FluentBuilder as _,
};
use crate::{
changes::Changes,
commits::Find,
tree::{Tree, text},
};
/// Who commits, how Git signs, and the message.
pub fn commit_box(
tree: &Entity<Tree>,
branch: SharedString,
staged: usize,
cx: &mut Context<Changes>,
) -> AnyElement {
let read = tree.read(cx);
let colors = cx.theme().colors.clone();
let (who, signs) = match read.active().and_then(|opened| opened.me.as_ref()) {
Some(Ok(me)) => {
let who = match (&me.name, &me.email) {
(Some(name), Some(email)) => format!("{name} <{email}>").into(),
_ => text(cx, "tree.me.unset", &[]),
};
let signs = me
.signs
.as_deref()
.map(|format| text(cx, "tree.me.signs", &[("format", format)]));
(who, signs)
}
Some(Err(error)) => (text(cx, "tree.me.unread", &[("error", error)]), None),
None => (SharedString::default(), None),
};
let (me, amend, commit) = (tree.downgrade(), tree.downgrade(), tree.downgrade());
let input = CommitInput::new("tree-commit", &read.subject, &read.body, branch, staged)
.amend(read.amend)
.on_amend(move |on, _, cx| {
amend.update(cx, |tree, cx| tree.set_amend(on, cx)).ok();
})
.when(!read.amend_loading, |input| {
input.on_commit(move |_, cx| {
commit.update(cx, |tree, cx| tree.commit(cx)).ok();
})
});
div()
.flex()
.flex_col()
.gap_1()
.p_2()
.border_t_1()
.border_color(colors.border)
.child(
div()
.flex()
.items_center()
.gap_2()
.child(
Button::new("tree-me", who)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.on_click(move |_, window, cx| {
me.update(cx, |tree, cx| tree.ask_identity(window, cx)).ok();
}),
)
.children(signs.map(|signs| div().text_color(colors.fg_muted).child(signs))),
)
.child(input)
.into_any_element()
}
fn find_key(find: Find) -> &'static str {
match find {
Find::Message => "message",
Find::Author => "author",
Find::Hash => "hash",
Find::Content => "content",
}
}
/// The search row, then the commits.
pub fn history(tree: &Entity<Tree>, cx: &mut Context<Changes>) -> AnyElement {
let read = tree.read(cx);
let Some(opened) = read.active() else {
return div().into_any_element();
};
let (id, hist) = (opened.id, &opened.hist);
let choices = ["message", "author", "hash", "content"]
.map(|key| Choice::new(key, text(cx, &format!("tree.find.{key}"), &[])));
let finding = tree.downgrade();
let find = Select::new("tree-find", choices)
.label(text(cx, "tree.find", &[]))
.size(ControlSize::Sm)
.selected(find_key(read.find))
.on_change(move |key, _, cx| {
let find = match key.as_ref() {
"author" => Find::Author,
"hash" => Find::Hash,
"content" => Find::Content,
_ => Find::Message,
};
finding
.update(cx, |tree, cx| {
tree.find = find;
tree.search(cx);
})
.ok();
});
let list = match (&hist.error, hist.rows.is_empty()) {
(Some(error), _) => ErrorView::new(
"tree-log-error",
IconName::TriangleAlert,
text(cx, "tree.log.unread", &[]),
)
.detail(error.clone())
.into_any_element(),
(None, true) if hist.read && !hist.loading => {
let key = if hist.query.is_some() {
"tree.log.none_found"
} else {
"tree.log.none"
};
EmptyState::new(
"tree-log-empty",
IconName::GitCommitHorizontal,
text(cx, key, &[]),
)
.into_any_element()
}
_ => {
let (pick, more) = (tree.downgrade(), tree.downgrade());
CommitList::new(("tree-log", id), hist.rows.clone())
.when(hist.query.is_none(), |list| list.graph(hist.graph.clone()))
.when_some(hist.pick.commit.clone(), |list, picked| {
list.selected(picked)
})
.on_pick(move |commit, _, cx| {
pick.update(cx, |tree, cx| tree.pick_commit(commit, cx))
.ok();
})
.on_end(move |cx| {
more.update(cx, |tree, cx| tree.more_log(cx)).ok();
})
.into_any_element()
}
};
div()
.size_full()
.flex()
.flex_col()
.child(
div().flex().gap_2().p_2().child(find).child(
div()
.flex_1()
.child(SearchInput::new("tree-search", &read.search).size(ControlSize::Sm)),
),
)
.child(div().flex_1().min_h_0().child(list))
.into_any_element()
}