diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 4b18af06b4bdb0fef7c3fb47e819fa3153e6c8f2..ebe6c89a8ace2a965138658a8faaccab66b2f1ef 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -2335,6 +2335,16 @@ impl ReadModel for RenderContext<'_, V> { } } +impl UpdateModel for RenderContext<'_, V> { + fn update_model(&mut self, handle: &ModelHandle, update: F) -> S + where + T: Entity, + F: FnOnce(&mut T, &mut ModelContext) -> S, + { + self.app.update_model(handle, update) + } +} + impl AsRef for ViewContext<'_, M> { fn as_ref(&self) -> &AppContext { &self.app.cx diff --git a/gpui/src/font_cache.rs b/gpui/src/font_cache.rs index 3c11b9659cb26441ab7746bac6a6f306fdbcef42..c0255a7af5f251b9828e4788dba6443f30efcc5b 100644 --- a/gpui/src/font_cache.rs +++ b/gpui/src/font_cache.rs @@ -17,7 +17,7 @@ use std::{ pub struct FamilyId(usize); struct Family { - name: String, + name: Arc, font_ids: Vec, } @@ -49,7 +49,7 @@ impl FontCache { })) } - pub fn family_name(&self, family_id: FamilyId) -> Result { + pub fn family_name(&self, family_id: FamilyId) -> Result> { self.0 .read() .families @@ -62,7 +62,7 @@ impl FontCache { for name in names { let state = self.0.upgradable_read(); - if let Some(ix) = state.families.iter().position(|f| f.name == *name) { + if let Some(ix) = state.families.iter().position(|f| f.name.as_ref() == *name) { return Ok(FamilyId(ix)); } @@ -81,7 +81,7 @@ impl FontCache { } state.families.push(Family { - name: String::from(*name), + name: Arc::from(*name), font_ids, }); return Ok(family_id); @@ -141,8 +141,8 @@ impl FontCache { pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F { let bounding_box = self.metric(font_id, |m| m.bounding_box); - let width = self.scale_metric(bounding_box.width(), font_id, font_size); - let height = self.scale_metric(bounding_box.height(), font_id, font_size); + let width = bounding_box.width() * self.em_scale(font_id, font_size); + let height = bounding_box.height() * self.em_scale(font_id, font_size); vec2f(width, height) } @@ -154,28 +154,28 @@ impl FontCache { glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap(); bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap(); } - self.scale_metric(bounds.width(), font_id, font_size) + bounds.width() * self.em_scale(font_id, font_size) } pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 { let height = self.metric(font_id, |m| m.bounding_box.height()); - self.scale_metric(height, font_id, font_size) + (height * self.em_scale(font_id, font_size)).ceil() } pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 { - self.scale_metric(self.metric(font_id, |m| m.cap_height), font_id, font_size) + self.metric(font_id, |m| m.cap_height) * self.em_scale(font_id, font_size) } pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 { - self.scale_metric(self.metric(font_id, |m| m.ascent), font_id, font_size) + self.metric(font_id, |m| m.ascent) * self.em_scale(font_id, font_size) } pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 { - self.scale_metric(self.metric(font_id, |m| -m.descent), font_id, font_size) + self.metric(font_id, |m| -m.descent) * self.em_scale(font_id, font_size) } - pub fn scale_metric(&self, metric: f32, font_id: FontId, font_size: f32) -> f32 { - metric * font_size / self.metric(font_id, |m| m.units_per_em as f32) + pub fn em_scale(&self, font_id: FontId, font_size: f32) -> f32 { + font_size / self.metric(font_id, |m| m.units_per_em as f32) } pub fn line_wrapper(self: &Arc, font_id: FontId, font_size: f32) -> LineWrapperHandle { diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 96248c167577326cb74ed3ee6c1d7934f385f362..3ec8aad9626bf78e4beb79709b89e525be679ad9 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -1,5 +1,6 @@ use crate::{ color::Color, + font_cache::FamilyId, json::{json, ToJson}, text_layout::RunStyle, FontCache, @@ -22,6 +23,7 @@ pub type GlyphId = u32; pub struct TextStyle { pub color: Color, pub font_family_name: Arc, + pub font_family_id: FamilyId, pub font_id: FontId, pub font_size: f32, pub font_properties: Properties, @@ -85,11 +87,12 @@ impl TextStyle { font_cache: &FontCache, ) -> anyhow::Result { 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)?; + let font_family_id = font_cache.load_family(&[&font_family_name])?; + let font_id = font_cache.select_font(font_family_id, &font_properties)?; Ok(Self { color, font_family_name, + font_family_id, font_id, font_size, font_properties, @@ -124,6 +127,32 @@ impl TextStyle { } }) } + + pub fn line_height(&self, font_cache: &FontCache) -> f32 { + font_cache.line_height(self.font_id, self.font_size) + } + + pub fn em_width(&self, font_cache: &FontCache) -> f32 { + font_cache.em_width(self.font_id, self.font_size) + } + + pub fn descent(&self, font_cache: &FontCache) -> f32 { + font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache) + } + + fn em_scale(&self, font_cache: &FontCache) -> f32 { + font_cache.em_scale(self.font_id, self.font_size) + } +} + +impl From for HighlightStyle { + fn from(other: TextStyle) -> Self { + Self { + color: other.color, + font_properties: other.font_properties, + underline: other.underline, + } + } } impl HighlightStyle { diff --git a/server/src/rpc.rs b/server/src/rpc.rs index debd982366c7a4b9d1339963612d9e101dfcff0f..698414851e60fadd301b60cfc19ac31ad425ef6f 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1037,7 +1037,7 @@ mod tests { }; use zed::{ channel::{Channel, ChannelDetails, ChannelList}, - editor::{Editor, Insert}, + editor::{Editor, EditorStyle, Insert}, fs::{FakeFs, Fs as _}, language::LanguageRegistry, rpc::{self, Client, Credentials, EstablishConnectionError}, @@ -1121,7 +1121,14 @@ mod tests { .unwrap(); // Create a selection set as client B and see that selection set as client A. - let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, settings, cx)); + let editor_b = cx_b.add_view(window_b, |cx| { + Editor::for_buffer( + buffer_b, + settings, + |cx| EditorStyle::test(cx.font_cache()), + cx, + ) + }); buffer_a .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1) .await; diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index 1a2999379c4e46f82514349cddeee38ecb697467..f8f059487a38f9ee5cfc89d76f0f0ec50f9d8a58 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -107,8 +107,8 @@ shadow = { offset = [0, 2], blur = 16, color = "$shadow.0" } background = "$surface.1" corner_radius = 6 padding = { left = 8, right = 8, top = 7, bottom = 7 } -text = "$text.0.color" -placeholder_text = "$text.2.color" +text = "$text.0" +placeholder_text = "$text.2" selection = "$selection.host" border = { width = 1, color = "$border.0" } @@ -132,8 +132,8 @@ border = { width = 1, color = "$border.0" } background = "$surface.1" corner_radius = 6 padding = { left = 16, right = 16, top = 7, bottom = 7 } -text = "$text.0.color" -placeholder_text = "$text.2.color" +text = "$text.0" +placeholder_text = "$text.2" selection = "$selection.host" border = { width = 1, color = "$border.0" } @@ -153,7 +153,7 @@ background = "$state.hover" text = "$text.0" [editor] -text = "$text.1.color" +text = "$text.1" background = "$surface.1" gutter_background = "$surface.1" active_line_background = "$state.active_line" diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index d7752b9a53e944b6e5c1776ab280fd3ea95f025b..5b07214efb363a4db206fc71fcd44ec94ee018c7 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -54,10 +54,15 @@ impl ChatPanel { cx: &mut ViewContext, ) -> Self { let input_editor = cx.add_view(|cx| { - Editor::auto_height(4, settings.clone(), cx).with_style({ - let settings = settings.clone(); - move |_| settings.borrow().theme.chat_panel.input_editor.as_editor() - }) + Editor::auto_height( + 4, + settings.clone(), + { + let settings = settings.clone(); + move |_| settings.borrow().theme.chat_panel.input_editor.as_editor() + }, + cx, + ) }); let channel_select = cx.add_view(|cx| { let channel_list = channel_list.clone(); diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 3157b0cd2fbb28010d13d2b6ab5da4ec4ba35f6c..1b0eb4852b8e528c03adcaac7aaaec9498f18b5e 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -4,8 +4,8 @@ mod element; pub mod movement; use crate::{ - settings::{HighlightId, Settings}, - theme::{EditorStyle, Theme}, + settings::Settings, + theme::Theme, time::ReplicaId, util::{post_inc, Bias}, workspace, @@ -17,15 +17,9 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, - color::Color, - font_cache::FamilyId, - fonts::Properties as FontProperties, - geometry::vector::Vector2F, - keymap::Binding, - text_layout::{self, RunStyle}, - AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle, - MutableAppContext, RenderContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle, + action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, keymap::Binding, + text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, + MutableAppContext, RenderContext, Task, View, ViewContext, WeakViewHandle, }; use postage::watch; use serde::{Deserialize, Serialize}; @@ -34,8 +28,6 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, - collections::BTreeMap, - fmt::Write, iter::FromIterator, mem, ops::{Range, RangeInclusive}, @@ -278,6 +270,26 @@ pub enum EditorMode { Full, } +#[derive(Clone, Deserialize)] +pub struct EditorStyle { + pub text: TextStyle, + #[serde(default)] + pub placeholder_text: Option, + pub background: Color, + pub selection: SelectionStyle, + pub gutter_background: Color, + pub active_line_background: Color, + pub line_number: Color, + pub line_number_active: Color, + pub guest_selections: Vec, +} + +#[derive(Clone, Copy, Default, Deserialize)] +pub struct SelectionStyle { + pub cursor: Color, + pub selection: Color, +} + pub struct Editor { handle: WeakViewHandle, buffer: ModelHandle, @@ -290,7 +302,7 @@ pub struct Editor { scroll_position: Vector2F, scroll_top_anchor: Anchor, autoscroll_requested: bool, - build_style: Option EditorStyle>>>, + build_style: Rc EditorStyle>>, settings: watch::Receiver, focused: bool, cursors_visible: bool, @@ -305,8 +317,6 @@ pub struct Snapshot { pub display_snapshot: DisplayMapSnapshot, pub placeholder_text: Option>, pub theme: Arc, - pub font_family: FamilyId, - pub font_size: f32, is_focused: bool, scroll_position: Vector2F, scroll_top_anchor: Anchor, @@ -324,9 +334,13 @@ struct ClipboardSelection { } impl Editor { - pub fn single_line(settings: watch::Receiver, cx: &mut ViewContext) -> Self { + pub fn single_line( + settings: watch::Receiver, + build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, + cx: &mut ViewContext, + ) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); - let mut view = Self::for_buffer(buffer, settings, cx); + let mut view = Self::for_buffer(buffer, settings, build_style, cx); view.mode = EditorMode::SingleLine; view } @@ -334,10 +348,11 @@ impl Editor { pub fn auto_height( max_lines: usize, settings: watch::Receiver, + build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, cx: &mut ViewContext, ) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); - let mut view = Self::for_buffer(buffer, settings, cx); + let mut view = Self::for_buffer(buffer, settings, build_style, cx); view.mode = EditorMode::AutoHeight { max_lines }; view } @@ -345,10 +360,29 @@ impl Editor { pub fn for_buffer( buffer: ModelHandle, settings: watch::Receiver, + build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, cx: &mut ViewContext, ) -> Self { - let display_map = - cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.clone(), None, cx)); + Self::new(buffer, settings, Rc::new(RefCell::new(build_style)), cx) + } + + fn new( + buffer: ModelHandle, + settings: watch::Receiver, + build_style: Rc EditorStyle>>, + cx: &mut ViewContext, + ) -> Self { + let style = build_style.borrow_mut()(cx); + let display_map = cx.add_model(|cx| { + DisplayMap::new( + buffer.clone(), + settings.borrow().tab_size, + style.text.font_id, + style.text.font_size, + None, + cx, + ) + }); cx.observe(&buffer, Self::on_buffer_changed).detach(); cx.subscribe(&buffer, Self::on_buffer_event).detach(); cx.observe(&display_map, Self::on_display_map_changed) @@ -376,7 +410,7 @@ impl Editor { next_selection_id, add_selections_state: None, select_larger_syntax_node_stack: Vec::new(), - build_style: None, + build_style, scroll_position: Vector2F::zero(), scroll_top_anchor: Anchor::min(), autoscroll_requested: false, @@ -390,14 +424,6 @@ impl Editor { } } - pub fn with_style( - mut self, - f: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, - ) -> Self { - self.build_style = Some(Rc::new(RefCell::new(f))); - self - } - pub fn replica_id(&self, cx: &AppContext) -> ReplicaId { self.buffer.read(cx).replica_id() } @@ -416,8 +442,6 @@ impl Editor { scroll_top_anchor: self.scroll_top_anchor.clone(), theme: settings.theme.clone(), placeholder_text: self.placeholder_text.clone(), - font_family: settings.buffer_font_family, - font_size: settings.buffer_font_size, is_focused: self .handle .upgrade(cx) @@ -2301,263 +2325,60 @@ impl Editor { } impl Snapshot { - pub fn scroll_position(&self) -> Vector2F { - compute_scroll_position( - &self.display_snapshot, - self.scroll_position, - &self.scroll_top_anchor, - ) + pub fn is_empty(&self) -> bool { + self.display_snapshot.is_empty() } - pub fn max_point(&self) -> DisplayPoint { - self.display_snapshot.max_point() + pub fn is_focused(&self) -> bool { + self.is_focused } - pub fn longest_row(&self) -> u32 { - self.display_snapshot.longest_row() + pub fn placeholder_text(&self) -> Option<&Arc> { + self.placeholder_text.as_ref() } - pub fn line_len(&self, display_row: u32) -> u32 { - self.display_snapshot.line_len(display_row) + pub fn buffer_row_count(&self) -> u32 { + self.display_snapshot.buffer_row_count() } - pub fn font_ascent(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - let ascent = font_cache.metric(font_id, |m| m.ascent); - font_cache.scale_metric(ascent, font_id, self.font_size) + pub fn buffer_rows(&self, start_row: u32) -> BufferRows { + self.display_snapshot.buffer_rows(start_row) } - pub fn font_descent(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - let descent = font_cache.metric(font_id, |m| m.descent); - font_cache.scale_metric(descent, font_id, self.font_size) + pub fn highlighted_chunks_for_rows( + &mut self, + display_rows: Range, + ) -> display_map::HighlightedChunks { + self.display_snapshot + .highlighted_chunks_for_rows(display_rows) } - pub fn line_height(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - font_cache.line_height(font_id, self.font_size).ceil() + pub fn theme(&self) -> &Arc { + &self.theme } - pub fn em_width(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - font_cache.em_width(font_id, self.font_size) + pub fn scroll_position(&self) -> Vector2F { + compute_scroll_position( + &self.display_snapshot, + self.scroll_position, + &self.scroll_top_anchor, + ) } - // TODO: Can we make this not return a result? - pub fn max_line_number_width( - &self, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - ) -> Result { - let font_size = self.font_size; - let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - let digit_count = (self.display_snapshot.buffer_row_count() as f32) - .log10() - .floor() as usize - + 1; - - Ok(layout_cache - .layout_str( - "1".repeat(digit_count).as_str(), - font_size, - &[( - digit_count, - RunStyle { - font_id, - color: Color::black(), - underline: false, - }, - )], - ) - .width()) + pub fn max_point(&self) -> DisplayPoint { + self.display_snapshot.max_point() } - pub fn layout_line_numbers( - &self, - rows: Range, - active_rows: &BTreeMap, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - theme: &Theme, - ) -> Result>> { - let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - - let mut layouts = Vec::with_capacity(rows.len()); - let mut line_number = String::new(); - for (ix, (buffer_row, soft_wrapped)) in self - .display_snapshot - .buffer_rows(rows.start) - .take((rows.end - rows.start) as usize) - .enumerate() - { - let display_row = rows.start + ix as u32; - let color = if active_rows.contains_key(&display_row) { - theme.editor.line_number_active - } else { - theme.editor.line_number - }; - if soft_wrapped { - layouts.push(None); - } else { - line_number.clear(); - write!(&mut line_number, "{}", buffer_row + 1).unwrap(); - layouts.push(Some(layout_cache.layout_str( - &line_number, - self.font_size, - &[( - line_number.len(), - RunStyle { - font_id, - color, - underline: false, - }, - )], - ))); - } - } - - Ok(layouts) + pub fn longest_row(&self) -> u32 { + self.display_snapshot.longest_row() } - pub fn layout_lines( - &mut self, - mut rows: Range, - style: &EditorStyle, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - ) -> Result> { - rows.end = cmp::min(rows.end, self.display_snapshot.max_point().row() + 1); - if rows.start >= rows.end { - return Ok(Vec::new()); - } - - // When the editor is empty and unfocused, then show the placeholder. - if self.display_snapshot.is_empty() && !self.is_focused { - let placeholder_lines = self - .placeholder_text - .as_ref() - .map_or("", AsRef::as_ref) - .split('\n') - .skip(rows.start as usize) - .take(rows.len()); - let font_id = font_cache - .select_font(self.font_family, &style.placeholder_text.font_properties)?; - return Ok(placeholder_lines - .into_iter() - .map(|line| { - layout_cache.layout_str( - line, - self.font_size, - &[( - line.len(), - RunStyle { - font_id, - color: style.placeholder_text.color, - underline: false, - }, - )], - ) - }) - .collect()); - } - - let mut prev_font_properties = FontProperties::new(); - let mut prev_font_id = font_cache - .select_font(self.font_family, &prev_font_properties) - .unwrap(); - - let mut layouts = Vec::with_capacity(rows.len()); - let mut line = String::new(); - let mut styles = Vec::new(); - let mut row = rows.start; - let mut line_exceeded_max_len = false; - let chunks = self - .display_snapshot - .highlighted_chunks_for_rows(rows.clone()); - - 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) { - for (ix, mut line_chunk) in chunk.split('\n').enumerate() { - if ix > 0 { - layouts.push(layout_cache.layout_str(&line, self.font_size, &styles)); - line.clear(); - styles.clear(); - row += 1; - line_exceeded_max_len = false; - if row == rows.end { - break 'outer; - } - } - - if !line_chunk.is_empty() && !line_exceeded_max_len { - let style = self - .theme - .syntax - .highlight_style(style_ix) - .unwrap_or(style.text.clone()); - // Avoid a lookup if the font properties match the previous ones. - let font_id = if style.font_properties == prev_font_properties { - prev_font_id - } else { - font_cache.select_font(self.font_family, &style.font_properties)? - }; - - if line.len() + line_chunk.len() > MAX_LINE_LEN { - let mut chunk_len = MAX_LINE_LEN - line.len(); - while !line_chunk.is_char_boundary(chunk_len) { - chunk_len -= 1; - } - line_chunk = &line_chunk[..chunk_len]; - line_exceeded_max_len = true; - } - - line.push_str(line_chunk); - styles.push(( - line_chunk.len(), - RunStyle { - font_id, - color: style.color, - underline: style.underline, - }, - )); - prev_font_id = font_id; - prev_font_properties = style.font_properties; - } - } - } - - Ok(layouts) + pub fn line_len(&self, display_row: u32) -> u32 { + self.display_snapshot.line_len(display_row) } - pub fn layout_line( - &self, - row: u32, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - ) -> Result { - let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - - let mut line = self.display_snapshot.line(row); - - if line.len() > MAX_LINE_LEN { - let mut len = MAX_LINE_LEN; - while !line.is_char_boundary(len) { - len -= 1; - } - line.truncate(len); - } - - Ok(layout_cache.layout_str( - &line, - self.font_size, - &[( - self.display_snapshot.line_len(row) as usize, - RunStyle { - font_id, - color: Color::black(), - underline: false, - }, - )], - )) + pub fn line(&self, display_row: u32) -> String { + self.display_snapshot.line(display_row) } pub fn prev_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) { @@ -2569,6 +2390,41 @@ impl Snapshot { } } +impl EditorStyle { + #[cfg(any(test, feature = "test-support"))] + pub fn test(font_cache: &gpui::FontCache) -> Self { + let font_family_name = Arc::from("Monaco"); + let font_properties = Default::default(); + let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap(); + let font_id = font_cache + .select_font(font_family_id, &font_properties) + .unwrap(); + Self { + text: TextStyle { + font_family_name, + font_family_id, + font_id, + font_size: 14., + color: Color::from_u32(0xff0000ff), + font_properties, + underline: false, + }, + placeholder_text: None, + background: Default::default(), + gutter_background: Default::default(), + active_line_background: Default::default(), + line_number: Default::default(), + line_number_active: Default::default(), + selection: Default::default(), + guest_selections: Default::default(), + } + } + + fn placeholder_text(&self) -> &TextStyle { + self.placeholder_text.as_ref().unwrap_or(&self.text) + } +} + fn compute_scroll_position( snapshot: &DisplayMapSnapshot, mut scroll_position: Vector2F, @@ -2604,10 +2460,10 @@ impl Entity for Editor { impl View for Editor { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let style = self - .build_style - .as_ref() - .map_or(Default::default(), |build| (build.borrow_mut())(cx)); + let style = self.build_style.borrow_mut()(cx); + self.display_map.update(cx, |map, cx| { + map.set_font(style.text.font_id, style.text.font_size, cx) + }); EditorElement::new(self.handle.clone(), style).boxed() } @@ -2659,8 +2515,34 @@ impl workspace::Item for Buffer { settings: watch::Receiver, cx: &mut ViewContext, ) -> Self::View { - Editor::for_buffer(handle, settings.clone(), cx) - .with_style(move |_| settings.borrow().theme.editor.clone()) + Editor::for_buffer( + handle, + settings.clone(), + move |cx| { + let settings = settings.borrow(); + let font_cache = cx.font_cache(); + let font_family_id = settings.buffer_font_family; + let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); + let font_properties = Default::default(); + let font_id = font_cache + .select_font(font_family_id, &font_properties) + .unwrap(); + let font_size = settings.buffer_font_size; + + let mut theme = settings.theme.editor.clone(); + theme.text = TextStyle { + color: theme.text.color, + font_family_name, + font_family_id, + font_id, + font_size, + font_properties, + underline: false, + }; + theme + }, + cx, + ) } } @@ -2697,10 +2579,14 @@ impl workspace::ItemView for Editor { where Self: Sized, { - let mut clone = Editor::for_buffer(self.buffer.clone(), self.settings.clone(), cx); + let mut clone = Editor::new( + self.buffer.clone(), + self.settings.clone(), + self.build_style.clone(), + cx, + ); clone.scroll_position = self.scroll_position; clone.scroll_top_anchor = self.scroll_top_anchor.clone(); - clone.build_style = self.build_style.clone(); Some(clone) } @@ -2742,9 +2628,8 @@ mod tests { 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::test(&cx).1; - let (_, editor) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, editor) = + cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, cx); @@ -2810,9 +2695,7 @@ mod tests { 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::test(&cx).1; - let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(2, 2), false, cx); @@ -2844,9 +2727,7 @@ mod tests { 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::test(&cx).1; - let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(3, 4), false, cx); @@ -2882,33 +2763,6 @@ mod tests { }); } - #[gpui::test] - fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) { - let layout_cache = TextLayoutCache::new(cx.platform().fonts()); - let font_cache = cx.font_cache().clone(); - - let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); - - let settings = settings::test(&cx).1; - let (_, editor) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings.clone(), cx) - }); - - let layouts = editor.update(cx, |editor, cx| { - editor - .snapshot(cx) - .layout_line_numbers( - 0..6, - &Default::default(), - &font_cache, - &layout_cache, - &settings.borrow().theme, - ) - .unwrap() - }); - assert_eq!(layouts.len(), 6); - } - #[gpui::test] fn test_fold(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| { @@ -2937,7 +2791,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { @@ -3005,7 +2859,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }); buffer.update(cx, |buffer, cx| { @@ -3082,7 +2936,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx)); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }); assert_eq!('ⓐ'.len_utf8(), 3); @@ -3140,7 +2994,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx)); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { view.select_display_ranges(&[empty_range(0, "ⓐⓑⓒⓓⓔ".len())], cx) @@ -3170,9 +3024,7 @@ mod tests { 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::test(&cx).1; - let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3315,9 +3167,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx)); let settings = settings::test(&cx).1; - let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3509,9 +3359,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx)); let settings = settings::test(&cx).1; - let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.set_wrap_width(130., cx); @@ -3572,7 +3420,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { @@ -3608,7 +3456,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { @@ -3637,9 +3485,7 @@ mod tests { fn test_delete_line(cx: &mut gpui::MutableAppContext) { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3663,9 +3509,7 @@ mod tests { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], cx) .unwrap(); @@ -3682,9 +3526,7 @@ mod tests { fn test_duplicate_line(cx: &mut gpui::MutableAppContext) { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3711,9 +3553,7 @@ mod tests { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3739,9 +3579,7 @@ mod tests { fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -3840,7 +3678,7 @@ mod tests { let settings = settings::test(&cx).1; let view = cx .add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + build_editor(buffer.clone(), settings, cx) }) .1; @@ -3973,9 +3811,7 @@ mod tests { fn test_select_all(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx)); let settings = settings::test(&cx).1; - let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer, settings, cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_all(&SelectAll, cx); assert_eq!( @@ -3989,9 +3825,7 @@ mod tests { fn test_select_line(cx: &mut gpui::MutableAppContext) { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -4037,9 +3871,7 @@ mod tests { fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -4107,9 +3939,7 @@ mod tests { fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) { 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) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)], cx) @@ -4295,7 +4125,7 @@ mod tests { let history = History::new(text.into()); Buffer::from_history(0, history, None, lang.cloned(), cx) }); - let (_, view) = cx.add_window(|cx| Editor::for_buffer(buffer, settings.clone(), cx)); + let (_, view) = cx.add_window(|cx| build_editor(buffer, settings.clone(), cx)); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) .await; @@ -4433,6 +4263,40 @@ mod tests { let point = DisplayPoint::new(row as u32, column as u32); point..point } + + fn build_editor( + buffer: ModelHandle, + settings: watch::Receiver, + cx: &mut ViewContext, + ) -> Editor { + let style = { + let font_cache = cx.font_cache(); + let settings = settings.borrow(); + EditorStyle { + text: TextStyle { + color: Default::default(), + font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(), + font_family_id: settings.buffer_font_family, + font_id: font_cache + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(), + font_size: settings.buffer_font_size, + font_properties: Default::default(), + underline: false, + }, + placeholder_text: None, + background: Default::default(), + selection: Default::default(), + gutter_background: Default::default(), + active_line_background: Default::default(), + line_number: Default::default(), + line_number_active: Default::default(), + guest_selections: Default::default(), + } + }; + + Editor::for_buffer(buffer, settings, move |_| style.clone(), cx) + } } trait RangeExt { diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 16d4da79b4c6446fa02f42c10469297dc397ccb0..e4b8cc0886603f1257bac097445a08db7f0b9871 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -2,14 +2,13 @@ mod fold_map; mod tab_map; mod wrap_map; -use super::{buffer, Anchor, Bias, Buffer, Point, Settings, ToOffset, ToPoint}; +use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint}; use fold_map::FoldMap; -use gpui::{Entity, ModelContext, ModelHandle}; -use postage::watch; +use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; use std::ops::Range; use tab_map::TabMap; -pub use wrap_map::BufferRows; use wrap_map::WrapMap; +pub use wrap_map::{BufferRows, HighlightedChunks}; pub struct DisplayMap { buffer: ModelHandle, @@ -25,13 +24,16 @@ impl Entity for DisplayMap { impl DisplayMap { pub fn new( buffer: ModelHandle, - settings: watch::Receiver, + tab_size: usize, + font_id: FontId, + font_size: f32, wrap_width: Option, cx: &mut ModelContext, ) -> Self { let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx); - let (tab_map, snapshot) = TabMap::new(snapshot, settings.borrow().tab_size); - let wrap_map = cx.add_model(|cx| WrapMap::new(snapshot, settings, wrap_width, cx)); + let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); + let wrap_map = + cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx)); cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); DisplayMap { buffer, @@ -85,6 +87,11 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); } + pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { + self.wrap_map + .update(cx, |map, cx| map.set_font(font_id, font_size, cx)); + } + pub fn set_wrap_width(&self, width: Option, cx: &mut ModelContext) -> bool { self.wrap_map .update(cx, |map, cx| map.set_wrap_width(width, cx)) @@ -367,12 +374,12 @@ mod tests { .unwrap_or(10); let font_cache = cx.font_cache().clone(); - let settings = Settings { - tab_size: rng.gen_range(1..=4), - buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), - buffer_font_size: 14.0, - ..cx.read(Settings::test) - }; + let tab_size = rng.gen_range(1..=4); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; let max_wrap_width = 300.0; let mut wrap_width = if rng.gen_bool(0.1) { None @@ -380,7 +387,7 @@ mod tests { Some(rng.gen_range(0.0..=max_wrap_width)) }; - log::info!("tab size: {}", settings.tab_size); + log::info!("tab size: {}", tab_size); log::info!("wrap width: {:?}", wrap_width); let buffer = cx.add_model(|cx| { @@ -388,9 +395,10 @@ mod tests { let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text, cx) }); - let settings = watch::channel_with(settings).1; - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx)); + let map = cx.add_model(|cx| { + DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx) + }); let (_observer, notifications) = Observer::new(&map, &mut cx); let mut fold_count = 0; @@ -529,26 +537,27 @@ mod tests { } #[gpui::test] - async fn test_soft_wraps(mut cx: gpui::TestAppContext) { + fn test_soft_wraps(cx: &mut MutableAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.foreground().forbid_parking(); let font_cache = cx.font_cache(); - let settings = Settings { - buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), - buffer_font_size: 12.0, - tab_size: 4, - ..cx.read(Settings::test) - }; + let tab_size = 4; + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 12.0; let wrap_width = Some(64.); let text = "one two three four five\nsix seven eight"; let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx)); - let (mut settings_tx, settings_rx) = watch::channel_with(settings); - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings_rx, wrap_width, cx)); + let map = cx.add_model(|cx| { + DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx) + }); - let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); + let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( snapshot.chunks_at(0).collect::(), "one two \nthree four \nfive\nsix seven \neight" @@ -592,23 +601,21 @@ mod tests { (DisplayPoint::new(2, 4), SelectionGoal::Column(10)) ); - buffer.update(&mut cx, |buffer, cx| { + buffer.update(cx, |buffer, cx| { let ix = buffer.text().find("seven").unwrap(); buffer.edit(vec![ix..ix], "and ", cx); }); - let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); + let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( snapshot.chunks_at(1).collect::(), "three four \nfive\nsix and \nseven eight" ); // Re-wrap on font size changes - settings_tx.borrow_mut().buffer_font_size += 3.; - - map.next_notification(&mut cx).await; + map.update(cx, |map, cx| map.set_font(font_id, font_size + 3., cx)); - let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); + let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( snapshot.chunks_at(1).collect::(), "three \nfour five\nsix and \nseven \neight" @@ -619,11 +626,16 @@ mod tests { fn test_chunks_at(cx: &mut gpui::MutableAppContext) { let text = sample_text(6, 6); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let settings = watch::channel_with(Settings { - tab_size: 4, - ..Settings::test(cx) + let tab_size = 4; + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + let map = cx.add_model(|cx| { + DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx) }); - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); buffer.update(cx, |buffer, cx| { buffer.edit( vec![ @@ -695,13 +707,16 @@ mod tests { }); buffer.condition(&cx, |buf, _| !buf.is_parsing()).await; - let settings = cx.update(|cx| { - watch::channel_with(Settings { - tab_size: 2, - ..Settings::test(cx) - }) - }); - let map = cx.add_model(|cx| DisplayMap::new(buffer, settings.1, None, cx)); + let tab_size = 2; + let font_cache = cx.font_cache(); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + + let map = + cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx)); assert_eq!( cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)), vec![ @@ -782,15 +797,16 @@ mod tests { buffer.condition(&cx, |buf, _| !buf.is_parsing()).await; let font_cache = cx.font_cache(); - let settings = cx.update(|cx| { - watch::channel_with(Settings { - tab_size: 4, - buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(), - buffer_font_size: 16.0, - ..Settings::test(cx) - }) - }); - let map = cx.add_model(|cx| DisplayMap::new(buffer, settings.1, Some(40.0), cx)); + + let tab_size = 4; + let family_id = font_cache.load_family(&["Courier"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 16.0; + + let map = cx + .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx)); assert_eq!( cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)), [ @@ -825,11 +841,17 @@ mod tests { let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n"; let display_text = "\n'a', 'α', '✋', '❎', '🍐'\n"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let settings = watch::channel_with(Settings { - tab_size: 4, - ..Settings::test(cx) + + let tab_size = 4; + let font_cache = cx.font_cache(); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + let map = cx.add_model(|cx| { + DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx) }); - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); let map = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!(map.text(), display_text); @@ -863,11 +885,17 @@ mod tests { fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) { let text = "✅\t\tα\nβ\t\n🏀β\t\tγ"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let settings = watch::channel_with(Settings { - tab_size: 4, - ..Settings::test(cx) + let tab_size = 4; + let font_cache = cx.font_cache(); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + + let map = cx.add_model(|cx| { + DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx) }); - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); let map = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!(map.text(), "✅ α\nβ \n🏀β γ"); assert_eq!( @@ -924,11 +952,16 @@ mod tests { #[gpui::test] fn test_max_point(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx)); - let settings = watch::channel_with(Settings { - tab_size: 4, - ..Settings::test(cx) + let tab_size = 4; + let font_cache = cx.font_cache(); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + let map = cx.add_model(|cx| { + DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx) }); - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); assert_eq!( map.update(cx, |map, cx| map.snapshot(cx)).max_point(), DisplayPoint::new(1, 11) diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index d648a052aab0c6fb4ffc626d803d47983568d3a7..86e01f7e5695f1e4aa4ae1ac7dfaedd170b5c6a8 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -2,14 +2,14 @@ use super::{ fold_map, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; -use crate::{editor::Point, settings::HighlightId, util::Bias, Settings}; +use crate::{editor::Point, settings::HighlightId, util::Bias}; use gpui::{ + fonts::FontId, sum_tree::{self, Cursor, SumTree}, text_layout::LineWrapper, Entity, ModelContext, Task, }; use lazy_static::lazy_static; -use postage::{prelude::Stream, watch}; use smol::future::yield_now; use std::{collections::VecDeque, ops::Range, time::Duration}; @@ -18,8 +18,7 @@ pub struct WrapMap { pending_edits: VecDeque<(TabSnapshot, Vec)>, wrap_width: Option, background_task: Option>, - _watch_settings: Task<()>, - settings: watch::Receiver, + font: (FontId, f32), } impl Entity for WrapMap { @@ -76,36 +75,17 @@ pub struct BufferRows<'a> { impl WrapMap { pub fn new( tab_snapshot: TabSnapshot, - settings: watch::Receiver, + font_id: FontId, + font_size: f32, wrap_width: Option, cx: &mut ModelContext, ) -> Self { - let _watch_settings = cx.spawn_weak({ - let mut prev_font = ( - settings.borrow().buffer_font_size, - settings.borrow().buffer_font_family, - ); - let mut settings = settings.clone(); - move |this, mut cx| async move { - while let Some(settings) = settings.recv().await { - if let Some(this) = this.upgrade(&cx) { - let font = (settings.buffer_font_size, settings.buffer_font_family); - if font != prev_font { - prev_font = font; - this.update(&mut cx, |this, cx| this.rewrap(cx)); - } - } - } - } - }); - let mut this = Self { + font: (font_id, font_size), wrap_width: None, pending_edits: Default::default(), snapshot: Snapshot::new(tab_snapshot), - settings, background_task: None, - _watch_settings, }; this.set_wrap_width(wrap_width, cx); @@ -128,6 +108,13 @@ impl WrapMap { self.snapshot.clone() } + pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { + if (font_id, font_size) != self.font { + self.font = (font_id, font_size); + self.rewrap(cx) + } + } + pub fn set_wrap_width(&mut self, wrap_width: Option, cx: &mut ModelContext) -> bool { if wrap_width == self.wrap_width { return false; @@ -144,15 +131,9 @@ impl WrapMap { if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); let font_cache = cx.font_cache().clone(); - let settings = self.settings.clone(); + let (font_id, font_size) = self.font; let task = cx.background().spawn(async move { - let mut line_wrapper = { - let settings = settings.borrow(); - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - font_cache.line_wrapper(font_id, settings.buffer_font_size) - }; + let mut line_wrapper = font_cache.line_wrapper(font_id, font_size); let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); new_snapshot @@ -222,15 +203,9 @@ impl WrapMap { let pending_edits = self.pending_edits.clone(); let mut snapshot = self.snapshot.clone(); let font_cache = cx.font_cache().clone(); - let settings = self.settings.clone(); + let (font_id, font_size) = self.font; let update_task = cx.background().spawn(async move { - let mut line_wrapper = { - let settings = settings.borrow(); - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - font_cache.line_wrapper(font_id, settings.buffer_font_size) - }; + let mut line_wrapper = font_cache.line_wrapper(font_id, font_size); for (tab_snapshot, edits) in pending_edits { snapshot @@ -950,13 +925,14 @@ mod tests { } else { Some(rng.gen_range(0.0..=1000.0)) }; - let settings = Settings { - tab_size: rng.gen_range(1..=4), - buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), - buffer_font_size: 14.0, - ..cx.read(Settings::test) - }; - log::info!("Tab size: {}", settings.tab_size); + let tab_size = rng.gen_range(1..=4); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + + log::info!("Tab size: {}", tab_size); log::info!("Wrap width: {:?}", wrap_width); let buffer = cx.add_model(|cx| { @@ -965,7 +941,7 @@ mod tests { Buffer::new(0, text, cx) }); let (mut fold_map, folds_snapshot) = cx.read(|cx| FoldMap::new(buffer.clone(), cx)); - let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), settings.tab_size); + let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); log::info!( "Unwrapped text (no folds): {:?}", buffer.read_with(&cx, |buf, _| buf.text()) @@ -976,16 +952,13 @@ mod tests { ); log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text()); - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - let mut line_wrapper = LineWrapper::new(font_id, settings.buffer_font_size, font_system); + let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system); let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); - let settings = watch::channel_with(settings).1; - let wrap_map = cx - .add_model(|cx| WrapMap::new(tabs_snapshot.clone(), settings.clone(), wrap_width, cx)); + let wrap_map = cx.add_model(|cx| { + WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx) + }); let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 8ab6c52cc5fcf9d15027a0940fbb08cbb6c2414a..0912265099191d2aacabaf95bfdaec7d0124c01a 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -1,5 +1,8 @@ -use super::{DisplayPoint, Editor, EditorMode, Insert, Scroll, Select, SelectPhase, Snapshot}; -use crate::{theme::EditorStyle, time::ReplicaId}; +use super::{ + DisplayPoint, Editor, EditorMode, EditorStyle, Insert, Scroll, Select, SelectPhase, Snapshot, + MAX_LINE_LEN, +}; +use crate::{theme::HighlightId, time::ReplicaId}; use gpui::{ color::Color, geometry::{ @@ -9,7 +12,7 @@ use gpui::{ }, json::{self, ToJson}, keymap::Keystroke, - text_layout::{self, TextLayoutCache}, + text_layout::{self, RunStyle, TextLayoutCache}, AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; @@ -18,6 +21,7 @@ use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, collections::{BTreeMap, HashMap}, + fmt::Write, ops::Range, }; @@ -374,6 +378,176 @@ impl EditorElement { cx.scene.pop_layer(); } + + fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 { + let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1; + + cx.text_layout_cache + .layout_str( + "1".repeat(digit_count).as_str(), + self.style.text.font_size, + &[( + digit_count, + RunStyle { + font_id: self.style.text.font_id, + color: Color::black(), + underline: false, + }, + )], + ) + .width() + } + + fn layout_line_numbers( + &self, + rows: Range, + active_rows: &BTreeMap, + snapshot: &Snapshot, + cx: &LayoutContext, + ) -> Vec> { + let mut layouts = Vec::with_capacity(rows.len()); + let mut line_number = String::new(); + for (ix, (buffer_row, soft_wrapped)) in snapshot + .buffer_rows(rows.start) + .take((rows.end - rows.start) as usize) + .enumerate() + { + let display_row = rows.start + ix as u32; + let color = if active_rows.contains_key(&display_row) { + self.style.line_number_active + } else { + self.style.line_number + }; + if soft_wrapped { + layouts.push(None); + } else { + line_number.clear(); + write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + layouts.push(Some(cx.text_layout_cache.layout_str( + &line_number, + self.style.text.font_size, + &[( + line_number.len(), + RunStyle { + font_id: self.style.text.font_id, + color, + underline: false, + }, + )], + ))); + } + } + + layouts + } + + fn layout_lines( + &mut self, + mut rows: Range, + snapshot: &mut Snapshot, + cx: &LayoutContext, + ) -> Vec { + rows.end = cmp::min(rows.end, snapshot.max_point().row() + 1); + if rows.start >= rows.end { + return Vec::new(); + } + + // When the editor is empty and unfocused, then show the placeholder. + if snapshot.is_empty() && !snapshot.is_focused() { + let placeholder_style = self.style.placeholder_text(); + let placeholder_text = snapshot.placeholder_text(); + let placeholder_lines = placeholder_text + .as_ref() + .map_or("", AsRef::as_ref) + .split('\n') + .skip(rows.start as usize) + .take(rows.len()); + return placeholder_lines + .map(|line| { + cx.text_layout_cache.layout_str( + line, + placeholder_style.font_size, + &[( + line.len(), + RunStyle { + font_id: placeholder_style.font_id, + color: placeholder_style.color, + underline: false, + }, + )], + ) + }) + .collect(); + } + + let mut prev_font_properties = self.style.text.font_properties.clone(); + let mut prev_font_id = self.style.text.font_id; + + let theme = snapshot.theme().clone(); + let mut layouts = Vec::with_capacity(rows.len()); + let mut line = String::new(); + let mut styles = Vec::new(); + let mut row = rows.start; + let mut line_exceeded_max_len = false; + let chunks = snapshot.highlighted_chunks_for_rows(rows.clone()); + + 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) { + for (ix, mut line_chunk) in chunk.split('\n').enumerate() { + if ix > 0 { + layouts.push(cx.text_layout_cache.layout_str( + &line, + self.style.text.font_size, + &styles, + )); + line.clear(); + styles.clear(); + row += 1; + line_exceeded_max_len = false; + if row == rows.end { + break 'outer; + } + } + + if !line_chunk.is_empty() && !line_exceeded_max_len { + let style = theme + .syntax + .highlight_style(style_ix) + .unwrap_or(self.style.text.clone().into()); + // Avoid a lookup if the font properties match the previous ones. + let font_id = if style.font_properties == prev_font_properties { + prev_font_id + } else { + cx.font_cache + .select_font(self.style.text.font_family_id, &style.font_properties) + .unwrap_or(self.style.text.font_id) + }; + + if line.len() + line_chunk.len() > MAX_LINE_LEN { + let mut chunk_len = MAX_LINE_LEN - line.len(); + while !line_chunk.is_char_boundary(chunk_len) { + chunk_len -= 1; + } + line_chunk = &line_chunk[..chunk_len]; + line_exceeded_max_len = true; + } + + line.push_str(line_chunk); + styles.push(( + line_chunk.len(), + RunStyle { + font_id, + color: style.color, + underline: style.underline, + }, + )); + prev_font_id = font_id; + prev_font_properties = style.font_properties; + } + } + } + + layouts + } } impl Element for EditorElement { @@ -390,30 +564,22 @@ impl Element for EditorElement { unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); } - let font_cache = &cx.font_cache; - let layout_cache = &cx.text_layout_cache; let snapshot = self.snapshot(cx.app); - let line_height = snapshot.line_height(font_cache); + let line_height = self.style.text.line_height(cx.font_cache); let gutter_padding; let gutter_width; if snapshot.mode == EditorMode::Full { - gutter_padding = snapshot.em_width(cx.font_cache); - match snapshot.max_line_number_width(cx.font_cache, cx.text_layout_cache) { - Err(error) => { - log::error!("error computing max line number width: {}", error); - return (size, None); - } - Ok(width) => gutter_width = width + gutter_padding * 2.0, - } + gutter_padding = self.style.text.em_width(cx.font_cache); + gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; } else { gutter_padding = 0.0; gutter_width = 0.0 }; let text_width = size.x() - gutter_width; - let text_offset = vec2f(-snapshot.font_descent(cx.font_cache), 0.); - let em_width = snapshot.em_width(font_cache); + let text_offset = vec2f(-self.style.text.descent(cx.font_cache), 0.); + let em_width = self.style.text.em_width(cx.font_cache); let overscroll = vec2f(em_width, 0.); let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width; let snapshot = self.update_view(cx.app, |view, cx| { @@ -488,51 +654,18 @@ impl Element for EditorElement { }); let line_number_layouts = if snapshot.mode == EditorMode::Full { - let settings = self - .view - .upgrade(cx.app) - .unwrap() - .read(cx.app) - .settings - .borrow(); - match snapshot.layout_line_numbers( - start_row..end_row, - &active_rows, - cx.font_cache, - cx.text_layout_cache, - &settings.theme, - ) { - Err(error) => { - log::error!("error laying out line numbers: {}", error); - return (size, None); - } - Ok(layouts) => layouts, - } + self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx) } else { Vec::new() }; let mut max_visible_line_width = 0.0; - let line_layouts = match snapshot.layout_lines( - start_row..end_row, - &self.style, - font_cache, - layout_cache, - ) { - Err(error) => { - log::error!("error laying out lines: {}", error); - return (size, None); - } - Ok(layouts) => { - for line in &layouts { - if line.width() > max_visible_line_width { - max_visible_line_width = line.width(); - } - } - - layouts + let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); + for line in &line_layouts { + if line.width() > max_visible_line_width { + max_visible_line_width = line.width(); } - }; + } let mut layout = LayoutState { size, @@ -542,6 +675,7 @@ impl Element for EditorElement { overscroll, text_offset, snapshot, + style: self.style.clone(), active_rows, line_layouts, line_number_layouts, @@ -551,15 +685,18 @@ impl Element for EditorElement { max_visible_line_width, }; + let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x(); + let scroll_width = layout.scroll_width(cx.text_layout_cache); + let max_glyph_width = self.style.text.em_width(&cx.font_cache); self.update_view(cx.app, |view, cx| { - let clamped = view.clamp_scroll_left(layout.scroll_max(font_cache, layout_cache).x()); + let clamped = view.clamp_scroll_left(scroll_max); let autoscrolled; if autoscroll_horizontally { autoscrolled = view.autoscroll_horizontally( start_row, layout.text_size.x(), - layout.scroll_width(font_cache, layout_cache), - layout.snapshot.em_width(font_cache), + scroll_width, + max_glyph_width, &layout.line_layouts, cx, ); @@ -659,6 +796,7 @@ pub struct LayoutState { gutter_size: Vector2F, gutter_padding: f32, text_size: Vector2F, + style: EditorStyle, snapshot: Snapshot, active_rows: BTreeMap, line_layouts: Vec, @@ -672,20 +810,16 @@ pub struct LayoutState { } impl LayoutState { - fn scroll_width(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> f32 { + fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 { let row = self.snapshot.longest_row(); - let longest_line_width = self - .snapshot - .layout_line(row, font_cache, layout_cache) - .unwrap() - .width(); + let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width(); longest_line_width.max(self.max_visible_line_width) + self.overscroll.x() } fn scroll_max(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> Vector2F { let text_width = self.text_size.x(); - let scroll_width = self.scroll_width(font_cache, layout_cache); - let em_width = self.snapshot.em_width(font_cache); + let scroll_width = self.scroll_width(layout_cache); + let em_width = self.style.text.em_width(font_cache); let max_row = self.snapshot.max_point().row(); vec2f( @@ -693,6 +827,36 @@ impl LayoutState { max_row.saturating_sub(1) as f32, ) } + + pub fn layout_line( + &self, + row: u32, + snapshot: &Snapshot, + layout_cache: &TextLayoutCache, + ) -> text_layout::Line { + let mut line = snapshot.line(row); + + if line.len() > MAX_LINE_LEN { + let mut len = MAX_LINE_LEN; + while !line.is_char_boundary(len) { + len -= 1; + } + line.truncate(len); + } + + layout_cache.layout_str( + &line, + self.style.text.font_size, + &[( + snapshot.line_len(row) as usize, + RunStyle { + font_id: self.style.text.font_id, + color: Color::black(), + underline: false, + }, + )], + ) + } } pub struct PaintState { @@ -864,3 +1028,42 @@ fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { delta.powf(1.2) / 300.0 } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + editor::{Buffer, Editor, EditorStyle}, + settings, + test::sample_text, + }; + + #[gpui::test] + fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) { + let font_cache = cx.font_cache().clone(); + let settings = settings::test(&cx).1; + let style = EditorStyle::test(&font_cache); + + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); + let (window_id, editor) = cx.add_window(Default::default(), |cx| { + Editor::for_buffer( + buffer, + settings.clone(), + { + let style = style.clone(); + move |_| style.clone() + }, + cx, + ) + }); + let element = EditorElement::new(editor.downgrade(), style); + + let layouts = editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let mut presenter = cx.build_presenter(window_id, 30.); + let mut layout_cx = presenter.build_layout_context(false, cx); + element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx) + }); + assert_eq!(layouts.len(), 6); + } +} diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index aec3932a7d9ad96154f44e95783e9d8a70801c61..8f5bc6f20a814536366f9077f1509d611dc1cf29 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -180,16 +180,21 @@ fn char_kind(c: char) -> CharKind { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor::{display_map::DisplayMap, Buffer}, - test::test_app_state, - }; + use crate::editor::{display_map::DisplayMap, Buffer}; #[gpui::test] fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) { - let settings = test_app_state(cx).settings.clone(); + let tab_size = 4; + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + 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, tab_size, font_id, font_size, None, cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)).unwrap(), diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 23bec79e0076046f3d483f9baa4972c7e418a2f1..8f3217b2e58521b6dcb9f45ac67e992d90c9b420 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -275,10 +275,14 @@ impl FileFinder { cx.observe(&workspace, Self::workspace_updated).detach(); let query_editor = cx.add_view(|cx| { - Editor::single_line(settings.clone(), cx).with_style({ - let settings = settings.clone(); - move |_| settings.borrow().theme.selector.input_editor.as_editor() - }) + Editor::single_line( + settings.clone(), + { + let settings = settings.clone(); + move |_| settings.borrow().theme.selector.input_editor.as_editor() + }, + cx, + ) }); cx.subscribe(&query_editor, Self::on_query_editor_event) .detach(); diff --git a/zed/src/theme.rs b/zed/src/theme.rs index a96945fecc1011d9c1de9ef941560f863350491d..4458183e6dde3ef563cdd3fac1c60d56ce996553 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -2,6 +2,7 @@ mod highlight_map; mod resolution; mod theme_registry; +use crate::editor::{EditorStyle, SelectionStyle}; use anyhow::Result; use gpui::{ color::Color, @@ -158,35 +159,16 @@ pub struct ContainedLabel { pub label: LabelStyle, } -#[derive(Clone, Deserialize)] -pub struct EditorStyle { - pub text: HighlightStyle, - #[serde(default)] - pub placeholder_text: HighlightStyle, - pub background: Color, - pub selection: SelectionStyle, - pub gutter_background: Color, - pub active_line_background: Color, - pub line_number: Color, - pub line_number_active: Color, - pub guest_selections: Vec, -} - #[derive(Clone, Deserialize)] pub struct InputEditorStyle { #[serde(flatten)] pub container: ContainerStyle, - pub text: HighlightStyle, - pub placeholder_text: HighlightStyle, + pub text: TextStyle, + #[serde(default)] + pub placeholder_text: Option, pub selection: SelectionStyle, } -#[derive(Clone, Copy, Default, Deserialize)] -pub struct SelectionStyle { - pub cursor: Color, - pub selection: Color, -} - impl SyntaxTheme { pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self { Self { highlights } @@ -204,30 +186,6 @@ impl SyntaxTheme { } } -impl Default for EditorStyle { - fn default() -> Self { - Self { - text: HighlightStyle { - color: Color::from_u32(0xff0000ff), - font_properties: Default::default(), - underline: false, - }, - placeholder_text: HighlightStyle { - color: Color::from_u32(0x00ff00ff), - font_properties: Default::default(), - underline: false, - }, - background: Default::default(), - gutter_background: Default::default(), - active_line_background: Default::default(), - line_number: Default::default(), - line_number_active: Default::default(), - selection: Default::default(), - guest_selections: Default::default(), - } - } -} - impl InputEditorStyle { pub fn as_editor(&self) -> EditorStyle { EditorStyle { @@ -238,7 +196,11 @@ impl InputEditorStyle { .background_color .unwrap_or(Color::transparent_black()), selection: self.selection, - ..Default::default() + gutter_background: Default::default(), + active_line_background: Default::default(), + line_number: Default::default(), + line_number_active: Default::default(), + guest_selections: Default::default(), } } } diff --git a/zed/src/theme_selector.rs b/zed/src/theme_selector.rs index 6a623b120e6a3fcdc5eb679b2fcdd22c18ba6ec4..4bb657d619a6d47e41cf3ed08c1564277fd6c8df 100644 --- a/zed/src/theme_selector.rs +++ b/zed/src/theme_selector.rs @@ -58,10 +58,14 @@ impl ThemeSelector { cx: &mut ViewContext, ) -> Self { let query_editor = cx.add_view(|cx| { - Editor::single_line(settings.clone(), cx).with_style({ - let settings = settings.clone(); - move |_| settings.borrow().theme.selector.input_editor.as_editor() - }) + Editor::single_line( + settings.clone(), + { + let settings = settings.clone(); + move |_| settings.borrow().theme.selector.input_editor.as_editor() + }, + cx, + ) }); cx.subscribe(&query_editor, Self::on_query_editor_event)