feat: pr tab switching btwn body/diff

This commit is contained in:
2026-05-25 23:49:33 +01:00
parent 91a57cbc0f
commit 231353bea4
13 changed files with 500 additions and 352 deletions

View File

@@ -234,12 +234,12 @@ fn render_github_fixtures(fixture_root: &Path) -> String {
output.push_str(&string_literal(&path)); output.push_str(&string_literal(&path));
output.push_str(", "); output.push_str(", ");
match reff { match reff {
| Some(reff) => { | Some(reff) => {
output.push_str("Some("); output.push_str("Some(");
output.push_str(&string_literal(&reff)); output.push_str(&string_literal(&reff));
output.push(')'); output.push(')');
} }
| None => output.push_str("None"), | None => output.push_str("None"),
} }
output.push_str(") => Some("); output.push_str(") => Some(");
output.push_str(&string_literal(&content)); 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(&string_literal(&id));
output.push_str(", "); output.push_str(", ");
match previous_end_cursor.as_deref() { match previous_end_cursor.as_deref() {
| Some(after) => output.push_str(&format!("Some({})", string_literal(after))), | Some(after) => output.push_str(&format!("Some({})", string_literal(after))),
| None => output.push_str("None"), | None => output.push_str("None"),
} }
output.push_str(") => Some("); output.push_str(") => Some(");
output.push_str(&string_literal(&fixture.json)); 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"]) { match required_string(issue, &["state"]) {
| "open" => "OPEN", | "open" => "OPEN",
| "closed" => "CLOSED", | "closed" => "CLOSED",
| state => panic!("unsupported pull request state in fixture: {state}"), | 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 owner = parts[0].clone();
let repo = parts[1].clone(); let repo = parts[1].clone();
let reff = match parts[2].as_str() { let reff = match parts[2].as_str() {
| "@default" => None, | "@default" => None,
| value => Some(value.to_owned()), | value => Some(value.to_owned()),
}; };
let virtual_path = parts[3..].join("/"); let virtual_path = parts[3..].join("/");
let content = fs::read_to_string(&path).unwrap_or_else(|err| { let content = fs::read_to_string(&path).unwrap_or_else(|err| {

View File

@@ -1,6 +1,6 @@
use std::{cell::RefCell, rc::Rc, sync::Arc}; 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::{ use crate::{
component::code_view::{self, CodeLine, code_line, code_line_with_highlights}, component::code_view::{self, CodeLine, code_line, code_line_with_highlights},

View File

@@ -3,4 +3,5 @@ pub(crate) mod code_view;
pub(crate) mod diff_view; pub(crate) mod diff_view;
pub(crate) mod font_icon; pub(crate) mod font_icon;
pub(crate) mod markdown; pub(crate) mod markdown;
pub(crate) mod segmented_control;
pub(crate) mod text; pub(crate) mod text;

View File

@@ -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<T>
where
T: Copy + Eq + 'static,
{
items: Vec<SegmentedControlItem<T>>,
selected_value: Option<T>,
on_select: Option<Rc<dyn Fn(&T, &mut gpui::Window, &mut gpui::App)>>,
}
struct SegmentedControlItem<T>
where
T: Copy + Eq + 'static,
{
value: T,
icon: font_icon::FontIcon,
}
pub(crate) fn segmented_control<T>() -> SegmentedControl<T>
where
T: Copy + Eq + 'static,
{
SegmentedControl {
items: Vec::new(),
selected_value: None,
on_select: None,
}
}
impl<T> SegmentedControl<T>
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<T> gpui::RenderOnce for SegmentedControl<T>
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<gpui::AnyElement> = 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)
}
}

View File

@@ -137,13 +137,13 @@ where
})?; })?;
match wait_state { match wait_state {
| WaitState::Cached => { | WaitState::Cached => {
return Ok(ent); return Ok(ent);
} }
| WaitState::Waiting { rx, sub } => { | WaitState::Waiting { rx, sub } => {
_ = sub; _ = sub;
_ = rx.await; _ = rx.await;
} }
} }
} }
} }
@@ -181,13 +181,17 @@ where
let state = query.raw.read(cx); let state = query.raw.read(cx);
match &state.data { match &state.data {
| QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading, | QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading,
| QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<F::Data>().unwrap()), | QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<F::Data>().unwrap()),
| QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<F::Error>().unwrap()), | QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<F::Error>().unwrap()),
} }
} }
pub fn watch_query<E, F, H>(query: &Entity<F>, on_notify: H, cx: &mut gpui::Context<E>) -> gpui::Subscription pub fn watch_query<E, F, H>(
query: &Entity<F>,
on_notify: H,
cx: &mut gpui::Context<E>,
) -> gpui::Subscription
where where
E: 'static, E: 'static,
F: QueryFn, F: QueryFn,
@@ -308,14 +312,14 @@ where
entity.raw.update(cx, |state, cx| { entity.raw.update(cx, |state, cx| {
state.data = match result { state.data = match result {
| Ok(data) => { | Ok(data) => {
println!("[query] OK {}", q.key()); println!("[query] OK {}", q.key());
QueryData::Some(Box::new(data)) QueryData::Some(Box::new(data))
} }
| Err(err) => { | Err(err) => {
println!("[query] ERR {:?}: {:?}", q.key(), err); println!("[query] ERR {:?}: {:?}", q.key(), err);
QueryData::Err(Box::new(err)) QueryData::Err(Box::new(err))
} }
}; };
cx.notify(); cx.notify();
})?; })?;
@@ -341,8 +345,8 @@ where
.raw .raw
.update(cx, |query, cx| { .update(cx, |query, cx| {
query.data = match result { query.data = match result {
| Ok(data) => QueryData::Some(Box::new(data)), | Ok(data) => QueryData::Some(Box::new(data)),
| Err(err) => QueryData::Err(Box::new(err)), | Err(err) => QueryData::Err(Box::new(err)),
}; };
cx.notify(); cx.notify();
true true

View File

@@ -157,8 +157,8 @@ impl gpui::RenderOnce for IssueListItem {
} }
let repo_name_text = match self.repo_name { let repo_name_text = match self.repo_name {
| Some(name) => text(name), | Some(name) => text(name),
| None => text("Unknown repo"), | None => text("Unknown repo"),
} }
.text_xs() .text_xs()
.opacity(0.5); .opacity(0.5);
@@ -171,21 +171,21 @@ impl gpui::RenderOnce for IssueListItem {
.bg(theme.colors.surface) .bg(theme.colors.surface)
} else { } else {
match self.status { match self.status {
| api::issues::PullRequestState::Closed => pill( | api::issues::PullRequestState::Closed => pill(
text("Closed").text_color(theme.colors.danger_on_solid), text("Closed").text_color(theme.colors.danger_on_solid),
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger_on_solid), font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger_on_solid),
) )
.bg(theme.colors.danger_solid), .bg(theme.colors.danger_solid),
| api::issues::PullRequestState::Merged => pill( | api::issues::PullRequestState::Merged => pill(
text("Merged").text_color(theme.colors.accent_on_solid), text("Merged").text_color(theme.colors.accent_on_solid),
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.accent_on_solid), font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.accent_on_solid),
) )
.bg(theme.colors.accent_solid), .bg(theme.colors.accent_solid),
| _ => pill( | _ => pill(
text("Open").text_color(theme.colors.success_on_solid), text("Open").text_color(theme.colors.success_on_solid),
font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success_on_solid), font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success_on_solid),
) )
.bg(theme.colors.success_solid), .bg(theme.colors.success_solid),
} }
}; };

View File

@@ -117,8 +117,8 @@ impl PullRequestDiffView {
) { ) {
if let Some(diff) = { if let Some(diff) = {
match read_query(query, cx) { match read_query(query, cx) {
| QueryStatus::Loaded(diff) => Some(Arc::clone(diff)), | QueryStatus::Loaded(diff) => Some(Arc::clone(diff)),
| _ => None, | _ => None,
} }
} { } {
self.load_diff_view(diff, cx); self.load_diff_view(diff, cx);
@@ -153,16 +153,16 @@ impl PullRequestDiffView {
_ = cx _ = cx
.spawn(async move |weak, cx| match tokio::join!(t1, t2) { .spawn(async move |weak, cx| match tokio::join!(t1, t2) {
| (Some(old_side_highlights), Some(new_side_highlights)) => { | (Some(old_side_highlights), Some(new_side_highlights)) => {
_ = weak.update(cx, |this, cx| { _ = weak.update(cx, |this, cx| {
this.diff_view_state this.diff_view_state
.set_old_side_highlights(old_side_highlights); .set_old_side_highlights(old_side_highlights);
this.diff_view_state this.diff_view_state
.set_new_side_highlights(new_side_highlights); .set_new_side_highlights(new_side_highlights);
cx.notify(); cx.notify();
}); });
} }
| _ => {} | _ => {}
}) })
.detach(); .detach();
} }
@@ -184,18 +184,18 @@ impl gpui::Render for PullRequestDiffView {
.unwrap_or(QueryStatus::Loading); .unwrap_or(QueryStatus::Loading);
match content_diff { match content_diff {
| QueryStatus::Err(_) | QueryStatus::Loading => div() | QueryStatus::Err(_) | QueryStatus::Loading => div()
.size_full() .size_full()
.bg(theme.colors.surface) .bg(theme.colors.surface)
.p_4() .p_4()
.child(text("asd")), .child(text("asd")),
| QueryStatus::Loaded(_) => match &self.diff_view_content { | QueryStatus::Loaded(_) => match &self.diff_view_content {
| Some(content) => div() | Some(content) => div()
.size_full() .size_full()
.child(diff_view(self.diff_view_state.clone(), content.clone())), .child(diff_view(self.diff_view_state.clone(), content.clone())),
| None => div(), | None => div(),
}, },
} }
} }
} }

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use gpui::{ use gpui::{
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
div, img, linear_gradient, prelude::FluentBuilder, div, img, prelude::FluentBuilder,
}; };
use crate::{ use crate::{
@@ -12,6 +12,7 @@ use crate::{
button::{self, Button, button}, button::{self, Button, button},
font_icon::{FontIcon, font_icon}, font_icon::{FontIcon, font_icon},
markdown::{self, MarkdownText}, markdown::{self, MarkdownText},
segmented_control::segmented_control,
text::text, text::text,
}, },
query::{self, QueryStatus, read_query, use_query, watch_query}, query::{self, QueryStatus, read_query, use_query, watch_query},
@@ -19,17 +20,23 @@ use crate::{
}; };
pub(crate) struct PullRequestView { pub(crate) struct PullRequestView {
current_tab: Tab,
markdown_viewer: Option<gpui::Entity<MarkdownText>>, markdown_viewer: Option<gpui::Entity<MarkdownText>>,
diff_view: Option<gpui::Entity<PullRequestDiffView>>, diff_view: Option<gpui::Entity<PullRequestDiffView>>,
pull_request_query: Option<query::Entity<api::issues::FetchPullRequest>>, pull_request_query: Option<query::Entity<api::issues::FetchPullRequest>>,
} }
#[derive(gpui::IntoElement)] #[derive(Clone, Copy, PartialEq, Eq)]
struct Toolbar {} enum Tab {
PullRequestBody,
DiffView,
}
pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView { pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
PullRequestView { PullRequestView {
current_tab: Tab::PullRequestBody,
markdown_viewer: None, markdown_viewer: None,
diff_view: None, diff_view: None,
pull_request_query: None, pull_request_query: None,
@@ -45,6 +52,7 @@ impl PullRequestView {
let query = use_query(api::issues::FetchPullRequest { id }, cx); let query = use_query(api::issues::FetchPullRequest { id }, cx);
self.pull_request_query = Some(query.clone()); self.pull_request_query = Some(query.clone());
self.current_tab = Tab::PullRequestBody;
_ = watch_query(&query, Self::sync_pull_request_query, cx).detach(); _ = watch_query(&query, Self::sync_pull_request_query, cx).detach();
@@ -98,6 +106,65 @@ impl PullRequestView {
cx.notify(); cx.notify();
} }
fn toolbar(&self, cx: &gpui::Context<Self>) -> gpui::AnyElement {
fn toolbar_button(id: impl Into<gpui::ElementId>) -> 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( fn pr_content(
&self, &self,
pr: &api::issues::DetailedPullRequest, pr: &api::issues::DetailedPullRequest,
@@ -114,41 +181,41 @@ impl PullRequestView {
.rounded_full(); .rounded_full();
match pr.state { match pr.state {
| api::issues::PullRequestState::Open => { | api::issues::PullRequestState::Open => {
status_pill = status_pill status_pill = status_pill
.bg(theme.colors.success_solid) .bg(theme.colors.success_solid)
.child( .child(
font_icon(FontIcon::PullRequestArrow) font_icon(FontIcon::PullRequestArrow)
.size_3() .size_3()
.text_color(theme.colors.success_on_solid), .text_color(theme.colors.success_on_solid),
) )
.child( .child(
text("Open") text("Open")
.text_color(theme.colors.success_on_solid) .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(), .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| { let merge_text = pr.author.as_ref().map(|author| {
@@ -228,7 +295,6 @@ impl PullRequestView {
.flex_col() .flex_col()
.bg(theme.colors.surface) .bg(theme.colors.surface)
.overflow_hidden() .overflow_hidden()
.child(Toolbar {})
.child( .child(
div() div()
.flex() .flex()
@@ -282,9 +348,18 @@ impl gpui::Render for PullRequestView {
_window: &mut gpui::Window, _window: &mut gpui::Window,
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement { ) -> impl gpui::IntoElement {
div().size_full().child(match &self.pull_request_query { div()
| Some(q) => match read_query(q, cx) { .size_full()
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx), .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() | QueryStatus::Err(e) => div()
.size_full() .size_full()
@@ -294,83 +369,10 @@ impl gpui::Render for PullRequestView {
.size_full() .size_full()
.child("loading pr content") .child("loading pr content")
.into_any_element(), .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(),
}) })
}
}
impl gpui::RenderOnce for Toolbar {
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
fn toolbar_button(id: impl Into<gpui::ElementId>) -> 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(),
)
} }
} }

View File

@@ -1,7 +1,7 @@
mod catppuccin; mod catppuccin;
pub(crate) mod syntax; pub(crate) mod syntax;
use gpui::Rgba; use gpui::{Background, Rgba};
#[allow(unused_imports)] #[allow(unused_imports)]
pub use syntax::{HIGHLIGHT_NAMES, ThemeSyntax, ThemeSyntaxHighlight}; pub use syntax::{HIGHLIGHT_NAMES, ThemeSyntax, ThemeSyntaxHighlight};
@@ -33,6 +33,7 @@ pub struct ThemeColors {
pub background: Rgba, pub background: Rgba,
pub surface: Rgba, pub surface: Rgba,
pub surface_elevated: Rgba, pub surface_elevated: Rgba,
pub surface_button: Background,
pub surface_chrome: Rgba, pub surface_chrome: Rgba,
pub surface_hover: Rgba, pub surface_hover: Rgba,
pub surface_active: Rgba, pub surface_active: Rgba,

View File

@@ -1,4 +1,5 @@
use crate::colors::{hex, hex_alpha}; use crate::colors::{hex, hex_alpha};
use gpui::{linear_color_stop, linear_gradient};
use super::{ use super::{
Theme, ThemeColors, ThemeMode, ThemeSyntaxHighlight, Theme, ThemeColors, ThemeMode, ThemeSyntaxHighlight,
@@ -44,6 +45,11 @@ pub(crate) fn latte() -> Theme {
background: hex(0xeff1f5), background: hex(0xeff1f5),
surface: hex(0xe6e9ef), surface: hex(0xe6e9ef),
surface_elevated: hex(0xeff1f5), 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_chrome: hex(0xdce0e8),
surface_hover: hex(0xdce0e8), surface_hover: hex(0xdce0e8),
surface_active: hex(0xccd0da), surface_active: hex(0xccd0da),
@@ -153,6 +159,11 @@ pub(crate) fn mocha() -> Theme {
background: hex(0x1e1e2e), background: hex(0x1e1e2e),
surface: hex(0x181825), surface: hex(0x181825),
surface_elevated: hex(0x45475a), 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_chrome: hex(0x11111b),
surface_hover: hex(0x313244), surface_hover: hex(0x313244),
surface_active: hex(0x45475a), surface_active: hex(0x45475a),

View File

@@ -44,117 +44,127 @@ pub(crate) fn diff_content(
for op in diff.ops() { for op in diff.ops() {
match op { match op {
| &similar::DiffOp::Equal { | &similar::DiffOp::Equal {
old_index, old_index,
new_index, new_index,
len, len,
} => { } => {
for i in 0..len { for i in 0..len {
let old_line = old_index + i; let old_line = old_index + i;
let new_line = new_index + i; let new_line = new_index + i;
let old_line_range = &old_line_ranges[old_line]; let old_line_range = &old_line_ranges[old_line];
let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?); let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?);
diff_lines.push(DiffLine { diff_lines.push(DiffLine {
op: Op::Equal, op: Op::Equal,
old_line: Some(old_line), old_line: Some(old_line),
old_content: Some(Arc::clone(&content)), old_content: Some(Arc::clone(&content)),
old_byte_range: old_line_range.clone(), old_byte_range: old_line_range.clone(),
new_line: Some(new_line), new_line: Some(new_line),
new_content: Some(content), new_content: Some(content),
new_byte_range: new_line_ranges[new_line].clone(), 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."
)
} }
};
diff_lines.push(diff_line);
} }
}
| &similar::DiffOp::Delete { | &similar::DiffOp::Insert {
old_index, old_len, .. new_index, new_len, ..
} => { } => {
for i in 0..old_len { for i in 0..new_len {
let old_line_range = &old_line_ranges[old_index]; let new_line_range = &new_line_ranges[new_index + i];
let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?); let content = Arc::from(new_content.slice(new_line_range.clone()).as_str()?);
diff_lines.push(DiffLine { diff_lines.push(DiffLine {
op: Op::Delete, op: Op::Insert,
old_line: Some(old_index + i), old_line: None,
old_content: Some(content), old_content: None,
old_byte_range: old_line_range.clone(), old_byte_range: 0..0,
new_line: None, new_line: Some(new_index + i),
new_content: None, new_content: Some(content),
new_byte_range: 0..0, 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,
})
}
} }
}
} }
} }

View File

@@ -27,17 +27,17 @@ pub(crate) fn classify_content(content: &[u8]) -> ContentType {
ContentType::Text ContentType::Text
} else { } else {
match memchr(0, &content[..content.len().min(8192)]) { match memchr(0, &content[..content.len().min(8192)]) {
| None => ContentType::Text, | None => ContentType::Text,
| Some(_) => ContentType::Binary, | Some(_) => ContentType::Binary,
} }
} }
} }
pub(crate) fn file_type_from_path(path: &str) -> FileType { pub(crate) fn file_type_from_path(path: &str) -> FileType {
match Path::new(path).extension().map(|it| it.to_str()).flatten() { match Path::new(path).extension().map(|it| it.to_str()).flatten() {
| Some("rs") => FileType::Rust, | Some("rs") => FileType::Rust,
| Some("js") | Some("jsx") => FileType::JavaScript, | Some("js") | Some("jsx") => FileType::JavaScript,
| _ => FileType::Unknown, | _ => FileType::Unknown,
} }
} }
@@ -55,18 +55,18 @@ pub(crate) fn line_ranges(content: &[u8]) -> Vec<std::ops::Range<usize>> {
let c = content[i]; let c = content[i];
match (c, content.get(i + 1)) { match (c, content.get(i + 1)) {
| (b'\r', Some(b'\n')) => { | (b'\r', Some(b'\n')) => {
// if \r found, check if its \r\n or if its a lone \r // if \r found, check if its \r\n or if its a lone \r
// if \r\n, then treat as one line break // if \r\n, then treat as one line break
ranges.push(line_start..i + 1); ranges.push(line_start..i + 1);
// because we already counted the \n byte, the next iter into it needs to be skipped // because we already counted the \n byte, the next iter into it needs to be skipped
skip_next = true; skip_next = true;
line_start = i + 2; line_start = i + 2;
} }
| _ => { | _ => {
ranges.push(line_start..i); ranges.push(line_start..i);
line_start = i + 1; line_start = i + 1;
} }
} }
} }

View File

@@ -6,16 +6,16 @@ fn ts_highlight_configuration_for_file_type(
file_type: util::file::FileType, file_type: util::file::FileType,
) -> Option<tree_sitter_highlight::HighlightConfiguration> { ) -> Option<tree_sitter_highlight::HighlightConfiguration> {
match file_type { match file_type {
| util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new( | util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new(
tree_sitter_rust::LANGUAGE.into(), tree_sitter_rust::LANGUAGE.into(),
"rust", "rust",
tree_sitter_rust::HIGHLIGHTS_QUERY, tree_sitter_rust::HIGHLIGHTS_QUERY,
tree_sitter_rust::INJECTIONS_QUERY, tree_sitter_rust::INJECTIONS_QUERY,
"", "",
) )
.ok(), .ok(),
| _ => None, | _ => None,
} }
} }
@@ -44,37 +44,37 @@ pub(crate) fn highlight_content(
for highlight_event in events { for highlight_event in events {
match highlight_event.ok()? { match highlight_event.ok()? {
| tree_sitter_highlight::HighlightEvent::HighlightStart(h) => { | tree_sitter_highlight::HighlightEvent::HighlightStart(h) => {
highlight = theme_syntax.as_slice()[h.0]; 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;
} }
let mut line = current_line; | tree_sitter_highlight::HighlightEvent::Source { start, end } => {
while line < line_ranges.len() && end > line_ranges[line].start { while current_line < line_ranges.len() && start >= line_ranges[current_line].end {
if highlights.get(line).is_none() {
highlights.push(Vec::new()); 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 => {
| tree_sitter_highlight::HighlightEvent::HighlightEnd => { highlight = default_highlight;
highlight = default_highlight; }
}
} }
} }