feat: pr file tree item switching
This commit is contained in:
@@ -28,6 +28,7 @@ pub(crate) struct FileTree {
|
||||
pub(crate) struct Item {
|
||||
item: util::file::FileTreeItem,
|
||||
is_expanded: bool,
|
||||
is_highlighed: bool,
|
||||
on_click: Box<dyn Fn(&mut gpui::Window, &mut gpui::App) + 'static>,
|
||||
}
|
||||
|
||||
@@ -38,6 +39,7 @@ struct FileTreeStateInner {
|
||||
list_state: gpui::ListState,
|
||||
collapsed_dirs: HashSet<Arc<str>>,
|
||||
visible_items: Vec<usize>,
|
||||
highlighted_items: HashSet<usize>,
|
||||
}
|
||||
|
||||
pub(crate) fn file_tree(
|
||||
@@ -57,6 +59,7 @@ impl FileTreeState {
|
||||
list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(50.)),
|
||||
collapsed_dirs: HashSet::new(),
|
||||
visible_items: Vec::new(),
|
||||
highlighted_items: HashSet::new(),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -66,6 +69,12 @@ impl FileTreeState {
|
||||
state.visible_items = (0..items.len()).collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
pub(crate) fn highlight_item(&mut self, index: usize) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.highlighted_items.clear();
|
||||
state.highlighted_items.insert(index);
|
||||
}
|
||||
|
||||
pub(crate) fn toggle_directory(
|
||||
&mut self,
|
||||
dir_path: &Arc<str>,
|
||||
@@ -125,6 +134,7 @@ impl gpui::RenderOnce for FileTree {
|
||||
let item = (self.item)(item_index, window, cx);
|
||||
let item = Item {
|
||||
is_expanded: !state.collapsed_dirs.contains(&item.full_path),
|
||||
is_highlighed: state.highlighted_items.contains(&item_index),
|
||||
item,
|
||||
on_click: Box::new(move |window, cx| {
|
||||
if let Some(f) = &on_item_click {
|
||||
@@ -153,7 +163,13 @@ impl gpui::RenderOnce for Item {
|
||||
.py_0p5()
|
||||
.pl(px(8. + (8 * self.item.level) as f32))
|
||||
.rounded_sm()
|
||||
.hover(|it| it.bg(theme.colors.surface_hover))
|
||||
.hover(|it| {
|
||||
if self.is_highlighed {
|
||||
it
|
||||
} else {
|
||||
it.bg(theme.colors.surface_hover)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, window, cx| (self.on_click)(window, cx))
|
||||
.when(
|
||||
matches!(self.item.kind, FileTreeItemKind::Directory),
|
||||
@@ -171,6 +187,10 @@ impl gpui::RenderOnce for Item {
|
||||
.when(matches!(self.item.kind, FileTreeItemKind::File), |it| {
|
||||
it.child(font_icon(FontIcon::FileBracesCorner).size_3())
|
||||
})
|
||||
.when(self.is_highlighed, |it| {
|
||||
it.bg(theme.colors.accent_muted)
|
||||
.text_color(theme.colors.accent_fg)
|
||||
})
|
||||
.child(text(gpui::SharedString::new(self.item.name)).text_sm())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod issue_list;
|
||||
mod pull_request_change_view;
|
||||
mod pull_request_diff_view;
|
||||
mod pull_request_file_tree;
|
||||
mod pull_request_view;
|
||||
|
||||
119
src/screen/dashboard/pull_request_change_view.rs
Normal file
119
src/screen/dashboard/pull_request_change_view.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
api, app,
|
||||
component::file_tree::{FileTreeState, file_tree},
|
||||
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||
screen::dashboard::pull_request_diff_view::PullRequestDiffView,
|
||||
util::{
|
||||
self,
|
||||
file::{FileTreeItem, FileTreeItemKind},
|
||||
},
|
||||
};
|
||||
use gpui::{AppContext, ParentElement, Styled, div};
|
||||
|
||||
pub(crate) struct PullRequestChangeView {
|
||||
selected_file_path: Option<Arc<str>>,
|
||||
diff_view: gpui::Entity<PullRequestDiffView>,
|
||||
|
||||
file_tree_query: query::Entity<api::issues::FetchPullRequestFileTree>,
|
||||
file_tree_state: FileTreeState,
|
||||
file_tree_items: Vec<FileTreeItem>,
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
pr_id: api::issues::Id,
|
||||
cx: &mut gpui::Context<PullRequestChangeView>,
|
||||
) -> PullRequestChangeView {
|
||||
let mut view = PullRequestChangeView {
|
||||
selected_file_path: None,
|
||||
diff_view: cx.new(|cx| PullRequestDiffView::new(pr_id.clone(), cx)),
|
||||
|
||||
file_tree_query: use_query(
|
||||
api::issues::FetchPullRequestFileTree {
|
||||
id: pr_id,
|
||||
first: 100,
|
||||
},
|
||||
cx,
|
||||
),
|
||||
file_tree_state: FileTreeState::new(),
|
||||
file_tree_items: Vec::new(),
|
||||
};
|
||||
view.on_create(cx);
|
||||
view
|
||||
}
|
||||
|
||||
impl PullRequestChangeView {
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
watch_query(
|
||||
&self.file_tree_query,
|
||||
|this, query, cx| {
|
||||
if let QueryStatus::Loaded(changed_files) = read_query(query, cx) {
|
||||
let tree = util::file::build_file_tree(changed_files, |f| &f.path);
|
||||
this.file_tree_state.reset(&tree);
|
||||
this.file_tree_items = tree;
|
||||
cx.notify();
|
||||
};
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_file_tree_item_click(&mut self, i: usize, cx: &mut gpui::Context<Self>) {
|
||||
let item = &self.file_tree_items[i];
|
||||
match item.kind {
|
||||
| FileTreeItemKind::Directory => {
|
||||
self.file_tree_state
|
||||
.toggle_directory(&item.full_path, &self.file_tree_items);
|
||||
cx.notify();
|
||||
}
|
||||
| FileTreeItemKind::File => {
|
||||
self.selected_file_path = Some(Arc::clone(&item.full_path));
|
||||
self.file_tree_state.highlight_item(i);
|
||||
self.diff_view.update(cx, |diff_view, cx| {
|
||||
diff_view.show_diff_for_file(&item.full_path, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for PullRequestChangeView {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let theme = app::current_theme(cx);
|
||||
let weak = cx.entity();
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.w_80()
|
||||
.h_full()
|
||||
.bg(theme.colors.surface_chrome)
|
||||
.border_r_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.p_1()
|
||||
.child(
|
||||
file_tree(self.file_tree_state.clone(), move |i, _, cx| {
|
||||
weak.read(cx).file_tree_items[i].clone()
|
||||
})
|
||||
.on_item_click(cx.listener(|this, i, _, cx| {
|
||||
this.handle_file_tree_item_click(*i, cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
gpui::AnyView::from(self.diff_view.clone())
|
||||
.cached(gpui::StyleRefinement::default().size_full()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,42 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{AppContext, IntoElement, div};
|
||||
|
||||
use crate::{
|
||||
api, app,
|
||||
component::{
|
||||
diff_view::{DiffViewContent, DiffViewState, diff_view},
|
||||
text::text,
|
||||
},
|
||||
api::{self},
|
||||
app,
|
||||
component::diff_view::{DiffViewContent, DiffViewState, diff_view},
|
||||
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||
screen::dashboard::pull_request_file_tree::{self, PullRequestFileTree},
|
||||
util::{self},
|
||||
util,
|
||||
};
|
||||
use gpui::{AppContext, ParentElement, Styled, div};
|
||||
|
||||
pub(crate) struct PullRequestDiffView {
|
||||
selected_file_path: Option<Arc<str>>,
|
||||
|
||||
pr_query: query::Entity<api::issues::FetchPullRequest>,
|
||||
file_tree_query: query::Entity<api::issues::FetchPullRequestFileTree>,
|
||||
content_diff_query: Option<query::Entity<api::repo::FetchFileDiff>>,
|
||||
|
||||
diff_view_state: DiffViewState,
|
||||
diff_view_content: Option<DiffViewContent>,
|
||||
|
||||
file_tree: gpui::Entity<PullRequestFileTree>,
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
pr_id: api::issues::Id,
|
||||
cx: &mut gpui::Context<PullRequestDiffView>,
|
||||
) -> PullRequestDiffView {
|
||||
let mut view = PullRequestDiffView {
|
||||
selected_file_path: None,
|
||||
pr_query: use_query(api::issues::FetchPullRequest { id: pr_id.clone() }, cx),
|
||||
file_tree_query: use_query(
|
||||
api::issues::FetchPullRequestFileTree {
|
||||
id: pr_id.clone(),
|
||||
first: 100,
|
||||
},
|
||||
cx,
|
||||
),
|
||||
content_diff_query: None,
|
||||
diff_view_state: DiffViewState::new(),
|
||||
diff_view_content: None,
|
||||
|
||||
file_tree: cx.new(|cx| pull_request_file_tree::new(pr_id, cx)),
|
||||
};
|
||||
view.on_create(cx);
|
||||
view
|
||||
current_file_path: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
impl PullRequestDiffView {
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
_ = cx
|
||||
.observe(&self.pr_query, |this, _, cx| {
|
||||
this.start_content_queries(cx);
|
||||
})
|
||||
.detach();
|
||||
pub(crate) fn new(pr_id: api::issues::Id, cx: &mut gpui::Context<Self>) -> Self {
|
||||
let mut s = Self {
|
||||
pr_query: use_query(api::issues::FetchPullRequest { id: pr_id }, cx),
|
||||
content_diff_query: None,
|
||||
diff_view_state: DiffViewState::new(),
|
||||
diff_view_content: None,
|
||||
current_file_path: None,
|
||||
};
|
||||
s.on_create(cx);
|
||||
s
|
||||
}
|
||||
|
||||
_ = cx
|
||||
.observe(&self.file_tree_query, |this, _, cx| {
|
||||
this.start_content_queries(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
// if pr is already loaded, start content queries
|
||||
pub(crate) fn show_diff_for_file(
|
||||
&mut self,
|
||||
file_path: &Arc<str>,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
self.current_file_path = Some(Arc::clone(file_path));
|
||||
self.start_content_queries(cx);
|
||||
}
|
||||
|
||||
@@ -71,14 +44,7 @@ impl PullRequestDiffView {
|
||||
if self.content_diff_query.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.selected_file_path.is_none()
|
||||
&& let QueryStatus::Loaded(files) = read_query(&self.file_tree_query, cx)
|
||||
{
|
||||
self.selected_file_path = files.first().map(|file| Arc::clone(&file.path));
|
||||
}
|
||||
|
||||
let Some(selected_file_path) = self.selected_file_path.as_deref() else {
|
||||
let Some(selected_file_path) = self.current_file_path.as_deref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -115,6 +81,14 @@ impl PullRequestDiffView {
|
||||
self.content_diff_query = Some(content_diff_query);
|
||||
}
|
||||
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
_ = cx
|
||||
.observe(&self.pr_query, |this, _, cx| {
|
||||
this.start_content_queries(cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn sync_content_diff_query(
|
||||
&mut self,
|
||||
query: &query::Entity<api::repo::FetchFileDiff>,
|
||||
@@ -122,8 +96,8 @@ impl PullRequestDiffView {
|
||||
) {
|
||||
if let Some(diff) = {
|
||||
match read_query(query, cx) {
|
||||
| QueryStatus::Loaded(diff) => Some(Arc::clone(diff)),
|
||||
| _ => None,
|
||||
| QueryStatus::Loaded(diff) => Some(Arc::clone(diff)),
|
||||
| _ => None,
|
||||
}
|
||||
} {
|
||||
self.load_diff_view(diff, cx);
|
||||
@@ -145,7 +119,7 @@ impl PullRequestDiffView {
|
||||
|
||||
let theme_syntax = theme.syntax;
|
||||
|
||||
if let Some(path) = &self.selected_file_path {
|
||||
if let Some(path) = &self.current_file_path {
|
||||
let path = Arc::clone(&path);
|
||||
let file_type = util::file::file_type_from_path(&path);
|
||||
|
||||
@@ -158,16 +132,16 @@ impl PullRequestDiffView {
|
||||
|
||||
_ = cx
|
||||
.spawn(async move |weak, cx| match tokio::join!(t1, t2) {
|
||||
| (Some(old_side_highlights), Some(new_side_highlights)) => {
|
||||
_ = weak.update(cx, |this, cx| {
|
||||
this.diff_view_state
|
||||
.set_old_side_highlights(old_side_highlights);
|
||||
this.diff_view_state
|
||||
.set_new_side_highlights(new_side_highlights);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
| _ => {}
|
||||
| (Some(old_side_highlights), Some(new_side_highlights)) => {
|
||||
_ = weak.update(cx, |this, cx| {
|
||||
this.diff_view_state
|
||||
.set_old_side_highlights(old_side_highlights);
|
||||
this.diff_view_state
|
||||
.set_new_side_highlights(new_side_highlights);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
| _ => {}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -178,42 +152,20 @@ impl gpui::Render for PullRequestDiffView {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
cx: &mut gpui::prelude::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
let content_diff = self
|
||||
.content_diff_query
|
||||
.as_ref()
|
||||
.map(|q| read_query(q, cx))
|
||||
.unwrap_or(QueryStatus::Loading);
|
||||
|
||||
match content_diff {
|
||||
| QueryStatus::Err(_) | QueryStatus::Loading => div()
|
||||
.size_full()
|
||||
.bg(theme.colors.surface)
|
||||
.p_4()
|
||||
.child(text("asd")),
|
||||
match (content_diff, &self.diff_view_content) {
|
||||
| (QueryStatus::Loaded(_), Some(content)) => {
|
||||
diff_view(self.diff_view_state.clone(), content.clone()).into_any_element()
|
||||
}
|
||||
|
||||
| QueryStatus::Loaded(_) => match &self.diff_view_content {
|
||||
| Some(content) => div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.w_80()
|
||||
.h_full()
|
||||
.bg(theme.colors.surface_chrome)
|
||||
.border_r_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.p_1()
|
||||
.child(self.file_tree.clone()),
|
||||
)
|
||||
.child(diff_view(self.diff_view_state.clone(), content.clone())),
|
||||
| None => div(),
|
||||
},
|
||||
| (_, _) => div().into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ use crate::{
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||
screen::dashboard::pull_request_diff_view::{self, PullRequestDiffView},
|
||||
screen::dashboard::pull_request_change_view::{self, PullRequestChangeView},
|
||||
};
|
||||
|
||||
pub(crate) struct PullRequestView {
|
||||
current_tab: Tab,
|
||||
|
||||
markdown_viewer: Option<gpui::Entity<MarkdownText>>,
|
||||
diff_view: Option<gpui::Entity<PullRequestDiffView>>,
|
||||
diff_view: Option<gpui::Entity<PullRequestChangeView>>,
|
||||
|
||||
pull_request_query: Option<query::Entity<api::issues::FetchPullRequest>>,
|
||||
}
|
||||
@@ -101,7 +101,7 @@ impl PullRequestView {
|
||||
}
|
||||
};
|
||||
|
||||
self.diff_view = pr_id.map(|id| cx.new(|cx| pull_request_diff_view::new(id, cx)));
|
||||
self.diff_view = pr_id.map(|id| cx.new(|cx| pull_request_change_view::new(id, cx)));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
api, app,
|
||||
screen::dashboard::{
|
||||
issue_list::{self, IssueList},
|
||||
pull_request_diff_view::{self, PullRequestDiffView},
|
||||
pull_request_change_view::{self, PullRequestChangeView},
|
||||
pull_request_view::{self, PullRequestView},
|
||||
titlebar::{self, TitleBar},
|
||||
},
|
||||
@@ -14,7 +14,7 @@ pub(crate) struct Screen {
|
||||
titlebar: gpui::Entity<TitleBar>,
|
||||
issue_list: gpui::Entity<IssueList>,
|
||||
pull_request_view: gpui::Entity<PullRequestView>,
|
||||
pull_request_diff_view: Option<gpui::Entity<PullRequestDiffView>>,
|
||||
pull_request_change_view: Option<gpui::Entity<PullRequestChangeView>>,
|
||||
|
||||
issue_filter: Option<&'static str>,
|
||||
}
|
||||
@@ -24,7 +24,7 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
|
||||
titlebar: cx.new(titlebar::new),
|
||||
issue_list: cx.new(issue_list::new),
|
||||
pull_request_view: cx.new(pull_request_view::new),
|
||||
pull_request_diff_view: None,
|
||||
pull_request_change_view: None,
|
||||
|
||||
issue_filter: None,
|
||||
};
|
||||
@@ -54,8 +54,8 @@ impl Screen {
|
||||
println!("change displayed pull request: {:?}", id);
|
||||
cx.notify();
|
||||
});
|
||||
self.pull_request_diff_view =
|
||||
Some(cx.new(|cx| pull_request_diff_view::new(id.clone(), cx)));
|
||||
self.pull_request_change_view =
|
||||
Some(cx.new(|cx| pull_request_change_view::new(id.clone(), cx)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user