refactor: redesign theme tokens and split catppuccin themes

This commit is contained in:
2026-05-13 20:02:26 +08:00
parent af5fd60eb5
commit 2c3de1fd6e
20 changed files with 797 additions and 667 deletions

View File

@@ -229,8 +229,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));
@@ -343,9 +343,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}"),
} }
} }

View File

@@ -136,7 +136,7 @@ where
{ {
let mut body: graphql_client::Response<T> = res.json().await?; let mut body: graphql_client::Response<T> = res.json().await?;
match body.data.take() { match body.data.take() {
None => Err(Error::GraphQLError(body.errors.unwrap_or_default())), | None => Err(Error::GraphQLError(body.errors.unwrap_or_default())),
Some(data) => Ok((body, data)), | Some(data) => Ok((body, data)),
} }
} }

View File

@@ -289,8 +289,8 @@ impl query::QueryFn for ListPullRequests {
} }
let query_string = match self.filter { let query_string = match self.filter {
| Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter), | Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter),
| None => "is:pr archived:false sort:updated-desc".into(), | None => "is:pr archived:false sort:updated-desc".into(),
}; };
let gql = let gql =
@@ -312,19 +312,19 @@ impl query::QueryFn for ListPullRequests {
.flatten() .flatten()
.filter_map(|edge| { .filter_map(|edge| {
edge.node.and_then(|n| match n { edge.node.and_then(|n| match n {
| PullRequestPaginationQuerySearchEdgesNode::PullRequest(p) => { | PullRequestPaginationQuerySearchEdgesNode::PullRequest(p) => {
Some(PullRequest { Some(PullRequest {
id: p.id.into(), id: p.id.into(),
title: p.title, title: p.title,
state: p.state, state: p.state,
is_draft: p.is_draft, is_draft: p.is_draft,
repo_slug: format!( repo_slug: format!(
"{}/{}", "{}/{}",
p.repository.owner.login, p.repository.name p.repository.owner.login, p.repository.name
), ),
}) })
} }
| _ => None, | _ => None,
}) })
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -369,32 +369,32 @@ impl query::QueryFn for FetchPullRequest {
"missing 'node' field on PullRequestQuery response".into(), "missing 'node' field on PullRequestQuery response".into(),
)) ))
.and_then(|n| match n { .and_then(|n| match n {
| PullRequestQueryNode::PullRequest(p) => { | PullRequestQueryNode::PullRequest(p) => {
let created_at = let created_at =
chrono::DateTime::parse_from_rfc3339(&p.created_at).map_err(|err| { chrono::DateTime::parse_from_rfc3339(&p.created_at).map_err(|err| {
api::Error::MalformedResponse(format!( api::Error::MalformedResponse(format!(
"invalid pull request createdAt {:?}: {err}", "invalid pull request createdAt {:?}: {err}",
p.created_at p.created_at
)) ))
})?; })?;
Ok(DetailedPullRequest { Ok(DetailedPullRequest {
title: p.title, title: p.title,
state: p.state, state: p.state,
is_draft: p.is_draft, is_draft: p.is_draft,
body: p.body, body: p.body,
author: p.author.map(|it| api::user::Actor { author: p.author.map(|it| api::user::Actor {
login: it.login, login: it.login,
avatar_url: it.avatar_url, avatar_url: it.avatar_url,
}), }),
base_branch_name: p.base_ref.map(|r| r.name), base_branch_name: p.base_ref.map(|r| r.name),
head_branch_name: p.head_ref.map(|r| r.name), head_branch_name: p.head_ref.map(|r| r.name),
created_at: Some(created_at), created_at: Some(created_at),
}) })
} }
| _ => Err(api::Error::MalformedResponse( | _ => Err(api::Error::MalformedResponse(
"unexpected node type on PullRequestQuery".into(), "unexpected node type on PullRequestQuery".into(),
)), )),
}) })
} }
} }
@@ -437,11 +437,11 @@ impl query::QueryFn for FetchPullRequestTimeline {
TimelineActor { TimelineActor {
kind: match on { kind: match on {
| actorFieldsOn::Bot => "Bot", | actorFieldsOn::Bot => "Bot",
| actorFieldsOn::EnterpriseUserAccount => "EnterpriseUserAccount", | actorFieldsOn::EnterpriseUserAccount => "EnterpriseUserAccount",
| actorFieldsOn::Mannequin => "Mannequin", | actorFieldsOn::Mannequin => "Mannequin",
| actorFieldsOn::Organization => "Organization", | actorFieldsOn::Organization => "Organization",
| actorFieldsOn::User => "User", | actorFieldsOn::User => "User",
} }
.into(), .into(),
name: login, name: login,
@@ -451,62 +451,62 @@ impl query::QueryFn for FetchPullRequestTimeline {
fn normalize_assignee(actor: assigneeFields) -> TimelineActor { fn normalize_assignee(actor: assigneeFields) -> TimelineActor {
match actor { match actor {
| assigneeFields::Bot(actor) => TimelineActor { | assigneeFields::Bot(actor) => TimelineActor {
kind: "Bot".into(), kind: "Bot".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
| assigneeFields::Mannequin(actor) => TimelineActor { | assigneeFields::Mannequin(actor) => TimelineActor {
kind: "Mannequin".into(), kind: "Mannequin".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
| assigneeFields::Organization(actor) => TimelineActor { | assigneeFields::Organization(actor) => TimelineActor {
kind: "Organization".into(), kind: "Organization".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
| assigneeFields::User(actor) => TimelineActor { | assigneeFields::User(actor) => TimelineActor {
kind: "User".into(), kind: "User".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
} }
} }
fn normalize_requested_reviewer(actor: requestedReviewerFields) -> TimelineActor { fn normalize_requested_reviewer(actor: requestedReviewerFields) -> TimelineActor {
match actor { match actor {
| requestedReviewerFields::Bot(actor) => TimelineActor { | requestedReviewerFields::Bot(actor) => TimelineActor {
kind: "Bot".into(), kind: "Bot".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
| requestedReviewerFields::Mannequin(actor) => TimelineActor { | requestedReviewerFields::Mannequin(actor) => TimelineActor {
kind: "Mannequin".into(), kind: "Mannequin".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
| requestedReviewerFields::Team(actor) => TimelineActor { | requestedReviewerFields::Team(actor) => TimelineActor {
kind: "Team".into(), kind: "Team".into(),
name: actor.name, name: actor.name,
avatar_url: None, avatar_url: None,
}, },
| requestedReviewerFields::User(actor) => TimelineActor { | requestedReviewerFields::User(actor) => TimelineActor {
kind: "User".into(), kind: "User".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
} }
} }
fn normalize_review_state(state: PullRequestReviewState) -> String { fn normalize_review_state(state: PullRequestReviewState) -> String {
match state { match state {
| PullRequestReviewState::PENDING => "PENDING", | PullRequestReviewState::PENDING => "PENDING",
| PullRequestReviewState::COMMENTED => "COMMENTED", | PullRequestReviewState::COMMENTED => "COMMENTED",
| PullRequestReviewState::APPROVED => "APPROVED", | PullRequestReviewState::APPROVED => "APPROVED",
| PullRequestReviewState::CHANGES_REQUESTED => "CHANGES_REQUESTED", | PullRequestReviewState::CHANGES_REQUESTED => "CHANGES_REQUESTED",
| PullRequestReviewState::DISMISSED => "DISMISSED", | PullRequestReviewState::DISMISSED => "DISMISSED",
| _ => "OTHER", | _ => "OTHER",
} }
.into() .into()
} }
@@ -726,10 +726,10 @@ impl query::QueryFn for FetchPullRequestTimeline {
"missing 'node' field on PullRequestTimelineQuery response".into(), "missing 'node' field on PullRequestTimelineQuery response".into(),
)) ))
.and_then(|node| match node { .and_then(|node| match node {
| PullRequestTimelineQueryNode::PullRequest(pull_request) => Ok(pull_request), | PullRequestTimelineQueryNode::PullRequest(pull_request) => Ok(pull_request),
| _ => Err(api::Error::MalformedResponse( | _ => Err(api::Error::MalformedResponse(
"unexpected node type on PullRequestTimelineQuery".into(), "unexpected node type on PullRequestTimelineQuery".into(),
)), )),
})?; })?;
let timeline = pull_request.timeline_items; let timeline = pull_request.timeline_items;

View File

@@ -167,7 +167,10 @@ mod tests {
.map(|author| author.login.as_str()), .map(|author| author.login.as_str()),
Some("kennethnym") Some("kennethnym")
); );
assert_eq!(documented_failover.base_branch_name.as_deref(), Some("main")); assert_eq!(
documented_failover.base_branch_name.as_deref(),
Some("main")
);
assert_eq!( assert_eq!(
documented_failover.head_branch_name.as_deref(), documented_failover.head_branch_name.as_deref(),
Some("docs/manual-failover-steps") Some("docs/manual-failover-steps")
@@ -203,7 +206,10 @@ mod tests {
Some(chrono::DateTime::parse_from_rfc3339("2026-05-03T07:40:00Z").unwrap()) Some(chrono::DateTime::parse_from_rfc3339("2026-05-03T07:40:00Z").unwrap())
); );
assert_eq!( assert_eq!(
worker_split.author.as_ref().map(|author| author.login.as_str()), worker_split
.author
.as_ref()
.map(|author| author.login.as_str()),
Some("leaferiksen") Some("leaferiksen")
); );
assert_eq!(worker_split.base_branch_name.as_deref(), Some("main")); assert_eq!(worker_split.base_branch_name.as_deref(), Some("main"));
@@ -295,36 +301,36 @@ mod tests {
.expect("third timeline fixture json should parse"); .expect("third timeline fixture json should parse");
let first_page_nodes = match first_page.node.as_ref() { let first_page_nodes = match first_page.node.as_ref() {
Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => { | Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
pull_request pull_request
.timeline_items .timeline_items
.nodes .nodes
.as_ref() .as_ref()
.expect("first timeline fixture page should contain timeline nodes") .expect("first timeline fixture page should contain timeline nodes")
} }
_ => panic!("first timeline fixture page should resolve to a pull request node"), | _ => panic!("first timeline fixture page should resolve to a pull request node"),
}; };
let second_page_nodes = match second_page.node.as_ref() { let second_page_nodes = match second_page.node.as_ref() {
Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => { | Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
pull_request pull_request
.timeline_items .timeline_items
.nodes .nodes
.as_ref() .as_ref()
.expect("second timeline fixture page should contain timeline nodes") .expect("second timeline fixture page should contain timeline nodes")
} }
_ => panic!("second timeline fixture page should resolve to a pull request node"), | _ => panic!("second timeline fixture page should resolve to a pull request node"),
}; };
let third_page_nodes = match third_page.node.as_ref() { let third_page_nodes = match third_page.node.as_ref() {
Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => { | Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
pull_request pull_request
.timeline_items .timeline_items
.nodes .nodes
.as_ref() .as_ref()
.expect("third timeline fixture page should contain timeline nodes") .expect("third timeline fixture page should contain timeline nodes")
} }
_ => panic!("third timeline fixture page should resolve to a pull request node"), | _ => panic!("third timeline fixture page should resolve to a pull request node"),
}; };
assert_eq!( assert_eq!(

View File

@@ -11,74 +11,85 @@ pub const fn hex(hex: u32) -> Rgba {
} }
} }
pub const fn hex_alpha(hex: u32, alpha: f32) -> Rgba {
let [_, r, g, b] = hex.to_be_bytes();
Rgba {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: alpha,
}
}
#[allow(dead_code)] #[allow(dead_code)]
pub const fn neutral(shade: u16) -> Rgba { pub const fn neutral(shade: u16) -> Rgba {
match shade { match shade {
50 => hex(0xfafafa), | 50 => hex(0xfafafa),
100 => hex(0xf5f5f5), | 100 => hex(0xf5f5f5),
200 => hex(0xe5e5e5), | 200 => hex(0xe5e5e5),
300 => hex(0xd4d4d4), | 300 => hex(0xd4d4d4),
400 => hex(0xa3a3a3), | 400 => hex(0xa3a3a3),
500 => hex(0x737373), | 500 => hex(0x737373),
600 => hex(0x525252), | 600 => hex(0x525252),
700 => hex(0x404040), | 700 => hex(0x404040),
800 => hex(0x262626), | 800 => hex(0x262626),
900 => hex(0x171717), | 900 => hex(0x171717),
950 => hex(0x0a0a0a), | 950 => hex(0x0a0a0a),
_ => panic!("unsupported Tailwind neutral shade"), | _ => panic!("unsupported Tailwind neutral shade"),
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub const fn violet(shade: u16) -> Rgba { pub const fn violet(shade: u16) -> Rgba {
match shade { match shade {
50 => hex(0xf5f3ff), | 50 => hex(0xf5f3ff),
100 => hex(0xede9fe), | 100 => hex(0xede9fe),
200 => hex(0xddd6fe), | 200 => hex(0xddd6fe),
300 => hex(0xc4b5fd), | 300 => hex(0xc4b5fd),
400 => hex(0xa78bfa), | 400 => hex(0xa78bfa),
500 => hex(0x8b5cf6), | 500 => hex(0x8b5cf6),
600 => hex(0x7c3aed), | 600 => hex(0x7c3aed),
700 => hex(0x6d28d9), | 700 => hex(0x6d28d9),
800 => hex(0x5b21b6), | 800 => hex(0x5b21b6),
900 => hex(0x4c1d95), | 900 => hex(0x4c1d95),
950 => hex(0x2e1065), | 950 => hex(0x2e1065),
_ => panic!("unsupported Tailwind violet shade"), | _ => panic!("unsupported Tailwind violet shade"),
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub const fn amber(shade: u16) -> Rgba { pub const fn amber(shade: u16) -> Rgba {
match shade { match shade {
50 => hex(0xfffbeb), | 50 => hex(0xfffbeb),
100 => hex(0xfef3c7), | 100 => hex(0xfef3c7),
200 => hex(0xfde68a), | 200 => hex(0xfde68a),
300 => hex(0xfcd34d), | 300 => hex(0xfcd34d),
400 => hex(0xfbbf24), | 400 => hex(0xfbbf24),
500 => hex(0xf59e0b), | 500 => hex(0xf59e0b),
600 => hex(0xd97706), | 600 => hex(0xd97706),
700 => hex(0xb45309), | 700 => hex(0xb45309),
800 => hex(0x92400e), | 800 => hex(0x92400e),
900 => hex(0x78350f), | 900 => hex(0x78350f),
950 => hex(0x451a03), | 950 => hex(0x451a03),
_ => panic!("unsupported Tailwind amber shade"), | _ => panic!("unsupported Tailwind amber shade"),
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub const fn red(shade: u16) -> Rgba { pub const fn red(shade: u16) -> Rgba {
match shade { match shade {
50 => hex(0xfef2f2), | 50 => hex(0xfef2f2),
100 => hex(0xfee2e2), | 100 => hex(0xfee2e2),
200 => hex(0xfecaca), | 200 => hex(0xfecaca),
300 => hex(0xfca5a5), | 300 => hex(0xfca5a5),
400 => hex(0xf87171), | 400 => hex(0xf87171),
500 => hex(0xef4444), | 500 => hex(0xef4444),
600 => hex(0xdc2626), | 600 => hex(0xdc2626),
700 => hex(0xb91c1c), | 700 => hex(0xb91c1c),
800 => hex(0x991b1b), | 800 => hex(0x991b1b),
900 => hex(0x7f1d1d), | 900 => hex(0x7f1d1d),
950 => hex(0x450a0a), | 950 => hex(0x450a0a),
_ => panic!("unsupported Tailwind red shade"), | _ => panic!("unsupported Tailwind red shade"),
} }
} }

View File

@@ -87,8 +87,8 @@ impl gpui::RenderOnce for Button {
let theme = app::current_theme(cx); let theme = app::current_theme(cx);
let icon_color = match self.variant { let icon_color = match self.variant {
| Variant::Primary => theme.colors.accent_text, | Variant::Primary => theme.colors.accent_on_solid,
| Variant::Secondary => theme.colors.text, | Variant::Secondary => theme.colors.text,
}; };
let mut children: Vec<AnyElement> = Vec::with_capacity(3); let mut children: Vec<AnyElement> = Vec::with_capacity(3);
@@ -115,10 +115,10 @@ impl gpui::RenderOnce for Button {
.py_0p5() .py_0p5()
.children(children) .children(children)
.when(matches!(self.variant, Variant::Primary), |div| { .when(matches!(self.variant, Variant::Primary), |div| {
div.bg(theme.colors.accent) div.bg(theme.colors.accent_solid)
.text_color(theme.colors.accent_text) .text_color(theme.colors.accent_on_solid)
.border_1() .border_1()
.border_color(theme.colors.border.blend(theme.colors.accent)) .border_color(theme.colors.border.blend(theme.colors.accent_solid))
}) })
.when(matches!(self.variant, Variant::Secondary), |div| { .when(matches!(self.variant, Variant::Secondary), |div| {
div.bg(theme.colors.surface_elevated) div.bg(theme.colors.surface_elevated)

View File

@@ -115,7 +115,7 @@ pub(crate) fn new(
impl Styled for ContentBlock { impl Styled for ContentBlock {
fn style(&mut self) -> &mut gpui::StyleRefinement { fn style(&mut self) -> &mut gpui::StyleRefinement {
match self { match self {
| ContentBlock::Text { style, .. } => style, | ContentBlock::Text { style, .. } => style,
} }
} }
} }
@@ -179,54 +179,56 @@ impl MarkdownText {
} }
match node.kind_id() { match node.kind_id() {
| MARKDOWN_KIND_ID_EMPHASIS => { | MARKDOWN_KIND_ID_EMPHASIS => {
highlights.push((
node_range!(),
gpui::HighlightStyle {
font_style: Some(gpui::FontStyle::Italic),
..Default::default()
},
));
}
| MARKDOWN_KIND_ID_STRONG_EMPHASIS => highlights.push((
node_range!(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
..Default::default()
},
)),
| MARKDOWN_KIND_ID_LINK => {
if cursor.goto_first_child() {
highlights.push(( highlights.push((
node_range!(), node_range!(),
gpui::HighlightStyle { gpui::HighlightStyle {
color: Some(theme.colors.accent.into()), font_style: Some(gpui::FontStyle::Italic),
underline: Some(gpui::UnderlineStyle {
color: Some(theme.colors.accent.into()),
thickness: px(1.),
wavy: false,
}),
..Default::default() ..Default::default()
}, },
)); ));
}
| MARKDOWN_KIND_ID_STRONG_EMPHASIS => highlights.push((
node_range!(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
..Default::default()
},
)),
if cursor.goto_next_sibling() | MARKDOWN_KIND_ID_LINK => {
&& let Ok(src) = cursor.node().utf8_text(content.as_bytes()) if cursor.goto_first_child() {
{ highlights.push((
links node_range!(),
.push((node_range!(), gpui::SharedString::from(String::from(src)))); gpui::HighlightStyle {
} else { color: Some(theme.colors.link.into()),
// the link src is invalid, use an empty string as a fallback underline: Some(gpui::UnderlineStyle {
// link on click handler will ignore empty string color: Some(theme.colors.link.into()),
links.push((node_range!(), "".into())) thickness: px(1.),
wavy: false,
}),
..Default::default()
},
));
if cursor.goto_next_sibling()
&& let Ok(src) = cursor.node().utf8_text(content.as_bytes())
{
links.push((
node_range!(),
gpui::SharedString::from(String::from(src)),
));
} else {
// the link src is invalid, use an empty string as a fallback
// link on click handler will ignore empty string
links.push((node_range!(), "".into()))
}
} }
} }
}
| _ => { | _ => {
// extend here to support more markdown node stylings // extend here to support more markdown node stylings
} }
}; };
if !cursor.goto_next_sibling() { if !cursor.goto_next_sibling() {
@@ -303,23 +305,23 @@ impl MarkdownText {
let marker_content = &content[marker_node.byte_range()]; let marker_content = &content[marker_node.byte_range()];
let list_marker_char = match marker_content { let list_marker_char = match marker_content {
// unordered list item // unordered list item
| "-" | "+" | "*" => Some("".to_string()), | "-" | "+" | "*" => Some("".to_string()),
| marker_content if ORDERED_LIST_MARKER_REGEX.is_match(marker_content) => { | marker_content if ORDERED_LIST_MARKER_REGEX.is_match(marker_content) => {
let i = list_index.get_or_insert_with(|| { let i = list_index.get_or_insert_with(|| {
marker_content marker_content
.strip_suffix('.') .strip_suffix('.')
.unwrap() .unwrap()
.parse::<usize>() .parse::<usize>()
.unwrap() .unwrap()
}); });
let j = *i; let j = *i;
*i = j + 1; *i = j + 1;
Some(format!("{j}.")) Some(format!("{j}."))
} }
| _ => None, | _ => None,
}; };
let Some(list_marker_char) = list_marker_char else { let Some(list_marker_char) = list_marker_char else {
@@ -331,9 +333,9 @@ impl MarkdownText {
let block = if cursor.goto_next_sibling() { let block = if cursor.goto_next_sibling() {
let mut b = block_for_node(cursor, content, 0, theme); let mut b = block_for_node(cursor, content, 0, theme);
match b { match b {
| ContentBlock::Text { | ContentBlock::Text {
ref mut decoration, .. ref mut decoration, ..
} => *decoration = Some(list_marker_char.into()), } => *decoration = Some(list_marker_char.into()),
} }
b b
} else { } else {
@@ -372,150 +374,150 @@ impl MarkdownText {
} }
match current_node.kind_id() { match current_node.kind_id() {
| MARKDOWN_KIND_ID_ATX_HEADING => { | MARKDOWN_KIND_ID_ATX_HEADING => {
if !cursor.goto_first_child() { if !cursor.goto_first_child() {
render_fallback_content(&cursor, &self.content, &mut self.blocks); render_fallback_content(&cursor, &self.content, &mut self.blocks);
continue; continue;
}
let marker_node_kind = cursor.node().kind_id();
let block = if cursor.goto_next_sibling()
&& cursor.node().kind_id() == MARKDOWN_KIND_ID_HEADING_CONTENT
{
// because HEADING_CONTENT node includes the space after the heading marker
// offset by 1 to exclude the space
block_for_node(&mut cursor, &self.content, 1, theme)
} else {
ContentBlock::Text {
decoration: None,
text: gpui::SharedString::new(&self.content[current_node.byte_range()]),
highlights: Vec::new(),
links: Vec::new(),
style: gpui::StyleRefinement::default(),
} }
};
let mut block = match marker_node_kind { let marker_node_kind = cursor.node().kind_id();
| MARKDOWN_KIND_ID_ATX_H1_MARKER => block
.text_size(rems(2.25))
.font_weight(gpui::FontWeight::EXTRA_BOLD)
.mb_6(),
| MARKDOWN_KIND_ID_ATX_H2_MARKER => block
.text_2xl()
.font_weight(gpui::FontWeight::BOLD)
.mt_12()
.mb_4(),
| MARKDOWN_KIND_ID_ATX_H3_MARKER => block
.text_xl()
.font_weight(gpui::FontWeight::SEMIBOLD)
.mt_8()
.mb_3(),
| MARKDOWN_KIND_ID_ATX_H4_MARKER => block
.text_base()
.font_weight(gpui::FontWeight::SEMIBOLD)
.mt_6()
.mb_2(),
| _ => block,
}
.text_color(theme.colors.text);
if is_first_heading { let block = if cursor.goto_next_sibling()
is_first_heading = false; && cursor.node().kind_id() == MARKDOWN_KIND_ID_HEADING_CONTENT
block = block.mt_0(); {
// because HEADING_CONTENT node includes the space after the heading marker
// offset by 1 to exclude the space
block_for_node(&mut cursor, &self.content, 1, theme)
} else {
ContentBlock::Text {
decoration: None,
text: gpui::SharedString::new(&self.content[current_node.byte_range()]),
highlights: Vec::new(),
links: Vec::new(),
style: gpui::StyleRefinement::default(),
}
};
let mut block = match marker_node_kind {
| MARKDOWN_KIND_ID_ATX_H1_MARKER => block
.text_size(rems(2.25))
.font_weight(gpui::FontWeight::EXTRA_BOLD)
.mb_6(),
| MARKDOWN_KIND_ID_ATX_H2_MARKER => block
.text_2xl()
.font_weight(gpui::FontWeight::BOLD)
.mt_12()
.mb_4(),
| MARKDOWN_KIND_ID_ATX_H3_MARKER => block
.text_xl()
.font_weight(gpui::FontWeight::SEMIBOLD)
.mt_8()
.mb_3(),
| MARKDOWN_KIND_ID_ATX_H4_MARKER => block
.text_base()
.font_weight(gpui::FontWeight::SEMIBOLD)
.mt_6()
.mb_2(),
| _ => block,
}
.text_color(theme.colors.text);
if is_first_heading {
is_first_heading = false;
block = block.mt_0();
}
cursor.goto_parent();
self.blocks.push(block);
} }
cursor.goto_parent(); | MARKDOWN_KIND_ID_PARAGRAPH => {
let block = block_for_node(&mut cursor, &self.content, 0, theme)
.text_color(theme.colors.text)
.text_sm();
self.blocks.push(block); self.blocks.push(block);
}
| MARKDOWN_KIND_ID_PARAGRAPH => {
let block = block_for_node(&mut cursor, &self.content, 0, theme)
.text_color(theme.colors.text)
.text_sm();
self.blocks.push(block);
}
| MARKDOWN_KIND_ID_TIGHT_LIST => {
let is_rendered =
render_list_node(&mut cursor, &self.content, &mut self.blocks, theme, 0);
if !is_rendered {
continue;
}
}
| MARKDOWN_KIND_ID_FENCED_CODE_BLOCK => {
// expected tree shape:
// fenced_code_block
// ├── info_string? (present if there is a language annotation)
// └── code_fence_content? (present if there is some content between the backticks)
if !cursor.goto_first_child() {
render_fallback_content(&cursor, &self.content, &mut self.blocks);
continue;
} }
let content = if cursor.node().kind_id() == MARKDOWN_KIND_ID_INFO_STRING { | MARKDOWN_KIND_ID_TIGHT_LIST => {
// skipping info string (which annotates the code block) let is_rendered =
if cursor.goto_next_sibling() { render_list_node(&mut cursor, &self.content, &mut self.blocks, theme, 0);
// this is code_fence_content node if !is_rendered {
continue;
}
}
| MARKDOWN_KIND_ID_FENCED_CODE_BLOCK => {
// expected tree shape:
// fenced_code_block
// ├── info_string? (present if there is a language annotation)
// └── code_fence_content? (present if there is some content between the backticks)
if !cursor.goto_first_child() {
render_fallback_content(&cursor, &self.content, &mut self.blocks);
continue;
}
let content = if cursor.node().kind_id() == MARKDOWN_KIND_ID_INFO_STRING {
// skipping info string (which annotates the code block)
if cursor.goto_next_sibling() {
// this is code_fence_content node
gpui::SharedString::new(
cursor
.node()
.utf8_text(self.content.as_bytes())
.unwrap_or_default(),
)
} else {
gpui::SharedString::default()
}
} else {
// assuming the current node is already code_fence_content
gpui::SharedString::new( gpui::SharedString::new(
cursor cursor
.node() .node()
.utf8_text(self.content.as_bytes()) .utf8_text(self.content.as_bytes())
.unwrap_or_default(), .unwrap_or_default(),
) )
} else { };
gpui::SharedString::default()
cursor.goto_parent();
let block = ContentBlock::Text {
decoration: None,
text: content,
highlights: Vec::new(),
links: Vec::new(),
style: gpui::StyleRefinement::default(),
} }
} else { .text_sm()
// assuming the current node is already code_fence_content
gpui::SharedString::new(
cursor
.node()
.utf8_text(self.content.as_bytes())
.unwrap_or_default(),
)
};
cursor.goto_parent();
let block = ContentBlock::Text {
decoration: None,
text: content,
highlights: Vec::new(),
links: Vec::new(),
style: gpui::StyleRefinement::default(),
}
.text_sm()
.text_color(theme.colors.text)
.line_height(relative(1.2))
.font_family("Menlo")
.px_3()
.py_2()
.rounded_sm()
.bg(theme.colors.surface)
.border_1()
.my_4()
.border_color(theme.colors.border);
self.blocks.push(block);
}
| _ => {
println!(
"[WARN] formatting not implemenetd for node type {:?}",
current_node.kind()
);
let block = block_for_node(&mut cursor, &self.content, 0, theme)
.text_color(theme.colors.text) .text_color(theme.colors.text)
.text_sm(); .line_height(relative(1.2))
.font_family("Menlo")
.px_3()
.py_2()
.rounded_sm()
.bg(theme.colors.code_bg)
.border_1()
.my_4()
.border_color(theme.colors.code_border);
self.blocks.push(block); self.blocks.push(block);
} }
| _ => {
println!(
"[WARN] formatting not implemenetd for node type {:?}",
current_node.kind()
);
let block = block_for_node(&mut cursor, &self.content, 0, theme)
.text_color(theme.colors.text)
.text_sm();
self.blocks.push(block);
}
} }
if !cursor.goto_next_sibling() { if !cursor.goto_next_sibling() {
@@ -533,55 +535,55 @@ impl gpui::Render for MarkdownText {
) -> impl gpui::prelude::IntoElement { ) -> impl gpui::prelude::IntoElement {
let children = self.blocks.iter().enumerate().map(|(i, block)| { let children = self.blocks.iter().enumerate().map(|(i, block)| {
match block { match block {
| ContentBlock::Text { | ContentBlock::Text {
decoration, decoration,
text, text,
highlights, highlights,
links, links,
style, style,
} => { } => {
let styled_text = let styled_text =
gpui::StyledText::new(text.clone()).with_highlights(highlights.clone()); gpui::StyledText::new(text.clone()).with_highlights(highlights.clone());
let content = if links.is_empty() { let content = if links.is_empty() {
div().w_full().child(styled_text) div().w_full().child(styled_text)
} else { } else {
// if link in block, interactive text is needed // if link in block, interactive text is needed
// to handle link clicks // to handle link clicks
let (link_ranges, srcs): (Vec<_>, Vec<_>) = links.iter().cloned().unzip(); let (link_ranges, srcs): (Vec<_>, Vec<_>) = links.iter().cloned().unzip();
let weak = cx.entity(); let weak = cx.entity();
let t = gpui::InteractiveText::new(i, styled_text).on_click( let t = gpui::InteractiveText::new(i, styled_text).on_click(
link_ranges, link_ranges,
move |i, _, cx| { move |i, _, cx| {
if let Some(src) = srcs.get(i) { if let Some(src) = srcs.get(i) {
weak.update(cx, |this, cx| { weak.update(cx, |this, cx| {
this.on_open_link(src, cx); this.on_open_link(src, cx);
cx.notify(); cx.notify();
}) })
} }
}, },
); );
div().w_full().child(t) div().w_full().child(t)
}; };
let mut div = match decoration { let mut div = match decoration {
| Some(d) => div() | Some(d) => div()
.w_full() .w_full()
.flex() .flex()
.flex_row() .flex_row()
.gap_2() .gap_2()
.items_start() .items_start()
.child(d.clone()) .child(d.clone())
.child(div().flex_1().min_w_0().child(content)), .child(div().flex_1().min_w_0().child(content)),
| None => div().w_full().child(content), | None => div().w_full().child(content),
}; };
div.style().refine(&style); div.style().refine(&style);
div div
} }
} }
}); });

View File

@@ -73,17 +73,17 @@ fn setup_application(cx: &mut gpui::App) {
let start = resume_application_state(cx); let start = resume_application_state(cx);
match start { match start {
Start::FromScratch => { | Start::FromScratch => {
let screen = setup_wizard::new(); let screen = setup_wizard::new();
_ = setup_wizard::open_window(screen, cx); _ = setup_wizard::open_window(screen, cx);
} }
Start::FromSetup(state) => { | Start::FromSetup(state) => {
let screen = setup_wizard::from_saved(state); let screen = setup_wizard::from_saved(state);
_ = setup_wizard::open_window(screen, cx); _ = setup_wizard::open_window(screen, cx);
} }
Start::FromSaved(_) => { | Start::FromSaved(_) => {
_ = dashboard::open_window(cx); _ = dashboard::open_window(cx);
} }
}; };
@@ -123,8 +123,8 @@ fn resume_application_state(cx: &mut gpui::App) -> Start {
println!("[main] setup status: {:?}", setup_status); println!("[main] setup status: {:?}", setup_status);
match setup_status { match setup_status {
setup_wizard::SetupStatus::NotStarted => Start::FromScratch, | setup_wizard::SetupStatus::NotStarted => Start::FromScratch,
setup_wizard::SetupStatus::InProgress(state) => Start::FromSetup(state), | setup_wizard::SetupStatus::InProgress(state) => Start::FromSetup(state),
setup_wizard::SetupStatus::Completed => Start::FromSaved(state), | setup_wizard::SetupStatus::Completed => Start::FromSaved(state),
} }
} }

View File

@@ -137,10 +137,10 @@ 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,9 +181,9 @@ 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()),
} }
} }
@@ -284,11 +284,11 @@ 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))
} }
@@ -317,8 +317,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

@@ -9,7 +9,7 @@ use crate::{
api::{self}, api::{self},
app, app,
component::{ component::{
font_icon::{FontIcon, font_icon}, font_icon::{FontIcon, FontIconSvg, font_icon},
text::text, text::text,
}, },
query::{self, QueryStatus, read_query, use_query}, query::{self, QueryStatus, read_query, use_query},
@@ -134,6 +134,19 @@ impl gpui::RenderOnce for IssueListItem {
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement { fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
let theme = app::current_theme(cx); let theme = app::current_theme(cx);
fn pill(label: impl gpui::IntoElement + gpui::Styled, icon: FontIconSvg) -> gpui::Div {
div()
.flex()
.flex_row()
.items_center()
.justify_start()
.rounded_full()
.px_2()
.gap_1()
.child(icon.size_3())
.child(label.text_xs())
}
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"),
@@ -141,39 +154,43 @@ impl gpui::RenderOnce for IssueListItem {
.text_xs() .text_xs()
.opacity(0.5); .opacity(0.5);
let icon = if self.is_draft { let status_pill = if self.is_draft {
font_icon(FontIcon::PullRequestDraft) pill(
.text_color(theme.colors.text) text("Draft").text_color(theme.colors.text),
.opacity(0.5) font_icon(FontIcon::PullRequestDraft).text_color(theme.colors.text),
)
.bg(theme.colors.surface)
} else { } else {
match self.status { match self.status {
| api::issues::PullRequestState::Closed => { | api::issues::PullRequestState::Closed => pill(
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger) 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::Merged => {
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.success)
}
| _ => font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success),
}
}
.flex_shrink_0()
.size_4();
let description_text = match self.description {
| Some(description) => text(description).text_xs(),
| None => text("No description provided").opacity(0.5).text_xs(),
}; };
let pills_row = div().flex().flex_row().gap_1().my_1().child(status_pill);
div() div()
.relative() .relative()
.w_full() .w_full()
.px_1p5() .px_3()
.py_1() .py_1()
.gap_2() .gap_2()
.flex() .flex()
.flex_row() .flex_row()
.items_center() .items_center()
.child(icon)
.child( .child(
div() div()
.flex_1() .flex_1()
@@ -191,33 +208,30 @@ impl gpui::RenderOnce for IssueListItem {
.min_w_0() .min_w_0()
.line_clamp(2), .line_clamp(2),
) )
.child(description_text), .child(pills_row),
) )
.when(!self.is_last, |it| { .when(!self.is_last, |it| {
it.border_b_1().border_color(theme.colors.border) it.border_b_1().border_color(theme.colors.border)
}) })
.when(self.is_selected, |it| { .when(self.is_selected, |it| {
it.bg(gpui::Rgba { it.bg(theme.colors.selection_bg)
a: 0.05, .overflow_hidden()
..theme.colors.accent .border_r_1()
}) .child(
.overflow_hidden() div()
.border_r_1() .absolute()
.child( .right_0()
div() .top_0()
.absolute() .bottom_0()
.right_0() .w_px()
.top_0() .bg(theme.colors.selection_border)
.bottom_0() .shadow(vec![gpui::BoxShadow {
.w_px() blur_radius: px(16.),
.bg(theme.colors.accent) spread_radius: px(2.),
.shadow(vec![gpui::BoxShadow { color: gpui::Hsla::from(theme.colors.selection_border).alpha(0.8),
blur_radius: px(16.), offset: point(px(-2.), px(0.)),
spread_radius: px(2.), }]),
color: gpui::Hsla::from(theme.colors.accent).alpha(0.8), )
offset: point(px(-2.), px(0.)),
}]),
)
}) })
} }
} }

View File

@@ -1,6 +1,6 @@
use gpui::{ use gpui::{
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
div, img, point, prelude::FluentBuilder, px, div, img, prelude::FluentBuilder,
}; };
use crate::{ use crate::{
@@ -24,7 +24,7 @@ pub(crate) struct PullRequestView {
#[derive(gpui::IntoElement)] #[derive(gpui::IntoElement)]
struct Toolbar {} struct Toolbar {}
pub fn new(cx: &mut gpui::Context<PullRequestView>) -> PullRequestView { pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
PullRequestView { PullRequestView {
markdown_viewer: None, markdown_viewer: None,
pull_request_query: None, pull_request_query: None,
@@ -89,37 +89,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) .bg(theme.colors.success_solid)
.child( .child(
font_icon(FontIcon::PullRequestArrow) font_icon(FontIcon::PullRequestArrow)
.size_3() .size_3()
.text_color(theme.colors.accent_text), .text_color(theme.colors.success_on_solid),
) )
.child(text("Open").text_color(theme.colors.accent_text).text_xs()); .child(
} text("Open")
| api::issues::PullRequestState::Closed => { .text_color(theme.colors.success_on_solid)
status_pill = status_pill .text_xs(),
.bg(theme.colors.danger) );
.child( }
font_icon(FontIcon::PullRequestClosed) | api::issues::PullRequestState::Closed => {
.size_3() status_pill = status_pill
.text_color(theme.colors.accent_text), .bg(theme.colors.danger_solid)
) .child(
.child( font_icon(FontIcon::PullRequestClosed)
text("Closed") .size_3()
.text_color(theme.colors.accent_text) .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::Merged => {
status_pill = status_pill.bg(theme.colors.accent).child(
text("Merged")
.text_color(theme.colors.accent_text)
.text_xs(),
);
}
} }
let merge_text = match ( let merge_text = match (
@@ -127,48 +131,48 @@ impl PullRequestView {
pr.base_branch_name.as_ref(), pr.base_branch_name.as_ref(),
pr.head_branch_name.as_ref(), pr.head_branch_name.as_ref(),
) { ) {
| (Some(author), Some(base_branch), Some(head_branch)) => { | (Some(author), Some(base_branch), Some(head_branch)) => {
let str = format!( let str = format!(
"{} requested to merge {} into {}", "{} requested to merge {} into {}",
author.login, head_branch, base_branch author.login, head_branch, base_branch
); );
let head_branch_text_offset = author.login.len() + 20; let head_branch_text_offset = author.login.len() + 20;
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6; let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
let highlights = [ let highlights = [
( (
0..author.login.len(), 0..author.login.len(),
gpui::HighlightStyle { gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD), font_weight: Some(gpui::FontWeight::BOLD),
..Default::default() ..Default::default()
}, },
), ),
( (
head_branch_text_offset..head_branch_text_offset + head_branch.len(), head_branch_text_offset..head_branch_text_offset + head_branch.len(),
gpui::HighlightStyle { gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD), font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent.into()), color: Some(theme.colors.accent_fg.into()),
..Default::default() ..Default::default()
}, },
), ),
( (
base_branch_text_offset..base_branch_text_offset + base_branch.len(), base_branch_text_offset..base_branch_text_offset + base_branch.len(),
gpui::HighlightStyle { gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD), font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent.into()), color: Some(theme.colors.accent_fg.into()),
..Default::default() ..Default::default()
}, },
), ),
]; ];
Some(( Some((
author, author,
gpui::StyledText::new(str).with_highlights(highlights), gpui::StyledText::new(str).with_highlights(highlights),
)) ))
} }
| _ => None, | _ => None,
}; };
let metadata_line = let metadata_line =
@@ -257,24 +261,24 @@ impl gpui::Render for PullRequestView {
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().size_full().child(match &self.pull_request_query {
| Some(q) => match read_query(q, cx) { | Some(q) => match read_query(q, cx) {
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx), | QueryStatus::Loaded(pr) => self.pr_content(pr, cx),
| QueryStatus::Err(e) => div() | QueryStatus::Err(e) => div()
.size_full() .size_full()
.child(format!("{:?}", e)) .child(format!("{:?}", e))
.into_any_element(), .into_any_element(),
| QueryStatus::Loading => div() | QueryStatus::Loading => div()
.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 { impl gpui::RenderOnce for Toolbar {
fn render(self, window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement { fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
fn toolbar_button(id: impl Into<gpui::ElementId>) -> Button { fn toolbar_button(id: impl Into<gpui::ElementId>) -> Button {
button(id) button(id)
.px_2p5() .px_2p5()

View File

@@ -5,7 +5,6 @@ use crate::{
screen::dashboard::{ screen::dashboard::{
issue_list::{self, IssueList}, issue_list::{self, IssueList},
pull_request_view::{self, PullRequestView}, pull_request_view::{self, PullRequestView},
sidebar::{self, Sidebar, SidebarItemValue},
titlebar::{self, TitleBar}, titlebar::{self, TitleBar},
}, },
}; };
@@ -13,7 +12,6 @@ use crate::{
pub(crate) struct Screen { pub(crate) struct Screen {
titlebar: gpui::Entity<TitleBar>, titlebar: gpui::Entity<TitleBar>,
issue_list: gpui::Entity<IssueList>, issue_list: gpui::Entity<IssueList>,
sidebar: gpui::Entity<Sidebar>,
pull_request_view: gpui::Entity<PullRequestView>, pull_request_view: gpui::Entity<PullRequestView>,
issue_filter: Option<&'static str>, issue_filter: Option<&'static str>,
@@ -23,7 +21,6 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
let mut screen = Screen { let mut screen = Screen {
titlebar: cx.new(titlebar::new), titlebar: cx.new(titlebar::new),
issue_list: cx.new(issue_list::new), issue_list: cx.new(issue_list::new),
sidebar: cx.new(|_| sidebar::new()),
pull_request_view: cx.new(pull_request_view::new), pull_request_view: cx.new(pull_request_view::new),
issue_filter: None, issue_filter: None,
@@ -34,35 +31,15 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
impl Screen { impl Screen {
fn on_create(&mut self, cx: &mut gpui::Context<Self>) { fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
let on_item_change = cx.listener(|this, value, _, cx| {
this.handle_sidebar_item_change(value, cx);
});
self.sidebar.update(cx, |sidebar, _| {
sidebar.on_item_change(on_item_change);
});
_ = cx _ = cx
.subscribe(&self.issue_list, |this, _, event, cx| match event { .subscribe(&self.issue_list, |this, _, event, cx| match event {
| issue_list::Event::ItemSelected(pr_id) => { | issue_list::Event::ItemSelected(pr_id) => {
this.handle_issue_list_item_selected(pr_id, cx); this.handle_issue_list_item_selected(pr_id, cx);
} }
}) })
.detach(); .detach();
} }
fn handle_sidebar_item_change(
&mut self,
value: &SidebarItemValue,
cx: &mut gpui::Context<Self>,
) {
match value {
| SidebarItemValue::PullRequest { filter } => {
self.issue_filter = Some(*filter);
cx.notify();
}
}
}
fn handle_issue_list_item_selected( fn handle_issue_list_item_selected(
&mut self, &mut self,
id: &api::issues::Id, id: &api::issues::Id,
@@ -97,21 +74,12 @@ impl gpui::Render for Screen {
.flex_1() .flex_1()
.min_h_0() .min_h_0()
.w_full() .w_full()
.child(
div()
.w_40()
.flex_shrink_0()
.h_full()
.child(self.sidebar.clone()),
)
.child( .child(
div() div()
.w_64() .w_64()
.flex_shrink_0() .flex_shrink_0()
.h_full() .h_full()
.bg(theme.colors.surface) .bg(theme.colors.surface_chrome)
.border_x_1()
.border_color(theme.colors.border)
.overflow_hidden() .overflow_hidden()
.child(self.issue_list.clone()), .child(self.issue_list.clone()),
) )

View File

@@ -160,19 +160,19 @@ impl gpui::RenderOnce for SidebarItem {
.px_2() .px_2()
.py_1() .py_1()
.gap_2() .gap_2()
.child(font_icon(self.icon).size_3().when(self.is_selected, |it| { .child(
it.text_color(theme.colors.accent_text) font_icon(self.icon)
})) .size_3()
.when(self.is_selected, |it| it.text_color(theme.colors.accent_fg)),
)
.child( .child(
text(self.title) text(self.title)
.text_sm() .text_sm()
.leading_tight() .leading_tight()
.when(self.is_selected, |it| { .when(self.is_selected, |it| it.text_color(theme.colors.accent_fg)),
it.text_color(theme.colors.accent_text)
}),
), ),
) )
.when_some(self.on_click, |it, f| it.on_click(f)) .when_some(self.on_click, |it, f| it.on_click(f))
.when(self.is_selected, |it| it.bg(theme.colors.accent)) .when(self.is_selected, |it| it.bg(theme.colors.selection_bg))
} }
} }

View File

@@ -1,7 +1,7 @@
use gpui::{ParentElement, Styled, TitlebarOptions, div}; use gpui::{ParentElement, Styled, div};
use crate::component::button::button; use crate::component::button::button;
use crate::query::{self, QueryStatus, read_query, use_lazy_query, use_query}; use crate::query::{self, QueryStatus, read_query, use_lazy_query};
use crate::{ use crate::{
api, app, api, app,
component::{ component::{
@@ -32,13 +32,13 @@ impl gpui::Render for TitleBar {
let user = read_query(&self.fetch_user_query, cx); let user = read_query(&self.fetch_user_query, cx);
let user_avatar = match user { let user_avatar = match user {
QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child( | QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child(
button("login-btn") button("login-btn")
.leading(font_icon(FontIcon::Github)) .leading(font_icon(FontIcon::Github))
.label("Login"), .label("Login"),
), ),
_ => div(), | _ => div(),
}; };
div() div()
@@ -50,7 +50,7 @@ impl gpui::Render for TitleBar {
.flex() .flex()
.px(g.safe_area.size.width) .px(g.safe_area.size.width)
.py_2() .py_2()
.bg(g.current_theme.colors.background) .bg(g.current_theme.colors.surface_chrome)
.text_color(g.current_theme.colors.text) .text_color(g.current_theme.colors.text)
.relative() .relative()
.border_b_1() .border_b_1()
@@ -61,7 +61,7 @@ impl gpui::Render for TitleBar {
} }
impl RepoSelector { impl RepoSelector {
pub fn new(cx: &mut gpui::Context<Self>) -> Self { pub fn new(_cx: &mut gpui::Context<Self>) -> Self {
Self {} Self {}
} }
} }

View File

@@ -189,7 +189,7 @@ impl GithubStepView {
let poll_interval = u64::from(*interval); let poll_interval = u64::from(*interval);
match read_query(query, cx) { match read_query(query, cx) {
QueryStatus::Loaded(data) => { | QueryStatus::Loaded(data) => {
let auth_tokens = api::AuthTokens { let auth_tokens = api::AuthTokens {
access_token: data.access_token.clone(), access_token: data.access_token.clone(),
}; };
@@ -226,7 +226,7 @@ impl GithubStepView {
.detach(); .detach();
} }
QueryStatus::Err(api::Error::Github(api::GithubError { error, .. })) => { | QueryStatus::Err(api::Error::Github(api::GithubError { error, .. })) => {
if error == "authorization_pending" { if error == "authorization_pending" {
cx.spawn(async move |weak, cx| { cx.spawn(async move |weak, cx| {
Timer::after(Duration::from_secs(poll_interval)).await; Timer::after(Duration::from_secs(poll_interval)).await;
@@ -242,7 +242,7 @@ impl GithubStepView {
} }
} }
_ => {} | _ => {}
} }
} }
@@ -263,8 +263,8 @@ impl GithubStepView {
let theme = app::current_theme(cx); let theme = app::current_theme(cx);
let (displayed_code, copyable_code) = match create_device_code_query { let (displayed_code, copyable_code) = match create_device_code_query {
QueryStatus::Loaded(data) => (data.user_code.as_str(), Some(data.user_code.clone())), | QueryStatus::Loaded(data) => (data.user_code.as_str(), Some(data.user_code.clone())),
_ => (self.placeholder_code.as_str(), None), | _ => (self.placeholder_code.as_str(), None),
}; };
let border_color = theme.colors.border.clone(); let border_color = theme.colors.border.clone();
@@ -358,14 +358,14 @@ impl gpui::Render for GithubStepView {
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement { ) -> impl gpui::IntoElement {
let (can_go_next, header, body) = match self.user_query { let (can_go_next, header, body) = match self.user_query {
None => (false, self.header(), self.device_code_area(cx)), | None => (false, self.header(), self.device_code_area(cx)),
Some(ref q) => { | Some(ref q) => {
let user_query = read_query(q, cx); let user_query = read_query(q, cx);
match user_query { match user_query {
QueryStatus::Loaded(user) => { | QueryStatus::Loaded(user) => {
(true, connected_header(), connected_body(user, cx)) (true, connected_header(), connected_body(user, cx))
} }
_ => (false, self.header(), self.device_code_area(cx)), | _ => (false, self.header(), self.device_code_area(cx)),
} }
} }
}; };
@@ -436,7 +436,7 @@ fn connected_body(user: &api::user::User, cx: &gpui::Context<GithubStepView>) ->
.rounded_2xl() .rounded_2xl()
.border_1() .border_1()
.w_full() .w_full()
.border_color(theme.colors.surface_elevated) .border_color(theme.colors.border)
.p_4() .p_4()
.child( .child(
div() div()
@@ -461,9 +461,13 @@ fn connected_body(user: &api::user::User, cx: &gpui::Context<GithubStepView>) ->
.child( .child(
div() div()
.rounded_full() .rounded_full()
.bg(theme.colors.accent) .bg(theme.colors.success_solid)
.p_1() .p_1()
.child(font_icon(FontIcon::Check).size_4()), .child(
font_icon(FontIcon::Check)
.size_4()
.text_color(theme.colors.success_on_solid),
),
), ),
) )
} }

View File

@@ -51,9 +51,9 @@ pub fn open_window(screen: Screen, cx: &mut gpui::App) -> anyhow::Result<()> {
impl Step { impl Step {
pub const fn order(&self) -> usize { pub const fn order(&self) -> usize {
match self { match self {
Step::Welcome => 0, | Step::Welcome => 0,
Step::ConnectToGithub => 1, | Step::ConnectToGithub => 1,
Step::SetupComplete => 2, | Step::SetupComplete => 2,
} }
} }
} }

View File

@@ -38,9 +38,9 @@ pub(crate) fn from_saved(state: StoredSetupState) -> Screen {
impl Screen { impl Screen {
fn advance_to_next_step(&mut self, cx: &mut gpui::Context<Self>) { fn advance_to_next_step(&mut self, cx: &mut gpui::Context<Self>) {
let next_step = match self.current_step { let next_step = match self.current_step {
Step::Welcome => Step::ConnectToGithub, | Step::Welcome => Step::ConnectToGithub,
Step::ConnectToGithub => Step::SetupComplete, | Step::ConnectToGithub => Step::SetupComplete,
_ => panic!(), | _ => panic!(),
}; };
self.current_step = next_step; self.current_step = next_step;
cx.notify(); cx.notify();
@@ -67,8 +67,8 @@ impl Screen {
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) -> &gpui::Entity<github_step::GithubStepView> { ) -> &gpui::Entity<github_step::GithubStepView> {
match self.github_step_view { match self.github_step_view {
Some(ref v) => v, | Some(ref v) => v,
None => { | None => {
let weak = cx.weak_entity(); let weak = cx.weak_entity();
self.github_step_view = Some(cx.new(|cx| { self.github_step_view = Some(cx.new(|cx| {
let mut v = github_step::new(cx); let mut v = github_step::new(cx);
@@ -90,9 +90,9 @@ impl Screen {
.enumerate() .enumerate()
.map(|(i, step)| { .map(|(i, step)| {
let label = match step { let label = match step {
Step::Welcome => "Welcome!", | Step::Welcome => "Welcome!",
Step::ConnectToGithub => "Connect to GitHub", | Step::ConnectToGithub => "Connect to GitHub",
Step::SetupComplete => "Complete!", | Step::SetupComplete => "Complete!",
}; };
let is_completed = i < self.current_step.order(); let is_completed = i < self.current_step.order();
let is_current = self.current_step == *step; let is_current = self.current_step == *step;
@@ -138,16 +138,16 @@ impl gpui::Render for Screen {
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement { ) -> impl gpui::IntoElement {
let step_view = match self.current_step { let step_view = match self.current_step {
Step::Welcome => welcome_step() | Step::Welcome => welcome_step()
.on_next(cx.listener(|this, _, _, cx| this.advance_to_next_step(cx))) .on_next(cx.listener(|this, _, _, cx| this.advance_to_next_step(cx)))
.into_any_element(), .into_any_element(),
Step::ConnectToGithub => match self.github_step_view { | Step::ConnectToGithub => match self.github_step_view {
Some(ref view) => view.clone().into_any_element(), | Some(ref view) => view.clone().into_any_element(),
None => self.init_github_step_view(cx).clone().into_any_element(), | None => self.init_github_step_view(cx).clone().into_any_element(),
}, },
Step::SetupComplete => setup_complete_step().into_any_element(), | Step::SetupComplete => setup_complete_step().into_any_element(),
}; };
let theme = app::current_theme(cx); let theme = app::current_theme(cx);
@@ -165,7 +165,7 @@ impl gpui::Render for Screen {
.justify_center() .justify_center()
.w_1_3() .w_1_3()
.h_full() .h_full()
.bg(theme.colors.surface) .bg(theme.colors.surface_chrome)
.relative() .relative()
.child( .child(
div() div()

View File

@@ -16,12 +16,12 @@ pub(crate) struct PersistedState {
pub(crate) fn data_dir_path() -> std::path::PathBuf { pub(crate) fn data_dir_path() -> std::path::PathBuf {
match std::env::consts::OS { match std::env::consts::OS {
"macos" => std::env::home_dir() | "macos" => std::env::home_dir()
.unwrap() .unwrap()
.join("Library") .join("Library")
.join("Application Support") .join("Application Support")
.join("novem"), .join("novem"),
_ => unimplemented!( | _ => unimplemented!(
"data_dir_path is unimplemented for OS: {}", "data_dir_path is unimplemented for OS: {}",
std::env::consts::OS std::env::consts::OS
), ),

View File

@@ -1,6 +1,6 @@
use gpui::Rgba; mod catppuccin;
use crate::colors::hex; use gpui::Rgba;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ThemeMode { pub enum ThemeMode {
@@ -28,15 +28,49 @@ 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_chrome: Rgba,
pub surface_hover: Rgba,
pub surface_active: Rgba,
pub border: Rgba, pub border: Rgba,
pub border_muted: Rgba,
pub border_strong: Rgba,
pub focus_ring: Rgba,
pub text: Rgba, pub text: Rgba,
pub text_muted: Rgba, pub text_muted: Rgba,
pub accent: Rgba, pub text_subtle: Rgba,
pub accent_hover: Rgba, pub text_disabled: Rgba,
pub accent_text: Rgba, pub icon_muted: Rgba,
pub success: Rgba, pub link: Rgba,
pub warning: Rgba, pub link_hover: Rgba,
pub danger: Rgba, pub code_bg: Rgba,
pub code_border: Rgba,
pub selection_bg: Rgba,
pub selection_border: Rgba,
pub accent_fg: Rgba,
pub accent_muted: Rgba,
pub accent_border: Rgba,
pub accent_solid: Rgba,
pub accent_on_solid: Rgba,
pub success_fg: Rgba,
pub success_muted: Rgba,
pub success_border: Rgba,
pub success_solid: Rgba,
pub success_on_solid: Rgba,
pub warning_fg: Rgba,
pub warning_muted: Rgba,
pub warning_border: Rgba,
pub warning_solid: Rgba,
pub warning_on_solid: Rgba,
pub danger_fg: Rgba,
pub danger_muted: Rgba,
pub danger_border: Rgba,
pub danger_solid: Rgba,
pub danger_on_solid: Rgba,
pub info_fg: Rgba,
pub info_muted: Rgba,
pub info_border: Rgba,
pub info_solid: Rgba,
pub info_on_solid: Rgba,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -52,20 +86,20 @@ impl ThemeFamily {
pub const fn id(self) -> &'static str { pub const fn id(self) -> &'static str {
match self { match self {
| Self::Catppuccin => "catppuccin", | Self::Catppuccin => catppuccin::FAMILY_ID,
} }
} }
pub const fn label(self) -> &'static str { pub const fn label(self) -> &'static str {
match self { match self {
| Self::Catppuccin => "Catppuccin", | Self::Catppuccin => catppuccin::FAMILY_LABEL,
} }
} }
pub const fn variant(self, mode: ThemeMode) -> ThemeVariant { pub const fn variant(self, mode: ThemeMode) -> ThemeVariant {
match (self, mode) { match (self, mode) {
| (Self::Catppuccin, ThemeMode::Light) => ThemeVariant::CatppuccinLatte, | (Self::Catppuccin, ThemeMode::Light) => ThemeVariant::CatppuccinLatte,
| (Self::Catppuccin, ThemeMode::Dark) => ThemeVariant::CatppuccinMocha, | (Self::Catppuccin, ThemeMode::Dark) => ThemeVariant::CatppuccinMocha,
} }
} }
@@ -92,64 +126,28 @@ impl ThemeVariant {
pub const fn family(self) -> ThemeFamily { pub const fn family(self) -> ThemeFamily {
match self { match self {
| Self::CatppuccinLatte | Self::CatppuccinMocha => ThemeFamily::Catppuccin, | Self::CatppuccinLatte | Self::CatppuccinMocha => ThemeFamily::Catppuccin,
} }
} }
pub const fn mode(self) -> ThemeMode { pub const fn mode(self) -> ThemeMode {
match self { match self {
| Self::CatppuccinLatte => ThemeMode::Light, | Self::CatppuccinLatte => ThemeMode::Light,
| Self::CatppuccinMocha => ThemeMode::Dark, | Self::CatppuccinMocha => ThemeMode::Dark,
} }
} }
pub const fn label(self) -> &'static str { pub const fn label(self) -> &'static str {
match self { match self {
| Self::CatppuccinLatte => "Catppuccin Latte", | Self::CatppuccinLatte => catppuccin::LATTE_LABEL,
| Self::CatppuccinMocha => "Catppuccin Mocha", | Self::CatppuccinMocha => catppuccin::MOCHA_LABEL,
} }
} }
pub const fn theme(self) -> Theme { pub const fn theme(self) -> Theme {
match self { match self {
| Self::CatppuccinLatte => Theme { | Self::CatppuccinLatte => catppuccin::latte(),
id: "catppuccin-latte", | Self::CatppuccinMocha => catppuccin::mocha(),
name: "Catppuccin Latte",
mode: ThemeMode::Light,
colors: ThemeColors {
background: hex(0xeff1f5),
surface: hex(0xeff1f5),
surface_elevated: hex(0xdce0e8),
border: hex(0xccd0da),
text: hex(0x4c4f69),
text_muted: hex(0x6c6f85),
accent: hex(0x8839ef),
accent_hover: hex(0x7287fd),
accent_text: hex(0xeff1f5),
success: hex(0x40a02b),
warning: hex(0xdf8e1d),
danger: hex(0xd20f39),
},
},
| Self::CatppuccinMocha => Theme {
id: "catppuccin-mocha",
name: "Catppuccin Mocha",
mode: ThemeMode::Dark,
colors: ThemeColors {
background: hex(0x1e1e2e),
surface: hex(0x181825),
surface_elevated: hex(0x313244),
border: hex(0x45475a),
text: hex(0xcdd6f4),
text_muted: hex(0xa6adc8),
accent: hex(0xcba6f7),
accent_hover: hex(0xb4befe),
accent_text: hex(0x1e1e2e),
success: hex(0xa6e3a1),
warning: hex(0xf9e2af),
danger: hex(0xf38ba8),
},
},
} }
} }
} }
@@ -157,8 +155,10 @@ impl ThemeVariant {
impl From<gpui::WindowAppearance> for ThemeMode { impl From<gpui::WindowAppearance> for ThemeMode {
fn from(value: gpui::WindowAppearance) -> Self { fn from(value: gpui::WindowAppearance) -> Self {
match value { match value {
| gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => ThemeMode::Light, | gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => {
| gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => ThemeMode::Dark, ThemeMode::Light
}
| gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => ThemeMode::Dark,
} }
} }
} }

121
src/theme/catppuccin.rs Normal file
View File

@@ -0,0 +1,121 @@
use crate::colors::{hex, hex_alpha};
use super::{Theme, ThemeColors, ThemeMode};
pub(crate) const FAMILY_ID: &str = "catppuccin";
pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
pub(crate) const LATTE_LABEL: &str = "Catppuccin Latte";
pub(crate) const MOCHA_LABEL: &str = "Catppuccin Mocha";
pub(crate) const fn latte() -> Theme {
Theme {
id: "catppuccin-latte",
name: LATTE_LABEL,
mode: ThemeMode::Light,
colors: ThemeColors {
background: hex(0xeff1f5),
surface: hex(0xeff1f5),
surface_elevated: hex(0xdce0e8),
surface_chrome: hex(0xe6e9ef),
surface_hover: hex(0xe6e9ef),
surface_active: hex(0xdce0e8),
border: hex(0xbcc0cc),
border_muted: hex(0xccd0da),
border_strong: hex(0xacb0be),
focus_ring: hex(0x7287fd),
text: hex(0x4c4f69),
text_muted: hex(0x5c5f77),
text_subtle: hex(0x6c6f85),
text_disabled: hex(0x9ca0b0),
icon_muted: hex(0x6c6f85),
link: hex(0x1e66f5),
link_hover: hex(0x7287fd),
code_bg: hex(0xe6e9ef),
code_border: hex(0xccd0da),
selection_bg: hex_alpha(0x8839ef, 0.10),
selection_border: hex_alpha(0x8839ef, 0.35),
accent_fg: hex(0x8839ef),
accent_muted: hex_alpha(0x8839ef, 0.12),
accent_border: hex_alpha(0x8839ef, 0.28),
accent_solid: hex(0x8839ef),
accent_on_solid: hex(0xeff1f5),
success_fg: hex(0x40a02b),
success_muted: hex_alpha(0x40a02b, 0.12),
success_border: hex_alpha(0x40a02b, 0.28),
success_solid: hex(0x40a02b),
success_on_solid: hex(0x1e1e2e),
warning_fg: hex(0xdf8e1d),
warning_muted: hex_alpha(0xdf8e1d, 0.12),
warning_border: hex_alpha(0xdf8e1d, 0.32),
warning_solid: hex(0xdf8e1d),
warning_on_solid: hex(0x1e1e2e),
danger_fg: hex(0xd20f39),
danger_muted: hex_alpha(0xd20f39, 0.12),
danger_border: hex_alpha(0xd20f39, 0.28),
danger_solid: hex(0xd20f39),
danger_on_solid: hex(0xeff1f5),
info_fg: hex(0x1e66f5),
info_muted: hex_alpha(0x1e66f5, 0.12),
info_border: hex_alpha(0x1e66f5, 0.28),
info_solid: hex(0x1e66f5),
info_on_solid: hex(0xeff1f5),
},
}
}
pub(crate) const fn mocha() -> Theme {
Theme {
id: "catppuccin-mocha",
name: MOCHA_LABEL,
mode: ThemeMode::Dark,
colors: ThemeColors {
background: hex(0x1e1e2e),
surface: hex(0x181825),
surface_elevated: hex(0x313244),
surface_chrome: hex(0x11111b),
surface_hover: hex(0x313244),
surface_active: hex(0x45475a),
border: hex(0x585b70),
border_muted: hex(0x45475a),
border_strong: hex(0x6c7086),
focus_ring: hex(0xb4befe),
text: hex(0xcdd6f4),
text_muted: hex(0xbac2de),
text_subtle: hex(0xa6adc8),
text_disabled: hex(0x7f849c),
icon_muted: hex(0xa6adc8),
link: hex(0x89b4fa),
link_hover: hex(0xb4befe),
code_bg: hex(0x11111b),
code_border: hex(0x45475a),
selection_bg: hex_alpha(0xcba6f7, 0.18),
selection_border: hex_alpha(0xcba6f7, 0.45),
accent_fg: hex(0xcba6f7),
accent_muted: hex_alpha(0xcba6f7, 0.18),
accent_border: hex_alpha(0xcba6f7, 0.34),
accent_solid: hex(0xcba6f7),
accent_on_solid: hex(0x1e1e2e),
success_fg: hex(0xa6e3a1),
success_muted: hex_alpha(0xa6e3a1, 0.18),
success_border: hex_alpha(0xa6e3a1, 0.34),
success_solid: hex(0xa6e3a1),
success_on_solid: hex(0x1e1e2e),
warning_fg: hex(0xf9e2af),
warning_muted: hex_alpha(0xf9e2af, 0.18),
warning_border: hex_alpha(0xf9e2af, 0.38),
warning_solid: hex(0xf9e2af),
warning_on_solid: hex(0x1e1e2e),
danger_fg: hex(0xf38ba8),
danger_muted: hex_alpha(0xf38ba8, 0.18),
danger_border: hex_alpha(0xf38ba8, 0.34),
danger_solid: hex(0xf38ba8),
danger_on_solid: hex(0x1e1e2e),
info_fg: hex(0x89b4fa),
info_muted: hex_alpha(0x89b4fa, 0.18),
info_border: hex_alpha(0x89b4fa, 0.34),
info_solid: hex(0x89b4fa),
info_on_solid: hex(0x1e1e2e),
},
}
}