Files
ElyOffice/apps/ely-tree/src/look_pane.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

196 lines
6.8 KiB
Rust

//! The middle for a file's history or blame (TREE-06).
use ely_gpui_component::{
buttons::{ButtonVariant, IconButton},
feedback::{EmptyState, ErrorView},
git::{Blame, BlameView, FileHistory},
primitives::IconName,
theme::{ActiveTheme as _, ControlSize},
};
use gpui::{
AnyElement, Context, IntoElement, ParentElement as _, SharedString, Styled as _, div,
prelude::FluentBuilder as _,
};
use crate::{
blame::Blamed,
hist::when,
look::Look,
tree::{Side, Tree, text},
};
/// Blame's rows: each owner's words and its age, newest 0.
fn blames(blamed: &Blamed, uncommitted: &SharedString) -> Vec<Blame> {
let times = blamed
.owners
.iter()
.filter(|owner| !owner.uncommitted())
.map(|owner| owner.at);
let (oldest, newest) = (times.clone().min().unwrap_or(0), times.max().unwrap_or(0));
let span = (newest - oldest).max(1) as f32;
blamed
.owners
.iter()
.map(|owner| match owner.uncommitted() {
true => Blame {
commit: owner.commit.clone().into(),
author: uncommitted.clone(),
when: SharedString::default(),
subject: SharedString::default(),
age: 0.0,
},
false => Blame {
commit: owner.commit.clone().into(),
author: owner.author.clone().into(),
when: when(owner.at),
subject: owner.subject.clone().into(),
age: (newest - owner.at) as f32 / span,
},
})
.collect()
}
impl Tree {
/// Shows a commit from blame in History.
fn show_commit(&mut self, commit: &str, cx: &mut Context<Self>) {
self.look = None;
self.set_side(Side::History, cx);
self.pick_commit(commit, cx);
}
pub fn look_pane(&self, look: &Look, cx: &mut Context<Self>) -> AnyElement {
let colors = cx.theme().colors.clone();
let (key, path) = match look {
Look::FileLog { path, .. } => ("tree.file_log.of", path),
Look::Blame { path, .. } => ("tree.blame.of", path),
};
let this = cx.weak_entity();
let head = div()
.flex()
.items_center()
.gap_2()
.px_2()
.py_1p5()
.border_b_1()
.border_color(colors.border)
.child(div().flex_1().min_w_0().text_color(colors.fg).child(text(
cx,
key,
&[("path", path)],
)))
.child(
IconButton::new("tree-look-close", IconName::X)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.tooltip(text(cx, "tree.close_look", &[]))
.on_click(move |_, _, cx| {
this.update(cx, |tree, cx| tree.close_look(cx)).ok();
}),
);
let body = match look {
Look::FileLog { touches: None, .. } | Look::Blame { read: None, .. } => {
div().into_any_element()
}
Look::FileLog {
touches: Some(Err(error)),
..
}
| Look::Blame {
read: Some(Err(error)),
..
} => ErrorView::new(
"tree-look-error",
IconName::TriangleAlert,
text(cx, "tree.look.unread", &[]),
)
.detail(error.clone())
.into_any_element(),
Look::FileLog {
touches: Some(Ok(touches)),
rows,
picked,
view,
repo,
..
} => {
let this = cx.weak_entity();
let list = FileHistory::new(("tree-file-log", *repo), rows.clone())
.when_some(
picked.map(|at| touches[at].commit.id.clone()),
|list, id| list.selected(id),
)
.on_pick({
let ids: Vec<String> = touches
.iter()
.map(|touch| touch.commit.id.clone())
.collect();
move |id, _, cx| {
if let Some(at) = ids.iter().position(|known| known == id.as_ref()) {
this.update(cx, |tree, cx| tree.pick_touch(at, cx)).ok();
}
}
});
let shown = match picked {
Some(at) => {
let id = SharedString::from(format!(
"tree-file-log-diff-{}",
touches[*at].commit.id
));
self.view_only(id.into(), &touches[*at].path, view.as_ref(), cx)
}
None => EmptyState::new(
"tree-file-log-none",
IconName::History,
text(cx, "tree.commit.pick", &[]),
)
.into_any_element(),
};
div()
.size_full()
.flex()
.child(
div()
.flex_none()
.w_96()
.h_full()
.p_2()
.border_r_1()
.border_color(colors.border)
.child(list),
)
.child(div().flex_1().min_w_0().h_full().child(shown))
.into_any_element()
}
Look::Blame {
read: Some(Ok(blamed)),
repo,
..
} => {
let uncommitted = text(cx, "tree.blame.uncommitted", &[]);
let this = cx.weak_entity();
BlameView::new(
("tree-blame", *repo),
blamed.code.clone(),
blamed.lines.clone(),
blames(blamed, &uncommitted),
)
.on_commit(move |commit, _, cx| {
// Lines not yet committed have no commit to show.
if !commit.bytes().all(|b| b == b'0') {
this.update(cx, |tree, cx| tree.show_commit(commit, cx))
.ok();
}
})
.into_any_element()
}
};
div()
.size_full()
.flex()
.flex_col()
.child(head)
.child(div().flex_1().min_h_0().child(body))
.into_any_element()
}
}