From 231353bea4ed1b62ce2cdea7f245ebf00352026e Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 25 May 2026 23:49:33 +0100 Subject: [PATCH] feat: pr tab switching btwn body/diff --- build.rs | 26 +- src/component/diff_view.rs | 2 +- src/component/mod.rs | 1 + src/component/segmented_control.rs | 119 +++++++++ src/query.rs | 46 ++-- src/screen/dashboard/issue_list.rs | 34 +-- .../dashboard/pull_request_diff_view.rs | 46 ++-- src/screen/dashboard/pull_request_view.rs | 234 +++++++++--------- src/theme.rs | 3 +- src/theme/catppuccin.rs | 11 + src/util/diff.rs | 224 +++++++++-------- src/util/file.rs | 34 +-- src/util/syntax_highlight.rs | 72 +++--- 13 files changed, 500 insertions(+), 352 deletions(-) create mode 100644 src/component/segmented_control.rs diff --git a/build.rs b/build.rs index bbf50f6..8b40aa4 100644 --- a/build.rs +++ b/build.rs @@ -234,12 +234,12 @@ fn render_github_fixtures(fixture_root: &Path) -> String { output.push_str(&string_literal(&path)); output.push_str(", "); match reff { - | Some(reff) => { - output.push_str("Some("); - output.push_str(&string_literal(&reff)); - output.push(')'); - } - | None => output.push_str("None"), + | Some(reff) => { + output.push_str("Some("); + output.push_str(&string_literal(&reff)); + output.push(')'); + } + | None => output.push_str("None"), } output.push_str(") => Some("); output.push_str(&string_literal(&content)); @@ -293,8 +293,8 @@ fn render_github_fixtures(fixture_root: &Path) -> String { output.push_str(&string_literal(&id)); output.push_str(", "); match previous_end_cursor.as_deref() { - | Some(after) => output.push_str(&format!("Some({})", string_literal(after))), - | None => output.push_str("None"), + | Some(after) => output.push_str(&format!("Some({})", string_literal(after))), + | None => output.push_str("None"), } output.push_str(") => Some("); output.push_str(&string_literal(&fixture.json)); @@ -429,9 +429,9 @@ fn issue_fixture_state(issue: &serde_json::Value) -> &'static str { } match required_string(issue, &["state"]) { - | "open" => "OPEN", - | "closed" => "CLOSED", - | state => panic!("unsupported pull request state in fixture: {state}"), + | "open" => "OPEN", + | "closed" => "CLOSED", + | state => panic!("unsupported pull request state in fixture: {state}"), } } @@ -500,8 +500,8 @@ fn collect_repo_file_content_fixtures( let owner = parts[0].clone(); let repo = parts[1].clone(); let reff = match parts[2].as_str() { - | "@default" => None, - | value => Some(value.to_owned()), + | "@default" => None, + | value => Some(value.to_owned()), }; let virtual_path = parts[3..].join("/"); let content = fs::read_to_string(&path).unwrap_or_else(|err| { diff --git a/src/component/diff_view.rs b/src/component/diff_view.rs index cc18bc9..c79428c 100644 --- a/src/component/diff_view.rs +++ b/src/component/diff_view.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; -use gpui::{HighlightStyle, IntoElement, ParentElement, Styled, div, list, px}; +use gpui::{IntoElement, ParentElement, Styled, div, list, px}; use crate::{ component::code_view::{self, CodeLine, code_line, code_line_with_highlights}, diff --git a/src/component/mod.rs b/src/component/mod.rs index 56ed26b..b479e65 100644 --- a/src/component/mod.rs +++ b/src/component/mod.rs @@ -3,4 +3,5 @@ pub(crate) mod code_view; pub(crate) mod diff_view; pub(crate) mod font_icon; pub(crate) mod markdown; +pub(crate) mod segmented_control; pub(crate) mod text; diff --git a/src/component/segmented_control.rs b/src/component/segmented_control.rs new file mode 100644 index 0000000..cc84847 --- /dev/null +++ b/src/component/segmented_control.rs @@ -0,0 +1,119 @@ +use std::rc::Rc; + +use gpui::{ + InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, div, + prelude::FluentBuilder, +}; + +use crate::{ + app, + component::font_icon::{self, font_icon}, +}; + +#[derive(gpui::IntoElement)] +pub(crate) struct SegmentedControl +where + T: Copy + Eq + 'static, +{ + items: Vec>, + selected_value: Option, + on_select: Option>, +} + +struct SegmentedControlItem +where + T: Copy + Eq + 'static, +{ + value: T, + icon: font_icon::FontIcon, +} + +pub(crate) fn segmented_control() -> SegmentedControl +where + T: Copy + Eq + 'static, +{ + SegmentedControl { + items: Vec::new(), + selected_value: None, + on_select: None, + } +} + +impl SegmentedControl +where + T: Copy + Eq + 'static, +{ + pub(crate) fn item(mut self, value: T, icon: font_icon::FontIcon) -> Self { + self.items + .extend(std::iter::once(SegmentedControlItem { value, icon })); + self + } + + pub(crate) fn on_select( + mut self, + f: impl Fn(&T, &mut gpui::Window, &mut gpui::App) + 'static, + ) -> Self { + self.on_select = Some(Rc::new(f)); + self + } + + pub(crate) fn selected_value(mut self, value: T) -> Self { + self.selected_value = Some(value); + self + } +} + +impl gpui::RenderOnce for SegmentedControl +where + T: Copy + Eq + 'static, +{ + fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement { + let theme = app::current_theme(cx); + + let mut children: Vec = Vec::with_capacity(self.items.len() * 2); + for (i, item) in self.items.into_iter().enumerate() { + let is_selected = Some(item.value) == self.selected_value; + let cb = self.on_select.as_ref().map(Rc::clone); + + // the segmented tab button + children.push( + div() + .id(i) + .px_2() + .py_1() + .bg(theme.colors.surface_button) + .child( + font_icon(item.icon) + .size_3p5() + .when(is_selected, |it| it.text_color(theme.colors.accent_solid)), + ) + .on_click(move |_, window, cx| { + if let Some(f) = &cb { + f(&item.value, window, cx); + } + }) + .into_any_element(), + ); + + // divider next to the segmented tab + children.push( + div() + .h_full() + .w_px() + .bg(theme.colors.border_strong) + .into_any_element(), + ); + } + children.truncate(children.len() - 1); + + div() + .flex() + .flex_row() + .items_center() + .justify_center() + .border_1() + .border_color(theme.colors.border_strong) + .rounded_sm() + .children(children) + } +} diff --git a/src/query.rs b/src/query.rs index 0a81828..684be1d 100644 --- a/src/query.rs +++ b/src/query.rs @@ -137,13 +137,13 @@ where })?; match wait_state { - | WaitState::Cached => { - return Ok(ent); - } - | WaitState::Waiting { rx, sub } => { - _ = sub; - _ = rx.await; - } + | WaitState::Cached => { + return Ok(ent); + } + | WaitState::Waiting { rx, sub } => { + _ = sub; + _ = rx.await; + } } } } @@ -181,13 +181,17 @@ where let state = query.raw.read(cx); match &state.data { - | QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading, - | QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::().unwrap()), - | QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::().unwrap()), + | QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading, + | QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::().unwrap()), + | QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::().unwrap()), } } -pub fn watch_query(query: &Entity, on_notify: H, cx: &mut gpui::Context) -> gpui::Subscription +pub fn watch_query( + query: &Entity, + on_notify: H, + cx: &mut gpui::Context, +) -> gpui::Subscription where E: 'static, F: QueryFn, @@ -308,14 +312,14 @@ where entity.raw.update(cx, |state, cx| { state.data = match result { - | Ok(data) => { - println!("[query] OK {}", q.key()); - QueryData::Some(Box::new(data)) - } - | Err(err) => { - println!("[query] ERR {:?}: {:?}", q.key(), err); - QueryData::Err(Box::new(err)) - } + | Ok(data) => { + println!("[query] OK {}", q.key()); + QueryData::Some(Box::new(data)) + } + | Err(err) => { + println!("[query] ERR {:?}: {:?}", q.key(), err); + QueryData::Err(Box::new(err)) + } }; cx.notify(); })?; @@ -341,8 +345,8 @@ where .raw .update(cx, |query, cx| { query.data = match result { - | Ok(data) => QueryData::Some(Box::new(data)), - | Err(err) => QueryData::Err(Box::new(err)), + | Ok(data) => QueryData::Some(Box::new(data)), + | Err(err) => QueryData::Err(Box::new(err)), }; cx.notify(); true diff --git a/src/screen/dashboard/issue_list.rs b/src/screen/dashboard/issue_list.rs index 625458a..00674f6 100644 --- a/src/screen/dashboard/issue_list.rs +++ b/src/screen/dashboard/issue_list.rs @@ -157,8 +157,8 @@ impl gpui::RenderOnce for IssueListItem { } let repo_name_text = match self.repo_name { - | Some(name) => text(name), - | None => text("Unknown repo"), + | Some(name) => text(name), + | None => text("Unknown repo"), } .text_xs() .opacity(0.5); @@ -171,21 +171,21 @@ impl gpui::RenderOnce for IssueListItem { .bg(theme.colors.surface) } else { match self.status { - | api::issues::PullRequestState::Closed => pill( - text("Closed").text_color(theme.colors.danger_on_solid), - font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger_on_solid), - ) - .bg(theme.colors.danger_solid), - | api::issues::PullRequestState::Merged => pill( - text("Merged").text_color(theme.colors.accent_on_solid), - font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.accent_on_solid), - ) - .bg(theme.colors.accent_solid), - | _ => pill( - text("Open").text_color(theme.colors.success_on_solid), - font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success_on_solid), - ) - .bg(theme.colors.success_solid), + | api::issues::PullRequestState::Closed => pill( + text("Closed").text_color(theme.colors.danger_on_solid), + font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger_on_solid), + ) + .bg(theme.colors.danger_solid), + | api::issues::PullRequestState::Merged => pill( + text("Merged").text_color(theme.colors.accent_on_solid), + font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.accent_on_solid), + ) + .bg(theme.colors.accent_solid), + | _ => pill( + text("Open").text_color(theme.colors.success_on_solid), + font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success_on_solid), + ) + .bg(theme.colors.success_solid), } }; diff --git a/src/screen/dashboard/pull_request_diff_view.rs b/src/screen/dashboard/pull_request_diff_view.rs index a4c4258..19225bc 100644 --- a/src/screen/dashboard/pull_request_diff_view.rs +++ b/src/screen/dashboard/pull_request_diff_view.rs @@ -117,8 +117,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); @@ -153,16 +153,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(); } @@ -184,18 +184,18 @@ impl gpui::Render for PullRequestDiffView { .unwrap_or(QueryStatus::Loading); match content_diff { - | QueryStatus::Err(_) | QueryStatus::Loading => div() - .size_full() - .bg(theme.colors.surface) - .p_4() - .child(text("asd")), + | QueryStatus::Err(_) | QueryStatus::Loading => div() + .size_full() + .bg(theme.colors.surface) + .p_4() + .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(), - }, + | QueryStatus::Loaded(_) => match &self.diff_view_content { + | Some(content) => div() + .size_full() + .child(diff_view(self.diff_view_state.clone(), content.clone())), + | None => div(), + }, } } } diff --git a/src/screen/dashboard/pull_request_view.rs b/src/screen/dashboard/pull_request_view.rs index a5f15b2..f6ff4ec 100644 --- a/src/screen/dashboard/pull_request_view.rs +++ b/src/screen/dashboard/pull_request_view.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use gpui::{ AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, - div, img, linear_gradient, prelude::FluentBuilder, + div, img, prelude::FluentBuilder, }; use crate::{ @@ -12,6 +12,7 @@ use crate::{ button::{self, Button, button}, font_icon::{FontIcon, font_icon}, markdown::{self, MarkdownText}, + segmented_control::segmented_control, text::text, }, query::{self, QueryStatus, read_query, use_query, watch_query}, @@ -19,17 +20,23 @@ use crate::{ }; pub(crate) struct PullRequestView { + current_tab: Tab, + markdown_viewer: Option>, diff_view: Option>, pull_request_query: Option>, } -#[derive(gpui::IntoElement)] -struct Toolbar {} +#[derive(Clone, Copy, PartialEq, Eq)] +enum Tab { + PullRequestBody, + DiffView, +} pub fn new(_cx: &mut gpui::Context) -> PullRequestView { PullRequestView { + current_tab: Tab::PullRequestBody, markdown_viewer: None, diff_view: None, pull_request_query: None, @@ -45,6 +52,7 @@ impl PullRequestView { let query = use_query(api::issues::FetchPullRequest { id }, cx); self.pull_request_query = Some(query.clone()); + self.current_tab = Tab::PullRequestBody; _ = watch_query(&query, Self::sync_pull_request_query, cx).detach(); @@ -98,6 +106,65 @@ impl PullRequestView { cx.notify(); } + fn toolbar(&self, cx: &gpui::Context) -> gpui::AnyElement { + fn toolbar_button(id: impl Into) -> Button { + button(id) + .px_2p5() + .py_1() + .variant(button::Variant::Secondary) + .border_0() + } + + fn divider() -> gpui::Div { + div().h_full().w_px() + } + + let theme = app::current_theme(cx); + + div() + .w_full() + .flex() + .flex_row() + .items_center() + .justify_start() + .p_1() + .bg(theme.colors.surface) + .border_b_1() + .border_color(theme.colors.border_muted) + .child( + segmented_control() + .selected_value(self.current_tab) + .item(Tab::PullRequestBody, FontIcon::MessageCircleMore) + .item(Tab::DiffView, FontIcon::FileBracesCorner) + .on_select(cx.listener(|this, tab, _, cx| { + this.current_tab = *tab; + cx.notify(); + })), + ) + .child(div().flex_1()) + .child( + toolbar_button("pr-close-btn") + .leading(font_icon(FontIcon::PullRequestClosed)) + .mr_1(), + ) + .child( + toolbar_button("pr-merge-btn") + .variant(button::Variant::Primary) + .leading(font_icon(FontIcon::GitMerge)) + .rounded_r_none(), + ) + .child(divider()) + .child( + toolbar_button("chevron") + .py_1() + .px_0p5() + .variant(button::Variant::Primary) + .leading(font_icon(FontIcon::ChevronDown)) + .rounded_l_none(), + ) + .into_any_element() + } + fn pr_content( &self, pr: &api::issues::DetailedPullRequest, @@ -114,41 +181,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) - .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) + | 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(), + ); + } } let merge_text = pr.author.as_ref().map(|author| { @@ -228,7 +295,6 @@ impl PullRequestView { .flex_col() .bg(theme.colors.surface) .overflow_hidden() - .child(Toolbar {}) .child( div() .flex() @@ -282,9 +348,18 @@ impl gpui::Render for PullRequestView { _window: &mut gpui::Window, cx: &mut gpui::Context, ) -> 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), + div() + .size_full() + .flex() + .flex_col() + .child(self.toolbar(cx)) + .child(match &self.pull_request_query { + | Some(q) => { + match read_query(q, cx) { + | QueryStatus::Loaded(pr) => match (&self.diff_view, self.current_tab) { + | (Some(diff_view), Tab::DiffView) => diff_view.clone().into_any_element(), + | _ => self.pr_content(pr, cx), + }, | QueryStatus::Err(e) => div() .size_full() @@ -294,83 +369,10 @@ impl gpui::Render for PullRequestView { .size_full() .child("loading pr content") .into_any_element(), - }, + } + } | None => div().size_full().child("no pr selected").into_any_element(), - }) - } -} - -impl gpui::RenderOnce for Toolbar { - fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement { - fn toolbar_button(id: impl Into) -> Button { - button(id) - .px_2p5() - .py_1() - .variant(button::Variant::Secondary) - .border_0() - } - - fn divider() -> gpui::Div { - div().h_full().w_px() - } - - let theme = app::current_theme(cx); - - div() - .w_full() - .flex() - .flex_row() - .items_center() - .justify_start() - .p_1() - .bg(theme.colors.surface) - .border_b_1() - .border_color(theme.colors.border_muted) - .child( - div() - .flex() - .flex_row() - .items_center() - .justify_center() - .border_t_1() - .border_color(theme.colors.border_strong) - .rounded_sm() - .child( - div() - .px_2() - .py_1() - .bg(theme.colors.surface_elevated) - .child(font_icon(FontIcon::MessageCircleMore).size_3p5()), - ) - .child( - div() - .px_2() - .py_1() - .bg(theme.colors.surface_elevated) - .child(font_icon(FontIcon::FileBracesCorner).size_3p5()), - ), - ) - .child(div().flex_1()) - .child( - toolbar_button("pr-close-btn") - .leading(font_icon(FontIcon::PullRequestClosed)) - .mr_1(), - ) - .child( - toolbar_button("pr-merge-btn") - .variant(button::Variant::Primary) - .leading(font_icon(FontIcon::GitMerge)) - .rounded_r_none(), - ) - .child(divider()) - .child( - toolbar_button("chevron") - .py_1() - .px_0p5() - .variant(button::Variant::Primary) - .leading(font_icon(FontIcon::ChevronDown)) - .rounded_l_none(), - ) + }) } } diff --git a/src/theme.rs b/src/theme.rs index bce8afa..832ea12 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,7 +1,7 @@ mod catppuccin; pub(crate) mod syntax; -use gpui::Rgba; +use gpui::{Background, Rgba}; #[allow(unused_imports)] pub use syntax::{HIGHLIGHT_NAMES, ThemeSyntax, ThemeSyntaxHighlight}; @@ -33,6 +33,7 @@ pub struct ThemeColors { pub background: Rgba, pub surface: Rgba, pub surface_elevated: Rgba, + pub surface_button: Background, pub surface_chrome: Rgba, pub surface_hover: Rgba, pub surface_active: Rgba, diff --git a/src/theme/catppuccin.rs b/src/theme/catppuccin.rs index 7065039..87c49dc 100644 --- a/src/theme/catppuccin.rs +++ b/src/theme/catppuccin.rs @@ -1,4 +1,5 @@ use crate::colors::{hex, hex_alpha}; +use gpui::{linear_color_stop, linear_gradient}; use super::{ Theme, ThemeColors, ThemeMode, ThemeSyntaxHighlight, @@ -44,6 +45,11 @@ pub(crate) fn latte() -> Theme { background: hex(0xeff1f5), surface: hex(0xe6e9ef), surface_elevated: hex(0xeff1f5), + surface_button: linear_gradient( + 180., + linear_color_stop(hex(0xf6f7fb), 0.), + linear_color_stop(hex(0xeff1f5), 1.), + ), surface_chrome: hex(0xdce0e8), surface_hover: hex(0xdce0e8), surface_active: hex(0xccd0da), @@ -153,6 +159,11 @@ pub(crate) fn mocha() -> Theme { background: hex(0x1e1e2e), surface: hex(0x181825), surface_elevated: hex(0x45475a), + surface_button: linear_gradient( + 180., + linear_color_stop(hex(0x4f5068), 0.), + linear_color_stop(hex(0x45475a), 1.), + ), surface_chrome: hex(0x11111b), surface_hover: hex(0x313244), surface_active: hex(0x45475a), diff --git a/src/util/diff.rs b/src/util/diff.rs index a7e9bc9..ce87b55 100644 --- a/src/util/diff.rs +++ b/src/util/diff.rs @@ -44,117 +44,127 @@ pub(crate) fn diff_content( for op in diff.ops() { match op { - | &similar::DiffOp::Equal { - old_index, - new_index, - len, - } => { - for i in 0..len { - let old_line = old_index + i; - let new_line = new_index + i; - let old_line_range = &old_line_ranges[old_line]; - let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?); - diff_lines.push(DiffLine { - op: Op::Equal, - old_line: Some(old_line), - old_content: Some(Arc::clone(&content)), - old_byte_range: old_line_range.clone(), - new_line: Some(new_line), - new_content: Some(content), - new_byte_range: new_line_ranges[new_line].clone(), - }); - } - } - - | &similar::DiffOp::Insert { - new_index, new_len, .. - } => { - for i in 0..new_len { - let new_line_range = &new_line_ranges[new_index + i]; - let content = Arc::from(new_content.slice(new_line_range.clone()).as_str()?); - diff_lines.push(DiffLine { - op: Op::Insert, - old_line: None, - old_content: None, - old_byte_range: 0..0, - new_line: Some(new_index + i), - new_content: Some(content), - new_byte_range: new_line_range.clone(), - }) - } - } - - | &similar::DiffOp::Replace { - old_index, - old_len, - new_index, - new_len, - } => { - for i in 0..new_len.max(old_len) { - let old_line = old_index + i; - let new_line = new_index + i; - - let diff_line = match (old_line_ranges.get(old_line), new_line_ranges.get(new_line)) - { - | (Some(old_range), Some(new_range)) => DiffLine { - op: Op::Replace, - old_line: Some(old_line), - old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)), - old_byte_range: old_range.clone(), - new_line: Some(new_line), - new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)), - new_byte_range: new_range.clone(), - }, - - | (None, Some(new_range)) => DiffLine { - op: Op::Replace, - old_line: None, - old_content: None, - old_byte_range: 0..0, - new_line: Some(new_index + i), - new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)), - new_byte_range: new_range.clone(), - }, - - | (Some(old_range), None) => DiffLine { - op: Op::Replace, - old_line: Some(old_index + i), - old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)), - old_byte_range: old_range.clone(), - new_line: None, - new_content: None, - new_byte_range: 0..0, - }, - - | (None, None) => { - // unlickly to happen, but if it does, idk - panic!( - "the unlikely happened: both old & new index of DiffOps::Replace don't point to any line in the parsed line ranges." - ) + | &similar::DiffOp::Equal { + old_index, + new_index, + len, + } => { + for i in 0..len { + let old_line = old_index + i; + let new_line = new_index + i; + let old_line_range = &old_line_ranges[old_line]; + let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?); + diff_lines.push(DiffLine { + op: Op::Equal, + old_line: Some(old_line), + old_content: Some(Arc::clone(&content)), + old_byte_range: old_line_range.clone(), + new_line: Some(new_line), + new_content: Some(content), + new_byte_range: new_line_ranges[new_line].clone(), + }); } - }; - - diff_lines.push(diff_line); } - } - | &similar::DiffOp::Delete { - old_index, old_len, .. - } => { - for i in 0..old_len { - let old_line_range = &old_line_ranges[old_index]; - let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?); - diff_lines.push(DiffLine { - op: Op::Delete, - old_line: Some(old_index + i), - old_content: Some(content), - old_byte_range: old_line_range.clone(), - new_line: None, - new_content: None, - new_byte_range: 0..0, - }) + | &similar::DiffOp::Insert { + new_index, new_len, .. + } => { + for i in 0..new_len { + let new_line_range = &new_line_ranges[new_index + i]; + let content = Arc::from(new_content.slice(new_line_range.clone()).as_str()?); + diff_lines.push(DiffLine { + op: Op::Insert, + old_line: None, + old_content: None, + old_byte_range: 0..0, + new_line: Some(new_index + i), + new_content: Some(content), + new_byte_range: new_line_range.clone(), + }) + } + } + + | &similar::DiffOp::Replace { + old_index, + old_len, + new_index, + new_len, + } => { + for i in 0..new_len.max(old_len) { + let old_line = old_index + i; + let new_line = new_index + i; + + let diff_line = match ( + old_line_ranges.get(old_line), + new_line_ranges.get(new_line), + ) { + | (Some(old_range), Some(new_range)) => DiffLine { + op: Op::Replace, + old_line: Some(old_line), + old_content: Some(Arc::from( + old_content.slice(old_range.clone()).as_str()?, + )), + old_byte_range: old_range.clone(), + new_line: Some(new_line), + new_content: Some(Arc::from( + new_content.slice(new_range.clone()).as_str()?, + )), + new_byte_range: new_range.clone(), + }, + + | (None, Some(new_range)) => DiffLine { + op: Op::Replace, + old_line: None, + old_content: None, + old_byte_range: 0..0, + new_line: Some(new_index + i), + new_content: Some(Arc::from( + new_content.slice(new_range.clone()).as_str()?, + )), + new_byte_range: new_range.clone(), + }, + + | (Some(old_range), None) => DiffLine { + op: Op::Replace, + old_line: Some(old_index + i), + old_content: Some(Arc::from( + old_content.slice(old_range.clone()).as_str()?, + )), + old_byte_range: old_range.clone(), + new_line: None, + new_content: None, + new_byte_range: 0..0, + }, + + | (None, None) => { + // unlickly to happen, but if it does, idk + panic!( + "the unlikely happened: both old & new index of DiffOps::Replace don't point to any line in the parsed line ranges." + ) + } + }; + + diff_lines.push(diff_line); + } + } + + | &similar::DiffOp::Delete { + old_index, old_len, .. + } => { + for i in 0..old_len { + let old_line_range = &old_line_ranges[old_index]; + let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?); + diff_lines.push(DiffLine { + op: Op::Delete, + old_line: Some(old_index + i), + old_content: Some(content), + old_byte_range: old_line_range.clone(), + new_line: None, + new_content: None, + new_byte_range: 0..0, + }) + } } - } } } diff --git a/src/util/file.rs b/src/util/file.rs index 9e807e2..2cf2c17 100644 --- a/src/util/file.rs +++ b/src/util/file.rs @@ -27,17 +27,17 @@ pub(crate) fn classify_content(content: &[u8]) -> ContentType { ContentType::Text } else { match memchr(0, &content[..content.len().min(8192)]) { - | None => ContentType::Text, - | Some(_) => ContentType::Binary, + | None => ContentType::Text, + | Some(_) => ContentType::Binary, } } } pub(crate) fn file_type_from_path(path: &str) -> FileType { match Path::new(path).extension().map(|it| it.to_str()).flatten() { - | Some("rs") => FileType::Rust, - | Some("js") | Some("jsx") => FileType::JavaScript, - | _ => FileType::Unknown, + | Some("rs") => FileType::Rust, + | Some("js") | Some("jsx") => FileType::JavaScript, + | _ => FileType::Unknown, } } @@ -55,18 +55,18 @@ pub(crate) fn line_ranges(content: &[u8]) -> Vec> { let c = content[i]; match (c, content.get(i + 1)) { - | (b'\r', Some(b'\n')) => { - // if \r found, check if its \r\n or if its a lone \r - // if \r\n, then treat as one line break - ranges.push(line_start..i + 1); - // because we already counted the \n byte, the next iter into it needs to be skipped - skip_next = true; - line_start = i + 2; - } - | _ => { - ranges.push(line_start..i); - line_start = i + 1; - } + | (b'\r', Some(b'\n')) => { + // if \r found, check if its \r\n or if its a lone \r + // if \r\n, then treat as one line break + ranges.push(line_start..i + 1); + // because we already counted the \n byte, the next iter into it needs to be skipped + skip_next = true; + line_start = i + 2; + } + | _ => { + ranges.push(line_start..i); + line_start = i + 1; + } } } diff --git a/src/util/syntax_highlight.rs b/src/util/syntax_highlight.rs index 9f7274e..1e04f97 100644 --- a/src/util/syntax_highlight.rs +++ b/src/util/syntax_highlight.rs @@ -6,16 +6,16 @@ fn ts_highlight_configuration_for_file_type( file_type: util::file::FileType, ) -> Option { match file_type { - | util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new( - tree_sitter_rust::LANGUAGE.into(), - "rust", - tree_sitter_rust::HIGHLIGHTS_QUERY, - tree_sitter_rust::INJECTIONS_QUERY, - "", - ) - .ok(), + | util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new( + tree_sitter_rust::LANGUAGE.into(), + "rust", + tree_sitter_rust::HIGHLIGHTS_QUERY, + tree_sitter_rust::INJECTIONS_QUERY, + "", + ) + .ok(), - | _ => None, + | _ => None, } } @@ -44,37 +44,37 @@ pub(crate) fn highlight_content( for highlight_event in events { match highlight_event.ok()? { - | tree_sitter_highlight::HighlightEvent::HighlightStart(h) => { - highlight = theme_syntax.as_slice()[h.0]; - } - | tree_sitter_highlight::HighlightEvent::Source { start, end } => { - while current_line < line_ranges.len() && start >= line_ranges[current_line].end { - highlights.push(Vec::new()); - current_line += 1; + | tree_sitter_highlight::HighlightEvent::HighlightStart(h) => { + highlight = theme_syntax.as_slice()[h.0]; } - let mut line = current_line; - while line < line_ranges.len() && end > line_ranges[line].start { - if highlights.get(line).is_none() { + | tree_sitter_highlight::HighlightEvent::Source { start, end } => { + while current_line < line_ranges.len() && start >= line_ranges[current_line].end { highlights.push(Vec::new()); + current_line += 1; + } + let mut line = current_line; + while line < line_ranges.len() && end > line_ranges[line].start { + if highlights.get(line).is_none() { + highlights.push(Vec::new()); + } + let line_range = &line_ranges[line]; + let highlight_start = start.max(line_range.start); + let highlight_end = end.min(line_range.end); + highlights[line].push(( + (highlight_start - line_range.start)..(highlight_end - line_range.start), + gpui::HighlightStyle { + color: Some(highlight.color.into()), + font_weight: highlight.font_weight, + font_style: Some(highlight.font_style), + ..Default::default() + }, + )); + line += 1; } - let line_range = &line_ranges[line]; - let highlight_start = start.max(line_range.start); - let highlight_end = end.min(line_range.end); - highlights[line].push(( - (highlight_start - line_range.start)..(highlight_end - line_range.start), - gpui::HighlightStyle { - color: Some(highlight.color.into()), - font_weight: highlight.font_weight, - font_style: Some(highlight.font_style), - ..Default::default() - }, - )); - line += 1; } - } - | tree_sitter_highlight::HighlightEvent::HighlightEnd => { - highlight = default_highlight; - } + | tree_sitter_highlight::HighlightEvent::HighlightEnd => { + highlight = default_highlight; + } } }