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.
This commit is contained in:
817 files changed
+143101
No files matched your search
@@ -0,0 +1,174 @@
|
||||
//! The Branches panel: branches, remote branches and tags (TREE-07).
|
||||
|
||||
use ely_gpui_component::{
|
||||
buttons::{Button, ButtonVariant},
|
||||
feedback::ErrorView,
|
||||
git::{Branch, BranchList, GitTag, TagList},
|
||||
primitives::IconName,
|
||||
theme::ControlSize,
|
||||
};
|
||||
use gpui::{
|
||||
AnyElement, Context, Entity, InteractiveElement as _, IntoElement, ParentElement as _,
|
||||
StatefulInteractiveElement as _, Styled as _, div, prelude::FluentBuilder as _,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
branches::{Kind, Ref},
|
||||
changes::Changes,
|
||||
hist::when,
|
||||
tree::{Tree, text},
|
||||
};
|
||||
|
||||
fn branch(found: &Ref) -> Branch {
|
||||
let (ahead, behind) = found.track.unwrap_or((0, 0));
|
||||
Branch {
|
||||
name: found.name.clone().into(),
|
||||
remote: found.kind == Kind::Remote,
|
||||
current: found.current,
|
||||
ahead,
|
||||
behind,
|
||||
subject: found.subject.clone().into(),
|
||||
when: when(found.at),
|
||||
}
|
||||
}
|
||||
|
||||
fn tag(found: &Ref) -> GitTag {
|
||||
GitTag {
|
||||
name: found.name.clone().into(),
|
||||
message: found.message.clone().map(Into::into),
|
||||
commit: found.commit.clone().into(),
|
||||
when: when(found.at),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn branches(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 refs = match &opened.refs {
|
||||
None => return div().into_any_element(),
|
||||
Some(Err(error)) => {
|
||||
return ErrorView::new(
|
||||
"tree-refs-error",
|
||||
IconName::TriangleAlert,
|
||||
text(cx, "tree.refs.unread", &[]),
|
||||
)
|
||||
.detail(error.clone())
|
||||
.into_any_element();
|
||||
}
|
||||
Some(Ok(refs)) => refs,
|
||||
};
|
||||
let picked = read.picked_ref.clone();
|
||||
// A name shows picked only in its own kind's list.
|
||||
let picked_in = move |kind: Kind| {
|
||||
picked
|
||||
.clone()
|
||||
.filter(|(at, _)| *at == kind)
|
||||
.map(|(_, name)| name)
|
||||
};
|
||||
let pick = |kind: Kind| {
|
||||
let this = tree.downgrade();
|
||||
move |name: &gpui::SharedString, _: &mut gpui::Window, cx: &mut gpui::App| {
|
||||
let name = name.to_string();
|
||||
this.update(cx, |tree, cx| {
|
||||
tree.picked_ref = Some((kind, name));
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
let switch = |kind: Kind| {
|
||||
let this = tree.downgrade();
|
||||
move |name: &gpui::SharedString, _: &mut gpui::Window, cx: &mut gpui::App| {
|
||||
this.update(cx, |tree, cx| tree.switch(name, kind, cx)).ok();
|
||||
}
|
||||
};
|
||||
// Each delete asks first, as from the details.
|
||||
let delete = || {
|
||||
let this = tree.downgrade();
|
||||
move |name: &gpui::SharedString, _: &mut gpui::Window, cx: &mut gpui::App| {
|
||||
this.update(cx, |tree, cx| tree.delete_branch(name, false, cx))
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
// Local branches, the current first, then remote ones.
|
||||
let mut local: Vec<&Ref> = refs.iter().filter(|r| r.kind == Kind::Local).collect();
|
||||
local.sort_by_key(|r| !r.current);
|
||||
let remote: Vec<&Ref> = refs.iter().filter(|r| r.kind == Kind::Remote).collect();
|
||||
let mut tags: Vec<&Ref> = refs.iter().filter(|r| r.kind == Kind::Tag).collect();
|
||||
tags.sort_by_key(|r| std::cmp::Reverse(r.at));
|
||||
let (new_branch, new_tag) = (tree.downgrade(), tree.downgrade());
|
||||
let actions = div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
.child(
|
||||
Button::new("tree-new-branch", text(cx, "tree.branch.new", &[]))
|
||||
.size(ControlSize::Sm)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(move |_, window, cx| {
|
||||
new_branch
|
||||
.update(cx, |tree, cx| tree.ask_branch(None, window, cx))
|
||||
.ok();
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("tree-new-tag", text(cx, "tree.tag.new", &[]))
|
||||
.size(ControlSize::Sm)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(move |_, window, cx| {
|
||||
new_tag
|
||||
.update(cx, |tree, cx| tree.ask_tag(None, window, cx))
|
||||
.ok();
|
||||
}),
|
||||
);
|
||||
let selected = |list: BranchList, kind: Kind| match picked_in(kind) {
|
||||
Some(name) => list.selected(name),
|
||||
None => list,
|
||||
};
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(actions)
|
||||
.child(
|
||||
div()
|
||||
.id("tree-refs-scroll")
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.overflow_y_scroll()
|
||||
.px_2()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
selected(
|
||||
BranchList::new("tree-local", local.iter().map(|r| branch(r))),
|
||||
Kind::Local,
|
||||
)
|
||||
.on_pick(pick(Kind::Local))
|
||||
.on_switch(switch(Kind::Local))
|
||||
.on_delete(delete()),
|
||||
)
|
||||
.when(!remote.is_empty(), |list| {
|
||||
list.child(
|
||||
selected(
|
||||
BranchList::new("tree-remote", remote.iter().map(|r| branch(r))),
|
||||
Kind::Remote,
|
||||
)
|
||||
.on_pick(pick(Kind::Remote))
|
||||
.on_switch(switch(Kind::Remote)),
|
||||
)
|
||||
})
|
||||
.when(!tags.is_empty(), |list| {
|
||||
let tags_list = TagList::new("tree-tags", tags.iter().map(|r| tag(r)))
|
||||
.on_pick(pick(Kind::Tag));
|
||||
list.child(match picked_in(Kind::Tag) {
|
||||
Some(name) => tags_list.selected(name),
|
||||
None => tags_list,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
Reference in new issue
Block a user