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
co-authored by Antonio Scandurra Nathan Sobo
parent ee9ee294ad
commit 3bb5610ad1
23 changed files with 438 additions and 397 deletions
+3 -15
View File
@@ -126,10 +126,8 @@ impl ChatPanel {
Container::new(
Label::new(
message.sender.github_login.clone(),
settings.ui_font_family,
settings.ui_font_size,
theme.sender.label.clone(),
)
.with_style(&theme.sender.label)
.boxed(),
)
.with_style(&theme.sender.container)
@@ -139,10 +137,8 @@ impl ChatPanel {
Container::new(
Label::new(
format_timestamp(message.timestamp, now),
settings.ui_font_family,
settings.ui_font_size,
theme.timestamp.label.clone(),
)
.with_style(&theme.timestamp.label)
.boxed(),
)
.with_style(&theme.timestamp.container)
@@ -150,15 +146,7 @@ impl ChatPanel {
)
.boxed(),
)
.with_child(
Text::new(
message.body.clone(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&theme.body)
.boxed(),
)
.with_child(Text::new(message.body.clone(), theme.body.clone()).boxed())
.boxed()
}
+26 -31
View File
@@ -2418,7 +2418,7 @@ impl Snapshot {
}
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.
let font_id = if style.font_properties == prev_font_properties {
prev_font_id
@@ -2632,19 +2632,14 @@ impl workspace::ItemView for Editor {
#[cfg(test)]
mod tests {
use super::*;
use crate::{
editor::Point,
language::LanguageRegistry,
settings,
test::{build_settings, sample_text},
};
use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text};
use buffer::History;
use unindent::Unindent;
#[gpui::test]
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 settings = settings::channel(&cx.font_cache()).unwrap().1;
let settings = settings::test(&cx).1;
let (_, editor) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
});
@@ -2712,7 +2707,7 @@ mod tests {
#[gpui::test]
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 settings = settings::channel(&cx.font_cache()).unwrap().1;
let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
});
@@ -2746,7 +2741,7 @@ mod tests {
#[gpui::test]
fn test_cancel(cx: &mut gpui::MutableAppContext) {
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| {
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 settings = settings::channel(&font_cache).unwrap().1;
let settings = settings::test(&cx).1;
let (_, editor) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings.clone(), cx)
});
@@ -2838,7 +2833,7 @@ mod tests {
cx,
)
});
let settings = settings::channel(&cx.font_cache()).unwrap().1;
let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx)
});
@@ -2906,7 +2901,7 @@ mod tests {
#[gpui::test]
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
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| {
Editor::for_buffer(buffer.clone(), settings, cx)
});
@@ -2983,7 +2978,7 @@ mod tests {
#[gpui::test]
fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
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| {
Editor::for_buffer(buffer.clone(), settings, cx)
});
@@ -3041,7 +3036,7 @@ mod tests {
#[gpui::test]
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 settings = settings::channel(&cx.font_cache()).unwrap().1;
let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx)
});
@@ -3072,7 +3067,7 @@ mod tests {
#[gpui::test]
fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
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| {
Editor::for_buffer(buffer, settings, cx)
});
@@ -3217,7 +3212,7 @@ mod tests {
fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
let buffer =
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| {
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) {
let buffer =
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| {
Editor::for_buffer(buffer, settings, cx)
});
@@ -3467,7 +3462,7 @@ mod tests {
cx,
)
});
let settings = settings::channel(&cx.font_cache()).unwrap().1;
let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx)
});
@@ -3503,7 +3498,7 @@ mod tests {
cx,
)
});
let settings = settings::channel(&cx.font_cache()).unwrap().1;
let settings = settings::test(&cx).1;
let (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx)
});
@@ -3532,7 +3527,7 @@ mod tests {
#[gpui::test]
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 (_, view) = cx.add_window(Default::default(), |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 (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
@@ -3577,7 +3572,7 @@ mod tests {
#[gpui::test]
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 (_, view) = cx.add_window(Default::default(), |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 (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
@@ -3634,7 +3629,7 @@ mod tests {
#[gpui::test]
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 (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
@@ -3719,7 +3714,7 @@ mod tests {
#[gpui::test]
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
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
.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer.clone(), settings, cx)
@@ -3854,7 +3849,7 @@ mod tests {
#[gpui::test]
fn test_select_all(cx: &mut gpui::MutableAppContext) {
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| {
Editor::for_buffer(buffer, settings, cx)
});
@@ -3869,7 +3864,7 @@ mod tests {
#[gpui::test]
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 (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
@@ -3917,7 +3912,7 @@ mod tests {
#[gpui::test]
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 (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
@@ -3984,7 +3979,7 @@ mod tests {
#[gpui::test]
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 (_, view) = cx.add_window(Default::default(), |cx| {
Editor::for_buffer(buffer, settings, cx)
@@ -4159,7 +4154,7 @@ mod tests {
#[gpui::test]
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 lang = languages.select_language("z.rs");
let text = r#"
+33 -18
View File
@@ -343,8 +343,8 @@ mod tests {
use crate::{
editor::movement,
language::{Language, LanguageConfig},
settings::Theme,
test::*,
theme::SyntaxTheme,
util::RandomCharIter,
};
use buffer::{History, SelectionGoal};
@@ -366,7 +366,7 @@ mod tests {
tab_size: rng.gen_range(1..=4),
buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
buffer_font_size: 14.0,
..Settings::new(&font_cache).unwrap()
..cx.read(Settings::test)
};
let max_wrap_width = 300.0;
let mut wrap_width = if rng.gen_bool(0.1) {
@@ -535,7 +535,7 @@ mod tests {
buffer_font_size: 12.0,
ui_font_size: 12.0,
tab_size: 4,
theme: Arc::new(Theme::default()),
..cx.read(Settings::test)
};
let wrap_width = Some(64.);
@@ -606,7 +606,10 @@ mod tests {
let map = cx.add_model(|cx| {
DisplayMap::new(
buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
Settings {
tab_size: 4,
..Settings::test(cx)
},
None,
cx,
)
@@ -660,13 +663,13 @@ mod tests {
(function_item name: (identifier) @fn.name)"#,
)
.unwrap();
let theme = Theme {
syntax: vec![
let theme = SyntaxTheme::new(
Default::default(),
vec![
("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
],
..Default::default()
};
);
let lang = Arc::new(Language {
config: LanguageConfig {
name: "Test".to_string(),
@@ -688,7 +691,10 @@ mod tests {
let map = cx.add_model(|cx| {
DisplayMap::new(
buffer,
Settings::new(cx.font_cache()).unwrap().with_tab_size(2),
Settings {
tab_size: 2,
..Settings::test(cx)
},
None,
cx,
)
@@ -750,13 +756,13 @@ mod tests {
(function_item name: (identifier) @fn.name)"#,
)
.unwrap();
let theme = Theme {
syntax: vec![
let theme = SyntaxTheme::new(
Default::default(),
vec![
("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
],
..Default::default()
};
);
let lang = Arc::new(Language {
config: LanguageConfig {
name: "Test".to_string(),
@@ -780,7 +786,7 @@ mod tests {
tab_size: 4,
buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(),
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));
assert_eq!(
@@ -820,7 +826,10 @@ mod tests {
let map = cx.add_model(|cx| {
DisplayMap::new(
buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
Settings {
tab_size: 4,
..Settings::test(cx)
},
None,
cx,
)
@@ -861,7 +870,10 @@ mod tests {
let map = cx.add_model(|cx| {
DisplayMap::new(
buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
Settings {
tab_size: 4,
..Settings::test(cx)
},
None,
cx,
)
@@ -925,7 +937,10 @@ mod tests {
let map = cx.add_model(|cx| {
DisplayMap::new(
buffer.clone(),
Settings::new(cx.font_cache()).unwrap().with_tab_size(4),
Settings {
tab_size: 4,
..Settings::test(cx)
},
None,
cx,
)
@@ -939,7 +954,7 @@ mod tests {
fn highlighted_chunks<'a>(
rows: Range<u32>,
map: &ModelHandle<DisplayMap>,
theme: &'a Theme,
theme: &'a SyntaxTheme,
cx: &mut MutableAppContext,
) -> Vec<(String, Option<&'a str>)> {
let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
+1 -1
View File
@@ -921,7 +921,7 @@ mod tests {
tab_size: rng.gen_range(1..=4),
buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
buffer_font_size: 14.0,
..Settings::new(&font_cache).unwrap()
..cx.read(Settings::test)
};
log::info!("Tab size: {}", settings.tab_size);
log::info!("Wrap width: {:?}", wrap_width);
+2 -2
View File
@@ -182,12 +182,12 @@ mod tests {
use super::*;
use crate::{
editor::{display_map::DisplayMap, Buffer},
test::build_app_state,
test::test_app_state,
};
#[gpui::test]
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 display_map = cx.add_model(|cx| DisplayMap::new(buffer, settings, None, cx));
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
+12 -28
View File
@@ -117,13 +117,7 @@ impl FileFinder {
if self.matches.is_empty() {
let settings = self.settings.borrow();
return Container::new(
Label::new(
"No matches".into(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&settings.theme.selector.label)
.boxed(),
Label::new("No matches".into(), settings.theme.selector.label.clone()).boxed(),
)
.with_margin_top(6.0)
.named("empty matches");
@@ -184,24 +178,14 @@ impl FileFinder {
1.0,
Flex::column()
.with_child(
Label::new(
file_name.to_string(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(file_name_positions)
.boxed(),
Label::new(file_name.to_string(), style.label.clone())
.with_highlights(file_name_positions)
.boxed(),
)
.with_child(
Label::new(
full_path,
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(full_path_positions)
.boxed(),
Label::new(full_path, style.label.clone())
.with_highlights(full_path_positions)
.boxed(),
)
.boxed(),
)
@@ -438,7 +422,7 @@ mod tests {
use crate::{
editor::{self, Insert},
fs::FakeFs,
test::{build_app_state, temp_tree},
test::{temp_tree, test_app_state},
workspace::Workspace,
};
use serde_json::json;
@@ -456,7 +440,7 @@ mod tests {
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));
workspace
.update(&mut cx, |workspace, cx| {
@@ -516,7 +500,7 @@ mod tests {
)
.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;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
@@ -578,7 +562,7 @@ mod tests {
fs::create_dir(&dir_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));
workspace
.update(&mut cx, |workspace, cx| {
@@ -625,7 +609,7 @@ mod tests {
"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));
workspace
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::settings::{HighlightMap, Theme};
use crate::{settings::HighlightMap, theme::SyntaxTheme};
use parking_lot::Mutex;
use rust_embed::RustEmbed;
use serde::Deserialize;
@@ -39,7 +39,7 @@ impl Language {
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);
}
}
@@ -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 {
language.set_theme(theme);
}
+3 -4
View File
@@ -22,11 +22,10 @@ fn main() {
let app = gpui::App::new(assets::Assets).unwrap();
let themes = settings::ThemeRegistry::new(assets::Assets);
let (settings_tx, settings) =
settings::channel_with_themes(&app.font_cache(), &themes).unwrap();
let themes = settings::ThemeRegistry::new(assets::Assets, app.font_cache());
let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap();
let languages = Arc::new(language::LanguageRegistry::new());
languages.set_theme(&settings.borrow().theme);
languages.set_theme(&settings.borrow().theme.syntax);
app.run(move |cx| {
let rpc = rpc::Client::new();
+24 -11
View File
@@ -17,11 +17,27 @@ pub struct Settings {
}
impl Settings {
pub fn new(font_cache: &FontCache) -> Result<Self> {
Self::new_with_theme(font_cache, Arc::new(Theme::default()))
#[cfg(any(test, feature = "test-support"))]
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 {
buffer_font_family: font_cache.load_family(&["Fira Code", "Monaco"])?,
buffer_font_size: 14.0,
@@ -38,13 +54,12 @@ impl Settings {
}
}
pub fn channel(
font_cache: &FontCache,
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
Ok(watch::channel_with(Settings::new(font_cache)?))
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &gpui::AppContext) -> (watch::Sender<Settings>, watch::Receiver<Settings>) {
watch::channel_with(Settings::test(cx))
}
pub fn channel_with_themes(
pub fn channel(
font_cache: &FontCache,
themes: &ThemeRegistry,
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
@@ -54,7 +69,5 @@ pub fn channel_with_themes(
panic!("failed to deserialize default theme: {:?}", err)
}
};
Ok(watch::channel_with(Settings::new_with_theme(
font_cache, theme,
)?))
Ok(watch::channel_with(Settings::new(font_cache, theme)?))
}
+5 -10
View File
@@ -6,11 +6,10 @@ use crate::{
settings::{self, ThemeRegistry},
time::ReplicaId,
user::UserStore,
AppState, Settings,
AppState,
};
use gpui::{AppContext, Entity, ModelHandle, MutableAppContext};
use gpui::{Entity, ModelHandle, MutableAppContext};
use parking_lot::Mutex;
use postage::watch;
use smol::channel;
use std::{
marker::PhantomData,
@@ -156,14 +155,10 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
}
}
pub fn build_settings(cx: &AppContext) -> watch::Receiver<Settings> {
settings::channel(&cx.font_cache()).unwrap().1
}
pub fn build_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
let (settings_tx, settings) = settings::channel(&cx.font_cache()).unwrap();
pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
let (settings_tx, settings) = settings::test(cx);
let languages = Arc::new(LanguageRegistry::new());
let themes = ThemeRegistry::new(());
let themes = ThemeRegistry::new((), cx.font_cache().clone());
let rpc = rpc::Client::new();
let user_store = Arc::new(UserStore::new(rpc.clone()));
Arc::new(AppState {
+53 -50
View File
@@ -5,9 +5,9 @@ use anyhow::Result;
use gpui::{
color::Color,
elements::{ContainerStyle, LabelStyle},
fonts::TextStyle,
fonts::{HighlightStyle, TextStyle},
};
use serde::{Deserialize, Deserializer};
use serde::{de, Deserialize};
use std::collections::HashMap;
pub use highlight_map::*;
@@ -15,7 +15,7 @@ pub use theme_registry::*;
pub const DEFAULT_THEME_NAME: &'static str = "dark";
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct Theme {
#[serde(default)]
pub name: String,
@@ -23,11 +23,15 @@ pub struct Theme {
pub chat_panel: ChatPanel,
pub selector: Selector,
pub editor: Editor,
#[serde(deserialize_with = "deserialize_syntax_theme")]
pub syntax: Vec<(String, TextStyle)>,
pub syntax: SyntaxTheme,
}
#[derive(Debug, Default, Deserialize)]
pub struct SyntaxTheme {
highlights: Vec<(String, HighlightStyle)>,
default_style: HighlightStyle,
}
#[derive(Deserialize)]
pub struct Workspace {
pub background: Color,
pub tab: Tab,
@@ -37,7 +41,7 @@ pub struct Workspace {
pub active_sidebar_icon: SidebarIcon,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct Tab {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -48,26 +52,26 @@ pub struct Tab {
pub icon_conflict: Color,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct SidebarIcon {
pub color: Color,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct ChatPanel {
#[serde(flatten)]
pub container: ContainerStyle,
pub message: ChatMessage,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct ChatMessage {
pub body: TextStyle,
pub sender: ContainedLabel,
pub timestamp: ContainedLabel,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct Selector {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -78,7 +82,7 @@ pub struct Selector {
pub active_item: ContainedLabel,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Deserialize)]
pub struct ContainedLabel {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -86,70 +90,69 @@ pub struct ContainedLabel {
pub label: LabelStyle,
}
#[derive(Debug, Deserialize)]
#[derive(Deserialize)]
pub struct Editor {
pub background: Color,
pub gutter_background: Color,
pub active_line_background: Color,
pub line_number: Color,
pub line_number_active: Color,
pub text: Color,
pub replicas: Vec<Replica>,
}
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[derive(Clone, Copy, Deserialize)]
pub struct Replica {
pub cursor: Color,
pub selection: Color,
}
impl Theme {
pub fn highlight_style(&self, id: HighlightId) -> TextStyle {
self.syntax
impl SyntaxTheme {
pub fn new(default_style: HighlightStyle, highlights: Vec<(String, HighlightStyle)>) -> Self {
Self {
default_style,
highlights,
}
}
pub fn highlight_style(&self, id: HighlightId) -> HighlightStyle {
self.highlights
.get(id.0 as usize)
.map(|entry| entry.1.clone())
.unwrap_or_else(|| TextStyle {
color: self.editor.text,
font_properties: Default::default(),
})
.unwrap_or_else(|| self.default_style.clone())
}
#[cfg(test)]
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 {
fn default() -> Self {
Self {
background: Default::default(),
gutter_background: Default::default(),
active_line_background: Default::default(),
line_number: Default::default(),
line_number_active: Default::default(),
text: Default::default(),
replicas: vec![Replica::default()],
}
}
}
impl<'de> Deserialize<'de> for SyntaxTheme {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut syntax_data: HashMap<String, HighlightStyle> =
Deserialize::deserialize(deserializer)?;
pub fn deserialize_syntax_theme<'de, D>(
deserializer: D,
) -> Result<Vec<(String, TextStyle)>, D::Error>
where
D: Deserializer<'de>,
{
let mut result = Vec::<(String, TextStyle)>::new();
let mut result = Self {
highlights: Vec::<(String, HighlightStyle)>::new(),
default_style: syntax_data
.remove("default")
.ok_or_else(|| de::Error::custom("must specify a default color in syntax theme"))?,
};
let syntax_data: HashMap<String, TextStyle> = Deserialize::deserialize(deserializer)?;
for (key, style) in syntax_data {
match result.binary_search_by(|(needle, _)| needle.cmp(&key)) {
Ok(i) | Err(i) => {
result.insert(i, (key, style));
for (key, style) in syntax_data {
match result
.highlights
.binary_search_by(|(needle, _)| needle.cmp(&key))
{
Ok(i) | Err(i) => {
result.highlights.insert(i, (key, style));
}
}
}
}
Ok(result)
Ok(result)
}
}
+7 -8
View File
@@ -1,4 +1,4 @@
use super::Theme;
use super::SyntaxTheme;
use std::sync::Arc;
#[derive(Clone, Debug)]
@@ -10,7 +10,7 @@ pub struct HighlightId(pub u32);
const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
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
// key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name.
@@ -19,7 +19,7 @@ impl HighlightMap {
.iter()
.map(|capture_name| {
theme
.syntax
.highlights
.iter()
.enumerate()
.filter_map(|(i, (key, _))| {
@@ -68,9 +68,9 @@ mod tests {
#[test]
fn test_highlight_map() {
let theme = Theme {
name: "test".into(),
syntax: [
let theme = SyntaxTheme::new(
Default::default(),
[
("function", Color::from_u32(0x100000ff)),
("function.method", Color::from_u32(0x200000ff)),
("function.async", Color::from_u32(0x300000ff)),
@@ -81,8 +81,7 @@ mod tests {
.iter()
.map(|(name, color)| (name.to_string(), (*color).into()))
.collect(),
..Default::default()
};
);
let capture_names = &[
"function.special".to_string(),
+15 -9
View File
@@ -1,5 +1,5 @@
use anyhow::{anyhow, Context, Result};
use gpui::AssetSource;
use gpui::{fonts, AssetSource, FontCache};
use json::{Map, Value};
use parking_lot::Mutex;
use serde_json as json;
@@ -11,6 +11,7 @@ pub struct ThemeRegistry {
assets: Box<dyn AssetSource>,
themes: Mutex<HashMap<String, Arc<Theme>>>,
theme_data: Mutex<HashMap<String, Arc<Value>>>,
font_cache: Arc<FontCache>,
}
#[derive(Default)]
@@ -38,11 +39,12 @@ enum Key {
}
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 {
assets: Box::new(source),
themes: Default::default(),
theme_data: Default::default(),
font_cache,
})
}
@@ -69,7 +71,10 @@ impl ThemeRegistry {
}
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();
let theme = Arc::new(theme);
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 {
use super::*;
use crate::{assets::Assets, theme::DEFAULT_THEME_NAME};
use gpui::MutableAppContext;
use rand::{prelude::StdRng, Rng};
#[test]
fn test_bundled_themes() {
let registry = ThemeRegistry::new(Assets);
#[gpui::test]
fn test_bundled_themes(cx: &mut MutableAppContext) {
let registry = ThemeRegistry::new(Assets, cx.font_cache().clone());
let mut has_default_theme = false;
for theme_name in registry.list() {
let theme = registry.get(&theme_name).unwrap();
@@ -528,8 +534,8 @@ mod tests {
assert!(has_default_theme);
}
#[test]
fn test_theme_extension() {
#[gpui::test]
fn test_theme_extension(cx: &mut MutableAppContext) {
let assets = TestAssets(&[
(
"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();
assert_eq!(
theme_data.as_ref(),
+6 -14
View File
@@ -204,13 +204,7 @@ impl ThemeSelector {
if self.matches.is_empty() {
let settings = self.settings.borrow();
return Container::new(
Label::new(
"No matches".into(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&settings.theme.selector.label)
.boxed(),
Label::new("No matches".into(), settings.theme.selector.label.clone()).boxed(),
)
.with_margin_top(6.0)
.named("empty matches");
@@ -247,14 +241,12 @@ impl ThemeSelector {
let container = Container::new(
Label::new(
theme_match.string.clone(),
settings.ui_font_family,
settings.ui_font_size,
if index == self.selected_index {
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())
.boxed(),
)
+8 -8
View File
@@ -1018,7 +1018,7 @@ mod tests {
use crate::{
editor::{Editor, Insert},
fs::FakeFs,
test::{build_app_state, temp_tree},
test::{temp_tree, test_app_state},
worktree::WorktreeHandle,
};
use serde_json::json;
@@ -1027,7 +1027,7 @@ mod tests {
#[gpui::test]
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!({
"a": {
"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));
workspace
@@ -1204,7 +1204,7 @@ mod tests {
fs.insert_file("/dir1/a.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);
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
@@ -1273,7 +1273,7 @@ mod tests {
"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));
workspace
.update(&mut cx, |workspace, cx| {
@@ -1318,7 +1318,7 @@ mod tests {
#[gpui::test]
async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
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));
workspace
.update(&mut cx, |workspace, cx| {
@@ -1417,7 +1417,7 @@ mod tests {
async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) {
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));
let window_id = *cx.window_ids().first().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));
workspace
.update(&mut cx, |workspace, cx| {
+5 -7
View File
@@ -208,14 +208,12 @@ impl Pane {
Align::new(
Label::new(
title,
settings.ui_font_family,
settings.ui_font_size,
if is_active {
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(),