Overhaul handling of font families

* Specify font families in the theme.
* Load fonts eagerly when loading themes, instead of loading
  them lazily when rendering.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-08-26 15:06:00 -07:00
parent ee9ee294ad
commit 3bb5610ad1
23 changed files with 438 additions and 397 deletions

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
color::Color, color::Color,
font_cache::FamilyId,
fonts::{FontId, TextStyle}, fonts::{FontId, TextStyle},
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -8,8 +7,7 @@ use crate::{
}, },
json::{ToJson, Value}, json::{ToJson, Value},
text_layout::Line, text_layout::Line,
DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
SizeConstraint,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
@ -17,37 +15,32 @@ use smallvec::{smallvec, SmallVec};
pub struct Label { pub struct Label {
text: String, text: String,
family_id: FamilyId,
font_size: f32,
style: LabelStyle, style: LabelStyle,
highlight_indices: Vec<usize>, highlight_indices: Vec<usize>,
} }
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct LabelStyle { pub struct LabelStyle {
pub text: TextStyle, pub text: TextStyle,
pub highlight_text: Option<TextStyle>, pub highlight_text: Option<TextStyle>,
} }
impl Label { impl From<TextStyle> for LabelStyle {
pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self { fn from(text: TextStyle) -> Self {
Self { LabelStyle {
text, text,
family_id, highlight_text: None,
font_size,
highlight_indices: Default::default(),
style: Default::default(),
} }
} }
}
pub fn with_style(mut self, style: &LabelStyle) -> Self { impl Label {
self.style = style.clone(); pub fn new(text: String, style: impl Into<LabelStyle>) -> Self {
self Self {
} text,
highlight_indices: Default::default(),
pub fn with_default_color(mut self, color: Color) -> Self { style: style.into(),
self.style.text.color = color; }
self
} }
pub fn with_highlights(mut self, indices: Vec<usize>) -> Self { pub fn with_highlights(mut self, indices: Vec<usize>) -> Self {
@ -55,11 +48,8 @@ impl Label {
self self
} }
fn compute_runs( fn compute_runs(&self) -> SmallVec<[(usize, FontId, Color); 8]> {
&self, let font_id = self.style.text.font_id;
font_cache: &FontCache,
font_id: FontId,
) -> SmallVec<[(usize, FontId, Color); 8]> {
if self.highlight_indices.is_empty() { if self.highlight_indices.is_empty() {
return smallvec![(self.text.len(), font_id, self.style.text.color)]; return smallvec![(self.text.len(), font_id, self.style.text.color)];
} }
@ -68,12 +58,7 @@ impl Label {
.style .style
.highlight_text .highlight_text
.as_ref() .as_ref()
.and_then(|style| { .map_or(font_id, |style| style.font_id);
font_cache
.select_font(self.family_id, &style.font_properties)
.ok()
})
.unwrap_or(font_id);
let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
let mut runs = SmallVec::new(); let mut runs = SmallVec::new();
@ -123,18 +108,18 @@ impl Element for Label {
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let font_id = cx let runs = self.compute_runs();
.font_cache let line = cx.text_layout_cache.layout_str(
.select_font(self.family_id, &self.style.text.font_properties) self.text.as_str(),
.unwrap(); self.style.text.font_size,
let runs = self.compute_runs(&cx.font_cache, font_id); runs.as_slice(),
let line = );
cx.text_layout_cache
.layout_str(self.text.as_str(), self.font_size, runs.as_slice());
let size = vec2f( let size = vec2f(
line.width().max(constraint.min.x()).min(constraint.max.x()), line.width().max(constraint.min.x()).min(constraint.max.x()),
cx.font_cache.line_height(font_id, self.font_size).ceil(), cx.font_cache
.line_height(self.style.text.font_id, self.style.text.font_size)
.ceil(),
); );
(size, line) (size, line)
@ -169,15 +154,13 @@ impl Element for Label {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, _: &DebugContext,
) -> Value { ) -> Value {
json!({ json!({
"type": "Label", "type": "Label",
"bounds": bounds.to_json(), "bounds": bounds.to_json(),
"text": &self.text, "text": &self.text,
"highlight_indices": self.highlight_indices, "highlight_indices": self.highlight_indices,
"font_family": cx.font_cache.family_name(self.family_id).unwrap(),
"font_size": self.font_size,
"style": self.style.to_json(), "style": self.style.to_json(),
}) })
} }
@ -201,48 +184,52 @@ mod tests {
#[crate::test(self)] #[crate::test(self)]
fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) { fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) {
let menlo = cx.font_cache().load_family(&["Menlo"]).unwrap(); let default_style = TextStyle::new(
let menlo_regular = cx "Menlo",
.font_cache() 12.,
.select_font(menlo, &FontProperties::new()) Default::default(),
.unwrap(); Color::black(),
let menlo_bold = cx cx.font_cache(),
.font_cache() )
.select_font(menlo, FontProperties::new().weight(Weight::BOLD)) .unwrap();
.unwrap(); let highlight_style = TextStyle::new(
let black = Color::black(); "Menlo",
let red = Color::new(255, 0, 0, 255); 12.,
*FontProperties::new().weight(Weight::BOLD),
Color::new(255, 0, 0, 255),
cx.font_cache(),
)
.unwrap();
let label = Label::new(
".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(),
LabelStyle {
text: default_style.clone(),
highlight_text: Some(highlight_style.clone()),
},
)
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0) let runs = label.compute_runs();
.with_style(&LabelStyle {
text: TextStyle {
color: black,
font_properties: Default::default(),
},
highlight_text: Some(TextStyle {
color: red,
font_properties: *FontProperties::new().weight(Weight::BOLD),
}),
})
.with_highlights(vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
]);
let runs = label.compute_runs(cx.font_cache().as_ref(), menlo_regular);
assert_eq!( assert_eq!(
runs.as_slice(), runs.as_slice(),
&[ &[
(".α".len(), menlo_regular, black), (".α".len(), default_style.font_id, default_style.color),
("βγ".len(), menlo_bold, red), ("βγ".len(), highlight_style.font_id, highlight_style.color),
("δ".len(), menlo_regular, black), ("δ".len(), default_style.font_id, default_style.color),
("ε".len(), menlo_bold, red), ("ε".len(), highlight_style.font_id, highlight_style.color),
(".ⓐ".len(), menlo_regular, black), (".ⓐ".len(), default_style.font_id, default_style.color),
("ⓑⓒ".len(), menlo_bold, red), ("ⓑⓒ".len(), highlight_style.font_id, highlight_style.color),
("ⓓⓔ.abcde.".len(), menlo_regular, black), (
"ⓓⓔ.abcde.".len(),
default_style.font_id,
default_style.color
),
] ]
); );
} }

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
color::Color, color::Color,
font_cache::FamilyId,
fonts::TextStyle, fonts::TextStyle,
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -14,8 +13,6 @@ use serde_json::json;
pub struct Text { pub struct Text {
text: String, text: String,
family_id: FamilyId,
font_size: f32,
style: TextStyle, style: TextStyle,
} }
@ -25,18 +22,8 @@ pub struct LayoutState {
} }
impl Text { impl Text {
pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self { pub fn new(text: String, style: TextStyle) -> Self {
Self { Self { text, style }
text,
family_id,
font_size,
style: Default::default(),
}
}
pub fn with_style(mut self, style: &TextStyle) -> Self {
self.style = style.clone();
self
} }
pub fn with_default_color(mut self, color: Color) -> Self { pub fn with_default_color(mut self, color: Color) -> Self {
@ -54,20 +41,17 @@ impl Element for Text {
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) { ) -> (Vector2F, Self::LayoutState) {
let font_id = cx let font_id = self.style.font_id;
.font_cache let line_height = cx.font_cache.line_height(font_id, self.style.font_size);
.select_font(self.family_id, &self.style.font_properties)
.unwrap();
let line_height = cx.font_cache.line_height(font_id, self.font_size);
let mut wrapper = cx.font_cache.line_wrapper(font_id, self.font_size); let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
let mut lines = Vec::new(); let mut lines = Vec::new();
let mut line_count = 0; let mut line_count = 0;
let mut max_line_width = 0_f32; let mut max_line_width = 0_f32;
for line in self.text.lines() { for line in self.text.lines() {
let shaped_line = cx.text_layout_cache.layout_str( let shaped_line = cx.text_layout_cache.layout_str(
line, line,
self.font_size, self.style.font_size,
&[(line.len(), font_id, self.style.color)], &[(line.len(), font_id, self.style.color)],
); );
let wrap_boundaries = wrapper let wrap_boundaries = wrapper
@ -123,14 +107,12 @@ impl Element for Text {
bounds: RectF, bounds: RectF,
_: &Self::LayoutState, _: &Self::LayoutState,
_: &Self::PaintState, _: &Self::PaintState,
cx: &DebugContext, _: &DebugContext,
) -> Value { ) -> Value {
json!({ json!({
"type": "Label", "type": "Text",
"bounds": bounds.to_json(), "bounds": bounds.to_json(),
"text": &self.text, "text": &self.text,
"font_family": cx.font_cache.family_name(self.family_id).unwrap(),
"font_size": self.font_size,
"style": self.style.to_json(), "style": self.style.to_json(),
}) })
} }

View file

@ -1,32 +1,35 @@
use crate::{ use crate::{
color::Color, color::Color,
json::{json, ToJson}, json::{json, ToJson},
FontCache,
}; };
use anyhow::anyhow;
pub use font_kit::{ pub use font_kit::{
metrics::Metrics, metrics::Metrics,
properties::{Properties, Stretch, Style, Weight}, properties::{Properties, Stretch, Style, Weight},
}; };
use serde::{de, Deserialize}; use serde::{de, Deserialize};
use serde_json::Value; use serde_json::Value;
use std::{cell::RefCell, sync::Arc};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontId(pub usize); pub struct FontId(pub usize);
pub type GlyphId = u32; pub type GlyphId = u32;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub struct TextStyle { pub struct TextStyle {
pub color: Color, pub color: Color,
pub font_family_name: Arc<str>,
pub font_id: FontId,
pub font_size: f32,
pub font_properties: Properties, pub font_properties: Properties,
} }
impl Default for TextStyle { #[derive(Clone, Debug, Default)]
fn default() -> Self { pub struct HighlightStyle {
Self { pub color: Color,
color: Color::from_u32(0xff0000ff), pub font_properties: Properties,
font_properties: Default::default(),
}
}
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@ -43,24 +46,117 @@ enum WeightJson {
black, black,
} }
thread_local! {
static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct TextStyleJson { struct TextStyleJson {
color: Color,
family: String,
weight: Option<WeightJson>,
#[serde(default)]
italic: bool,
size: f32,
}
#[derive(Deserialize)]
struct HighlightStyleJson {
color: Color, color: Color,
weight: Option<WeightJson>, weight: Option<WeightJson>,
#[serde(default)] #[serde(default)]
italic: bool, italic: bool,
} }
impl TextStyle {
pub fn new(
font_family_name: impl Into<Arc<str>>,
font_size: f32,
font_properties: Properties,
color: Color,
font_cache: &FontCache,
) -> anyhow::Result<Self> {
let font_family_name = font_family_name.into();
let family_id = font_cache.load_family(&[&font_family_name])?;
let font_id = font_cache.select_font(family_id, &font_properties)?;
Ok(Self {
color,
font_family_name,
font_id,
font_size,
font_properties,
})
}
fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
FONT_CACHE.with(|font_cache| {
if let Some(font_cache) = font_cache.borrow().as_ref() {
let font_properties = properties_from_json(json.weight, json.italic);
Self::new(
json.family,
json.size,
font_properties,
json.color,
font_cache,
)
} else {
Err(anyhow!(
"TextStyle can only be deserialized within a call to with_font_cache"
))
}
})
}
}
impl HighlightStyle {
fn from_json(json: HighlightStyleJson) -> Self {
let font_properties = properties_from_json(json.weight, json.italic);
Self {
color: json.color,
font_properties,
}
}
}
impl From<Color> for HighlightStyle {
fn from(color: Color) -> Self {
Self {
color,
font_properties: Default::default(),
}
}
}
impl<'de> Deserialize<'de> for TextStyle { impl<'de> Deserialize<'de> for TextStyle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let json = Value::deserialize(deserializer)?; Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(e))?)
}
}
impl ToJson for TextStyle {
fn to_json(&self) -> Value {
json!({
"color": self.color.to_json(),
"font_family": self.font_family_name.as_ref(),
"font_properties": self.font_properties.to_json(),
})
}
}
impl<'de> Deserialize<'de> for HighlightStyle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let json = serde_json::Value::deserialize(deserializer)?;
if json.is_object() { if json.is_object() {
let style_json: TextStyleJson = Ok(Self::from_json(
serde_json::from_value(json).map_err(de::Error::custom)?; serde_json::from_value(json).map_err(de::Error::custom)?,
Ok(style_json.into()) ))
} else { } else {
Ok(Self { Ok(Self {
color: serde_json::from_value(json).map_err(de::Error::custom)?, color: serde_json::from_value(json).map_err(de::Error::custom)?,
@ -70,47 +166,20 @@ impl<'de> Deserialize<'de> for TextStyle {
} }
} }
impl From<Color> for TextStyle { fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
fn from(color: Color) -> Self { let weight = match weight.unwrap_or(WeightJson::normal) {
Self { WeightJson::thin => Weight::THIN,
color, WeightJson::extra_light => Weight::EXTRA_LIGHT,
font_properties: Default::default(), WeightJson::light => Weight::LIGHT,
} WeightJson::normal => Weight::NORMAL,
} WeightJson::medium => Weight::MEDIUM,
} WeightJson::semibold => Weight::SEMIBOLD,
WeightJson::bold => Weight::BOLD,
impl ToJson for TextStyle { WeightJson::extra_bold => Weight::EXTRA_BOLD,
fn to_json(&self) -> Value { WeightJson::black => Weight::BLACK,
json!({ };
"color": self.color.to_json(), let style = if italic { Style::Italic } else { Style::Normal };
"font_properties": self.font_properties.to_json(), *Properties::new().weight(weight).style(style)
})
}
}
impl Into<TextStyle> for TextStyleJson {
fn into(self) -> TextStyle {
let weight = match self.weight.unwrap_or(WeightJson::normal) {
WeightJson::thin => Weight::THIN,
WeightJson::extra_light => Weight::EXTRA_LIGHT,
WeightJson::light => Weight::LIGHT,
WeightJson::normal => Weight::NORMAL,
WeightJson::medium => Weight::MEDIUM,
WeightJson::semibold => Weight::SEMIBOLD,
WeightJson::bold => Weight::BOLD,
WeightJson::extra_bold => Weight::EXTRA_BOLD,
WeightJson::black => Weight::BLACK,
};
let style = if self.italic {
Style::Italic
} else {
Style::Normal
};
TextStyle {
color: self.color,
font_properties: *Properties::new().weight(weight).style(style),
}
}
} }
impl ToJson for Properties { impl ToJson for Properties {
@ -164,3 +233,15 @@ impl ToJson for Stretch {
json!(self.0) json!(self.0)
} }
} }
pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
where
F: FnOnce() -> T,
{
FONT_CACHE.with(|cache| {
*cache.borrow_mut() = Some(font_cache);
let result = callback();
cache.borrow_mut().take();
result
})
}

View file

@ -943,7 +943,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
let (window_b, _) = cx_b.add_window(|_| EmptyView); let (window_b, _) = cx_b.add_window(|_| EmptyView);
let settings = settings::channel(&cx_b.font_cache()).unwrap().1; let settings = cx_b.read(settings::test).1;
let lang_registry = Arc::new(LanguageRegistry::new()); let lang_registry = Arc::new(LanguageRegistry::new());
// Connect to a server as 2 clients. // Connect to a server as 2 clients.

View file

@ -4,7 +4,7 @@ background = "$surface.0"
[workspace.tab] [workspace.tab]
text = "$text.2" text = "$text.2"
padding = { left = 10, right = 10 } padding = { left = 10, right = 10 }
icon_close = "$text.0" icon_close = "$text.0.color"
icon_dirty = "$status.info" icon_dirty = "$status.info"
icon_conflict = "$status.warn" icon_conflict = "$status.warn"
@ -17,10 +17,10 @@ text = "$text.0"
padding = { left = 10, right = 10 } padding = { left = 10, right = 10 }
[workspace.sidebar_icon] [workspace.sidebar_icon]
color = "$text.2" color = "$text.2.color"
[workspace.active_sidebar_icon] [workspace.active_sidebar_icon]
color = "$text.0" color = "$text.0.color"
[chat_panel] [chat_panel]
padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 } padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 }
@ -28,7 +28,7 @@ padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 }
[chat_panel.message] [chat_panel.message]
body = "$text.0" body = "$text.0"
sender.margin.right = 10.0 sender.margin.right = 10.0
sender.text = { color = "$text.0", weight = "bold" } sender.text = { extends = "$text.0", weight = "bold" }
timestamp.text = "$text.2" timestamp.text = "$text.2"
[selector] [selector]
@ -41,8 +41,8 @@ shadow = { offset = [0.0, 0.0], blur = 12.0, color = "#00000088" }
[selector.item] [selector.item]
background = "#424344" background = "#424344"
text = "#cccccc" text = "$text.1"
highlight_text = { color = "#18a3ff", weight = "bold" } highlight_text = { extends = "$text.base", color = "#18a3ff", weight = "bold" }
border = { color = "#000000", width = 1.0 } border = { color = "#000000", width = 1.0 }
padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 } padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 }
@ -54,10 +54,12 @@ background = "#094771"
background = "$surface.1" background = "$surface.1"
gutter_background = "$surface.1" gutter_background = "$surface.1"
active_line_background = "$surface.2" active_line_background = "$surface.2"
line_number = "$text.2" line_number = "$text.2.color"
line_number_active = "$text.0" line_number_active = "$text.0.color"
text = "$text.1"
replicas = [ replicas = [
{ selection = "#264f78", cursor = "$text.0" }, { selection = "#264f78", cursor = "$text.0.color" },
{ selection = "#504f31", cursor = "#fcf154" }, { selection = "#504f31", cursor = "#fcf154" },
] ]
[syntax]
default = "$text.1.color"

View file

@ -6,9 +6,10 @@ extends = "_base"
2 = "#131415" 2 = "#131415"
[text] [text]
0 = "#ffffff" base = { family = "Helvetica", size = 12.0 }
1 = "#b3b3b3" 0 = { extends = "$text.base", color = "#ffffff" }
2 = "#7b7d80" 1 = { extends = "$text.base", color = "#b3b3b3" }
2 = { extends = "$text.base", color = "#7b7d80" }
[status] [status]
good = "#4fac63" good = "#4fac63"

View file

@ -7,9 +7,10 @@ extends = "_base"
3 = "#3a3b3c" 3 = "#3a3b3c"
[text] [text]
0 = "#acacac" base = { family = "Inconsolata" }
1 = "#111111" 0 = { extends = "$text.base", color = "#acacac" }
2 = "#333333" 1 = { extends = "$text.base", color = "#111111" }
2 = { extends = "$text.base", color = "#333333" }
[status] [status]
good = "#4fac63" good = "#4fac63"

View file

@ -126,10 +126,8 @@ impl ChatPanel {
Container::new( Container::new(
Label::new( Label::new(
message.sender.github_login.clone(), message.sender.github_login.clone(),
settings.ui_font_family, theme.sender.label.clone(),
settings.ui_font_size,
) )
.with_style(&theme.sender.label)
.boxed(), .boxed(),
) )
.with_style(&theme.sender.container) .with_style(&theme.sender.container)
@ -139,10 +137,8 @@ impl ChatPanel {
Container::new( Container::new(
Label::new( Label::new(
format_timestamp(message.timestamp, now), format_timestamp(message.timestamp, now),
settings.ui_font_family, theme.timestamp.label.clone(),
settings.ui_font_size,
) )
.with_style(&theme.timestamp.label)
.boxed(), .boxed(),
) )
.with_style(&theme.timestamp.container) .with_style(&theme.timestamp.container)
@ -150,15 +146,7 @@ impl ChatPanel {
) )
.boxed(), .boxed(),
) )
.with_child( .with_child(Text::new(message.body.clone(), theme.body.clone()).boxed())
Text::new(
message.body.clone(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&theme.body)
.boxed(),
)
.boxed() .boxed()
} }

View file

@ -2418,7 +2418,7 @@ impl Snapshot {
} }
if !line_chunk.is_empty() && !line_exceeded_max_len { if !line_chunk.is_empty() && !line_exceeded_max_len {
let style = self.theme.highlight_style(style_ix); let style = self.theme.syntax.highlight_style(style_ix);
// Avoid a lookup if the font properties match the previous ones. // Avoid a lookup if the font properties match the previous ones.
let font_id = if style.font_properties == prev_font_properties { let font_id = if style.font_properties == prev_font_properties {
prev_font_id prev_font_id
@ -2632,19 +2632,14 @@ impl workspace::ItemView for Editor {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text};
editor::Point,
language::LanguageRegistry,
settings,
test::{build_settings, sample_text},
};
use buffer::History; use buffer::History;
use unindent::Unindent; use unindent::Unindent;
#[gpui::test] #[gpui::test]
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) { fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, editor) = cx.add_window(Default::default(), |cx| { let (_, editor) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -2712,7 +2707,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) { fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -2746,7 +2741,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_cancel(cx: &mut gpui::MutableAppContext) { fn test_cancel(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -2792,7 +2787,7 @@ mod tests {
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
let settings = settings::channel(&font_cache).unwrap().1; let settings = settings::test(&cx).1;
let (_, editor) = cx.add_window(Default::default(), |cx| { let (_, editor) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings.clone(), cx) Editor::for_buffer(buffer.clone(), settings.clone(), cx)
}); });
@ -2838,7 +2833,7 @@ mod tests {
cx, cx,
) )
}); });
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
}); });
@ -2906,7 +2901,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_move_cursor(cx: &mut gpui::MutableAppContext) { fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
}); });
@ -2983,7 +2978,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
}); });
@ -3041,7 +3036,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) { fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
}); });
@ -3072,7 +3067,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) { fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\n def", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\n def", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -3217,7 +3212,7 @@ mod tests {
fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) { fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
let buffer = let buffer =
cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx)); cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -3405,7 +3400,7 @@ mod tests {
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) { fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
let buffer = let buffer =
cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx)); cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -3467,7 +3462,7 @@ mod tests {
cx, cx,
) )
}); });
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
}); });
@ -3503,7 +3498,7 @@ mod tests {
cx, cx,
) )
}); });
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
}); });
@ -3532,7 +3527,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_delete_line(cx: &mut gpui::MutableAppContext) { fn test_delete_line(cx: &mut gpui::MutableAppContext) {
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3558,7 +3553,7 @@ mod tests {
); );
}); });
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3577,7 +3572,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_duplicate_line(cx: &mut gpui::MutableAppContext) { fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3606,7 +3601,7 @@ mod tests {
); );
}); });
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3634,7 +3629,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5), cx)); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5), cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3719,7 +3714,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_clipboard(cx: &mut gpui::MutableAppContext) { fn test_clipboard(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four five six ", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four five six ", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let view = cx let view = cx
.add_window(Default::default(), |cx| { .add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx) Editor::for_buffer(buffer.clone(), settings, cx)
@ -3854,7 +3849,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_select_all(cx: &mut gpui::MutableAppContext) { fn test_select_all(cx: &mut gpui::MutableAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx));
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
}); });
@ -3869,7 +3864,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_select_line(cx: &mut gpui::MutableAppContext) { fn test_select_line(cx: &mut gpui::MutableAppContext) {
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5), cx)); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5), cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3917,7 +3912,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) { fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5), cx)); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5), cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -3984,7 +3979,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) { fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
let settings = settings::channel(&cx.font_cache()).unwrap().1; let settings = settings::test(&cx).1;
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\n", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\n", cx));
let (_, view) = cx.add_window(Default::default(), |cx| { let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx) Editor::for_buffer(buffer, settings, cx)
@ -4159,7 +4154,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) { async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) {
let settings = cx.read(build_settings); let settings = cx.read(settings::test).1;
let languages = LanguageRegistry::new(); let languages = LanguageRegistry::new();
let lang = languages.select_language("z.rs"); let lang = languages.select_language("z.rs");
let text = r#" let text = r#"

View file

@ -343,8 +343,8 @@ mod tests {
use crate::{ use crate::{
editor::movement, editor::movement,
language::{Language, LanguageConfig}, language::{Language, LanguageConfig},
settings::Theme,
test::*, test::*,
theme::SyntaxTheme,
util::RandomCharIter, util::RandomCharIter,
}; };
use buffer::{History, SelectionGoal}; use buffer::{History, SelectionGoal};
@ -366,7 +366,7 @@ mod tests {
tab_size: rng.gen_range(1..=4), tab_size: rng.gen_range(1..=4),
buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
buffer_font_size: 14.0, buffer_font_size: 14.0,
..Settings::new(&font_cache).unwrap() ..cx.read(Settings::test)
}; };
let max_wrap_width = 300.0; let max_wrap_width = 300.0;
let mut wrap_width = if rng.gen_bool(0.1) { let mut wrap_width = if rng.gen_bool(0.1) {
@ -535,7 +535,7 @@ mod tests {
buffer_font_size: 12.0, buffer_font_size: 12.0,
ui_font_size: 12.0, ui_font_size: 12.0,
tab_size: 4, tab_size: 4,
theme: Arc::new(Theme::default()), ..cx.read(Settings::test)
}; };
let wrap_width = Some(64.); let wrap_width = Some(64.);
@ -606,7 +606,10 @@ mod tests {
let map = cx.add_model(|cx| { let map = cx.add_model(|cx| {
DisplayMap::new( DisplayMap::new(
buffer.clone(), buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4), Settings {
tab_size: 4,
..Settings::test(cx)
},
None, None,
cx, cx,
) )
@ -660,13 +663,13 @@ mod tests {
(function_item name: (identifier) @fn.name)"#, (function_item name: (identifier) @fn.name)"#,
) )
.unwrap(); .unwrap();
let theme = Theme { let theme = SyntaxTheme::new(
syntax: vec![ Default::default(),
vec![
("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
], ],
..Default::default() );
};
let lang = Arc::new(Language { let lang = Arc::new(Language {
config: LanguageConfig { config: LanguageConfig {
name: "Test".to_string(), name: "Test".to_string(),
@ -688,7 +691,10 @@ mod tests {
let map = cx.add_model(|cx| { let map = cx.add_model(|cx| {
DisplayMap::new( DisplayMap::new(
buffer, buffer,
Settings::new(cx.font_cache()).unwrap().with_tab_size(2), Settings {
tab_size: 2,
..Settings::test(cx)
},
None, None,
cx, cx,
) )
@ -750,13 +756,13 @@ mod tests {
(function_item name: (identifier) @fn.name)"#, (function_item name: (identifier) @fn.name)"#,
) )
.unwrap(); .unwrap();
let theme = Theme { let theme = SyntaxTheme::new(
syntax: vec![ Default::default(),
vec![
("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
], ],
..Default::default() );
};
let lang = Arc::new(Language { let lang = Arc::new(Language {
config: LanguageConfig { config: LanguageConfig {
name: "Test".to_string(), name: "Test".to_string(),
@ -780,7 +786,7 @@ mod tests {
tab_size: 4, tab_size: 4,
buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(), buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(),
buffer_font_size: 16.0, buffer_font_size: 16.0,
..Settings::new(&font_cache).unwrap() ..cx.read(Settings::test)
}; };
let map = cx.add_model(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx)); let map = cx.add_model(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx));
assert_eq!( assert_eq!(
@ -820,7 +826,10 @@ mod tests {
let map = cx.add_model(|cx| { let map = cx.add_model(|cx| {
DisplayMap::new( DisplayMap::new(
buffer.clone(), buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4), Settings {
tab_size: 4,
..Settings::test(cx)
},
None, None,
cx, cx,
) )
@ -861,7 +870,10 @@ mod tests {
let map = cx.add_model(|cx| { let map = cx.add_model(|cx| {
DisplayMap::new( DisplayMap::new(
buffer.clone(), buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4), Settings {
tab_size: 4,
..Settings::test(cx)
},
None, None,
cx, cx,
) )
@ -925,7 +937,10 @@ mod tests {
let map = cx.add_model(|cx| { let map = cx.add_model(|cx| {
DisplayMap::new( DisplayMap::new(
buffer.clone(), buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4), Settings {
tab_size: 4,
..Settings::test(cx)
},
None, None,
cx, cx,
) )
@ -939,7 +954,7 @@ mod tests {
fn highlighted_chunks<'a>( fn highlighted_chunks<'a>(
rows: Range<u32>, rows: Range<u32>,
map: &ModelHandle<DisplayMap>, map: &ModelHandle<DisplayMap>,
theme: &'a Theme, theme: &'a SyntaxTheme,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Vec<(String, Option<&'a str>)> { ) -> Vec<(String, Option<&'a str>)> {
let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));

View file

@ -921,7 +921,7 @@ mod tests {
tab_size: rng.gen_range(1..=4), tab_size: rng.gen_range(1..=4),
buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
buffer_font_size: 14.0, buffer_font_size: 14.0,
..Settings::new(&font_cache).unwrap() ..cx.read(Settings::test)
}; };
log::info!("Tab size: {}", settings.tab_size); log::info!("Tab size: {}", settings.tab_size);
log::info!("Wrap width: {:?}", wrap_width); log::info!("Wrap width: {:?}", wrap_width);

View file

@ -182,12 +182,12 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
editor::{display_map::DisplayMap, Buffer}, editor::{display_map::DisplayMap, Buffer},
test::build_app_state, test::test_app_state,
}; };
#[gpui::test] #[gpui::test]
fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) { fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) {
let settings = build_app_state(cx).settings.borrow().clone(); let settings = test_app_state(cx).settings.borrow().clone();
let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ", cx)); let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ", cx));
let display_map = cx.add_model(|cx| DisplayMap::new(buffer, settings, None, cx)); let display_map = cx.add_model(|cx| DisplayMap::new(buffer, settings, None, cx));
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));

View file

@ -117,13 +117,7 @@ impl FileFinder {
if self.matches.is_empty() { if self.matches.is_empty() {
let settings = self.settings.borrow(); let settings = self.settings.borrow();
return Container::new( return Container::new(
Label::new( Label::new("No matches".into(), settings.theme.selector.label.clone()).boxed(),
"No matches".into(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&settings.theme.selector.label)
.boxed(),
) )
.with_margin_top(6.0) .with_margin_top(6.0)
.named("empty matches"); .named("empty matches");
@ -184,24 +178,14 @@ impl FileFinder {
1.0, 1.0,
Flex::column() Flex::column()
.with_child( .with_child(
Label::new( Label::new(file_name.to_string(), style.label.clone())
file_name.to_string(), .with_highlights(file_name_positions)
settings.ui_font_family, .boxed(),
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(file_name_positions)
.boxed(),
) )
.with_child( .with_child(
Label::new( Label::new(full_path, style.label.clone())
full_path, .with_highlights(full_path_positions)
settings.ui_font_family, .boxed(),
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(full_path_positions)
.boxed(),
) )
.boxed(), .boxed(),
) )
@ -438,7 +422,7 @@ mod tests {
use crate::{ use crate::{
editor::{self, Insert}, editor::{self, Insert},
fs::FakeFs, fs::FakeFs,
test::{build_app_state, temp_tree}, test::{temp_tree, test_app_state},
workspace::Workspace, workspace::Workspace,
}; };
use serde_json::json; use serde_json::json;
@ -456,7 +440,7 @@ mod tests {
editor::init(cx); editor::init(cx);
}); });
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -516,7 +500,7 @@ mod tests {
) )
.await; .await;
let mut app_state = cx.update(build_app_state); let mut app_state = cx.update(test_app_state);
Arc::get_mut(&mut app_state).unwrap().fs = fs; Arc::get_mut(&mut app_state).unwrap().fs = fs;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
@ -578,7 +562,7 @@ mod tests {
fs::create_dir(&dir_path).unwrap(); fs::create_dir(&dir_path).unwrap();
fs::write(&file_path, "").unwrap(); fs::write(&file_path, "").unwrap();
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -625,7 +609,7 @@ mod tests {
"dir2": { "a.txt": "" } "dir2": { "a.txt": "" }
})); }));
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace

View file

@ -1,4 +1,4 @@
use crate::settings::{HighlightMap, Theme}; use crate::{settings::HighlightMap, theme::SyntaxTheme};
use parking_lot::Mutex; use parking_lot::Mutex;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use serde::Deserialize; use serde::Deserialize;
@ -39,7 +39,7 @@ impl Language {
self.highlight_map.lock().clone() self.highlight_map.lock().clone()
} }
pub fn set_theme(&self, theme: &Theme) { pub fn set_theme(&self, theme: &SyntaxTheme) {
*self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme); *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme);
} }
} }
@ -61,7 +61,7 @@ impl LanguageRegistry {
} }
} }
pub fn set_theme(&self, theme: &Theme) { pub fn set_theme(&self, theme: &SyntaxTheme) {
for language in &self.languages { for language in &self.languages {
language.set_theme(theme); language.set_theme(theme);
} }

View file

@ -22,11 +22,10 @@ fn main() {
let app = gpui::App::new(assets::Assets).unwrap(); let app = gpui::App::new(assets::Assets).unwrap();
let themes = settings::ThemeRegistry::new(assets::Assets); let themes = settings::ThemeRegistry::new(assets::Assets, app.font_cache());
let (settings_tx, settings) = let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap();
settings::channel_with_themes(&app.font_cache(), &themes).unwrap();
let languages = Arc::new(language::LanguageRegistry::new()); let languages = Arc::new(language::LanguageRegistry::new());
languages.set_theme(&settings.borrow().theme); languages.set_theme(&settings.borrow().theme.syntax);
app.run(move |cx| { app.run(move |cx| {
let rpc = rpc::Client::new(); let rpc = rpc::Client::new();

View file

@ -17,11 +17,27 @@ pub struct Settings {
} }
impl Settings { impl Settings {
pub fn new(font_cache: &FontCache) -> Result<Self> { #[cfg(any(test, feature = "test-support"))]
Self::new_with_theme(font_cache, Arc::new(Theme::default())) pub fn test(cx: &gpui::AppContext) -> Self {
lazy_static::lazy_static! {
static ref DEFAULT_THEME: parking_lot::Mutex<Option<Arc<Theme>>> = Default::default();
}
let mut theme_guard = DEFAULT_THEME.lock();
let theme = if let Some(theme) = theme_guard.as_ref() {
theme.clone()
} else {
let theme = ThemeRegistry::new(crate::assets::Assets, cx.font_cache().clone())
.get(DEFAULT_THEME_NAME)
.expect("failed to load default theme in tests");
*theme_guard = Some(theme.clone());
theme
};
Self::new(cx.font_cache(), theme).unwrap()
} }
pub fn new_with_theme(font_cache: &FontCache, theme: Arc<Theme>) -> Result<Self> { pub fn new(font_cache: &FontCache, theme: Arc<Theme>) -> Result<Self> {
Ok(Self { Ok(Self {
buffer_font_family: font_cache.load_family(&["Fira Code", "Monaco"])?, buffer_font_family: font_cache.load_family(&["Fira Code", "Monaco"])?,
buffer_font_size: 14.0, buffer_font_size: 14.0,
@ -38,13 +54,12 @@ impl Settings {
} }
} }
pub fn channel( #[cfg(any(test, feature = "test-support"))]
font_cache: &FontCache, pub fn test(cx: &gpui::AppContext) -> (watch::Sender<Settings>, watch::Receiver<Settings>) {
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> { watch::channel_with(Settings::test(cx))
Ok(watch::channel_with(Settings::new(font_cache)?))
} }
pub fn channel_with_themes( pub fn channel(
font_cache: &FontCache, font_cache: &FontCache,
themes: &ThemeRegistry, themes: &ThemeRegistry,
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> { ) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
@ -54,7 +69,5 @@ pub fn channel_with_themes(
panic!("failed to deserialize default theme: {:?}", err) panic!("failed to deserialize default theme: {:?}", err)
} }
}; };
Ok(watch::channel_with(Settings::new_with_theme( Ok(watch::channel_with(Settings::new(font_cache, theme)?))
font_cache, theme,
)?))
} }

View file

@ -6,11 +6,10 @@ use crate::{
settings::{self, ThemeRegistry}, settings::{self, ThemeRegistry},
time::ReplicaId, time::ReplicaId,
user::UserStore, user::UserStore,
AppState, Settings, AppState,
}; };
use gpui::{AppContext, Entity, ModelHandle, MutableAppContext}; use gpui::{Entity, ModelHandle, MutableAppContext};
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::watch;
use smol::channel; use smol::channel;
use std::{ use std::{
marker::PhantomData, marker::PhantomData,
@ -156,14 +155,10 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
} }
} }
pub fn build_settings(cx: &AppContext) -> watch::Receiver<Settings> { pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
settings::channel(&cx.font_cache()).unwrap().1 let (settings_tx, settings) = settings::test(cx);
}
pub fn build_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
let (settings_tx, settings) = settings::channel(&cx.font_cache()).unwrap();
let languages = Arc::new(LanguageRegistry::new()); let languages = Arc::new(LanguageRegistry::new());
let themes = ThemeRegistry::new(()); let themes = ThemeRegistry::new((), cx.font_cache().clone());
let rpc = rpc::Client::new(); let rpc = rpc::Client::new();
let user_store = Arc::new(UserStore::new(rpc.clone())); let user_store = Arc::new(UserStore::new(rpc.clone()));
Arc::new(AppState { Arc::new(AppState {

View file

@ -5,9 +5,9 @@ use anyhow::Result;
use gpui::{ use gpui::{
color::Color, color::Color,
elements::{ContainerStyle, LabelStyle}, elements::{ContainerStyle, LabelStyle},
fonts::TextStyle, fonts::{HighlightStyle, TextStyle},
}; };
use serde::{Deserialize, Deserializer}; use serde::{de, Deserialize};
use std::collections::HashMap; use std::collections::HashMap;
pub use highlight_map::*; pub use highlight_map::*;
@ -15,7 +15,7 @@ pub use theme_registry::*;
pub const DEFAULT_THEME_NAME: &'static str = "dark"; pub const DEFAULT_THEME_NAME: &'static str = "dark";
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct Theme { pub struct Theme {
#[serde(default)] #[serde(default)]
pub name: String, pub name: String,
@ -23,11 +23,15 @@ pub struct Theme {
pub chat_panel: ChatPanel, pub chat_panel: ChatPanel,
pub selector: Selector, pub selector: Selector,
pub editor: Editor, pub editor: Editor,
#[serde(deserialize_with = "deserialize_syntax_theme")] pub syntax: SyntaxTheme,
pub syntax: Vec<(String, TextStyle)>,
} }
#[derive(Debug, Default, Deserialize)] pub struct SyntaxTheme {
highlights: Vec<(String, HighlightStyle)>,
default_style: HighlightStyle,
}
#[derive(Deserialize)]
pub struct Workspace { pub struct Workspace {
pub background: Color, pub background: Color,
pub tab: Tab, pub tab: Tab,
@ -37,7 +41,7 @@ pub struct Workspace {
pub active_sidebar_icon: SidebarIcon, pub active_sidebar_icon: SidebarIcon,
} }
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct Tab { pub struct Tab {
#[serde(flatten)] #[serde(flatten)]
pub container: ContainerStyle, pub container: ContainerStyle,
@ -48,26 +52,26 @@ pub struct Tab {
pub icon_conflict: Color, pub icon_conflict: Color,
} }
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct SidebarIcon { pub struct SidebarIcon {
pub color: Color, pub color: Color,
} }
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct ChatPanel { pub struct ChatPanel {
#[serde(flatten)] #[serde(flatten)]
pub container: ContainerStyle, pub container: ContainerStyle,
pub message: ChatMessage, pub message: ChatMessage,
} }
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct ChatMessage { pub struct ChatMessage {
pub body: TextStyle, pub body: TextStyle,
pub sender: ContainedLabel, pub sender: ContainedLabel,
pub timestamp: ContainedLabel, pub timestamp: ContainedLabel,
} }
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct Selector { pub struct Selector {
#[serde(flatten)] #[serde(flatten)]
pub container: ContainerStyle, pub container: ContainerStyle,
@ -78,7 +82,7 @@ pub struct Selector {
pub active_item: ContainedLabel, pub active_item: ContainedLabel,
} }
#[derive(Debug, Default, Deserialize)] #[derive(Deserialize)]
pub struct ContainedLabel { pub struct ContainedLabel {
#[serde(flatten)] #[serde(flatten)]
pub container: ContainerStyle, pub container: ContainerStyle,
@ -86,70 +90,69 @@ pub struct ContainedLabel {
pub label: LabelStyle, pub label: LabelStyle,
} }
#[derive(Debug, Deserialize)] #[derive(Deserialize)]
pub struct Editor { pub struct Editor {
pub background: Color, pub background: Color,
pub gutter_background: Color, pub gutter_background: Color,
pub active_line_background: Color, pub active_line_background: Color,
pub line_number: Color, pub line_number: Color,
pub line_number_active: Color, pub line_number_active: Color,
pub text: Color,
pub replicas: Vec<Replica>, pub replicas: Vec<Replica>,
} }
#[derive(Clone, Copy, Debug, Default, Deserialize)] #[derive(Clone, Copy, Deserialize)]
pub struct Replica { pub struct Replica {
pub cursor: Color, pub cursor: Color,
pub selection: Color, pub selection: Color,
} }
impl Theme { impl SyntaxTheme {
pub fn highlight_style(&self, id: HighlightId) -> TextStyle { pub fn new(default_style: HighlightStyle, highlights: Vec<(String, HighlightStyle)>) -> Self {
self.syntax Self {
default_style,
highlights,
}
}
pub fn highlight_style(&self, id: HighlightId) -> HighlightStyle {
self.highlights
.get(id.0 as usize) .get(id.0 as usize)
.map(|entry| entry.1.clone()) .map(|entry| entry.1.clone())
.unwrap_or_else(|| TextStyle { .unwrap_or_else(|| self.default_style.clone())
color: self.editor.text,
font_properties: Default::default(),
})
} }
#[cfg(test)] #[cfg(test)]
pub fn highlight_name(&self, id: HighlightId) -> Option<&str> { pub fn highlight_name(&self, id: HighlightId) -> Option<&str> {
self.syntax.get(id.0 as usize).map(|e| e.0.as_str()) self.highlights.get(id.0 as usize).map(|e| e.0.as_str())
} }
} }
impl Default for Editor { impl<'de> Deserialize<'de> for SyntaxTheme {
fn default() -> Self { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
Self { where
background: Default::default(), D: serde::Deserializer<'de>,
gutter_background: Default::default(), {
active_line_background: Default::default(), let mut syntax_data: HashMap<String, HighlightStyle> =
line_number: Default::default(), Deserialize::deserialize(deserializer)?;
line_number_active: Default::default(),
text: Default::default(),
replicas: vec![Replica::default()],
}
}
}
pub fn deserialize_syntax_theme<'de, D>( let mut result = Self {
deserializer: D, highlights: Vec::<(String, HighlightStyle)>::new(),
) -> Result<Vec<(String, TextStyle)>, D::Error> default_style: syntax_data
where .remove("default")
D: Deserializer<'de>, .ok_or_else(|| de::Error::custom("must specify a default color in syntax theme"))?,
{ };
let mut result = Vec::<(String, TextStyle)>::new();
let syntax_data: HashMap<String, TextStyle> = Deserialize::deserialize(deserializer)?; for (key, style) in syntax_data {
for (key, style) in syntax_data { match result
match result.binary_search_by(|(needle, _)| needle.cmp(&key)) { .highlights
Ok(i) | Err(i) => { .binary_search_by(|(needle, _)| needle.cmp(&key))
result.insert(i, (key, style)); {
Ok(i) | Err(i) => {
result.highlights.insert(i, (key, style));
}
} }
} }
}
Ok(result) Ok(result)
}
} }

View file

@ -1,4 +1,4 @@
use super::Theme; use super::SyntaxTheme;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -10,7 +10,7 @@ pub struct HighlightId(pub u32);
const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
impl HighlightMap { impl HighlightMap {
pub fn new(capture_names: &[String], theme: &Theme) -> Self { pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self {
// For each capture name in the highlight query, find the longest // For each capture name in the highlight query, find the longest
// key in the theme's syntax styles that matches all of the // key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name. // dot-separated components of the capture name.
@ -19,7 +19,7 @@ impl HighlightMap {
.iter() .iter()
.map(|capture_name| { .map(|capture_name| {
theme theme
.syntax .highlights
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(i, (key, _))| { .filter_map(|(i, (key, _))| {
@ -68,9 +68,9 @@ mod tests {
#[test] #[test]
fn test_highlight_map() { fn test_highlight_map() {
let theme = Theme { let theme = SyntaxTheme::new(
name: "test".into(), Default::default(),
syntax: [ [
("function", Color::from_u32(0x100000ff)), ("function", Color::from_u32(0x100000ff)),
("function.method", Color::from_u32(0x200000ff)), ("function.method", Color::from_u32(0x200000ff)),
("function.async", Color::from_u32(0x300000ff)), ("function.async", Color::from_u32(0x300000ff)),
@ -81,8 +81,7 @@ mod tests {
.iter() .iter()
.map(|(name, color)| (name.to_string(), (*color).into())) .map(|(name, color)| (name.to_string(), (*color).into()))
.collect(), .collect(),
..Default::default() );
};
let capture_names = &[ let capture_names = &[
"function.special".to_string(), "function.special".to_string(),

View file

@ -1,5 +1,5 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use gpui::AssetSource; use gpui::{fonts, AssetSource, FontCache};
use json::{Map, Value}; use json::{Map, Value};
use parking_lot::Mutex; use parking_lot::Mutex;
use serde_json as json; use serde_json as json;
@ -11,6 +11,7 @@ pub struct ThemeRegistry {
assets: Box<dyn AssetSource>, assets: Box<dyn AssetSource>,
themes: Mutex<HashMap<String, Arc<Theme>>>, themes: Mutex<HashMap<String, Arc<Theme>>>,
theme_data: Mutex<HashMap<String, Arc<Value>>>, theme_data: Mutex<HashMap<String, Arc<Value>>>,
font_cache: Arc<FontCache>,
} }
#[derive(Default)] #[derive(Default)]
@ -38,11 +39,12 @@ enum Key {
} }
impl ThemeRegistry { impl ThemeRegistry {
pub fn new(source: impl AssetSource) -> Arc<Self> { pub fn new(source: impl AssetSource, font_cache: Arc<FontCache>) -> Arc<Self> {
Arc::new(Self { Arc::new(Self {
assets: Box::new(source), assets: Box::new(source),
themes: Default::default(), themes: Default::default(),
theme_data: Default::default(), theme_data: Default::default(),
font_cache,
}) })
} }
@ -69,7 +71,10 @@ impl ThemeRegistry {
} }
let theme_data = self.load(name, true)?; let theme_data = self.load(name, true)?;
let mut theme = serde_json::from_value::<Theme>(theme_data.as_ref().clone())?; let mut theme = fonts::with_font_cache(self.font_cache.clone(), || {
serde_json::from_value::<Theme>(theme_data.as_ref().clone())
})?;
theme.name = name.into(); theme.name = name.into();
let theme = Arc::new(theme); let theme = Arc::new(theme);
self.themes.lock().insert(name.to_string(), theme.clone()); self.themes.lock().insert(name.to_string(), theme.clone());
@ -512,11 +517,12 @@ fn value_at<'a>(object: &'a mut Map<String, Value>, key_path: &KeyPath) -> Optio
mod tests { mod tests {
use super::*; use super::*;
use crate::{assets::Assets, theme::DEFAULT_THEME_NAME}; use crate::{assets::Assets, theme::DEFAULT_THEME_NAME};
use gpui::MutableAppContext;
use rand::{prelude::StdRng, Rng}; use rand::{prelude::StdRng, Rng};
#[test] #[gpui::test]
fn test_bundled_themes() { fn test_bundled_themes(cx: &mut MutableAppContext) {
let registry = ThemeRegistry::new(Assets); let registry = ThemeRegistry::new(Assets, cx.font_cache().clone());
let mut has_default_theme = false; let mut has_default_theme = false;
for theme_name in registry.list() { for theme_name in registry.list() {
let theme = registry.get(&theme_name).unwrap(); let theme = registry.get(&theme_name).unwrap();
@ -528,8 +534,8 @@ mod tests {
assert!(has_default_theme); assert!(has_default_theme);
} }
#[test] #[gpui::test]
fn test_theme_extension() { fn test_theme_extension(cx: &mut MutableAppContext) {
let assets = TestAssets(&[ let assets = TestAssets(&[
( (
"themes/_base.toml", "themes/_base.toml",
@ -568,7 +574,7 @@ mod tests {
), ),
]); ]);
let registry = ThemeRegistry::new(assets); let registry = ThemeRegistry::new(assets, cx.font_cache().clone());
let theme_data = registry.load("light", true).unwrap(); let theme_data = registry.load("light", true).unwrap();
assert_eq!( assert_eq!(
theme_data.as_ref(), theme_data.as_ref(),

View file

@ -204,13 +204,7 @@ impl ThemeSelector {
if self.matches.is_empty() { if self.matches.is_empty() {
let settings = self.settings.borrow(); let settings = self.settings.borrow();
return Container::new( return Container::new(
Label::new( Label::new("No matches".into(), settings.theme.selector.label.clone()).boxed(),
"No matches".into(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&settings.theme.selector.label)
.boxed(),
) )
.with_margin_top(6.0) .with_margin_top(6.0)
.named("empty matches"); .named("empty matches");
@ -247,14 +241,12 @@ impl ThemeSelector {
let container = Container::new( let container = Container::new(
Label::new( Label::new(
theme_match.string.clone(), theme_match.string.clone(),
settings.ui_font_family, if index == self.selected_index {
settings.ui_font_size, theme.selector.active_item.label.clone()
} else {
theme.selector.item.label.clone()
},
) )
.with_style(if index == self.selected_index {
&theme.selector.active_item.label
} else {
&theme.selector.item.label
})
.with_highlights(theme_match.positions.clone()) .with_highlights(theme_match.positions.clone())
.boxed(), .boxed(),
) )

View file

@ -1018,7 +1018,7 @@ mod tests {
use crate::{ use crate::{
editor::{Editor, Insert}, editor::{Editor, Insert},
fs::FakeFs, fs::FakeFs,
test::{build_app_state, temp_tree}, test::{temp_tree, test_app_state},
worktree::WorktreeHandle, worktree::WorktreeHandle,
}; };
use serde_json::json; use serde_json::json;
@ -1027,7 +1027,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_open_paths_action(mut cx: gpui::TestAppContext) { async fn test_open_paths_action(mut cx: gpui::TestAppContext) {
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {
"aa": null, "aa": null,
@ -1100,7 +1100,7 @@ mod tests {
}, },
})); }));
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
@ -1204,7 +1204,7 @@ mod tests {
fs.insert_file("/dir1/a.txt", "".into()).await.unwrap(); fs.insert_file("/dir1/a.txt", "".into()).await.unwrap();
fs.insert_file("/dir2/b.txt", "".into()).await.unwrap(); fs.insert_file("/dir2/b.txt", "".into()).await.unwrap();
let mut app_state = cx.update(build_app_state); let mut app_state = cx.update(test_app_state);
Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(fs); Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(fs);
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
@ -1273,7 +1273,7 @@ mod tests {
"a.txt": "", "a.txt": "",
})); }));
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -1318,7 +1318,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) { async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
let dir = TempDir::new("test-new-file").unwrap(); let dir = TempDir::new("test-new-file").unwrap();
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -1417,7 +1417,7 @@ mod tests {
async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) {
cx.update(init); cx.update(init);
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
cx.dispatch_global_action(OpenNew(app_state)); cx.dispatch_global_action(OpenNew(app_state));
let window_id = *cx.window_ids().first().unwrap(); let window_id = *cx.window_ids().first().unwrap();
let workspace = cx.root_view::<Workspace>(window_id).unwrap(); let workspace = cx.root_view::<Workspace>(window_id).unwrap();
@ -1463,7 +1463,7 @@ mod tests {
}, },
})); }));
let app_state = cx.update(build_app_state); let app_state = cx.update(test_app_state);
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {

View file

@ -208,14 +208,12 @@ impl Pane {
Align::new( Align::new(
Label::new( Label::new(
title, title,
settings.ui_font_family, if is_active {
settings.ui_font_size, theme.workspace.active_tab.label.clone()
} else {
theme.workspace.tab.label.clone()
},
) )
.with_style(if is_active {
&theme.workspace.active_tab.label
} else {
&theme.workspace.tab.label
})
.boxed(), .boxed(),
) )
.boxed(), .boxed(),