feat: basic pr diff rendering
This commit is contained in:
@@ -1,21 +1,43 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
api, app,
|
||||
component::text::text,
|
||||
query::{self, QueryStatus, read_query, use_query},
|
||||
component::{
|
||||
diff_view::{DiffViewContent, DiffViewState, diff_view},
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, observe_query, read_query, use_query},
|
||||
};
|
||||
use gpui::{ParentElement, Styled, div};
|
||||
|
||||
pub(crate) struct PullRequestDiffView {
|
||||
selected_file_path: Option<String>,
|
||||
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>,
|
||||
}
|
||||
|
||||
fn new(pr_id: api::issues::Id, cx: &mut gpui::Context<PullRequestDiffView>) -> PullRequestDiffView {
|
||||
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 }, cx),
|
||||
pr_query: use_query(api::issues::FetchPullRequest { id: pr_id.clone() }, cx),
|
||||
file_tree_query: use_query(
|
||||
api::issues::FetchPullRequestFileTree {
|
||||
id: pr_id,
|
||||
first: 100,
|
||||
},
|
||||
cx,
|
||||
),
|
||||
content_diff_query: None,
|
||||
diff_view_state: DiffViewState::new(),
|
||||
diff_view_content: None,
|
||||
};
|
||||
view.on_create(cx);
|
||||
view
|
||||
@@ -29,22 +51,42 @@ impl PullRequestDiffView {
|
||||
})
|
||||
.detach();
|
||||
|
||||
_ = cx
|
||||
.observe(&self.file_tree_query, |this, _, cx| {
|
||||
this.start_content_queries(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
// if pr is already loaded, start content queries
|
||||
self.start_content_queries(cx);
|
||||
}
|
||||
|
||||
fn start_content_queries(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((old_file_ref, new_file_ref)) = ({
|
||||
if let QueryStatus::Loaded(pr) = read_query(&self.pr_query, cx) {
|
||||
Some((
|
||||
api::repo::FileRef {
|
||||
repo_slug: pr.base_repo_slug.clone(),
|
||||
path: pr.base_branch_name.clone(),
|
||||
path: Arc::from(selected_file_path),
|
||||
reff: Some(pr.base_ref.clone()),
|
||||
},
|
||||
api::repo::FileRef {
|
||||
repo_slug: pr.head_repo_slug.clone(),
|
||||
path: pr.head_branch_name.clone(),
|
||||
path: Arc::from(selected_file_path),
|
||||
reff: Some(pr.head_ref.clone()),
|
||||
},
|
||||
))
|
||||
@@ -63,6 +105,20 @@ 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();
|
||||
|
||||
self.content_diff_query = Some(content_diff_query);
|
||||
}
|
||||
}
|
||||
@@ -73,20 +129,27 @@ impl gpui::Render for PullRequestDiffView {
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
use gpui::{ParentElement, Styled, div};
|
||||
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
div()
|
||||
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(
|
||||
"Pull request diff rendering is still under construction. Launch the DiffOps playground with NOVEM_DIFFOPS_PLAYGROUND=1 cargo run.",
|
||||
)
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted),
|
||||
)
|
||||
.child(text("asd")),
|
||||
|
||||
| QueryStatus::Loaded(_) => match &self.diff_view_content {
|
||||
| Some(content) => div()
|
||||
.size_full()
|
||||
.child(diff_view(self.diff_view_state.clone(), content.clone())),
|
||||
| None => div(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query},
|
||||
screen::dashboard::pull_request_diff_view::PullRequestDiffView,
|
||||
screen::dashboard::pull_request_diff_view::{self, PullRequestDiffView},
|
||||
};
|
||||
|
||||
pub(crate) struct PullRequestView {
|
||||
@@ -49,12 +49,14 @@ impl PullRequestView {
|
||||
_ = 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);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
@@ -78,6 +80,25 @@ impl PullRequestView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn load_pr_diff(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
let Some(query) = &self.pull_request_query else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pr_id = {
|
||||
let data = read_query(&query, cx);
|
||||
if let QueryStatus::Loaded(pr) = data {
|
||||
Some(pr.id.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
self.diff_view = pr_id.map(|id| cx.new(|cx| pull_request_diff_view::new(id, cx)));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pr_content(
|
||||
&self,
|
||||
pr: &api::issues::DetailedPullRequest,
|
||||
@@ -264,7 +285,11 @@ impl gpui::Render for PullRequestView {
|
||||
) -> impl gpui::IntoElement {
|
||||
div().size_full().child(match &self.pull_request_query {
|
||||
| Some(q) => match read_query(q, cx) {
|
||||
| QueryStatus::Loaded(pr) => self.pr_content(pr, 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))
|
||||
@@ -274,6 +299,7 @@ impl gpui::Render for PullRequestView {
|
||||
.child("loading pr content")
|
||||
.into_any_element(),
|
||||
},
|
||||
|
||||
| None => div().size_full().child("no pr selected").into_any_element(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
api, app,
|
||||
screen::dashboard::{
|
||||
issue_list::{self, IssueList},
|
||||
pull_request_diff_view::{self, PullRequestDiffView},
|
||||
pull_request_view::{self, PullRequestView},
|
||||
titlebar::{self, TitleBar},
|
||||
},
|
||||
@@ -13,6 +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>>,
|
||||
|
||||
issue_filter: Option<&'static str>,
|
||||
}
|
||||
@@ -22,6 +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,
|
||||
|
||||
issue_filter: None,
|
||||
};
|
||||
@@ -33,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();
|
||||
}
|
||||
@@ -50,7 +53,9 @@ impl Screen {
|
||||
view.change_displayed_pull_request(id.clone(), cx);
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use bytes::Bytes;
|
||||
use gpui::{
|
||||
AnyElement, AppContext, InteractiveElement, IntoElement, ParentElement,
|
||||
StatefulInteractiveElement, Styled, div, point, px, size,
|
||||
StatefulInteractiveElement,
|
||||
Styled, div, point, px, size,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -10,7 +13,7 @@ use crate::{
|
||||
button::{self, button},
|
||||
text::text,
|
||||
},
|
||||
util::diff::{ContentDiff, DiffRow, DiffSide, Op, Span, diff_content},
|
||||
util::diff::{ContentDiff, DiffLine, Op, diff_content},
|
||||
};
|
||||
|
||||
pub(crate) fn is_enabled() -> bool {
|
||||
@@ -51,7 +54,29 @@ pub(crate) struct Screen {
|
||||
struct DiffCase {
|
||||
title: &'static str,
|
||||
description: &'static str,
|
||||
diff: ContentDiff,
|
||||
old_lines: Vec<SourceLine>,
|
||||
new_lines: Vec<SourceLine>,
|
||||
op_groups: Vec<OpGroup>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SourceLine {
|
||||
line_number: usize,
|
||||
content: Arc<str>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct OpGroup {
|
||||
op: Op,
|
||||
old_range: Range<usize>,
|
||||
new_range: Range<usize>,
|
||||
rows: Vec<DiffLine>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SourceSide {
|
||||
Old,
|
||||
New,
|
||||
}
|
||||
|
||||
fn new(_cx: &mut gpui::Context<Screen>) -> Screen {
|
||||
@@ -108,28 +133,17 @@ impl gpui::Render for Screen {
|
||||
.collect();
|
||||
|
||||
let op_cards: Vec<AnyElement> = case
|
||||
.diff
|
||||
.spans()
|
||||
.op_groups
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, span)| render_op_card(index, span, theme).into_any_element())
|
||||
.map(|(index, group)| render_op_card(index, group, theme).into_any_element())
|
||||
.collect();
|
||||
|
||||
let op_groups: Vec<AnyElement> = case
|
||||
.diff
|
||||
.spans()
|
||||
.op_groups
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, span)| {
|
||||
render_op_group(
|
||||
index,
|
||||
span,
|
||||
case.diff.rows_for_span(index),
|
||||
&case.diff,
|
||||
theme,
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.map(|(index, group)| render_op_group(index, group, theme).into_any_element())
|
||||
.collect();
|
||||
|
||||
div()
|
||||
@@ -160,7 +174,7 @@ impl gpui::Render for Screen {
|
||||
.child(text("DiffOps Playground").text_lg())
|
||||
.child(
|
||||
text(
|
||||
"Sample content is diffed once at startup, then the UI renders the stored DiffOps and aligned rows.",
|
||||
"Sample content is diffed once at startup, then the UI derives grouped ops and aligned rows from the stored diff rows.",
|
||||
)
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted),
|
||||
@@ -188,7 +202,7 @@ impl gpui::Render for Screen {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(text("Precomputed DiffOps").text_sm())
|
||||
.child(text("Derived Op Groups").text_sm())
|
||||
.children(op_cards),
|
||||
),
|
||||
),
|
||||
@@ -218,9 +232,9 @@ impl gpui::Render for Screen {
|
||||
.child(
|
||||
text(format!(
|
||||
"{} ops, {} old lines, {} new lines",
|
||||
case.diff.spans().len(),
|
||||
case.diff.old_line_count(),
|
||||
case.diff.new_line_count(),
|
||||
case.op_groups.len(),
|
||||
line_count(&case.old_lines),
|
||||
line_count(&case.new_lines),
|
||||
))
|
||||
.text_xs()
|
||||
.font_family("Menlo")
|
||||
@@ -238,11 +252,11 @@ impl gpui::Render for Screen {
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(
|
||||
panel_header("Old", case.diff.old_line_count(), theme)
|
||||
panel_header("Old", line_count(&case.old_lines), theme)
|
||||
.flex_1(),
|
||||
)
|
||||
.child(
|
||||
panel_header("New", case.diff.new_line_count(), theme)
|
||||
panel_header("New", line_count(&case.new_lines), theme)
|
||||
.flex_1(),
|
||||
),
|
||||
)
|
||||
@@ -259,8 +273,12 @@ impl gpui::Render for Screen {
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(text("Source Content").text_sm())
|
||||
.child(render_source_content(&case.diff, theme))
|
||||
.child(text("DiffOps Render").text_sm())
|
||||
.child(render_source_content(
|
||||
&case.old_lines,
|
||||
&case.new_lines,
|
||||
theme,
|
||||
))
|
||||
.child(text("Diff Rows Render").text_sm())
|
||||
.children(op_groups),
|
||||
),
|
||||
),
|
||||
@@ -273,7 +291,7 @@ fn sample_cases() -> Vec<DiffCase> {
|
||||
vec![
|
||||
DiffCase::new(
|
||||
"Insert Block",
|
||||
"A pure insert leaves the old side with an empty anchor span such as 2..2 while the new side grows.",
|
||||
"A pure insert leaves the old side with an empty anchor range such as 2..2 while the new side grows.",
|
||||
r#"fn config() {
|
||||
let host = "localhost";
|
||||
start(host);
|
||||
@@ -288,7 +306,7 @@ fn sample_cases() -> Vec<DiffCase> {
|
||||
),
|
||||
DiffCase::new(
|
||||
"Delete Block",
|
||||
"A delete keeps the old side non-empty and gives the new side an empty anchor span at the removal point.",
|
||||
"A delete keeps the old side non-empty and gives the new side an empty anchor range at the removal point.",
|
||||
r#"fn handle(req: Request) {
|
||||
trace_request(&req);
|
||||
authorize(&req);
|
||||
@@ -303,7 +321,7 @@ fn sample_cases() -> Vec<DiffCase> {
|
||||
),
|
||||
DiffCase::new(
|
||||
"Replace Span",
|
||||
"A replace can cover different line counts on each side. The viewer pairs rows by position inside the op span.",
|
||||
"A replace can cover different line counts on each side. The viewer pairs rows by position inside the derived op group.",
|
||||
r#"fn render() {
|
||||
let theme = current_theme(cx);
|
||||
layout(theme);
|
||||
@@ -318,7 +336,7 @@ fn sample_cases() -> Vec<DiffCase> {
|
||||
),
|
||||
DiffCase::new(
|
||||
"Mixed Hunk",
|
||||
"This sample produces several DiffOps in sequence so you can see equal, replace, insert, and delete spans together.",
|
||||
"This sample produces several op groups in sequence so you can see equal, replace, insert, and delete rows together.",
|
||||
r#"use crate::auth::Token;
|
||||
use crate::http::Client;
|
||||
|
||||
@@ -350,13 +368,18 @@ impl DiffCase {
|
||||
old: &'static str,
|
||||
new: &'static str,
|
||||
) -> Self {
|
||||
let diff = diff_content(
|
||||
Bytes::from_static(old.as_bytes()),
|
||||
Bytes::from_static(new.as_bytes()),
|
||||
)
|
||||
.expect("sample content should always be valid utf-8");
|
||||
|
||||
Self {
|
||||
title,
|
||||
description,
|
||||
diff: diff_content(
|
||||
Bytes::from_static(old.as_bytes()),
|
||||
Bytes::from_static(new.as_bytes()),
|
||||
),
|
||||
old_lines: collect_source_lines(&diff, SourceSide::Old),
|
||||
new_lines: collect_source_lines(&diff, SourceSide::New),
|
||||
op_groups: collect_op_groups(&diff),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,27 +405,28 @@ fn panel_header(label: &'static str, line_count: usize, theme: &crate::theme::Th
|
||||
)
|
||||
}
|
||||
|
||||
fn render_source_content(diff: &ContentDiff, theme: &crate::theme::Theme) -> gpui::Div {
|
||||
fn render_source_content(
|
||||
old_lines: &[SourceLine],
|
||||
new_lines: &[SourceLine],
|
||||
theme: &crate::theme::Theme,
|
||||
) -> gpui::Div {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.gap_2()
|
||||
.child(render_source_panel("Old Content", DiffSide::Old, diff, theme).flex_1())
|
||||
.child(render_source_panel("New Content", DiffSide::New, diff, theme).flex_1())
|
||||
.child(render_source_panel("Old Content", old_lines, theme).flex_1())
|
||||
.child(render_source_panel("New Content", new_lines, theme).flex_1())
|
||||
}
|
||||
|
||||
fn render_source_panel(
|
||||
title: &'static str,
|
||||
side: DiffSide,
|
||||
diff: &ContentDiff,
|
||||
lines: &[SourceLine],
|
||||
theme: &crate::theme::Theme,
|
||||
) -> gpui::Div {
|
||||
let line_count = match side {
|
||||
| DiffSide::Old => diff.old_line_count(),
|
||||
| DiffSide::New => diff.new_line_count(),
|
||||
};
|
||||
let line_count = line_count(lines);
|
||||
|
||||
let lines: Vec<AnyElement> = (0..line_count)
|
||||
let rows: Vec<AnyElement> = lines
|
||||
.iter()
|
||||
.map(|line| {
|
||||
div()
|
||||
.flex()
|
||||
@@ -418,7 +442,7 @@ fn render_source_panel(
|
||||
.font_family("Menlo")
|
||||
.text_xs()
|
||||
.text_color(theme.colors.text_subtle)
|
||||
.child(format!("{:>4}", line + 1)),
|
||||
.child(format!("{:>4}", line.line_number + 1)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -429,7 +453,7 @@ fn render_source_panel(
|
||||
.font_family("Menlo")
|
||||
.text_xs()
|
||||
.text_color(theme.colors.text)
|
||||
.child(display_bytes(diff.line_slice_at(side, line))),
|
||||
.child(display_text(&line.content)),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
@@ -460,11 +484,11 @@ fn render_source_panel(
|
||||
.text_color(theme.colors.text_subtle),
|
||||
),
|
||||
)
|
||||
.child(div().flex().flex_col().children(lines))
|
||||
.child(div().flex().flex_col().children(rows))
|
||||
}
|
||||
|
||||
fn render_op_card(index: usize, span: &Span, theme: &crate::theme::Theme) -> gpui::Div {
|
||||
let colors = tag_colors(span.op, theme);
|
||||
fn render_op_card(index: usize, group: &OpGroup, theme: &crate::theme::Theme) -> gpui::Div {
|
||||
let colors = tag_colors(group.op, theme);
|
||||
|
||||
div()
|
||||
.rounded_md()
|
||||
@@ -476,38 +500,33 @@ fn render_op_card(index: usize, span: &Span, theme: &crate::theme::Theme) -> gpu
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
text(format!("Op {index}: {}", tag_label(span.op)))
|
||||
text(format!("Op {index}: {}", tag_label(group.op)))
|
||||
.text_sm()
|
||||
.text_color(colors.foreground),
|
||||
)
|
||||
.child(
|
||||
text(format!(
|
||||
"old {:?} new {:?}",
|
||||
span.old_range, span.new_range
|
||||
group.old_range, group.new_range
|
||||
))
|
||||
.text_xs()
|
||||
.font_family("Menlo")
|
||||
.text_color(theme.colors.text_muted),
|
||||
)
|
||||
.child(
|
||||
text(format!("{:?}", span.op))
|
||||
text(format!("{} aligned rows", group.rows.len()))
|
||||
.text_xs()
|
||||
.font_family("Menlo")
|
||||
.text_color(theme.colors.text_subtle),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_op_group(
|
||||
index: usize,
|
||||
span: &Span,
|
||||
rows: Vec<DiffRow>,
|
||||
diff: &ContentDiff,
|
||||
theme: &crate::theme::Theme,
|
||||
) -> gpui::Div {
|
||||
let colors = tag_colors(span.op, theme);
|
||||
let row_elements: Vec<AnyElement> = rows
|
||||
.into_iter()
|
||||
.map(|row| render_row(row, diff, theme).into_any_element())
|
||||
fn render_op_group(index: usize, group: &OpGroup, theme: &crate::theme::Theme) -> gpui::Div {
|
||||
let colors = tag_colors(group.op, theme);
|
||||
let row_elements: Vec<AnyElement> = group
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| render_row(index, row, theme).into_any_element())
|
||||
.collect();
|
||||
|
||||
div()
|
||||
@@ -527,14 +546,14 @@ fn render_op_group(
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(
|
||||
text(format!("Op {index}: {}", tag_label(span.op)))
|
||||
text(format!("Op {index}: {}", tag_label(group.op)))
|
||||
.text_sm()
|
||||
.text_color(colors.foreground),
|
||||
)
|
||||
.child(
|
||||
text(format!(
|
||||
"old {:?} new {:?}",
|
||||
span.old_range, span.new_range
|
||||
group.old_range, group.new_range
|
||||
))
|
||||
.text_xs()
|
||||
.font_family("Menlo")
|
||||
@@ -544,16 +563,7 @@ fn render_op_group(
|
||||
.child(div().flex().flex_col().children(row_elements))
|
||||
}
|
||||
|
||||
fn render_row(row: DiffRow, diff: &ContentDiff, theme: &crate::theme::Theme) -> gpui::Div {
|
||||
let old_text = row
|
||||
.old_content_range
|
||||
.as_ref()
|
||||
.map(|range| display_bytes(diff.line_slice(DiffSide::Old, range)));
|
||||
let new_text = row
|
||||
.new_content_range
|
||||
.as_ref()
|
||||
.map(|range| display_bytes(diff.line_slice(DiffSide::New, range)));
|
||||
|
||||
fn render_row(op_index: usize, row: &DiffLine, theme: &crate::theme::Theme) -> gpui::Div {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
@@ -561,18 +571,18 @@ fn render_row(row: DiffRow, diff: &ContentDiff, theme: &crate::theme::Theme) ->
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(render_line_cell(
|
||||
row.op_index,
|
||||
op_index,
|
||||
row.op,
|
||||
row.old_line,
|
||||
old_text,
|
||||
row.old_content.as_ref().map(|_| row.old_line),
|
||||
row.old_content.as_deref().map(display_text),
|
||||
true,
|
||||
theme,
|
||||
))
|
||||
.child(render_line_cell(
|
||||
row.op_index,
|
||||
op_index,
|
||||
row.op,
|
||||
row.new_line,
|
||||
new_text,
|
||||
row.new_content.as_ref().map(|_| row.new_line),
|
||||
row.new_content.as_deref().map(display_text),
|
||||
false,
|
||||
theme,
|
||||
))
|
||||
@@ -619,7 +629,7 @@ fn render_line_cell(
|
||||
.font_family("Menlo")
|
||||
.text_xs()
|
||||
.text_color(colors.foreground)
|
||||
.child(content.unwrap_or_else(|| format!("anchor for span {op_index}"))),
|
||||
.child(content.unwrap_or_else(|| format!("anchor for op {op_index}"))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -632,10 +642,10 @@ fn tag_label(op: Op) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn display_bytes(bytes: &[u8]) -> String {
|
||||
fn display_text(text: &str) -> String {
|
||||
let mut rendered = String::new();
|
||||
|
||||
for ch in String::from_utf8_lossy(bytes).chars() {
|
||||
for ch in text.chars() {
|
||||
match ch {
|
||||
| '\n' => rendered.push_str("\\n"),
|
||||
| '\r' => rendered.push_str("\\r"),
|
||||
@@ -651,6 +661,91 @@ fn display_bytes(bytes: &[u8]) -> String {
|
||||
rendered
|
||||
}
|
||||
|
||||
fn line_count(lines: &[SourceLine]) -> usize {
|
||||
lines.last().map(|line| line.line_number + 1).unwrap_or(0)
|
||||
}
|
||||
|
||||
fn collect_source_lines(diff: &ContentDiff, side: SourceSide) -> Vec<SourceLine> {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for i in 0..diff.len() {
|
||||
let row = diff.get(i);
|
||||
|
||||
match side {
|
||||
| SourceSide::Old => {
|
||||
if let Some(content) = &row.old_content {
|
||||
lines.push(SourceLine {
|
||||
line_number: row.old_line,
|
||||
content: Arc::clone(content),
|
||||
});
|
||||
}
|
||||
}
|
||||
| SourceSide::New => {
|
||||
if let Some(content) = &row.new_content {
|
||||
lines.push(SourceLine {
|
||||
line_number: row.new_line,
|
||||
content: Arc::clone(content),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
fn collect_op_groups(diff: &ContentDiff) -> Vec<OpGroup> {
|
||||
let mut groups = Vec::new();
|
||||
let mut start = 0;
|
||||
|
||||
while start < diff.len() {
|
||||
let op = diff.get(start).op;
|
||||
let mut end = start + 1;
|
||||
|
||||
while end < diff.len() && diff.get(end).op == op {
|
||||
end += 1;
|
||||
}
|
||||
|
||||
let rows: Vec<DiffLine> = (start..end).map(|i| diff.get(i).clone()).collect();
|
||||
|
||||
groups.push(OpGroup {
|
||||
op,
|
||||
old_range: group_range(&rows, SourceSide::Old),
|
||||
new_range: group_range(&rows, SourceSide::New),
|
||||
rows,
|
||||
});
|
||||
|
||||
start = end;
|
||||
}
|
||||
|
||||
groups
|
||||
}
|
||||
|
||||
fn group_range(rows: &[DiffLine], side: SourceSide) -> Range<usize> {
|
||||
let anchor = match side {
|
||||
| SourceSide::Old => rows.first().map(|row| row.old_line).unwrap_or(0),
|
||||
| SourceSide::New => rows.first().map(|row| row.new_line).unwrap_or(0),
|
||||
};
|
||||
|
||||
let mut first = None;
|
||||
let mut last = None;
|
||||
|
||||
for line_number in rows.iter().filter_map(|row| match side {
|
||||
| SourceSide::Old => row.old_content.as_ref().map(|_| row.old_line),
|
||||
| SourceSide::New => row.new_content.as_ref().map(|_| row.new_line),
|
||||
}) {
|
||||
if first.is_none() {
|
||||
first = Some(line_number);
|
||||
}
|
||||
last = Some(line_number);
|
||||
}
|
||||
|
||||
match (first, last) {
|
||||
| (Some(start), Some(end)) => start..end + 1,
|
||||
| _ => anchor..anchor,
|
||||
}
|
||||
}
|
||||
|
||||
struct Colors {
|
||||
background: gpui::Rgba,
|
||||
border: gpui::Rgba,
|
||||
|
||||
Reference in New Issue
Block a user