From ad9712db7034308b0c27bf2c6fdc08963c96fa8d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 12:26:18 -0600 Subject: [PATCH 01/18] Move EditorStyle into editor module --- zed/src/editor.rs | 48 +++++++++++++++++++++++++++++++++++++-- zed/src/editor/element.rs | 6 +++-- zed/src/theme.rs | 45 +----------------------------------- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 3157b0cd2fbb28010d13d2b6ab5da4ec4ba35f6c..8926d5afd5074e5cc34563e0ee571fca3d958a0d 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -5,7 +5,7 @@ pub mod movement; use crate::{ settings::{HighlightId, Settings}, - theme::{EditorStyle, Theme}, + theme::Theme, time::ReplicaId, util::{post_inc, Bias}, workspace, @@ -20,7 +20,7 @@ use gpui::{ action, color::Color, font_cache::FamilyId, - fonts::Properties as FontProperties, + fonts::{HighlightStyle, Properties as FontProperties}, geometry::vector::Vector2F, keymap::Binding, text_layout::{self, RunStyle}, @@ -278,6 +278,26 @@ pub enum EditorMode { Full, } +#[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, Copy, Default, Deserialize)] +pub struct SelectionStyle { + pub cursor: Color, + pub selection: Color, +} + pub struct Editor { handle: WeakViewHandle, buffer: ModelHandle, @@ -2569,6 +2589,30 @@ impl Snapshot { } } +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(), + } + } +} + fn compute_scroll_position( snapshot: &DisplayMapSnapshot, mut scroll_position: Vector2F, diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 8ab6c52cc5fcf9d15027a0940fbb08cbb6c2414a..2dec968d78d67de9d9e30c20e6a7fd455915ab4a 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -1,5 +1,7 @@ -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, +}; +use crate::time::ReplicaId; use gpui::{ color::Color, geometry::{ diff --git a/zed/src/theme.rs b/zed/src/theme.rs index a96945fecc1011d9c1de9ef941560f863350491d..cf89173d8b847c106e5337ea7abddfd5530b78a7 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,20 +159,6 @@ 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)] @@ -181,12 +168,6 @@ pub struct InputEditorStyle { 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 +185,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 { From 606aa148a609f273d3cf10c76260cc35d792f3b4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 13:20:12 -0600 Subject: [PATCH 02/18] Require a build_style callback to be passed to Editor on construction We're going to use this to control the text style, so it really doesn't make sense to allow an editor to be constructed without it. --- server/src/rpc.rs | 4 +- zed/src/chat_panel.rs | 13 +++-- zed/src/editor.rs | 104 +++++++++++++++++++++----------------- zed/src/file_finder.rs | 12 +++-- zed/src/theme_selector.rs | 12 +++-- 5 files changed, 87 insertions(+), 58 deletions(-) diff --git a/server/src/rpc.rs b/server/src/rpc.rs index debd982366c7a4b9d1339963612d9e101dfcff0f..caabd6e5a3309407d6713b1ceed0c45ede6ac0f2 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1121,7 +1121,9 @@ 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, |_| Default::default(), cx) + }); buffer_a .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1) .await; 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 8926d5afd5074e5cc34563e0ee571fca3d958a0d..fe4677db5f10b4afe4e883cd45ef094ba183d817 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -310,7 +310,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, @@ -344,9 +344,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 } @@ -354,10 +358,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 } @@ -365,6 +370,16 @@ impl Editor { pub fn for_buffer( buffer: ModelHandle, settings: watch::Receiver, + build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, + cx: &mut ViewContext, + ) -> Self { + 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 display_map = @@ -396,7 +411,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, @@ -410,14 +425,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() } @@ -2648,10 +2655,7 @@ 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); EditorElement::new(self.handle.clone(), style).boxed() } @@ -2703,8 +2707,12 @@ 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 |_| settings.borrow().theme.editor.clone(), + cx, + ) } } @@ -2741,10 +2749,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) } @@ -2787,7 +2799,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); editor.update(cx, |view, cx| { @@ -2855,7 +2867,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -2889,7 +2901,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -2935,7 +2947,7 @@ mod tests { let settings = settings::test(&cx).1; let (_, editor) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings.clone(), cx) + Editor::for_buffer(buffer.clone(), settings.clone(), |_| Default::default(), cx) }); let layouts = editor.update(cx, |editor, cx| { @@ -2981,7 +2993,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -3049,7 +3061,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) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }); buffer.update(cx, |buffer, cx| { @@ -3126,7 +3138,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) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }); assert_eq!('ⓐ'.len_utf8(), 3); @@ -3184,7 +3196,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) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges(&[empty_range(0, "ⓐⓑⓒⓓⓔ".len())], cx) @@ -3215,7 +3227,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3360,7 +3372,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3554,7 +3566,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -3616,7 +3628,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -3652,7 +3664,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -3682,7 +3694,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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3708,7 +3720,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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], cx) @@ -3727,7 +3739,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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3756,7 +3768,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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3784,7 +3796,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.fold_ranges( @@ -3884,7 +3896,7 @@ mod tests { let settings = settings::test(&cx).1; let view = cx .add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, cx) + Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) }) .1; @@ -4018,7 +4030,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_all(&SelectAll, cx); @@ -4034,7 +4046,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.select_display_ranges( @@ -4082,7 +4094,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { view.fold_ranges( @@ -4152,7 +4164,7 @@ mod tests { 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) + Editor::for_buffer(buffer, settings, |_| Default::default(), cx) }); view.update(cx, |view, cx| { @@ -4339,7 +4351,9 @@ 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| { + Editor::for_buffer(buffer, settings.clone(), |_| Default::default(), cx) + }); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) .await; 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_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) From c21b754c4c197e8a888182acb094fd9a0fd427a4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 13:23:42 -0600 Subject: [PATCH 03/18] Make placeholder text style optional --- zed/src/editor.rs | 18 ++++++++++-------- zed/src/theme.rs | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/zed/src/editor.rs b/zed/src/editor.rs index fe4677db5f10b4afe4e883cd45ef094ba183d817..15a312ce5f22122a94b5c9fa862e28e2dfdfb924 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -282,7 +282,7 @@ pub enum EditorMode { pub struct EditorStyle { pub text: HighlightStyle, #[serde(default)] - pub placeholder_text: HighlightStyle, + pub placeholder_text: Option, pub background: Color, pub selection: SelectionStyle, pub gutter_background: Color, @@ -2468,7 +2468,7 @@ impl Snapshot { .skip(rows.start as usize) .take(rows.len()); let font_id = font_cache - .select_font(self.font_family, &style.placeholder_text.font_properties)?; + .select_font(self.font_family, &style.placeholder_text().font_properties)?; return Ok(placeholder_lines .into_iter() .map(|line| { @@ -2479,7 +2479,7 @@ impl Snapshot { line.len(), RunStyle { font_id, - color: style.placeholder_text.color, + color: style.placeholder_text().color, underline: false, }, )], @@ -2596,6 +2596,12 @@ impl Snapshot { } } +impl EditorStyle { + fn placeholder_text(&self) -> &HighlightStyle { + self.placeholder_text.as_ref().unwrap_or(&self.text) + } +} + impl Default for EditorStyle { fn default() -> Self { Self { @@ -2604,11 +2610,7 @@ impl Default for EditorStyle { font_properties: Default::default(), underline: false, }, - placeholder_text: HighlightStyle { - color: Color::from_u32(0x00ff00ff), - font_properties: Default::default(), - underline: false, - }, + placeholder_text: None, background: Default::default(), gutter_background: Default::default(), active_line_background: Default::default(), diff --git a/zed/src/theme.rs b/zed/src/theme.rs index cf89173d8b847c106e5337ea7abddfd5530b78a7..52e1b0d0a8478e63ab507e38a506a5421f69efd9 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -164,7 +164,8 @@ pub struct InputEditorStyle { #[serde(flatten)] pub container: ContainerStyle, pub text: HighlightStyle, - pub placeholder_text: HighlightStyle, + #[serde(default)] + pub placeholder_text: Option, pub selection: SelectionStyle, } From a1f0693599eac138efa5bcc79c92f24b9d01ac32 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 14:12:38 -0600 Subject: [PATCH 04/18] Specify full TextStyles in EditorStyle --- gpui/src/fonts.rs | 10 +++ server/src/rpc.rs | 9 ++- zed/assets/themes/_base.toml | 10 +-- zed/src/editor.rs | 126 ++++++++++++++++------------------- zed/src/theme.rs | 10 ++- 5 files changed, 86 insertions(+), 79 deletions(-) diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 96248c167577326cb74ed3ee6c1d7934f385f362..3d3ff1efb6897d6a544ace1f78b11c8985b775da 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -126,6 +126,16 @@ impl TextStyle { } } +impl From for HighlightStyle { + fn from(other: TextStyle) -> Self { + Self { + color: other.color, + font_properties: other.font_properties, + underline: other.underline, + } + } +} + impl HighlightStyle { fn from_json(json: HighlightStyleJson) -> Self { let font_properties = properties_from_json(json.weight, json.italic); diff --git a/server/src/rpc.rs b/server/src/rpc.rs index caabd6e5a3309407d6713b1ceed0c45ede6ac0f2..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}, @@ -1122,7 +1122,12 @@ mod tests { // 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, |_| Default::default(), 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) 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/editor.rs b/zed/src/editor.rs index 15a312ce5f22122a94b5c9fa862e28e2dfdfb924..8be376e987c632b5f6941c23939e3d761d7e2101 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -20,7 +20,7 @@ use gpui::{ action, color::Color, font_cache::FamilyId, - fonts::{HighlightStyle, Properties as FontProperties}, + fonts::{Properties as FontProperties, TextStyle}, geometry::vector::Vector2F, keymap::Binding, text_layout::{self, RunStyle}, @@ -280,9 +280,9 @@ pub enum EditorMode { #[derive(Clone, Deserialize)] pub struct EditorStyle { - pub text: HighlightStyle, + pub text: TextStyle, #[serde(default)] - pub placeholder_text: Option, + pub placeholder_text: Option, pub background: Color, pub selection: SelectionStyle, pub gutter_background: Color, @@ -2520,7 +2520,7 @@ impl Snapshot { .theme .syntax .highlight_style(style_ix) - .unwrap_or(style.text.clone()); + .unwrap_or(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 @@ -2597,17 +2597,19 @@ impl Snapshot { } impl EditorStyle { - fn placeholder_text(&self) -> &HighlightStyle { - self.placeholder_text.as_ref().unwrap_or(&self.text) - } -} - -impl Default for EditorStyle { - fn default() -> Self { + #[cfg(any(test, feature ="test-support"))] + pub fn test(font_cache: &FontCache) -> Self { + let font_family_name = "Monaco"; + let font_properties = Default::default(); + let family_id = font_cache.load_family(&[font_family_name]).unwrap(); + let font_id = font_cache.select_font(family_id, &font_properties).unwrap(); Self { - text: HighlightStyle { + text: TextStyle { + font_family_name: font_family_name.into(), + font_id, + font_size: 14., color: Color::from_u32(0xff0000ff), - font_properties: Default::default(), + font_properties, underline: false, }, placeholder_text: None, @@ -2620,6 +2622,10 @@ impl Default for EditorStyle { guest_selections: Default::default(), } } + + fn placeholder_text(&self) -> &TextStyle { + self.placeholder_text.as_ref().unwrap_or(&self.text) + } } fn compute_scroll_position( @@ -2800,9 +2806,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, |_| Default::default(), 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); @@ -2868,9 +2873,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, |_| Default::default(), 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); @@ -2902,9 +2905,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, |_| Default::default(), 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); @@ -2949,7 +2950,7 @@ mod tests { let settings = settings::test(&cx).1; let (_, editor) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings.clone(), |_| Default::default(), cx) + build_editor(buffer, settings.clone(), cx) }); let layouts = editor.update(cx, |editor, cx| { @@ -2995,7 +2996,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { @@ -3063,7 +3064,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, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }); buffer.update(cx, |buffer, cx| { @@ -3140,7 +3141,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, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }); assert_eq!('ⓐ'.len_utf8(), 3); @@ -3198,7 +3199,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, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { view.select_display_ranges(&[empty_range(0, "ⓐⓑⓒⓓⓔ".len())], cx) @@ -3228,9 +3229,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3373,9 +3372,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3567,9 +3564,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, |_| Default::default(), 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); @@ -3630,7 +3625,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { @@ -3666,7 +3661,7 @@ mod tests { }); let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }); view.update(cx, |view, cx| { @@ -3695,9 +3690,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3721,9 +3714,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, |_| Default::default(), 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(); @@ -3740,9 +3731,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3769,9 +3758,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -3797,9 +3784,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -3898,7 +3883,7 @@ mod tests { let settings = settings::test(&cx).1; let view = cx .add_window(Default::default(), |cx| { - Editor::for_buffer(buffer.clone(), settings, |_| Default::default(), cx) + build_editor(buffer.clone(), settings, cx) }) .1; @@ -4031,9 +4016,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, |_| Default::default(), 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!( @@ -4047,9 +4030,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( &[ @@ -4095,9 +4076,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, |_| Default::default(), cx) - }); + let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.fold_ranges( vec![ @@ -4165,9 +4144,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, |_| Default::default(), 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) @@ -4353,9 +4330,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(), |_| Default::default(), 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; @@ -4493,6 +4468,19 @@ 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 { + Editor::for_buffer( + buffer, + settings, + move |cx| EditorStyle::test(cx.font_cache()), + cx, + ) + } } trait RangeExt { diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 52e1b0d0a8478e63ab507e38a506a5421f69efd9..4458183e6dde3ef563cdd3fac1c60d56ce996553 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -163,9 +163,9 @@ pub struct ContainedLabel { pub struct InputEditorStyle { #[serde(flatten)] pub container: ContainerStyle, - pub text: HighlightStyle, + pub text: TextStyle, #[serde(default)] - pub placeholder_text: Option, + pub placeholder_text: Option, pub selection: SelectionStyle, } @@ -196,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(), } } } From 4f0c9a3e317a9a51981a9fc1c6da7a40409f6a90 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 14:43:19 -0600 Subject: [PATCH 05/18] Build workspace editor TextStyle from font fields in settings We'll specify values in the theme but we'll only end up using the color for these editors. --- gpui/src/font_cache.rs | 8 ++++---- gpui/src/fonts.rs | 7 +++++-- zed/src/editor.rs | 37 +++++++++++++++++++++++++++++++------ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/gpui/src/font_cache.rs b/gpui/src/font_cache.rs index 3c11b9659cb26441ab7746bac6a6f306fdbcef42..ddb7c9c28801ee490e03c8bd43c17189742dcf40 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); diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 3d3ff1efb6897d6a544ace1f78b11c8985b775da..9df36464f6c065206d4538129c3dbe1192281282 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, diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 8be376e987c632b5f6941c23939e3d761d7e2101..3bdb200226f0d9222891c6e7220ca9e6a0cf440e 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -2597,15 +2597,18 @@ impl Snapshot { } impl EditorStyle { - #[cfg(any(test, feature ="test-support"))] + #[cfg(any(test, feature = "test-support"))] pub fn test(font_cache: &FontCache) -> Self { - let font_family_name = "Monaco"; + let font_family_name = Arc::from("Monaco"); let font_properties = Default::default(); - let family_id = font_cache.load_family(&[font_family_name]).unwrap(); - let font_id = font_cache.select_font(family_id, &font_properties).unwrap(); + 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_name.into(), + font_family_name, + font_family_id, font_id, font_size: 14., color: Color::from_u32(0xff0000ff), @@ -2718,7 +2721,29 @@ impl workspace::Item for Buffer { Editor::for_buffer( handle, settings.clone(), - move |_| settings.borrow().theme.editor.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, ) } From f13a3544fcaf4ab87e88fa3f7d81be2fac9f643e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 16:43:43 -0600 Subject: [PATCH 06/18] Move editor layout code into element Now that most of the layout code is based on the EditorStyle struct, I think it makes more sense to put it in the element. --- gpui/src/font_cache.rs | 18 +- gpui/src/fonts.rs | 16 ++ zed/src/editor.rs | 313 ++++---------------------------- zed/src/editor/display_map.rs | 2 +- zed/src/editor/element.rs | 331 +++++++++++++++++++++++++++------- 5 files changed, 330 insertions(+), 350 deletions(-) diff --git a/gpui/src/font_cache.rs b/gpui/src/font_cache.rs index ddb7c9c28801ee490e03c8bd43c17189742dcf40..c0255a7af5f251b9828e4788dba6443f30efcc5b 100644 --- a/gpui/src/font_cache.rs +++ b/gpui/src/font_cache.rs @@ -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 9df36464f6c065206d4538129c3dbe1192281282..3ec8aad9626bf78e4beb79709b89e525be679ad9 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -127,6 +127,22 @@ 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 { diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 3bdb200226f0d9222891c6e7220ca9e6a0cf440e..af7f0e4db339246a96a9e3e21dc48c19222b2ce3 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -4,7 +4,7 @@ mod element; pub mod movement; use crate::{ - settings::{HighlightId, Settings}, + settings::Settings, theme::Theme, time::ReplicaId, util::{post_inc, Bias}, @@ -17,15 +17,10 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, - color::Color, - font_cache::FamilyId, - fonts::{Properties as FontProperties, TextStyle}, - 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, font_cache::FamilyId, 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 +29,6 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, - collections::BTreeMap, - fmt::Write, iter::FromIterator, mem, ops::{Range, RangeInclusive}, @@ -2328,263 +2321,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().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 { - 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) { @@ -2598,7 +2388,7 @@ impl Snapshot { impl EditorStyle { #[cfg(any(test, feature = "test-support"))] - pub fn test(font_cache: &FontCache) -> Self { + 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(); @@ -2966,33 +2756,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| { - build_editor(buffer, 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| { diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 16d4da79b4c6446fa02f42c10469297dc397ccb0..cdfc17f46c28f84c14336630142c501e2bb5835c 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -8,8 +8,8 @@ use gpui::{Entity, ModelContext, ModelHandle}; use postage::watch; 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, diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 2dec968d78d67de9d9e30c20e6a7fd455915ab4a..0912265099191d2aacabaf95bfdaec7d0124c01a 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -1,7 +1,8 @@ use super::{ DisplayPoint, Editor, EditorMode, EditorStyle, Insert, Scroll, Select, SelectPhase, Snapshot, + MAX_LINE_LEN, }; -use crate::time::ReplicaId; +use crate::{theme::HighlightId, time::ReplicaId}; use gpui::{ color::Color, geometry::{ @@ -11,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, }; @@ -20,6 +21,7 @@ use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, collections::{BTreeMap, HashMap}, + fmt::Write, ops::Range, }; @@ -376,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 { @@ -392,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| { @@ -490,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); + 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(); } - Ok(layouts) => { - for line in &layouts { - if line.width() > max_visible_line_width { - max_visible_line_width = line.width(); - } - } - - layouts - } - }; + } let mut layout = LayoutState { size, @@ -544,6 +675,7 @@ impl Element for EditorElement { overscroll, text_offset, snapshot, + style: self.style.clone(), active_rows, line_layouts, line_number_layouts, @@ -553,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, ); @@ -661,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, @@ -674,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( @@ -695,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 { @@ -866,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); + } +} From 68039b9d482be2de595ca057485b9b2a78ba80c4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 16 Sep 2021 16:46:35 -0600 Subject: [PATCH 07/18] Remove font_family and font_size from editor::Snapshot We'll rely on the style struct instead. --- zed/src/editor.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/zed/src/editor.rs b/zed/src/editor.rs index af7f0e4db339246a96a9e3e21dc48c19222b2ce3..c27d91d3404d211ce43263cbc0896494c2c89258 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -17,10 +17,9 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, color::Color, font_cache::FamilyId, fonts::TextStyle, geometry::vector::Vector2F, + action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, - ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, - WeakViewHandle, + ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, WeakViewHandle, }; use postage::watch; use serde::{Deserialize, Serialize}; @@ -318,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, @@ -436,8 +433,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) From 42bf88b52a2355f53eadc5cf26d85ccd2f9eb04a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 Sep 2021 15:39:19 -0700 Subject: [PATCH 08/18] Base soft wrapping on TextStyle instead of Settings --- gpui/src/app.rs | 10 ++ zed/src/editor.rs | 55 +++++++-- zed/src/editor/display_map.rs | 159 +++++++++++++++---------- zed/src/editor/display_map/wrap_map.rs | 87 +++++--------- zed/src/editor/movement.rs | 17 ++- 5 files changed, 191 insertions(+), 137 deletions(-) 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/zed/src/editor.rs b/zed/src/editor.rs index c27d91d3404d211ce43263cbc0896494c2c89258..1b0eb4852b8e528c03adcaac7aaaec9498f18b5e 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -17,9 +17,9 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, - keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, - ModelHandle, MutableAppContext, RenderContext, Task, 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}; @@ -372,8 +372,17 @@ impl Editor { build_style: Rc EditorStyle>>, cx: &mut ViewContext, ) -> Self { - let display_map = - cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.clone(), None, cx)); + 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) @@ -2452,6 +2461,9 @@ impl Entity for Editor { impl View for Editor { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { 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() } @@ -4257,12 +4269,33 @@ mod tests { settings: watch::Receiver, cx: &mut ViewContext, ) -> Editor { - Editor::for_buffer( - buffer, - settings, - move |cx| EditorStyle::test(cx.font_cache()), - cx, - ) + 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) } } diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index cdfc17f46c28f84c14336630142c501e2bb5835c..e4b8cc0886603f1257bac097445a08db7f0b9871 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -2,10 +2,9 @@ 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; use wrap_map::WrapMap; @@ -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/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(), From 9a9c8aec3f0342f1c1cec7f25f2af0c25e273304 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 Sep 2021 16:12:03 -0700 Subject: [PATCH 09/18] Fix deadlock when handling incoming RPC messages We need to drop the lock on the rpc::ClientState when handling an incoming messages in case those message handlers attempt to interact with the client and grab the lock. --- zed/src/rpc.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zed/src/rpc.rs b/zed/src/rpc.rs index fe1dde4ffba2d2579fcfe036cb3b162126fa1847..d4315a44d42f7de2b40a07eeaabefd7d1994f7c8 100644 --- a/zed/src/rpc.rs +++ b/zed/src/rpc.rs @@ -371,11 +371,11 @@ impl Client { if let Some(extract_entity_id) = state.entity_id_extractors.get(&message.payload_type_id()) { + let payload_type_id = message.payload_type_id(); let entity_id = (extract_entity_id)(message.as_ref()); - if let Some(handler) = state - .model_handlers - .get_mut(&(message.payload_type_id(), entity_id)) - { + let handler_key = (payload_type_id, entity_id); + if let Some(mut handler) = state.model_handlers.remove(&handler_key) { + drop(state); // Avoid deadlocks if the handler interacts with rpc::Client let start_time = Instant::now(); log::info!("RPC client message {}", message.payload_type_name()); (handler)(message, &mut cx); @@ -383,6 +383,10 @@ impl Client { "RPC message handled. duration:{:?}", start_time.elapsed() ); + this.state + .write() + .model_handlers + .insert(handler_key, handler); } else { log::info!("unhandled message {}", message.payload_type_name()); } From 9691267dc8e7377086b2c5ebe7ab5e16fc6e65d9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 Sep 2021 16:17:47 -0700 Subject: [PATCH 10/18] Only blink local cursors --- gpui/src/presenter.rs | 8 ++++++++ zed/src/editor.rs | 14 +++++++------- zed/src/editor/element.rs | 3 ++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index b2fa59b848aa2bb3ca53c9e5565104a6186bb1dc..2062397e9e6547dbe4ea9083485a8b8c0a519475 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -286,6 +286,14 @@ impl<'a> PaintContext<'a> { } } +impl<'a> Deref for PaintContext<'a> { + type Target = AppContext; + + fn deref(&self) -> &Self::Target { + self.app + } +} + pub struct EventContext<'a> { rendered_views: &'a mut HashMap, dispatched_actions: Vec, diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 1b0eb4852b8e528c03adcaac7aaaec9498f18b5e..98f0b4a0231e8d2cfba9eab21f2a88d72ff602e0 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -305,7 +305,7 @@ pub struct Editor { build_style: Rc EditorStyle>>, settings: watch::Receiver, focused: bool, - cursors_visible: bool, + show_local_cursors: bool, blink_epoch: usize, blinking_paused: bool, mode: EditorMode, @@ -416,7 +416,7 @@ impl Editor { autoscroll_requested: false, settings, focused: false, - cursors_visible: false, + show_local_cursors: false, blink_epoch: 0, blinking_paused: false, mode: EditorMode::Full, @@ -2253,7 +2253,7 @@ impl Editor { } fn pause_cursor_blinking(&mut self, cx: &mut ViewContext) { - self.cursors_visible = true; + self.show_local_cursors = true; cx.notify(); let epoch = self.next_blink_epoch(); @@ -2278,7 +2278,7 @@ impl Editor { fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext) { if epoch == self.blink_epoch && self.focused && !self.blinking_paused { - self.cursors_visible = !self.cursors_visible; + self.show_local_cursors = !self.show_local_cursors; cx.notify(); let epoch = self.next_blink_epoch(); @@ -2295,8 +2295,8 @@ impl Editor { } } - pub fn cursors_visible(&self) -> bool { - self.cursors_visible + pub fn show_local_cursors(&self) -> bool { + self.show_local_cursors } fn on_buffer_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { @@ -2483,7 +2483,7 @@ impl View for Editor { fn on_blur(&mut self, cx: &mut ViewContext) { self.focused = false; - self.cursors_visible = false; + self.show_local_cursors = false; self.buffer.update(cx, |buffer, cx| { buffer.set_active_selection_set(None, cx).unwrap(); }); diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 0912265099191d2aacabaf95bfdaec7d0124c01a..7c65b1a22d19d498ffd6f58df4db81a78d5fdb82 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -269,6 +269,7 @@ impl EditorElement { let view = self.view(cx.app); let settings = self.view(cx.app).settings.borrow(); let theme = &settings.theme.editor; + let local_replica_id = view.replica_id(cx); let scroll_position = layout.snapshot.scroll_position(); let start_row = scroll_position.y() as u32; let scroll_top = scroll_position.y() * layout.line_height; @@ -338,7 +339,7 @@ impl EditorElement { selection.paint(bounds, cx.scene); } - if view.cursors_visible() { + if view.show_local_cursors() || *replica_id != local_replica_id { let cursor_position = selection.end; if (start_row..end_row).contains(&cursor_position.row()) { let cursor_row_layout = From b5c76ccc95d6459e2d9f9da22d2af2d8be96c040 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 Sep 2021 16:45:09 -0700 Subject: [PATCH 11/18] Render close icons on all tabs when tab bar is hovered --- zed/src/workspace/pane.rs | 69 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index c0cd6bb9fd7e47141cc633a55cb9eeb00e48ca81..f4d409ae5df489295d61633a124fc1fe193e8b94 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -185,13 +185,13 @@ impl Pane { theme.workspace.tab.label.text.font_size, ); - let mut row = Flex::row(); - for (ix, item) in self.items.iter().enumerate() { - let is_active = ix == self.active_item; + enum Tabs {} + let tabs = MouseEventHandler::new::(0, cx, |mouse_state, cx| { + let mut row = Flex::row(); + for (ix, item) in self.items.iter().enumerate() { + let is_active = ix == self.active_item; - enum Tab {} - row.add_child( - MouseEventHandler::new::(item.id(), cx, |mouse_state, cx| { + row.add_child({ let mut title = item.title(cx); if title.len() > MAX_TAB_TITLE_LEN { let mut truncated_len = MAX_TAB_TITLE_LEN; @@ -276,7 +276,7 @@ impl Pane { ) .with_child( Align::new( - ConstrainedBox::new(if is_active || mouse_state.hovered { + ConstrainedBox::new(if mouse_state.hovered { let item_id = item.id(); enum TabCloseButton {} let icon = Svg::new("icons/x.svg"); @@ -316,36 +316,37 @@ impl Pane { }) .boxed() }) - .boxed(), - ) - } + } - // Ensure there's always a minimum amount of space after the last tab, - // so that the tab's border doesn't abut the window's border. - let mut border = Border::bottom(1.0, Color::default()); - border.color = theme.workspace.tab.container.border.color; - - row.add_child( - ConstrainedBox::new( - Container::new(Empty::new().boxed()) - .with_border(border) - .boxed(), - ) - .with_min_width(20.) - .named("fixed-filler"), - ); + // Ensure there's always a minimum amount of space after the last tab, + // so that the tab's border doesn't abut the window's border. + let mut border = Border::bottom(1.0, Color::default()); + border.color = theme.workspace.tab.container.border.color; - row.add_child( - Expanded::new( - 0.0, - Container::new(Empty::new().boxed()) - .with_border(border) - .boxed(), - ) - .named("filler"), - ); + row.add_child( + ConstrainedBox::new( + Container::new(Empty::new().boxed()) + .with_border(border) + .boxed(), + ) + .with_min_width(20.) + .named("fixed-filler"), + ); + + row.add_child( + Expanded::new( + 0.0, + Container::new(Empty::new().boxed()) + .with_border(border) + .boxed(), + ) + .named("filler"), + ); + + row.boxed() + }); - ConstrainedBox::new(row.boxed()) + ConstrainedBox::new(tabs.boxed()) .with_height(line_height + 16.) .named("tabs") } From 928779154e484a6f476901d461eb7737c21dd249 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 17 Sep 2021 16:59:46 -0700 Subject: [PATCH 12/18] Tweak spacing so tab close buttons feel more balanced --- zed/assets/themes/_base.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index f8f059487a38f9ee5cfc89d76f0f0ec50f9d8a58..57f25eb26fc331311a2dc93536554471ec549602 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -18,7 +18,7 @@ width = 16 [workspace.tab] text = "$text.2" -padding = { left = 10, right = 10 } +padding = { left = 12, right = 12 } icon_width = 8 spacing = 10 icon_close = "$text.2.color" From c7e2b6dacb541686d2f297e9c9a6e244044eff48 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 18 Sep 2021 10:37:32 -0700 Subject: [PATCH 13/18] Expand the hit area area around tab close icons --- gpui/src/elements/container.rs | 11 +++++++++++ gpui/src/elements/mouse_event_handler.rs | 3 ++- zed/src/workspace/pane.rs | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/gpui/src/elements/container.rs b/gpui/src/elements/container.rs index eeda6f206d139c210259744f9604fb30f90d1fae..abbafe4d034e76926ff79f794a286a24a727d927 100644 --- a/gpui/src/elements/container.rs +++ b/gpui/src/elements/container.rs @@ -348,6 +348,17 @@ enum Spacing { }, } +impl Padding { + pub fn uniform(padding: f32) -> Self { + Self { + top: padding, + left: padding, + bottom: padding, + right: padding, + } + } +} + impl ToJson for Padding { fn to_json(&self) -> serde_json::Value { let mut value = json!({}); diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs index 3b28409f9fa7ee67d90401bb40357ca012558b24..2cc01c3080f22a7d3587dd4750879ee8287dc5a8 100644 --- a/gpui/src/elements/mouse_event_handler.rs +++ b/gpui/src/elements/mouse_event_handler.rs @@ -116,7 +116,8 @@ impl Element for MouseEventHandler { let hit_bounds = RectF::from_points( bounds.origin() - vec2f(self.padding.left, self.padding.top), bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom), - ); + ) + .round_out(); self.state.update(cx, |state, cx| match event { Event::MouseMoved { diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index f4d409ae5df489295d61633a124fc1fe193e8b94..31ec57354ad131f8542f211c5ee88cfb2520ad8f 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -292,6 +292,7 @@ impl Pane { } }, ) + .with_padding(Padding::uniform(4.)) .with_cursor_style(CursorStyle::PointingHand) .on_click(move |cx| { cx.dispatch_action(CloseItem(item_id)) From af99d0ef42e6f16232e6eefec8d43e679e0362f7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 18 Sep 2021 11:46:22 -0700 Subject: [PATCH 14/18] Attempt to assign a language when a new buffer is saved --- zed/src/editor.rs | 5 +++++ zed/src/editor/buffer.rs | 13 ++++++++++++- zed/src/worktree.rs | 7 +++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 98f0b4a0231e8d2cfba9eab21f2a88d72ff602e0..25403d3aae44c3ef22c735700f80bfd1ea46f0dd 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -4,6 +4,7 @@ mod element; pub mod movement; use crate::{ + language::Language, settings::Settings, theme::Theme, time::ReplicaId, @@ -449,6 +450,10 @@ impl Editor { } } + pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc> { + self.buffer.read(cx).language() + } + pub fn set_placeholder_text( &mut self, placeholder_text: impl Into>, diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 97e0202cec4a6295e478b2078ad5d393df22ab50..43e5693bd43b2eaf6edbae60ef026648881e6827 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -714,9 +714,16 @@ impl Buffer { path: impl Into>, cx: &mut ModelContext, ) -> Task> { + let path = path.into(); let handle = cx.handle(); let text = self.visible_text.clone(); let version = self.version.clone(); + + if let Some(language) = worktree.read(cx).languages().select_language(&path).cloned() { + self.language = Some(language); + self.reparse(cx); + } + let save_as = worktree.update(cx, |worktree, cx| { worktree .as_local_mut() @@ -871,7 +878,11 @@ impl Buffer { cx.spawn(move |this, mut cx| async move { let new_tree = parse_task.await; this.update(&mut cx, move |this, cx| { - let parse_again = this.version > parsed_version; + let language_changed = + this.language.as_ref().map_or(true, |curr_language| { + !Arc::ptr_eq(curr_language, &language) + }); + let parse_again = this.version > parsed_version || language_changed; *this.syntax_tree.lock() = Some(SyntaxTree { tree: new_tree, dirty: false, diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 7b2fea91756c42a5cb20b05a0314d1a40e61029a..f952bef22f44d1a7c420eb97e3ea13922e62078c 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -268,6 +268,13 @@ impl Worktree { } } + pub fn languages(&self) -> &Arc { + match self { + Worktree::Local(worktree) => &worktree.languages, + Worktree::Remote(worktree) => &worktree.languages, + } + } + pub fn snapshot(&self) -> Snapshot { match self { Worktree::Local(worktree) => worktree.snapshot(), From 9e6c54ba0cf804bf3d0a414fd4ccc8688fae4a06 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 19 Sep 2021 17:33:46 -0700 Subject: [PATCH 15/18] Test language assignment when new buffers are saved --- zed/src/editor/buffer.rs | 4 ++++ zed/src/workspace.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 43e5693bd43b2eaf6edbae60ef026648881e6827..82aa10d4383e40157b8550403b5ec230178dac35 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -801,6 +801,10 @@ impl Buffer { cx.emit(Event::FileHandleChanged); } + pub fn language(&self) -> Option<&Arc> { + self.language.as_ref() + } + pub fn parse_count(&self) -> usize { self.parse_count } diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index ff3666e0de077cc8667716482f2479ba220e0825..1a83c358760fc43755a853068e18c6b40df5cc59 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -1476,7 +1476,7 @@ mod tests { }); cx.simulate_new_path_selection(|parent_dir| { assert_eq!(parent_dir, dir.path()); - Some(parent_dir.join("the-new-name")) + Some(parent_dir.join("the-new-name.rs")) }); cx.read(|cx| { assert!(editor.is_dirty(cx)); @@ -1489,8 +1489,10 @@ mod tests { .await; cx.read(|cx| { assert!(!editor.is_dirty(cx)); - assert_eq!(editor.title(cx), "the-new-name"); + assert_eq!(editor.title(cx), "the-new-name.rs"); }); + // The language is assigned based on the path + editor.read_with(&cx, |editor, cx| assert!(editor.language(cx).is_some())); // Edit the file and save it again. This time, there is no filename prompt. editor.update(&mut cx, |editor, cx| { @@ -1504,7 +1506,7 @@ mod tests { editor .condition(&cx, |editor, cx| !editor.is_dirty(cx)) .await; - cx.read(|cx| assert_eq!(editor.title(cx), "the-new-name")); + cx.read(|cx| assert_eq!(editor.title(cx), "the-new-name.rs")); // Open the same newly-created file in another pane item. The new editor should reuse // the same buffer. @@ -1512,7 +1514,7 @@ mod tests { workspace.open_new_file(&OpenNew(app_state.clone()), cx); workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); assert!(workspace - .open_entry((tree.id(), Path::new("the-new-name").into()), cx) + .open_entry((tree.id(), Path::new("the-new-name.rs").into()), cx) .is_none()); }); let editor2 = workspace.update(&mut cx, |workspace, cx| { From 1719d7da2a2674834c43753337f83cd8f172efa2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 19 Sep 2021 17:34:04 -0700 Subject: [PATCH 16/18] Suppress SVG loading errors in tests --- gpui/Cargo.toml | 3 +++ gpui/src/elements/svg.rs | 5 +++-- zed/Cargo.toml | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 7cc202e0b642dd6a22caa58b6008b8f9dee7b03c..be86a788d818bec2e565baff397fe0b39167dd04 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -4,6 +4,9 @@ edition = "2018" name = "gpui" version = "0.1.0" +[features] +test-support = [] + [dependencies] arrayvec = "0.7.1" async-task = "4.0.3" diff --git a/gpui/src/elements/svg.rs b/gpui/src/elements/svg.rs index 55e11a92531341262e69e79ff6365a2c20cfa5b6..3e93d3adae3cd721a2c6e4ff501bbda0bf6b5f86 100644 --- a/gpui/src/elements/svg.rs +++ b/gpui/src/elements/svg.rs @@ -47,8 +47,9 @@ impl Element for Svg { ); (size, Some(tree)) } - Err(error) => { - log::error!("{}", error); + Err(_error) => { + #[cfg(not(any(test, feature = "test-support")))] + log::error!("{}", _error); (constraint.min, None) } } diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 8d27fcd4c540b58544dd6223960b74409afcae86..17d42a04c95253c5f0943724a7cb52ea238c8faf 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -14,7 +14,7 @@ name = "Zed" path = "src/main.rs" [features] -test-support = ["tempdir", "zrpc/test-support"] +test-support = ["tempdir", "zrpc/test-support", "gpui/test-support"] [dependencies] anyhow = "1.0.38" @@ -69,6 +69,7 @@ serde_json = { version = "1.0.64", features = ["preserve_order"] } tempdir = { version = "0.3.7" } unindent = "0.1.7" zrpc = { path = "../zrpc", features = ["test-support"] } +gpui = { path = "../gpui", features = ["test-support"] } [package.metadata.bundle] icon = ["app-icon@2x.png", "app-icon.png"] From cb2d8bac1da9c04bb007dac3edb50401a6cf64ef Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Sep 2021 19:42:24 +0200 Subject: [PATCH 17/18] Use bullseye-slim for migration Dockerfile Closes #154 Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- Dockerfile.migrator | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.migrator b/Dockerfile.migrator index 99c21b2230387b2ab1d31016a5c9494d573d21c0..24b58da839e1ca09880574649694c33cb32b1fc1 100644 --- a/Dockerfile.migrator +++ b/Dockerfile.migrator @@ -1,12 +1,12 @@ # syntax = docker/dockerfile:1.2 -FROM rust as builder +FROM rust:1.55-bullseye as builder WORKDIR app RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=./target \ cargo install sqlx-cli --root=/app --target-dir=/app/target --version 0.5.7 -FROM debian:buster-slim as runtime +FROM debian:bullseye-slim as runtime RUN apt-get update; \ apt-get install -y --no-install-recommends libssl1.1 WORKDIR app From 4279451150704e5338ff1196a269bd9fb0eead8c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 20 Sep 2021 14:28:02 -0600 Subject: [PATCH 18/18] Fix language selection when saving new buffers as a single-file worktree Co-Authored-By: Max Brunsfeld --- zed/src/editor/buffer.rs | 10 ++++----- zed/src/language.rs | 23 +++++++++++--------- zed/src/workspace.rs | 46 +++++++++++++++++++++++++++++++++++++++- zed/src/worktree.rs | 27 ++++++++++++----------- 4 files changed, 78 insertions(+), 28 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 82aa10d4383e40157b8550403b5ec230178dac35..0ff7f9c148d418ee617c432b753d7201a5ea4312 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -719,11 +719,6 @@ impl Buffer { let text = self.visible_text.clone(); let version = self.version.clone(); - if let Some(language) = worktree.read(cx).languages().select_language(&path).cloned() { - self.language = Some(language); - self.reparse(cx); - } - let save_as = worktree.update(cx, |worktree, cx| { worktree .as_local_mut() @@ -734,6 +729,11 @@ impl Buffer { cx.spawn(|this, mut cx| async move { save_as.await.map(|new_file| { this.update(&mut cx, |this, cx| { + if let Some(language) = new_file.select_language(cx) { + this.language = Some(language); + this.reparse(cx); + } + let mtime = new_file.mtime; this.file = Some(new_file); this.did_save(version, mtime, cx); diff --git a/zed/src/language.rs b/zed/src/language.rs index bcea6c25adf3122f24976293c7efdc4c4d222ace..0297909efd646fef8f1ca24983e051bbd0b8c1cb 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -35,6 +35,10 @@ pub struct LanguageRegistry { } impl Language { + pub fn name(&self) -> &str { + self.config.name.as_str() + } + pub fn highlight_map(&self) -> HighlightMap { self.highlight_map.lock().clone() } @@ -133,27 +137,26 @@ mod tests { // matching file extension assert_eq!( - registry.select_language("zed/lib.rs").map(get_name), + registry.select_language("zed/lib.rs").map(|l| l.name()), Some("Rust") ); assert_eq!( - registry.select_language("zed/lib.mk").map(get_name), + registry.select_language("zed/lib.mk").map(|l| l.name()), Some("Make") ); // matching filename assert_eq!( - registry.select_language("zed/Makefile").map(get_name), + registry.select_language("zed/Makefile").map(|l| l.name()), Some("Make") ); // matching suffix that is not the full file extension or filename - assert_eq!(registry.select_language("zed/cars").map(get_name), None); - assert_eq!(registry.select_language("zed/a.cars").map(get_name), None); - assert_eq!(registry.select_language("zed/sumk").map(get_name), None); - - fn get_name(language: &Arc) -> &str { - language.config.name.as_str() - } + assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None); + assert_eq!( + registry.select_language("zed/a.cars").map(|l| l.name()), + None + ); + assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None); } } diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 1a83c358760fc43755a853068e18c6b40df5cc59..e69d9b2bb4f25b199c12f539771111e4a1c5c055 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -1466,6 +1466,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert!(!editor.is_dirty(cx.as_ref())); assert_eq!(editor.title(cx.as_ref()), "untitled"); + assert!(editor.language(cx).is_none()); editor.insert(&Insert("hi".into()), cx); assert!(editor.is_dirty(cx.as_ref())); }); @@ -1492,7 +1493,9 @@ mod tests { assert_eq!(editor.title(cx), "the-new-name.rs"); }); // The language is assigned based on the path - editor.read_with(&cx, |editor, cx| assert!(editor.language(cx).is_some())); + editor.read_with(&cx, |editor, cx| { + assert_eq!(editor.language(cx).unwrap().name(), "Rust") + }); // Edit the file and save it again. This time, there is no filename prompt. editor.update(&mut cx, |editor, cx| { @@ -1530,6 +1533,47 @@ mod tests { }) } + #[gpui::test] + async fn test_setting_language_when_saving_as_single_file_worktree( + mut cx: gpui::TestAppContext, + ) { + let dir = TempDir::new("test-new-file").unwrap(); + let app_state = cx.update(test_app_state); + let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); + + // Create a new untitled buffer + let editor = workspace.update(&mut cx, |workspace, cx| { + workspace.open_new_file(&OpenNew(app_state.clone()), cx); + workspace + .active_item(cx) + .unwrap() + .to_any() + .downcast::() + .unwrap() + }); + + editor.update(&mut cx, |editor, cx| { + assert!(editor.language(cx).is_none()); + editor.insert(&Insert("hi".into()), cx); + assert!(editor.is_dirty(cx.as_ref())); + }); + + // Save the buffer. This prompts for a filename. + workspace.update(&mut cx, |workspace, cx| { + workspace.save_active_item(&Save, cx) + }); + cx.simulate_new_path_selection(|_| Some(dir.path().join("the-new-name.rs"))); + + editor + .condition(&cx, |editor, cx| !editor.is_dirty(cx)) + .await; + + // The language is assigned based on the path + editor.read_with(&cx, |editor, cx| { + assert_eq!(editor.language(cx).unwrap().name(), "Rust") + }); + } + #[gpui::test] async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { cx.update(init); diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index f952bef22f44d1a7c420eb97e3ea13922e62078c..f94269c6aaca0c366b277f3b3ccdf6491d83b862 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -6,7 +6,7 @@ use crate::{ fs::{self, Fs}, fuzzy, fuzzy::CharBag, - language::LanguageRegistry, + language::{Language, LanguageRegistry}, rpc::{self, proto}, time::{self, ReplicaId}, util::{Bias, TryFutureExt}, @@ -268,13 +268,6 @@ impl Worktree { } } - pub fn languages(&self) -> &Arc { - match self { - Worktree::Local(worktree) => &worktree.languages, - Worktree::Remote(worktree) => &worktree.languages, - } - } - pub fn snapshot(&self) -> Snapshot { match self { Worktree::Local(worktree) => worktree.snapshot(), @@ -784,7 +777,6 @@ impl LocalWorktree { } }); - let languages = self.languages.clone(); let path = Arc::from(path); cx.spawn(|this, mut cx| async move { if let Some(existing_buffer) = existing_buffer { @@ -793,8 +785,8 @@ impl LocalWorktree { let (file, contents) = this .update(&mut cx, |this, cx| this.as_local().unwrap().load(&path, cx)) .await?; - let language = languages.select_language(&path).cloned(); let buffer = cx.add_model(|cx| { + let language = file.select_language(cx); Buffer::from_history(0, History::new(contents.into()), Some(file), language, cx) }); this.update(&mut cx, |this, _| { @@ -1127,7 +1119,6 @@ impl RemoteWorktree { }); let rpc = self.rpc.clone(); - let languages = self.languages.clone(); let replica_id = self.replica_id; let remote_worktree_id = self.remote_id; let path = path.to_string_lossy().to_string(); @@ -1139,7 +1130,7 @@ impl RemoteWorktree { .read_with(&cx, |tree, _| tree.entry_for_path(&path).cloned()) .ok_or_else(|| anyhow!("file does not exist"))?; let file = File::new(entry.id, handle, entry.path, entry.mtime); - let language = languages.select_language(&path).cloned(); + let language = cx.read(|cx| file.select_language(cx)); let response = rpc .request(proto::OpenBuffer { worktree_id: remote_worktree_id as u64, @@ -1616,6 +1607,18 @@ impl File { self.worktree.read(cx).abs_path.join(&self.path) } + pub fn select_language(&self, cx: &AppContext) -> Option> { + let worktree = self.worktree.read(cx); + let mut full_path = PathBuf::new(); + full_path.push(worktree.root_name()); + full_path.push(&self.path); + let languages = match self.worktree.read(cx) { + Worktree::Local(worktree) => &worktree.languages, + Worktree::Remote(worktree) => &worktree.languages, + }; + languages.select_language(&full_path).cloned() + } + /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. pub fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option {