feat: syntax highlighting for diff view

This commit is contained in:
2026-05-25 00:08:22 +01:00
parent b3e041a257
commit a6cf96ea96
20 changed files with 1295 additions and 722 deletions

View File

@@ -10,7 +10,7 @@ use crate::{
font_icon::{FontIcon, FontIconSvg, font_icon},
text::text,
},
query::{self, QueryStatus, read_query, use_query},
query::{self, QueryStatus, read_query, use_query, watch_query},
util::str::ToSharedString,
};
@@ -56,28 +56,44 @@ pub(crate) fn new(cx: &mut gpui::Context<IssueList>) -> IssueList {
impl IssueList {
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
cx.observe(&self.pr_query, |this, _, cx| {
let data = read_query(&this.pr_query, cx);
if let QueryStatus::Loaded(res) = data {
let old_len = this.list_state.item_count();
let new_len = res.items.len();
let pr_query = self.pr_query.clone();
let new_items = res.items.iter().enumerate().map(|(i, it)| IssueListItem {
watch_query(&pr_query, Self::sync_pr_query, cx).detach();
}
fn sync_pr_query(
&mut self,
query: &query::Entity<api::issues::ListPullRequests>,
cx: &mut gpui::Context<Self>,
) {
let data = read_query(query, cx);
if let QueryStatus::Loaded(res) = data {
let selected_id = self
.list_items
.iter()
.find(|item| item.is_selected)
.map(|item| item.id.clone());
let old_len = self.list_state.item_count();
let new_len = res.items.len();
self.list_items = res
.items
.iter()
.enumerate()
.map(|(i, it)| IssueListItem {
is_selected: selected_id.as_ref().is_some_and(|id| *id == it.id),
id: it.id.clone(),
repo_name: Some(it.repo_slug.to_shared_string()),
title: it.title.to_shared_string(),
description: None,
status: it.state,
is_selected: false,
is_last: i == new_len - 1,
is_draft: it.is_draft,
});
})
.collect();
this.list_items.splice(old_len..old_len, new_items);
this.list_state.splice(old_len..old_len, new_len);
}
})
.detach();
self.list_state.splice(0..old_len, new_len);
}
}
fn on_item_click(&mut self, i: usize, cx: &mut gpui::Context<Self>) {

View File

@@ -6,9 +6,10 @@ use crate::{
diff_view::{DiffViewContent, DiffViewState, diff_view},
text::text,
},
query::{self, QueryStatus, observe_query, read_query, use_query},
query::{self, QueryStatus, read_query, use_query, watch_query},
util,
};
use gpui::{ParentElement, Styled, div};
use gpui::{AppContext, ParentElement, Styled, div};
pub(crate) struct PullRequestDiffView {
selected_file_path: Option<Arc<str>>,
@@ -104,23 +105,68 @@ impl PullRequestDiffView {
},
cx,
);
_ = observe_query(
&content_diff_query,
|this, query, cx| {
if let QueryStatus::Loaded(diff) = read_query(query, cx) {
println!("diff len {}", diff.len());
this.diff_view_state.reset(diff.len());
this.diff_view_content = Some(Arc::clone(diff).into());
}
cx.notify();
},
cx,
)
.detach();
_ = watch_query(&content_diff_query, Self::sync_content_diff_query, cx).detach();
self.content_diff_query = Some(content_diff_query);
}
fn sync_content_diff_query(
&mut self,
query: &query::Entity<api::repo::FetchFileDiff>,
cx: &mut gpui::Context<Self>,
) {
if let Some(diff) = {
match read_query(query, cx) {
| QueryStatus::Loaded(diff) => Some(Arc::clone(diff)),
| _ => None,
}
} {
self.load_diff_view(diff, cx);
cx.notify();
}
}
fn load_diff_view(
&mut self,
content_diff: Arc<util::diff::ContentDiff>,
cx: &mut gpui::Context<Self>,
) {
let theme = app::current_theme(cx);
let old_content = content_diff.old_content.clone();
let new_content = content_diff.new_content.clone();
self.diff_view_state.reset(content_diff.len());
self.diff_view_content = Some(content_diff.into());
let theme_syntax = theme.syntax;
if let Some(path) = &self.selected_file_path {
let path = Arc::clone(&path);
let file_type = util::file::file_type_from_path(&path);
let t1 = cx.background_spawn(async move {
util::syntax_highlight::highlight_content(old_content, file_type, &theme_syntax)
});
let t2 = cx.background_spawn(async move {
util::syntax_highlight::highlight_content(new_content, file_type, &theme_syntax)
});
_ = 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();
});
}
| _ => {}
})
.detach();
}
}
}
impl gpui::Render for PullRequestDiffView {

View File

@@ -14,7 +14,7 @@ use crate::{
markdown::{self, MarkdownText},
text::text,
},
query::{self, QueryStatus, read_query, use_query},
query::{self, QueryStatus, read_query, use_query, watch_query},
screen::dashboard::pull_request_diff_view::{self, PullRequestDiffView},
};
@@ -46,21 +46,20 @@ impl PullRequestView {
self.pull_request_query = Some(query.clone());
_ = cx
.observe(&query.clone(), move |this, _, cx| {
this.load_markdown_content(cx);
this.load_pr_diff(cx);
})
.detach();
// cached query will not trigger observe callback
// this is required so that content is loaded immediately for cached query
self.load_markdown_content(cx);
self.load_pr_diff(cx);
_ = watch_query(&query, Self::sync_pull_request_query, cx).detach();
cx.notify();
}
fn sync_pull_request_query(
&mut self,
_query: &query::Entity<api::issues::FetchPullRequest>,
cx: &mut gpui::Context<Self>,
) {
self.load_markdown_content(cx);
self.load_pr_diff(cx);
}
fn load_markdown_content(&mut self, cx: &mut gpui::Context<Self>) {
let Some(query) = &self.pull_request_query else {
return;
@@ -115,41 +114,41 @@ impl PullRequestView {
.rounded_full();
match pr.state {
| api::issues::PullRequestState::Open => {
status_pill = status_pill
.bg(theme.colors.success_solid)
.child(
font_icon(FontIcon::PullRequestArrow)
.size_3()
.text_color(theme.colors.success_on_solid),
)
.child(
text("Open")
.text_color(theme.colors.success_on_solid)
| api::issues::PullRequestState::Open => {
status_pill = status_pill
.bg(theme.colors.success_solid)
.child(
font_icon(FontIcon::PullRequestArrow)
.size_3()
.text_color(theme.colors.success_on_solid),
)
.child(
text("Open")
.text_color(theme.colors.success_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Closed => {
status_pill = status_pill
.bg(theme.colors.danger_solid)
.child(
font_icon(FontIcon::PullRequestClosed)
.size_3()
.text_color(theme.colors.danger_on_solid),
)
.child(
text("Closed")
.text_color(theme.colors.danger_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Merged => {
status_pill = status_pill.bg(theme.colors.accent_solid).child(
text("Merged")
.text_color(theme.colors.accent_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Closed => {
status_pill = status_pill
.bg(theme.colors.danger_solid)
.child(
font_icon(FontIcon::PullRequestClosed)
.size_3()
.text_color(theme.colors.danger_on_solid),
)
.child(
text("Closed")
.text_color(theme.colors.danger_on_solid)
.text_xs(),
);
}
| api::issues::PullRequestState::Merged => {
status_pill = status_pill.bg(theme.colors.accent_solid).child(
text("Merged")
.text_color(theme.colors.accent_on_solid)
.text_xs(),
);
}
}
}
let merge_text = pr.author.as_ref().map(|author| {
@@ -284,23 +283,23 @@ impl gpui::Render for PullRequestView {
cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
div().size_full().child(match &self.pull_request_query {
| Some(q) => match read_query(q, cx) {
| QueryStatus::Loaded(pr) => match &self.diff_view {
| Some(v) => v.clone().into_any_element(),
| None => self.pr_content(pr, cx),
},
| Some(q) => match read_query(q, cx) {
| QueryStatus::Loaded(pr) => match &self.diff_view {
| Some(v) => v.clone().into_any_element(),
| None => self.pr_content(pr, cx),
},
| QueryStatus::Err(e) => div()
.size_full()
.child(format!("{:?}", e))
.into_any_element(),
| QueryStatus::Loading => div()
.size_full()
.child("loading pr content")
.into_any_element(),
},
| QueryStatus::Err(e) => div()
.size_full()
.child(format!("{:?}", e))
.into_any_element(),
| QueryStatus::Loading => div()
.size_full()
.child("loading pr content")
.into_any_element(),
},
| None => div().size_full().child("no pr selected").into_any_element(),
| None => div().size_full().child("no pr selected").into_any_element(),
})
}
}

View File

@@ -36,9 +36,9 @@ impl Screen {
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
_ = cx
.subscribe(&self.issue_list, |this, _, event, cx| match event {
| issue_list::Event::ItemSelected(pr_id) => {
this.handle_issue_list_item_selected(pr_id, cx);
}
| issue_list::Event::ItemSelected(pr_id) => {
this.handle_issue_list_item_selected(pr_id, cx);
}
})
.detach();
}