From 6865a42df9718498f136c03e512c9710e62ce494 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 11 Jan 2022 17:23:11 -0800 Subject: [PATCH 01/19] Show error+warning counts in project diagnostics tab Allow workspace items' tab contents to be arbitrary elements Co-Authored-By: Nathan Sobo --- Cargo.lock | 1 + crates/diagnostics/Cargo.toml | 1 + crates/diagnostics/src/diagnostics.rs | 52 ++++++++++++++++++++------- crates/editor/src/editor.rs | 19 ++++++++-- crates/editor/src/items.rs | 19 +++------- crates/file_finder/src/file_finder.rs | 10 +++++- crates/theme/src/theme.rs | 3 ++ crates/workspace/src/pane.rs | 48 ++++++++----------------- crates/workspace/src/workspace.rs | 14 +++++--- crates/zed/assets/icons/no.svg | 4 +++ crates/zed/assets/icons/warning.svg | 4 +-- crates/zed/assets/themes/_base.toml | 3 ++ crates/zed/src/zed.rs | 20 +++++++---- 13 files changed, 121 insertions(+), 77 deletions(-) create mode 100644 crates/zed/assets/icons/no.svg diff --git a/Cargo.lock b/Cargo.lock index 8c3174d68d40df5c364bee8327318476b8dd0fa7..24fe3a6d9dffcd5c7bf443d82aac43b79d277298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1412,6 +1412,7 @@ dependencies = [ "postage", "project", "serde_json", + "theme", "unindent", "util", "workspace", diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 5da4c9c8faa804b4a95fd9f3ebf35ec5849b525b..df3022ef4335d6527c417766eeaa9d2dcbb66b76 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -13,6 +13,7 @@ editor = { path = "../editor" } language = { path = "../language" } gpui = { path = "../gpui" } project = { path = "../project" } +theme = { path = "../theme" } util = { path = "../util" } workspace = { path = "../workspace" } postage = { version = "0.4", features = ["futures-traits"] } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 7fc94286ffa06d56ca1a59dfb6c5037b04c6ce9d..d5ebd1cfdbe752c535d1c9b71b52349f2bf2f86b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -14,7 +14,7 @@ use gpui::{ }; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; -use project::{Project, ProjectPath, WorktreeId}; +use project::{DiagnosticSummary, Project, ProjectPath, WorktreeId}; use std::{cmp::Ordering, mem, ops::Range, sync::Arc}; use util::TryFutureExt; use workspace::Workspace; @@ -47,6 +47,7 @@ struct ProjectDiagnosticsEditor { model: ModelHandle, workspace: WeakViewHandle, editor: ViewHandle, + summary: DiagnosticSummary, excerpts: ModelHandle, path_states: Vec, paths_to_update: HashMap>, @@ -120,9 +121,11 @@ impl ProjectDiagnosticsEditor { let project = model.read(cx).project.clone(); cx.subscribe(&project, |this, _, event, cx| match event { project::Event::DiskBasedDiagnosticsUpdated { worktree_id } => { + this.summary = this.model.read(cx).project.read(cx).diagnostic_summary(cx); if let Some(paths) = this.paths_to_update.remove(&worktree_id) { this.update_excerpts(paths, cx); } + cx.emit(Event::TitleChanged) } project::Event::DiagnosticsUpdated(path) => { this.paths_to_update @@ -141,13 +144,11 @@ impl ProjectDiagnosticsEditor { cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event)) .detach(); - let paths_to_update = project - .read(cx) - .diagnostic_summaries(cx) - .map(|e| e.0) - .collect(); + let project = project.read(cx); + let paths_to_update = project.diagnostic_summaries(cx).map(|e| e.0).collect(); let this = Self { model, + summary: project.diagnostic_summary(cx), workspace, excerpts, editor, @@ -544,8 +545,38 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { self.model.clone() } - fn title(&self, _: &AppContext) -> String { - "Project Diagnostics".to_string() + fn tab_content(&self, style: &theme::Tab, _: &AppContext) -> ElementBox { + let theme = &self.settings.borrow().theme.project_diagnostics; + let icon_width = theme.tab_icon_width; + let icon_spacing = theme.tab_icon_spacing; + let summary_spacing = theme.tab_summary_spacing; + Flex::row() + .with_children([ + Svg::new("icons/no.svg") + .with_color(style.label.text.color) + .constrained() + .with_width(icon_width) + .aligned() + .contained() + .with_margin_right(icon_spacing) + .named("no-icon"), + Label::new(self.summary.error_count.to_string(), style.label.clone()) + .aligned() + .boxed(), + Svg::new("icons/warning.svg") + .with_color(style.label.text.color) + .constrained() + .with_width(icon_width) + .aligned() + .contained() + .with_margin_left(summary_spacing) + .with_margin_right(icon_spacing) + .named("warn-icon"), + Label::new(self.summary.warning_count.to_string(), style.label.clone()) + .aligned() + .boxed(), + ]) + .boxed() } fn project_path(&self, _: &AppContext) -> Option { @@ -586,10 +617,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { } fn should_update_tab_on_event(event: &Event) -> bool { - matches!( - event, - Event::Saved | Event::Dirtied | Event::FileHandleChanged - ) + matches!(event, Event::Saved | Event::Dirtied | Event::TitleChanged) } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index eb412f3dcb5343c8548a7d7a6228756713959c91..90c0b00b71f265bb875cfefb9d65462cad585282 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -535,6 +535,19 @@ impl Editor { &self.buffer } + pub fn title(&self, cx: &AppContext) -> String { + let filename = self + .buffer() + .read(cx) + .file(cx) + .and_then(|file| file.file_name()); + if let Some(name) = filename { + name.to_string_lossy().into() + } else { + "untitled".into() + } + } + pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> EditorSnapshot { EditorSnapshot { mode: self.mode, @@ -3619,8 +3632,8 @@ impl Editor { language::Event::Edited => cx.emit(Event::Edited), language::Event::Dirtied => cx.emit(Event::Dirtied), language::Event::Saved => cx.emit(Event::Saved), - language::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged), - language::Event::Reloaded => cx.emit(Event::FileHandleChanged), + language::Event::FileHandleChanged => cx.emit(Event::TitleChanged), + language::Event::Reloaded => cx.emit(Event::TitleChanged), language::Event::Closed => cx.emit(Event::Closed), _ => {} } @@ -3727,7 +3740,7 @@ pub enum Event { Blurred, Dirtied, Saved, - FileHandleChanged, + TitleChanged, Closed, } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8abcc76ef9fa20290e3919b1ee295e3daf666aed..74641a72883e2b0b3959c076dfbbb7549be5d89f 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -102,17 +102,9 @@ impl ItemView for Editor { BufferItemHandle(self.buffer.read(cx).as_singleton().unwrap()) } - fn title(&self, cx: &AppContext) -> String { - let filename = self - .buffer() - .read(cx) - .file(cx) - .and_then(|file| file.file_name()); - if let Some(name) = filename { - name.to_string_lossy().into() - } else { - "untitled".into() - } + fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { + let title = self.title(cx); + Label::new(title, style.label.clone()).boxed() } fn project_path(&self, cx: &AppContext) -> Option { @@ -218,10 +210,7 @@ impl ItemView for Editor { } fn should_update_tab_on_event(event: &Event) -> bool { - matches!( - event, - Event::Saved | Event::Dirtied | Event::FileHandleChanged - ) + matches!(event, Event::Saved | Event::Dirtied | Event::TitleChanged) } } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 707a3bfb20602820f3be4f86681bb1605b04e0b8..3f3a57e3cc4f7e6416adffd100c84f45e3c47441 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -491,7 +491,15 @@ mod tests { .await; cx.read(|cx| { let active_item = active_pane.read(cx).active_item().unwrap(); - assert_eq!(active_item.title(cx), "bandana"); + assert_eq!( + active_item + .to_any() + .downcast::() + .unwrap() + .read(cx) + .title(cx), + "bandana" + ); }); } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3bd78f26342604792072b4946c668264ef323bdc..acefeaad0da8afe7082da580f0c1a3cd2604520b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -235,6 +235,9 @@ pub struct ProjectDiagnostics { pub container: ContainerStyle, pub empty_message: TextStyle, pub status_bar_item: ContainedText, + pub tab_icon_width: f32, + pub tab_icon_spacing: f32, + pub tab_summary_spacing: f32, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 731db29d634049ea6cebeec5b2369c4a98fa63fa..39ab5848cfc7ef832c291a62c128232001421c4e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -55,8 +55,6 @@ pub enum Event { Split(SplitDirection), } -const MAX_TAB_TITLE_LEN: usize = 24; - #[derive(Debug, Eq, PartialEq)] pub struct State { pub tabs: Vec, @@ -213,15 +211,12 @@ impl Pane { let is_active = ix == self.active_item; row.add_child({ - let mut title = item_view.title(cx); - if title.len() > MAX_TAB_TITLE_LEN { - let mut truncated_len = MAX_TAB_TITLE_LEN; - while !title.is_char_boundary(truncated_len) { - truncated_len -= 1; - } - title.truncate(truncated_len); - title.push('…'); - } + let tab_style = if is_active { + theme.workspace.active_tab.clone() + } else { + theme.workspace.tab.clone() + }; + let title = item_view.tab_content(&tab_style, cx); let mut style = if is_active { theme.workspace.active_tab.clone() @@ -270,29 +265,16 @@ impl Pane { .boxed(), ) .with_child( - Container::new( - Align::new( - Label::new( - title, - if is_active { - theme.workspace.active_tab.label.clone() - } else { - theme.workspace.tab.label.clone() - }, - ) - .boxed(), - ) - .boxed(), - ) - .with_style(ContainerStyle { - margin: Margin { - left: style.spacing, - right: style.spacing, + Container::new(Align::new(title).boxed()) + .with_style(ContainerStyle { + margin: Margin { + left: style.spacing, + right: style.spacing, + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }) - .boxed(), + }) + .boxed(), ) .with_child( Align::new( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index eec0a1d446aa81b8231eafb7b8fcfd785a6bc6d3..9a6995949149c646f81782dbc63ba7188ef80590 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -142,7 +142,7 @@ pub trait ItemView: View { type ItemHandle: ItemHandle; fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; - fn title(&self, cx: &AppContext) -> String; + fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn clone_on_split(&self, _: &mut ViewContext) -> Option where @@ -197,7 +197,7 @@ pub trait WeakItemHandle { pub trait ItemViewHandle { fn item_handle(&self, cx: &AppContext) -> Box; - fn title(&self, cx: &AppContext) -> String; + fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn project_path(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; @@ -308,8 +308,8 @@ impl ItemViewHandle for ViewHandle { Box::new(self.read(cx).item_handle(cx)) } - fn title(&self, cx: &AppContext) -> String { - self.read(cx).title(cx) + fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox { + self.read(cx).tab_content(style, cx) } fn project_path(&self, cx: &AppContext) -> Option { @@ -980,7 +980,11 @@ impl Workspace { } pub fn activate_next_pane(&mut self, cx: &mut ViewContext) { - let ix = self.panes.iter().position(|pane| pane == &self.active_pane).unwrap(); + let ix = self + .panes + .iter() + .position(|pane| pane == &self.active_pane) + .unwrap(); let next_ix = (ix + 1) % self.panes.len(); self.activate_pane(self.panes[next_ix].clone(), cx); } diff --git a/crates/zed/assets/icons/no.svg b/crates/zed/assets/icons/no.svg new file mode 100644 index 0000000000000000000000000000000000000000..799a6dcc0fc8e243bbb89bd11b5e556194c7d9be --- /dev/null +++ b/crates/zed/assets/icons/no.svg @@ -0,0 +1,4 @@ + + + + diff --git a/crates/zed/assets/icons/warning.svg b/crates/zed/assets/icons/warning.svg index 09ebc28669a6ed2a92fd43a36e60c6e3f330ceb6..845d07a15ae8ca3abbcfaaf4feaabfc623117eff 100644 --- a/crates/zed/assets/icons/warning.svg +++ b/crates/zed/assets/icons/warning.svg @@ -1,3 +1,3 @@ - - + + diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 95dc7eee606646aa8af18046214681b8ffad739e..f3e65cdf41f474ad22385227202851035b200723 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -281,3 +281,6 @@ border = { width = 1, top = true, color = "$border.0" } background = "$surface.1" empty_message = "$text.0" status_bar_item = { extends = "$text.2", margin.right = 10 } +tab_icon_width = 9 +tab_icon_spacing = 3 +tab_summary_spacing = 10 diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 61300d1f56ad74785e9ccd16a4dfe8bd0cf391a4..9c7c4410fd7d719b9f20e92632e29962c25a7abf 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -382,6 +382,10 @@ mod tests { .read(cx) .active_item() .unwrap() + .to_any() + .downcast::() + .unwrap() + .read(cx) .title(cx), "a.txt" ); @@ -413,6 +417,10 @@ mod tests { .read(cx) .active_item() .unwrap() + .to_any() + .downcast::() + .unwrap() + .read(cx) .title(cx), "b.txt" ); @@ -502,14 +510,14 @@ 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.is_dirty(cx)); + assert_eq!(editor.title(cx), "untitled"); assert!(Arc::ptr_eq( editor.language(cx).unwrap(), &language::PLAIN_TEXT )); editor.handle_input(&editor::Input("hi".into()), cx); - assert!(editor.is_dirty(cx.as_ref())); + assert!(editor.is_dirty(cx)); }); // Save the buffer. This prompts for a filename. @@ -522,7 +530,7 @@ mod tests { }); cx.read(|cx| { assert!(editor.is_dirty(cx)); - assert_eq!(editor.title(cx), "untitled"); + assert_eq!(editor.read(cx).title(cx), "untitled"); }); // When the save completes, the buffer's title is updated. @@ -531,7 +539,7 @@ mod tests { .await; cx.read(|cx| { assert!(!editor.is_dirty(cx)); - assert_eq!(editor.title(cx), "the-new-name.rs"); + assert_eq!(editor.read(cx).title(cx), "the-new-name.rs"); }); // The language is assigned based on the path editor.read_with(&cx, |editor, cx| { @@ -550,7 +558,7 @@ mod tests { editor .condition(&cx, |editor, cx| !editor.is_dirty(cx)) .await; - cx.read(|cx| assert_eq!(editor.title(cx), "the-new-name.rs")); + cx.read(|cx| assert_eq!(editor.read(cx).title(cx), "the-new-name.rs")); // Open the same newly-created file in another pane item. The new editor should reuse // the same buffer. From e7ec01635052e4ec6181fd374c578493b670b9d7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 Jan 2022 12:23:24 +0100 Subject: [PATCH 02/19] Restyle diagnostic headers --- crates/diagnostics/src/diagnostics.rs | 118 ++++++++++++++++++++------ crates/editor/src/editor.rs | 39 ++++++--- crates/theme/src/theme.rs | 39 ++++++++- crates/zed/assets/themes/_base.toml | 20 ++++- crates/zed/assets/themes/black.toml | 9 +- 5 files changed, 176 insertions(+), 49 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 62fc3bf321809b6074a59552b84ec290d3c8f0f7..754156397145cfeb86458dbb3bd16e49f5cd4f06 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -3,7 +3,7 @@ pub mod items; use anyhow::Result; use collections::{BTreeSet, HashMap, HashSet}; use editor::{ - diagnostic_block_renderer, diagnostic_style, + diagnostic_block_renderer, display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock}, items::BufferItemHandle, Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, @@ -12,7 +12,7 @@ use gpui::{ action, elements::*, keymap::Binding, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; +use language::{Bias, Buffer, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath}; use std::{ @@ -345,16 +345,14 @@ impl ProjectDiagnosticsEditor { if is_first_excerpt_for_group { is_first_excerpt_for_group = false; let primary = &group.entries[group.primary_ix].diagnostic; - let mut header = primary.clone(); - header.message = + let message = primary.message.split('\n').next().unwrap().to_string(); group_state.block_count += 1; blocks_to_add.push(BlockProperties { position: header_position, height: 2, render: diagnostic_header_renderer( - header, - true, + message, self.build_settings.clone(), ), disposition: BlockDisposition::Above, @@ -651,41 +649,105 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettings) -> RenderBlock { Arc::new(move |cx| { let settings = build_settings(cx); - let file_path = if let Some(file) = buffer.read(&**cx).file() { - file.path().to_string_lossy().to_string() - } else { - "untitled".to_string() - }; - let mut text_style = settings.style.text.clone(); let style = settings.style.diagnostic_path_header; - text_style.color = style.text; - Label::new(file_path, text_style) + + let mut filename = None; + let mut path = None; + if let Some(file) = buffer.read(&**cx).file() { + filename = file + .path() + .file_name() + .map(|f| f.to_string_lossy().to_string()); + path = file + .path() + .parent() + .map(|p| p.to_string_lossy().to_string()); + } + + Flex::row() + .with_child( + Label::new( + filename.unwrap_or_else(|| "untitled".to_string()), + style.filename.text.clone(), + ) + .contained() + .with_style(style.filename.container) + .boxed(), + ) + .with_children(path.map(|path| { + Label::new(path, style.path.text.clone()) + .contained() + .with_style(style.path.container) + .boxed() + })) .aligned() .left() .contained() - .with_style(style.header) + .with_style(style.container) .with_padding_left(cx.line_number_x) .expanded() .named("path header block") }) } -fn diagnostic_header_renderer( - diagnostic: Diagnostic, - is_valid: bool, - build_settings: BuildSettings, -) -> RenderBlock { +fn diagnostic_header_renderer(message: String, build_settings: BuildSettings) -> RenderBlock { + enum Run { + Text(Range), + Code(Range), + } + + let mut prev_ix = 0; + let mut inside_block = false; + let mut runs = Vec::new(); + for (backtick_ix, _) in message.match_indices('`') { + if backtick_ix > prev_ix { + if inside_block { + runs.push(Run::Code(prev_ix..backtick_ix)); + } else { + runs.push(Run::Text(prev_ix..backtick_ix)); + } + } + + inside_block = !inside_block; + prev_ix = backtick_ix + 1; + } + if prev_ix < message.len() { + if inside_block { + runs.push(Run::Code(prev_ix..message.len())); + } else { + runs.push(Run::Text(prev_ix..message.len())); + } + } + Arc::new(move |cx| { let settings = build_settings(cx); - let mut text_style = settings.style.text.clone(); - let diagnostic_style = diagnostic_style(diagnostic.severity, is_valid, &settings.style); - text_style.color = diagnostic_style.text; - Text::new(diagnostic.message.clone(), text_style) - .with_soft_wrap(false) - .aligned() - .left() + let style = &settings.style.diagnostic_header; + + Flex::row() + .with_children(runs.iter().map(|run| { + let container_style; + let text_style; + let range; + match run { + Run::Text(run_range) => { + container_style = Default::default(); + text_style = style.text.clone(); + range = run_range.clone(); + } + Run::Code(run_range) => { + container_style = style.highlighted_text.container; + text_style = style.highlighted_text.text.clone(); + range = run_range.clone(); + } + } + Label::new(message[range].to_string(), text_style) + .contained() + .with_style(container_style) + .aligned() + .boxed() + })) .contained() - .with_style(diagnostic_style.header) + .with_style(style.container) .with_padding_left(cx.line_number_x) .expanded() .named("diagnostic header") diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0c393d93ab68420585ec9da62b81f00f59031271..bb24e8ff75e626f03b1589860491bef4f15bf080 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3814,16 +3814,17 @@ impl EditorSettings { let font_id = font_cache .select_font(font_family_id, &font_properties) .unwrap(); + let text = gpui::fonts::TextStyle { + font_family_name, + font_family_id, + font_id, + font_size: 14., + color: gpui::color::Color::from_u32(0xff0000ff), + font_properties, + underline: None, + }; EditorStyle { - text: gpui::fonts::TextStyle { - font_family_name, - font_family_id, - font_id, - font_size: 14., - color: gpui::color::Color::from_u32(0xff0000ff), - font_properties, - underline: None, - }, + text: text.clone(), placeholder_text: None, background: Default::default(), gutter_background: Default::default(), @@ -3834,7 +3835,25 @@ impl EditorSettings { selection: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - diagnostic_path_header: Default::default(), + diagnostic_path_header: theme::DiagnosticPathHeader { + container: Default::default(), + filename: theme::ContainedText { + container: Default::default(), + text: text.clone(), + }, + path: theme::ContainedText { + container: Default::default(), + text: text.clone(), + }, + }, + diagnostic_header: theme::DiagnosticHeader { + container: Default::default(), + text: text.clone(), + highlighted_text: theme::ContainedText { + container: Default::default(), + text: text.clone(), + }, + }, error_diagnostic: Default::default(), invalid_error_diagnostic: Default::default(), warning_diagnostic: Default::default(), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3bd78f26342604792072b4946c668264ef323bdc..ac383d2d5226fb53f1348fbc8d10e30eed11eac0 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -251,7 +251,8 @@ pub struct EditorStyle { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, - pub diagnostic_path_header: DiagnosticStyle, + pub diagnostic_path_header: DiagnosticPathHeader, + pub diagnostic_header: DiagnosticHeader, pub error_diagnostic: DiagnosticStyle, pub invalid_error_diagnostic: DiagnosticStyle, pub warning_diagnostic: DiagnosticStyle, @@ -262,6 +263,22 @@ pub struct EditorStyle { pub invalid_hint_diagnostic: DiagnosticStyle, } +#[derive(Clone, Deserialize, Default)] +pub struct DiagnosticPathHeader { + #[serde(flatten)] + pub container: ContainerStyle, + pub filename: ContainedText, + pub path: ContainedText, +} + +#[derive(Clone, Deserialize, Default)] +pub struct DiagnosticHeader { + #[serde(flatten)] + pub container: ContainerStyle, + pub text: TextStyle, + pub highlighted_text: ContainedText, +} + #[derive(Copy, Clone, Deserialize, Default)] pub struct DiagnosticStyle { pub text: Color, @@ -317,7 +334,25 @@ impl InputEditorStyle { line_number_active: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - diagnostic_path_header: Default::default(), + diagnostic_path_header: DiagnosticPathHeader { + container: Default::default(), + filename: ContainedText { + container: Default::default(), + text: self.text.clone(), + }, + path: ContainedText { + container: Default::default(), + text: self.text.clone(), + }, + }, + diagnostic_header: DiagnosticHeader { + container: Default::default(), + text: self.text.clone(), + highlighted_text: ContainedText { + container: Default::default(), + text: self.text.clone(), + }, + }, error_diagnostic: Default::default(), invalid_error_diagnostic: Default::default(), warning_diagnostic: Default::default(), diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index c4c3cf512564792735661268dc6d6d59ae61a8a9..852553c96d03f46d2933bbb5f25120fe6a7b2600 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -187,7 +187,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -257,9 +257,21 @@ invalid_information_diagnostic = { text = "$text.3.color" } invalid_hint_diagnostic = { text = "$text.3.color" } [editor.diagnostic_path_header] -text = "$text.0.color" -header.background = "#ffffff08" -header.border = { width = 1, top = true, color = "$border.0" } +filename = { extends = "$text.0", size = 14 } +path = { extends = "$text.1", size = 14, margin.left = 4 } + +[editor.diagnostic_header] +background = "$state.active_line" +border = { width = 1, top = true, bottom = true, color = "$border.0" } +text = { extends = "$text.1", size = 14 } + +[editor.diagnostic_header.highlighted_text] +extends = "$editor.diagnostic_header.text" +color = "$text.0.color" +background = "#ffffff1f" +padding.left = 3 +padding.right = 3 +corner_radius = 3 [editor.error_diagnostic] text = "$status.bad" diff --git a/crates/zed/assets/themes/black.toml b/crates/zed/assets/themes/black.toml index a822fa7d3346c1ba2b5aac359ceb1ac940c9a73f..972ef37132c12e4b6a3f9aad2e5b78dccccf3cb2 100644 --- a/crates/zed/assets/themes/black.toml +++ b/crates/zed/assets/themes/black.toml @@ -1,12 +1,12 @@ extends = "_base" [surface] -0 = "#222324" -1 = "#141516" +0 = "#222222" +1 = "#0f0b0c" 2 = "#131415" [border] -0 = "#0F1011" +0 = "#000000B2" [text] 0 = { extends = "$text.base", color = "#ffffff" } @@ -36,7 +36,7 @@ warn = "#faca50" bad = "#b7372e" [state] -active_line = "#00000033" +active_line = "#161313" highlighted_line = "#faca5033" hover = "#00000033" @@ -50,7 +50,6 @@ comment = "#6a9955" property = "#4e94ce" variant = "#4fc1ff" constant = "#9cdcfe" - title = { color = "#9cdcfe", weight = "bold" } emphasis = "#4ec9b0" "emphasis.strong" = { color = "#4ec9b0", weight = "bold" } From ec0ca2a1b6f5f9f5aecfad74790bf86e8dc6c418 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 Jan 2022 15:13:50 +0100 Subject: [PATCH 03/19] Display icon next to primary message --- crates/diagnostics/src/diagnostics.rs | 28 +++++++++++++++++-- crates/editor/src/editor.rs | 1 + crates/theme/src/theme.rs | 9 ++++++ .../zed/assets/icons/diagnostic-error-10.svg | 3 ++ .../assets/icons/diagnostic-warning-10.svg | 3 ++ crates/zed/assets/themes/_base.toml | 3 +- 6 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 crates/zed/assets/icons/diagnostic-error-10.svg create mode 100644 crates/zed/assets/icons/diagnostic-warning-10.svg diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 754156397145cfeb86458dbb3bd16e49f5cd4f06..c411e31ad89e37653a60b4b6b786077acb24d8b6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -12,7 +12,9 @@ use gpui::{ action, elements::*, keymap::Binding, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::{Bias, Buffer, DiagnosticEntry, Point, Selection, SelectionGoal}; +use language::{ + Bias, Buffer, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, +}; use postage::watch; use project::{Project, ProjectPath}; use std::{ @@ -353,6 +355,7 @@ impl ProjectDiagnosticsEditor { height: 2, render: diagnostic_header_renderer( message, + primary.severity, self.build_settings.clone(), ), disposition: BlockDisposition::Above, @@ -661,7 +664,7 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin path = file .path() .parent() - .map(|p| p.to_string_lossy().to_string()); + .map(|p| p.to_string_lossy().to_string() + "/"); } Flex::row() @@ -690,7 +693,11 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin }) } -fn diagnostic_header_renderer(message: String, build_settings: BuildSettings) -> RenderBlock { +fn diagnostic_header_renderer( + message: String, + severity: DiagnosticSeverity, + build_settings: BuildSettings, +) -> RenderBlock { enum Run { Text(Range), Code(Range), @@ -722,8 +729,23 @@ fn diagnostic_header_renderer(message: String, build_settings: BuildSettings) -> Arc::new(move |cx| { let settings = build_settings(cx); let style = &settings.style.diagnostic_header; + let icon = if severity == DiagnosticSeverity::ERROR { + Svg::new("icons/diagnostic-error-10.svg") + .with_color(settings.style.error_diagnostic.text) + } else { + Svg::new("icons/diagnostic-warning-10.svg") + .with_color(settings.style.warning_diagnostic.text) + }; Flex::row() + .with_child( + icon.constrained() + .with_height(style.icon.width) + .aligned() + .contained() + .with_style(style.icon.container) + .boxed(), + ) .with_children(runs.iter().map(|run| { let container_style; let text_style; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb24e8ff75e626f03b1589860491bef4f15bf080..ec0e3cb8356f6309a6b086e1d783d176c9b1922d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3853,6 +3853,7 @@ impl EditorSettings { container: Default::default(), text: text.clone(), }, + icon: Default::default(), }, error_diagnostic: Default::default(), invalid_error_diagnostic: Default::default(), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ac383d2d5226fb53f1348fbc8d10e30eed11eac0..d9383d80a01742e888d4c9471137b9d44f748dcd 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -277,6 +277,14 @@ pub struct DiagnosticHeader { pub container: ContainerStyle, pub text: TextStyle, pub highlighted_text: ContainedText, + pub icon: DiagnosticHeaderIcon, +} + +#[derive(Clone, Deserialize, Default)] +pub struct DiagnosticHeaderIcon { + #[serde(flatten)] + pub container: ContainerStyle, + pub width: f32, } #[derive(Copy, Clone, Deserialize, Default)] @@ -352,6 +360,7 @@ impl InputEditorStyle { container: Default::default(), text: self.text.clone(), }, + icon: Default::default(), }, error_diagnostic: Default::default(), invalid_error_diagnostic: Default::default(), diff --git a/crates/zed/assets/icons/diagnostic-error-10.svg b/crates/zed/assets/icons/diagnostic-error-10.svg new file mode 100644 index 0000000000000000000000000000000000000000..d01bc3a23d6d3dd08800fd7ffaf7887c4e0be711 --- /dev/null +++ b/crates/zed/assets/icons/diagnostic-error-10.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/zed/assets/icons/diagnostic-warning-10.svg b/crates/zed/assets/icons/diagnostic-warning-10.svg new file mode 100644 index 0000000000000000000000000000000000000000..bfd9ed7c6f7f553b55cc5b708be976026692dbc5 --- /dev/null +++ b/crates/zed/assets/icons/diagnostic-warning-10.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 852553c96d03f46d2933bbb5f25120fe6a7b2600..acead59906396c5ea492f9c76ba8142bb0911fb5 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -258,12 +258,13 @@ invalid_hint_diagnostic = { text = "$text.3.color" } [editor.diagnostic_path_header] filename = { extends = "$text.0", size = 14 } -path = { extends = "$text.1", size = 14, margin.left = 4 } +path = { extends = "$text.2", size = 14, margin.left = 12 } [editor.diagnostic_header] background = "$state.active_line" border = { width = 1, top = true, bottom = true, color = "$border.0" } text = { extends = "$text.1", size = 14 } +icon = { width = 10, margin.right = 8 } [editor.diagnostic_header.highlighted_text] extends = "$editor.diagnostic_header.text" From 7e55353de84512c8a393aeb2344dc6a461024fe5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 Jan 2022 15:23:58 +0100 Subject: [PATCH 04/19] Show error code in diagnostic header --- crates/diagnostics/src/diagnostics.rs | 32 ++++++++++++++++----------- crates/editor/src/editor.rs | 4 ++++ crates/theme/src/theme.rs | 5 +++++ crates/zed/assets/themes/_base.toml | 1 + 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index c411e31ad89e37653a60b4b6b786077acb24d8b6..6741cbc7e4c919322af4200edd710fc0b669e7f4 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -13,7 +13,7 @@ use gpui::{ MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{ - Bias, Buffer, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, + Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, }; use postage::watch; use project::{Project, ProjectPath}; @@ -346,16 +346,16 @@ impl ProjectDiagnosticsEditor { if is_first_excerpt_for_group { is_first_excerpt_for_group = false; - let primary = &group.entries[group.primary_ix].diagnostic; - let message = + let mut primary = + group.entries[group.primary_ix].diagnostic.clone(); + primary.message = primary.message.split('\n').next().unwrap().to_string(); group_state.block_count += 1; blocks_to_add.push(BlockProperties { position: header_position, height: 2, render: diagnostic_header_renderer( - message, - primary.severity, + primary, self.build_settings.clone(), ), disposition: BlockDisposition::Above, @@ -694,8 +694,7 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin } fn diagnostic_header_renderer( - message: String, - severity: DiagnosticSeverity, + diagnostic: Diagnostic, build_settings: BuildSettings, ) -> RenderBlock { enum Run { @@ -706,7 +705,7 @@ fn diagnostic_header_renderer( let mut prev_ix = 0; let mut inside_block = false; let mut runs = Vec::new(); - for (backtick_ix, _) in message.match_indices('`') { + for (backtick_ix, _) in diagnostic.message.match_indices('`') { if backtick_ix > prev_ix { if inside_block { runs.push(Run::Code(prev_ix..backtick_ix)); @@ -718,18 +717,18 @@ fn diagnostic_header_renderer( inside_block = !inside_block; prev_ix = backtick_ix + 1; } - if prev_ix < message.len() { + if prev_ix < diagnostic.message.len() { if inside_block { - runs.push(Run::Code(prev_ix..message.len())); + runs.push(Run::Code(prev_ix..diagnostic.message.len())); } else { - runs.push(Run::Text(prev_ix..message.len())); + runs.push(Run::Text(prev_ix..diagnostic.message.len())); } } Arc::new(move |cx| { let settings = build_settings(cx); let style = &settings.style.diagnostic_header; - let icon = if severity == DiagnosticSeverity::ERROR { + let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/diagnostic-error-10.svg") .with_color(settings.style.error_diagnostic.text) } else { @@ -762,12 +761,19 @@ fn diagnostic_header_renderer( range = run_range.clone(); } } - Label::new(message[range].to_string(), text_style) + Label::new(diagnostic.message[range].to_string(), text_style) .contained() .with_style(container_style) .aligned() .boxed() })) + .with_children(diagnostic.code.clone().map(|code| { + Label::new(code, style.code.text.clone()) + .contained() + .with_style(style.code.container) + .aligned() + .boxed() + })) .contained() .with_style(style.container) .with_padding_left(cx.line_number_x) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ec0e3cb8356f6309a6b086e1d783d176c9b1922d..47ba589823e96f424057d69a2be8d69ab43ffbb8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3853,6 +3853,10 @@ impl EditorSettings { container: Default::default(), text: text.clone(), }, + code: theme::ContainedText { + container: Default::default(), + text: text.clone(), + }, icon: Default::default(), }, error_diagnostic: Default::default(), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d9383d80a01742e888d4c9471137b9d44f748dcd..49a1f57d3991f788b9c474a1438f2e6c14206f7e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -277,6 +277,7 @@ pub struct DiagnosticHeader { pub container: ContainerStyle, pub text: TextStyle, pub highlighted_text: ContainedText, + pub code: ContainedText, pub icon: DiagnosticHeaderIcon, } @@ -360,6 +361,10 @@ impl InputEditorStyle { container: Default::default(), text: self.text.clone(), }, + code: ContainedText { + container: Default::default(), + text: self.text.clone(), + }, icon: Default::default(), }, error_diagnostic: Default::default(), diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index acead59906396c5ea492f9c76ba8142bb0911fb5..9a221a235577a6a9a0e5a945a691312637b0afd5 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -264,6 +264,7 @@ path = { extends = "$text.2", size = 14, margin.left = 12 } background = "$state.active_line" border = { width = 1, top = true, bottom = true, color = "$border.0" } text = { extends = "$text.1", size = 14 } +code = { extends = "$text.2", size = 14, margin.left = 10 } icon = { width = 10, margin.right = 8 } [editor.diagnostic_header.highlighted_text] From 65d4c33c0ead053d9771fd704d114c782a8bd2cc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 Jan 2022 15:46:33 +0100 Subject: [PATCH 05/19] Format messages within backticks using bold instead of with a background --- crates/diagnostics/src/diagnostics.rs | 77 ++++++++++----------------- crates/editor/src/editor.rs | 17 +++--- crates/theme/src/theme.rs | 10 ++-- crates/zed/assets/themes/_base.toml | 11 ++-- 4 files changed, 45 insertions(+), 70 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 6741cbc7e4c919322af4200edd710fc0b669e7f4..5ea0dcbd1d24f362815e141450707efc3c3aca2c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -697,34 +697,7 @@ fn diagnostic_header_renderer( diagnostic: Diagnostic, build_settings: BuildSettings, ) -> RenderBlock { - enum Run { - Text(Range), - Code(Range), - } - - let mut prev_ix = 0; - let mut inside_block = false; - let mut runs = Vec::new(); - for (backtick_ix, _) in diagnostic.message.match_indices('`') { - if backtick_ix > prev_ix { - if inside_block { - runs.push(Run::Code(prev_ix..backtick_ix)); - } else { - runs.push(Run::Text(prev_ix..backtick_ix)); - } - } - - inside_block = !inside_block; - prev_ix = backtick_ix + 1; - } - if prev_ix < diagnostic.message.len() { - if inside_block { - runs.push(Run::Code(prev_ix..diagnostic.message.len())); - } else { - runs.push(Run::Text(prev_ix..diagnostic.message.len())); - } - } - + let (message, highlights) = highlight_diagnostic_message(&diagnostic.message); Arc::new(move |cx| { let settings = build_settings(cx); let style = &settings.style.diagnostic_header; @@ -745,28 +718,14 @@ fn diagnostic_header_renderer( .with_style(style.icon.container) .boxed(), ) - .with_children(runs.iter().map(|run| { - let container_style; - let text_style; - let range; - match run { - Run::Text(run_range) => { - container_style = Default::default(); - text_style = style.text.clone(); - range = run_range.clone(); - } - Run::Code(run_range) => { - container_style = style.highlighted_text.container; - text_style = style.highlighted_text.text.clone(); - range = run_range.clone(); - } - } - Label::new(diagnostic.message[range].to_string(), text_style) + .with_child( + Label::new(message.clone(), style.message.label.clone()) + .with_highlights(highlights.clone()) .contained() - .with_style(container_style) + .with_style(style.message.container) .aligned() - .boxed() - })) + .boxed(), + ) .with_children(diagnostic.code.clone().map(|code| { Label::new(code, style.code.text.clone()) .contained() @@ -782,6 +741,28 @@ fn diagnostic_header_renderer( }) } +fn highlight_diagnostic_message(message: &str) -> (String, Vec) { + let mut message_without_backticks = String::new(); + let mut prev_offset = 0; + let mut inside_block = false; + let mut highlights = Vec::new(); + for (match_ix, (offset, _)) in message + .match_indices('`') + .chain([(message.len(), "")]) + .enumerate() + { + message_without_backticks.push_str(&message[prev_offset..offset]); + if inside_block { + highlights.extend(prev_offset - match_ix..offset - match_ix); + } + + inside_block = !inside_block; + prev_offset = offset + 1; + } + + (message_without_backticks, highlights) +} + fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock { Arc::new(move |cx| { let settings = build_settings(cx); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 47ba589823e96f424057d69a2be8d69ab43ffbb8..cff35a7e3215fb274d179c5bc78003f54df1b365 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3803,6 +3803,8 @@ impl Deref for EditorSnapshot { impl EditorSettings { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &AppContext) -> Self { + use theme::{ContainedLabel, ContainedText, DiagnosticHeader, DiagnosticPathHeader}; + Self { tab_size: 4, soft_wrap: SoftWrap::None, @@ -3835,25 +3837,24 @@ impl EditorSettings { selection: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - diagnostic_path_header: theme::DiagnosticPathHeader { + diagnostic_path_header: DiagnosticPathHeader { container: Default::default(), - filename: theme::ContainedText { + filename: ContainedText { container: Default::default(), text: text.clone(), }, - path: theme::ContainedText { + path: ContainedText { container: Default::default(), text: text.clone(), }, }, - diagnostic_header: theme::DiagnosticHeader { + diagnostic_header: DiagnosticHeader { container: Default::default(), - text: text.clone(), - highlighted_text: theme::ContainedText { + message: ContainedLabel { container: Default::default(), - text: text.clone(), + label: text.clone().into(), }, - code: theme::ContainedText { + code: ContainedText { container: Default::default(), text: text.clone(), }, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 49a1f57d3991f788b9c474a1438f2e6c14206f7e..cc06d1fce4958ac649780d9d6387371430d6403b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -221,7 +221,7 @@ pub struct ContainedText { pub text: TextStyle, } -#[derive(Deserialize, Default)] +#[derive(Clone, Deserialize, Default)] pub struct ContainedLabel { #[serde(flatten)] pub container: ContainerStyle, @@ -275,8 +275,7 @@ pub struct DiagnosticPathHeader { pub struct DiagnosticHeader { #[serde(flatten)] pub container: ContainerStyle, - pub text: TextStyle, - pub highlighted_text: ContainedText, + pub message: ContainedLabel, pub code: ContainedText, pub icon: DiagnosticHeaderIcon, } @@ -356,10 +355,9 @@ impl InputEditorStyle { }, diagnostic_header: DiagnosticHeader { container: Default::default(), - text: self.text.clone(), - highlighted_text: ContainedText { + message: ContainedLabel { container: Default::default(), - text: self.text.clone(), + label: self.text.clone().into(), }, code: ContainedText { container: Default::default(), diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 9a221a235577a6a9a0e5a945a691312637b0afd5..9acaafdacffd9f35b291ac6139717caf55a02da8 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -263,17 +263,12 @@ path = { extends = "$text.2", size = 14, margin.left = 12 } [editor.diagnostic_header] background = "$state.active_line" border = { width = 1, top = true, bottom = true, color = "$border.0" } -text = { extends = "$text.1", size = 14 } code = { extends = "$text.2", size = 14, margin.left = 10 } icon = { width = 10, margin.right = 8 } -[editor.diagnostic_header.highlighted_text] -extends = "$editor.diagnostic_header.text" -color = "$text.0.color" -background = "#ffffff1f" -padding.left = 3 -padding.right = 3 -corner_radius = 3 +[editor.diagnostic_header.message] +text = { extends = "$text.1", size = 14 } +highlight_text = { extends = "$text.0", size = 14, weight = "bold" } [editor.error_diagnostic] text = "$status.bad" From 7250974aa638f7a1e17ce51f28dd3453a8914688 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 Jan 2022 17:07:12 +0100 Subject: [PATCH 06/19] Style block decorations Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 27 +-------- crates/editor/src/editor.rs | 85 +++++++++++++++++++-------- crates/editor/src/element.rs | 7 ++- crates/theme/src/theme.rs | 24 ++++---- crates/zed/assets/themes/_base.toml | 40 ++++++++++--- 5 files changed, 117 insertions(+), 66 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 5ea0dcbd1d24f362815e141450707efc3c3aca2c..dd32b5beff62ca5b9facd488b68071f20b3548d3 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -5,6 +5,7 @@ use collections::{BTreeSet, HashMap, HashSet}; use editor::{ diagnostic_block_renderer, display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock}, + highlight_diagnostic_message, items::BufferItemHandle, Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, }; @@ -703,10 +704,10 @@ fn diagnostic_header_renderer( let style = &settings.style.diagnostic_header; let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/diagnostic-error-10.svg") - .with_color(settings.style.error_diagnostic.text) + .with_color(settings.style.error_diagnostic.message.text.color) } else { Svg::new("icons/diagnostic-warning-10.svg") - .with_color(settings.style.warning_diagnostic.text) + .with_color(settings.style.warning_diagnostic.message.text.color) }; Flex::row() @@ -741,28 +742,6 @@ fn diagnostic_header_renderer( }) } -fn highlight_diagnostic_message(message: &str) -> (String, Vec) { - let mut message_without_backticks = String::new(); - let mut prev_offset = 0; - let mut inside_block = false; - let mut highlights = Vec::new(); - for (match_ix, (offset, _)) in message - .match_indices('`') - .chain([(message.len(), "")]) - .enumerate() - { - message_without_backticks.push_str(&message[prev_offset..offset]); - if inside_block { - highlights.extend(prev_offset - match_ix..offset - match_ix); - } - - inside_block = !inside_block; - prev_offset = offset + 1; - } - - (message_without_backticks, highlights) -} - fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock { Arc::new(move |cx| { let settings = build_settings(cx); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cff35a7e3215fb274d179c5bc78003f54df1b365..847fe897331f10323119ecf3e44fe6288302dadd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3825,6 +3825,10 @@ impl EditorSettings { font_properties, underline: None, }; + let default_diagnostic_style = DiagnosticStyle { + message: text.clone().into(), + header: Default::default(), + }; EditorStyle { text: text.clone(), placeholder_text: None, @@ -3860,14 +3864,14 @@ impl EditorSettings { }, icon: Default::default(), }, - error_diagnostic: Default::default(), - invalid_error_diagnostic: Default::default(), - warning_diagnostic: Default::default(), - invalid_warning_diagnostic: Default::default(), - information_diagnostic: Default::default(), - invalid_information_diagnostic: Default::default(), - hint_diagnostic: Default::default(), - invalid_hint_diagnostic: Default::default(), + error_diagnostic: default_diagnostic_style.clone(), + invalid_error_diagnostic: default_diagnostic_style.clone(), + warning_diagnostic: default_diagnostic_style.clone(), + invalid_warning_diagnostic: default_diagnostic_style.clone(), + information_diagnostic: default_diagnostic_style.clone(), + invalid_information_diagnostic: default_diagnostic_style.clone(), + hint_diagnostic: default_diagnostic_style.clone(), + invalid_hint_diagnostic: default_diagnostic_style.clone(), } }, } @@ -4008,33 +4012,68 @@ pub fn diagnostic_block_renderer( is_valid: bool, build_settings: BuildSettings, ) -> RenderBlock { + let mut highlighted_lines = Vec::new(); + for line in diagnostic.message.lines() { + highlighted_lines.push(highlight_diagnostic_message(line)); + } + Arc::new(move |cx: &BlockContext| { let settings = build_settings(cx); - let mut text_style = settings.style.text.clone(); - text_style.color = diagnostic_style(diagnostic.severity, is_valid, &settings.style).text; - Text::new(diagnostic.message.clone(), text_style) - .with_soft_wrap(false) - .contained() - .with_margin_left(cx.anchor_x) + let style = diagnostic_style(diagnostic.severity, is_valid, &settings.style).message; + Flex::column() + .with_children(highlighted_lines.iter().map(|(line, highlights)| { + Label::new(line.clone(), style.clone()) + .with_highlights(highlights.clone()) + .contained() + .with_margin_left(cx.anchor_x) + .boxed() + })) + .aligned() + .left() .boxed() }) } +pub fn highlight_diagnostic_message(message: &str) -> (String, Vec) { + let mut message_without_backticks = String::new(); + let mut prev_offset = 0; + let mut inside_block = false; + let mut highlights = Vec::new(); + for (match_ix, (offset, _)) in message + .match_indices('`') + .chain([(message.len(), "")]) + .enumerate() + { + message_without_backticks.push_str(&message[prev_offset..offset]); + if inside_block { + highlights.extend(prev_offset - match_ix..offset - match_ix); + } + + inside_block = !inside_block; + prev_offset = offset + 1; + } + + (message_without_backticks, highlights) +} + pub fn diagnostic_style( severity: DiagnosticSeverity, valid: bool, style: &EditorStyle, ) -> DiagnosticStyle { match (severity, valid) { - (DiagnosticSeverity::ERROR, true) => style.error_diagnostic, - (DiagnosticSeverity::ERROR, false) => style.invalid_error_diagnostic, - (DiagnosticSeverity::WARNING, true) => style.warning_diagnostic, - (DiagnosticSeverity::WARNING, false) => style.invalid_warning_diagnostic, - (DiagnosticSeverity::INFORMATION, true) => style.information_diagnostic, - (DiagnosticSeverity::INFORMATION, false) => style.invalid_information_diagnostic, - (DiagnosticSeverity::HINT, true) => style.hint_diagnostic, - (DiagnosticSeverity::HINT, false) => style.invalid_hint_diagnostic, - _ => Default::default(), + (DiagnosticSeverity::ERROR, true) => style.error_diagnostic.clone(), + (DiagnosticSeverity::ERROR, false) => style.invalid_error_diagnostic.clone(), + (DiagnosticSeverity::WARNING, true) => style.warning_diagnostic.clone(), + (DiagnosticSeverity::WARNING, false) => style.invalid_warning_diagnostic.clone(), + (DiagnosticSeverity::INFORMATION, true) => style.information_diagnostic.clone(), + (DiagnosticSeverity::INFORMATION, false) => style.invalid_information_diagnostic.clone(), + (DiagnosticSeverity::HINT, true) => style.hint_diagnostic.clone(), + (DiagnosticSeverity::HINT, false) => style.invalid_hint_diagnostic.clone(), + _ => DiagnosticStyle { + message: style.text.clone().into(), + header: Default::default(), + }, } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ff4b792338a9a9fd82a9fb6348d68e5d512d85ed..44f6f06b1a75f1eb725ceb75b2efc5c34cf94f27 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -549,7 +549,12 @@ impl EditorElement { .chunks(rows.clone(), Some(&style.syntax)) .map(|chunk| { let highlight = if let Some(severity) = chunk.diagnostic { - let underline = Some(super::diagnostic_style(severity, true, style).text); + let underline = Some( + super::diagnostic_style(severity, true, style) + .message + .text + .color, + ); if let Some(mut highlight) = chunk.highlight_style { highlight.underline = underline; Some(highlight) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index cc06d1fce4958ac649780d9d6387371430d6403b..60a08015b5a85d3feaf56b827c224736e563f7b7 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -287,9 +287,9 @@ pub struct DiagnosticHeaderIcon { pub width: f32, } -#[derive(Copy, Clone, Deserialize, Default)] +#[derive(Clone, Deserialize, Default)] pub struct DiagnosticStyle { - pub text: Color, + pub message: LabelStyle, #[serde(default)] pub header: ContainerStyle, } @@ -327,6 +327,10 @@ impl EditorStyle { impl InputEditorStyle { pub fn as_editor(&self) -> EditorStyle { + let default_diagnostic_style = DiagnosticStyle { + message: self.text.clone().into(), + header: Default::default(), + }; EditorStyle { text: self.text.clone(), placeholder_text: self.placeholder_text.clone(), @@ -365,14 +369,14 @@ impl InputEditorStyle { }, icon: Default::default(), }, - error_diagnostic: Default::default(), - invalid_error_diagnostic: Default::default(), - warning_diagnostic: Default::default(), - invalid_warning_diagnostic: Default::default(), - information_diagnostic: Default::default(), - invalid_information_diagnostic: Default::default(), - hint_diagnostic: Default::default(), - invalid_hint_diagnostic: Default::default(), + error_diagnostic: default_diagnostic_style.clone(), + invalid_error_diagnostic: default_diagnostic_style.clone(), + warning_diagnostic: default_diagnostic_style.clone(), + invalid_warning_diagnostic: default_diagnostic_style.clone(), + information_diagnostic: default_diagnostic_style.clone(), + invalid_information_diagnostic: default_diagnostic_style.clone(), + hint_diagnostic: default_diagnostic_style.clone(), + invalid_hint_diagnostic: default_diagnostic_style.clone(), } } } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 9acaafdacffd9f35b291ac6139717caf55a02da8..ae3d5e1417d713013cfd77c79f625f347db278bf 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -251,10 +251,6 @@ line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" error_color = "$status.bad" -invalid_error_diagnostic = { text = "$text.3.color" } -invalid_warning_diagnostic = { text = "$text.3.color" } -invalid_information_diagnostic = { text = "$text.3.color" } -invalid_hint_diagnostic = { text = "$text.3.color" } [editor.diagnostic_path_header] filename = { extends = "$text.0", size = 14 } @@ -271,21 +267,49 @@ text = { extends = "$text.1", size = 14 } highlight_text = { extends = "$text.0", size = 14, weight = "bold" } [editor.error_diagnostic] -text = "$status.bad" header.border = { width = 1, top = true, color = "$border.0" } +[editor.error_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$status.bad" } +highlight_text = { extends = "$editor.text", size = 14, color = "$status.bad", weight = "bold" } + [editor.warning_diagnostic] -text = "$status.warn" header.border = { width = 1, top = true, color = "$border.0" } +[editor.warning_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$status.warn" } +highlight_text = { extends = "$editor.text", size = 14, color = "$status.warn", weight = "bold" } + [editor.information_diagnostic] -text = "$status.info" border = { width = 1, top = true, color = "$border.0" } +[editor.information_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$status.info" } +highlight_text = { extends = "$editor.text", size = 14, color = "$status.info", weight = "bold" } + [editor.hint_diagnostic] -text = "$status.info" border = { width = 1, top = true, color = "$border.0" } +[editor.hint_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$status.info" } +highlight_text = { extends = "$editor.text", size = 14, color = "$status.info", weight = "bold" } + +[editor.invalid_error_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$text.3.color" } +highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } + +[editor.invalid_warning_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$text.3.color" } +highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } + +[editor.invalid_information_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$text.3.color" } +highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } + +[editor.invalid_hint_diagnostic.message] +text = { extends = "$editor.text", size = 14, color = "$text.3.color" } +highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } + [project_diagnostics] background = "$surface.1" empty_message = "$text.0" From 121b45e249d6b5a54f05d31677a830e9beb38385 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 25 Jan 2022 17:49:50 +0100 Subject: [PATCH 07/19] Sanitize language server diagnostics coming from Rust Co-Authored-By: Nathan Sobo --- Cargo.lock | 24 ++++++++++++------------ crates/language/src/language.rs | 17 +++++++++++++++++ crates/project/src/project.rs | 3 ++- crates/zed/Cargo.toml | 1 + crates/zed/src/language.rs | 27 +++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc7b6921a7ab67126cfbeab322582b9ded3d6f0a..9fcf1c4d63685a5f1f7e5b752caa2add0e258754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -2814,9 +2814,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" @@ -2922,9 +2922,9 @@ dependencies = [ [[package]] name = "nom" -version = "6.2.1" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", "funty", @@ -3765,21 +3765,20 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.22" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" @@ -4451,7 +4450,7 @@ checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c" dependencies = [ "lazy_static", "maplit", - "nom 6.2.1", + "nom 6.1.2", "regex", "unicode_categories", ] @@ -5748,6 +5747,7 @@ dependencies = [ "project", "project_panel", "rand 0.8.3", + "regex", "rpc", "rsa", "rust-embed", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 72b883df0cea0ffa02c3dfdce29da2892f1ed08b..91d774f2a436cb3a2d630484d5a8765ee9daa650 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -39,6 +39,10 @@ pub trait ToPointUtf16 { fn to_point_utf16(self) -> PointUtf16; } +pub trait DiagnosticProcessor: 'static + Send + Sync { + fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams); +} + #[derive(Default, Deserialize)] pub struct LanguageConfig { pub name: String, @@ -69,6 +73,7 @@ pub struct BracketPair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, + pub(crate) diagnostic_processor: Option>, } pub struct Grammar { @@ -135,6 +140,7 @@ impl Language { highlight_map: Default::default(), }) }), + diagnostic_processor: None, } } @@ -178,6 +184,11 @@ impl Language { Ok(self) } + pub fn with_diagnostics_processor(mut self, processor: impl DiagnosticProcessor) -> Self { + self.diagnostic_processor = Some(Box::new(processor)); + self + } + pub fn name(&self) -> &str { self.config.name.as_str() } @@ -225,6 +236,12 @@ impl Language { .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref()) } + pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { + if let Some(processor) = self.diagnostic_processor.as_ref() { + processor.process_diagnostics(diagnostics); + } + } + pub fn brackets(&self) -> &[BracketPair] { &self.config.brackets } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8cba48132cb46744013ccb8be1c783985a40277c..e94c78ed490d1a1fa88dc4e9e417e412b4709130 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -822,7 +822,8 @@ impl Project { send.await.log_err(); } } - LspEvent::DiagnosticsUpdate(params) => { + LspEvent::DiagnosticsUpdate(mut params) => { + language.process_diagnostics(&mut params); this.update(&mut cx, |this, cx| { this.update_diagnostics(params, &disk_based_sources, cx) .log_err(); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 6ad90e8f6ec2c2bb4bb4f818ae1b45b2587473f8..d288d2288562730a88fff70fe5337e24b36fb025 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -73,6 +73,7 @@ num_cpus = "1.13.0" parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } rand = "0.8.3" +regex = "1.5" rsa = "0.4" rust-embed = { version = "6.2", features = ["include-exclude"] } serde = { version = "1", features = ["derive"] } diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 98f6ab93d27675f391ee9164649490a055d3066e..90ce03f57a0725777eac208e00347c6369bdd8e9 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -1,4 +1,6 @@ pub use language::*; +use lazy_static::lazy_static; +use regex::Regex; use rust_embed::RustEmbed; use std::borrow::Cow; use std::{str, sync::Arc}; @@ -7,6 +9,30 @@ use std::{str, sync::Arc}; #[folder = "languages"] struct LanguageDir; +struct RustDiagnosticProcessor; + +impl DiagnosticProcessor for RustDiagnosticProcessor { + fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) { + lazy_static! { + static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`").unwrap(); + } + + for diagnostic in &mut params.diagnostics { + for message in diagnostic + .related_information + .iter_mut() + .flatten() + .map(|info| &mut info.message) + .chain([&mut diagnostic.message]) + { + if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") { + *message = sanitized; + } + } + } + } +} + pub fn build_language_registry() -> LanguageRegistry { let mut languages = LanguageRegistry::default(); languages.add(Arc::new(rust())); @@ -26,6 +52,7 @@ fn rust() -> Language { .unwrap() .with_outline_query(load_query("rust/outline.scm").as_ref()) .unwrap() + .with_diagnostics_processor(RustDiagnosticProcessor) } fn markdown() -> Language { From 0c6fd157eefa3d297bdd4be157f027902ad9bacd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 25 Jan 2022 12:40:53 -0700 Subject: [PATCH 08/19] Scale diagnostic headers icons with editor font and right align w/ line numbers Co-Authored-By: Max Brunsfeld --- crates/diagnostics/src/diagnostics.rs | 15 +++++++++------ crates/editor/src/display_map/block_map.rs | 8 ++++++-- crates/editor/src/editor.rs | 3 ++- crates/editor/src/element.rs | 14 ++++++++++---- crates/theme/src/theme.rs | 13 ++++--------- crates/zed/assets/themes/_base.toml | 3 ++- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index dd32b5beff62ca5b9facd488b68071f20b3548d3..05c8799b751d80b35428cdb04388a2088657c78a 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -688,7 +688,7 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin .left() .contained() .with_style(style.container) - .with_padding_left(cx.line_number_x) + .with_padding_left(cx.gutter_padding) .expanded() .named("path header block") }) @@ -702,6 +702,7 @@ fn diagnostic_header_renderer( Arc::new(move |cx| { let settings = build_settings(cx); let style = &settings.style.diagnostic_header; + let icon_width = cx.em_width * style.icon_width_factor; let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/diagnostic-error-10.svg") .with_color(settings.style.error_diagnostic.message.text.color) @@ -713,10 +714,9 @@ fn diagnostic_header_renderer( Flex::row() .with_child( icon.constrained() - .with_height(style.icon.width) + .with_width(icon_width) .aligned() .contained() - .with_style(style.icon.container) .boxed(), ) .with_child( @@ -724,6 +724,7 @@ fn diagnostic_header_renderer( .with_highlights(highlights.clone()) .contained() .with_style(style.message.container) + .with_margin_left(cx.gutter_padding) .aligned() .boxed(), ) @@ -736,7 +737,7 @@ fn diagnostic_header_renderer( })) .contained() .with_style(style.container) - .with_padding_left(cx.line_number_x) + .with_padding_left(cx.gutter_width - cx.gutter_padding - icon_width) .expanded() .named("diagnostic header") }) @@ -748,7 +749,7 @@ fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock { let text_style = settings.style.text.clone(); Label::new("…".to_string(), text_style) .contained() - .with_padding_left(cx.line_number_x) + .with_padding_left(cx.gutter_padding) .named("collapsed context") }) } @@ -1194,7 +1195,9 @@ mod tests { .render(&BlockContext { cx, anchor_x: 0., - line_number_x: 0., + gutter_padding: 0., + gutter_width: 0., + em_width: 0., }) .name() .map(|s| (row, s.to_string())) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index d660307eb19059d33954e7897fd3dc5d82c96a83..fa39e07f1336fcf4e4c3c759310d1cb15c901b08 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -69,7 +69,9 @@ where pub struct BlockContext<'a> { pub cx: &'a AppContext, pub anchor_x: f32, - pub line_number_x: f32, + pub gutter_width: f32, + pub gutter_padding: f32, + pub em_width: f32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -947,7 +949,9 @@ mod tests { .render(&BlockContext { cx, anchor_x: 0., - line_number_x: 0., + gutter_padding: 0., + gutter_width: 0., + em_width: 0., }) .name() .unwrap() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 847fe897331f10323119ecf3e44fe6288302dadd..7278df73d0d78766adb68c48ad501b25f6e7718b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3834,6 +3834,7 @@ impl EditorSettings { placeholder_text: None, background: Default::default(), gutter_background: Default::default(), + gutter_padding_factor: 2., active_line_background: Default::default(), highlighted_line_background: Default::default(), line_number: Default::default(), @@ -3862,7 +3863,7 @@ impl EditorSettings { container: Default::default(), text: text.clone(), }, - icon: Default::default(), + icon_width_factor: Default::default(), }, error_diagnostic: default_diagnostic_style.clone(), invalid_error_diagnostic: default_diagnostic_style.clone(), diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 44f6f06b1a75f1eb725ceb75b2efc5c34cf94f27..b097ea9d4022669a86e906babb6289048659b857 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -586,7 +586,9 @@ impl EditorElement { rows: Range, snapshot: &EditorSnapshot, width: f32, - line_number_x: f32, + gutter_padding: f32, + gutter_width: f32, + em_width: f32, text_x: f32, line_height: f32, style: &EditorStyle, @@ -614,7 +616,9 @@ impl EditorElement { let mut element = block.render(&BlockContext { cx, anchor_x, - line_number_x, + gutter_padding, + gutter_width, + em_width, }); element.layout( SizeConstraint { @@ -645,12 +649,12 @@ impl Element for EditorElement { let snapshot = self.snapshot(cx.app); let style = self.settings.style.clone(); - let line_height = style.text.line_height(cx.font_cache); + let line_height = dbg!(style.text.line_height(cx.font_cache)); let gutter_padding; let gutter_width; if snapshot.mode == EditorMode::Full { - gutter_padding = style.text.em_width(cx.font_cache); + gutter_padding = style.text.em_width(cx.font_cache) * style.gutter_padding_factor; gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; } else { gutter_padding = 0.0; @@ -781,6 +785,8 @@ impl Element for EditorElement { &snapshot, size.x(), gutter_padding, + gutter_width, + em_width, gutter_width + text_offset.x(), line_height, &style, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 60a08015b5a85d3feaf56b827c224736e563f7b7..ffe3600353291d1efc6d74990265350165ca4c9b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -245,6 +245,7 @@ pub struct EditorStyle { pub background: Color, pub selection: SelectionStyle, pub gutter_background: Color, + pub gutter_padding_factor: f32, pub active_line_background: Color, pub highlighted_line_background: Color, pub line_number: Color, @@ -277,14 +278,7 @@ pub struct DiagnosticHeader { pub container: ContainerStyle, pub message: ContainedLabel, pub code: ContainedText, - pub icon: DiagnosticHeaderIcon, -} - -#[derive(Clone, Deserialize, Default)] -pub struct DiagnosticHeaderIcon { - #[serde(flatten)] - pub container: ContainerStyle, - pub width: f32, + pub icon_width_factor: f32, } #[derive(Clone, Deserialize, Default)] @@ -340,6 +334,7 @@ impl InputEditorStyle { .unwrap_or(Color::transparent_black()), selection: self.selection, gutter_background: Default::default(), + gutter_padding_factor: Default::default(), active_line_background: Default::default(), highlighted_line_background: Default::default(), line_number: Default::default(), @@ -367,7 +362,7 @@ impl InputEditorStyle { container: Default::default(), text: self.text.clone(), }, - icon: Default::default(), + icon_width_factor: Default::default(), }, error_diagnostic: default_diagnostic_style.clone(), invalid_error_diagnostic: default_diagnostic_style.clone(), diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index ae3d5e1417d713013cfd77c79f625f347db278bf..37eb555ad09ee2fd954d9e31621344e59434af90 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -244,6 +244,7 @@ text = "$text.0" text = "$text.1" background = "$surface.1" gutter_background = "$surface.1" +gutter_padding_factor = 2.5 active_line_background = "$state.active_line" highlighted_line_background = "$state.highlighted_line" line_number = "$text.2.color" @@ -260,7 +261,7 @@ path = { extends = "$text.2", size = 14, margin.left = 12 } background = "$state.active_line" border = { width = 1, top = true, bottom = true, color = "$border.0" } code = { extends = "$text.2", size = 14, margin.left = 10 } -icon = { width = 10, margin.right = 8 } +icon_width_factor = 1.5 [editor.diagnostic_header.message] text = { extends = "$text.1", size = 14 } From 78b57e4d9d70a117f95a8267bf680e16d9163f86 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Jan 2022 12:10:11 -0800 Subject: [PATCH 09/19] Scale diagnostic header and message text with editor font Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 25 ++++++----- crates/editor/src/editor.rs | 22 ++++++--- crates/editor/src/element.rs | 2 +- crates/gpui/src/elements/label.rs | 7 +++ crates/gpui/src/fonts.rs | 5 +++ crates/theme/src/theme.rs | 6 +++ crates/zed/assets/themes/_base.toml | 65 ++++++++++++++------------- 7 files changed, 82 insertions(+), 50 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 05c8799b751d80b35428cdb04388a2088657c78a..6b168ea1703e008a8eada8d08501b7776f78ef14 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -654,6 +654,7 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin Arc::new(move |cx| { let settings = build_settings(cx); let style = settings.style.diagnostic_path_header; + let font_size = (style.text_scale_factor * settings.style.text.font_size).round(); let mut filename = None; let mut path = None; @@ -672,14 +673,14 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin .with_child( Label::new( filename.unwrap_or_else(|| "untitled".to_string()), - style.filename.text.clone(), + style.filename.text.clone().with_font_size(font_size), ) .contained() .with_style(style.filename.container) .boxed(), ) .with_children(path.map(|path| { - Label::new(path, style.path.text.clone()) + Label::new(path, style.path.text.clone().with_font_size(font_size)) .contained() .with_style(style.path.container) .boxed() @@ -702,6 +703,7 @@ fn diagnostic_header_renderer( Arc::new(move |cx| { let settings = build_settings(cx); let style = &settings.style.diagnostic_header; + let font_size = (style.text_scale_factor * settings.style.text.font_size).round(); let icon_width = cx.em_width * style.icon_width_factor; let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/diagnostic-error-10.svg") @@ -720,16 +722,19 @@ fn diagnostic_header_renderer( .boxed(), ) .with_child( - Label::new(message.clone(), style.message.label.clone()) - .with_highlights(highlights.clone()) - .contained() - .with_style(style.message.container) - .with_margin_left(cx.gutter_padding) - .aligned() - .boxed(), + Label::new( + message.clone(), + style.message.label.clone().with_font_size(font_size), + ) + .with_highlights(highlights.clone()) + .contained() + .with_style(style.message.container) + .with_margin_left(cx.gutter_padding) + .aligned() + .boxed(), ) .with_children(diagnostic.code.clone().map(|code| { - Label::new(code, style.code.text.clone()) + Label::new(code, style.code.text.clone().with_font_size(font_size)) .contained() .with_style(style.code.container) .aligned() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7278df73d0d78766adb68c48ad501b25f6e7718b..323f9ce3c40f2c252249b83c7d1c0b60f7991c42 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3828,6 +3828,7 @@ impl EditorSettings { let default_diagnostic_style = DiagnosticStyle { message: text.clone().into(), header: Default::default(), + text_scale_factor: 1., }; EditorStyle { text: text.clone(), @@ -3852,6 +3853,7 @@ impl EditorSettings { container: Default::default(), text: text.clone(), }, + text_scale_factor: 1., }, diagnostic_header: DiagnosticHeader { container: Default::default(), @@ -3863,7 +3865,8 @@ impl EditorSettings { container: Default::default(), text: text.clone(), }, - icon_width_factor: Default::default(), + icon_width_factor: 1., + text_scale_factor: 1., }, error_diagnostic: default_diagnostic_style.clone(), invalid_error_diagnostic: default_diagnostic_style.clone(), @@ -4020,14 +4023,18 @@ pub fn diagnostic_block_renderer( Arc::new(move |cx: &BlockContext| { let settings = build_settings(cx); - let style = diagnostic_style(diagnostic.severity, is_valid, &settings.style).message; + let style = diagnostic_style(diagnostic.severity, is_valid, &settings.style); + let font_size = (style.text_scale_factor * settings.style.text.font_size).round(); Flex::column() .with_children(highlighted_lines.iter().map(|(line, highlights)| { - Label::new(line.clone(), style.clone()) - .with_highlights(highlights.clone()) - .contained() - .with_margin_left(cx.anchor_x) - .boxed() + Label::new( + line.clone(), + style.message.clone().with_font_size(font_size), + ) + .with_highlights(highlights.clone()) + .contained() + .with_margin_left(cx.anchor_x) + .boxed() })) .aligned() .left() @@ -4074,6 +4081,7 @@ pub fn diagnostic_style( _ => DiagnosticStyle { message: style.text.clone().into(), header: Default::default(), + text_scale_factor: 1., }, } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b097ea9d4022669a86e906babb6289048659b857..b4175658c8c8be680c38796fd5588b67c6dc73ae 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -649,7 +649,7 @@ impl Element for EditorElement { let snapshot = self.snapshot(cx.app); let style = self.settings.style.clone(); - let line_height = dbg!(style.text.line_height(cx.font_cache)); + let line_height = style.text.line_height(cx.font_cache); let gutter_padding; let gutter_width; diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs index f78e3973e9670884594d6c82201c379638c2d3d3..20180c380d3fca9eb6e114022aa2a8cd6ba1125a 100644 --- a/crates/gpui/src/elements/label.rs +++ b/crates/gpui/src/elements/label.rs @@ -33,6 +33,13 @@ impl From for LabelStyle { } } +impl LabelStyle { + pub fn with_font_size(mut self, font_size: f32) -> Self { + self.text.font_size = font_size; + self + } +} + impl Label { pub fn new(text: String, style: impl Into) -> Self { Self { diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 25e16b717065d0e701e588fd5c3ec2c8c8a5a9fe..61082f6aaed686e631a679c815c7c9515df2f6b9 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -107,6 +107,11 @@ impl TextStyle { }) } + pub fn with_font_size(mut self, font_size: f32) -> Self { + self.font_size = font_size; + self + } + pub fn to_run(&self) -> RunStyle { RunStyle { font_id: self.font_id, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ffe3600353291d1efc6d74990265350165ca4c9b..5d6142b9e408838ce113bf6c6b94c7b9a438e5da 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -270,6 +270,7 @@ pub struct DiagnosticPathHeader { pub container: ContainerStyle, pub filename: ContainedText, pub path: ContainedText, + pub text_scale_factor: f32, } #[derive(Clone, Deserialize, Default)] @@ -278,6 +279,7 @@ pub struct DiagnosticHeader { pub container: ContainerStyle, pub message: ContainedLabel, pub code: ContainedText, + pub text_scale_factor: f32, pub icon_width_factor: f32, } @@ -286,6 +288,7 @@ pub struct DiagnosticStyle { pub message: LabelStyle, #[serde(default)] pub header: ContainerStyle, + pub text_scale_factor: f32, } #[derive(Clone, Copy, Default, Deserialize)] @@ -324,6 +327,7 @@ impl InputEditorStyle { let default_diagnostic_style = DiagnosticStyle { message: self.text.clone().into(), header: Default::default(), + text_scale_factor: 1., }; EditorStyle { text: self.text.clone(), @@ -351,6 +355,7 @@ impl InputEditorStyle { container: Default::default(), text: self.text.clone(), }, + text_scale_factor: 1., }, diagnostic_header: DiagnosticHeader { container: Default::default(), @@ -363,6 +368,7 @@ impl InputEditorStyle { text: self.text.clone(), }, icon_width_factor: Default::default(), + text_scale_factor: 1., }, error_diagnostic: default_diagnostic_style.clone(), invalid_error_diagnostic: default_diagnostic_style.clone(), diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 37eb555ad09ee2fd954d9e31621344e59434af90..b9858b504fd75c6938e09d5c08743442fe42971d 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -187,7 +187,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -256,12 +256,14 @@ error_color = "$status.bad" [editor.diagnostic_path_header] filename = { extends = "$text.0", size = 14 } path = { extends = "$text.2", size = 14, margin.left = 12 } +text_scale_factor = 0.857 [editor.diagnostic_header] background = "$state.active_line" border = { width = 1, top = true, bottom = true, color = "$border.0" } code = { extends = "$text.2", size = 14, margin.left = 10 } icon_width_factor = 1.5 +text_scale_factor = 0.857 [editor.diagnostic_header.message] text = { extends = "$text.1", size = 14 } @@ -269,47 +271,46 @@ highlight_text = { extends = "$text.0", size = 14, weight = "bold" } [editor.error_diagnostic] header.border = { width = 1, top = true, color = "$border.0" } +text_scale_factor = 0.857 [editor.error_diagnostic.message] text = { extends = "$editor.text", size = 14, color = "$status.bad" } highlight_text = { extends = "$editor.text", size = 14, color = "$status.bad", weight = "bold" } [editor.warning_diagnostic] -header.border = { width = 1, top = true, color = "$border.0" } - -[editor.warning_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$status.warn" } -highlight_text = { extends = "$editor.text", size = 14, color = "$status.warn", weight = "bold" } +extends = "$editor.error_diagnostic" +message.text.color = "$status.warn" +message.highlight_text.color = "$status.warn" [editor.information_diagnostic] -border = { width = 1, top = true, color = "$border.0" } - -[editor.information_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$status.info" } -highlight_text = { extends = "$editor.text", size = 14, color = "$status.info", weight = "bold" } +extends = "$editor.error_diagnostic" +message.text.color = "$status.info" +message.highlight_text.color = "$status.info" [editor.hint_diagnostic] -border = { width = 1, top = true, color = "$border.0" } - -[editor.hint_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$status.info" } -highlight_text = { extends = "$editor.text", size = 14, color = "$status.info", weight = "bold" } - -[editor.invalid_error_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$text.3.color" } -highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } - -[editor.invalid_warning_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$text.3.color" } -highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } - -[editor.invalid_information_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$text.3.color" } -highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } - -[editor.invalid_hint_diagnostic.message] -text = { extends = "$editor.text", size = 14, color = "$text.3.color" } -highlight_text = { extends = "$editor.text", size = 14, color = "$text.3.color", weight = "bold" } +extends = "$editor.error_diagnostic" +message.text.color = "$status.info" +message.highlight_text.color = "$status.info" + +[editor.invalid_error_diagnostic] +extends = "$editor.error_diagnostic" +message.text.color = "$text.3.color" +message.highlight_text.color = "$text.3.color" + +[editor.invalid_warning_diagnostic] +extends = "$editor.warning_diagnostic" +message.text.color = "$text.3.color" +message.highlight_text.color = "$text.3.color" + +[editor.invalid_information_diagnostic] +extends = "$editor.information_diagnostic" +message.text.color = "$text.3.color" +message.highlight_text.color = "$text.3.color" + +[editor.invalid_hint_diagnostic] +extends = "$editor.hint_diagnostic" +message.text.color = "$text.3.color" +message.highlight_text.color = "$text.3.color" [project_diagnostics] background = "$surface.1" From dbe969893c061084c67224cd8eb3d6e0de6ee4c1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Jan 2022 12:31:55 -0800 Subject: [PATCH 10/19] Show icons and counts in the project diagnostics tab Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 4 ++-- crates/editor/src/items.rs | 23 ++++--------------- .../assets/icons/diagnostic-summary-error.svg | 3 +++ .../icons/diagnostic-summary-warning.svg | 3 +++ crates/zed/assets/icons/no.svg | 4 ---- crates/zed/assets/icons/warning.svg | 3 --- crates/zed/assets/themes/_base.toml | 4 ++-- 7 files changed, 15 insertions(+), 29 deletions(-) create mode 100644 crates/zed/assets/icons/diagnostic-summary-error.svg create mode 100644 crates/zed/assets/icons/diagnostic-summary-warning.svg delete mode 100644 crates/zed/assets/icons/no.svg delete mode 100644 crates/zed/assets/icons/warning.svg diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index a6c351002dfaf300d752a2e1718e4628728b71ea..ba3043871f3dd471d3f8727890c1bf577fa7bb52 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -563,7 +563,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { let summary_spacing = theme.tab_summary_spacing; Flex::row() .with_children([ - Svg::new("icons/no.svg") + Svg::new("icons/diagnostic-summary-error.svg") .with_color(style.label.text.color) .constrained() .with_width(icon_width) @@ -574,7 +574,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { Label::new(self.summary.error_count.to_string(), style.label.clone()) .aligned() .boxed(), - Svg::new("icons/warning.svg") + Svg::new("icons/diagnostic-summary-warning.svg") .with_color(style.label.text.color) .constrained() .with_width(icon_width) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 82a398f6bec6e4ec4d500af7bccb8db8702d74e4..3e05fcff431217315c673adc403d06f402faf802 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -330,24 +330,11 @@ impl View for DiagnosticMessage { fn render(&mut self, _: &mut RenderContext) -> ElementBox { if let Some(diagnostic) = &self.diagnostic { let theme = &self.settings.borrow().theme.workspace.status_bar; - Flex::row() - .with_child( - Svg::new("icons/warning.svg") - .with_color(theme.diagnostic_icon_color) - .constrained() - .with_height(theme.diagnostic_icon_size) - .contained() - .with_margin_right(theme.diagnostic_icon_spacing) - .boxed(), - ) - .with_child( - Label::new( - diagnostic.message.lines().next().unwrap().to_string(), - theme.diagnostic_message.clone(), - ) - .boxed(), - ) - .boxed() + Label::new( + diagnostic.message.lines().next().unwrap().to_string(), + theme.diagnostic_message.clone(), + ) + .boxed() } else { Empty::new().boxed() } diff --git a/crates/zed/assets/icons/diagnostic-summary-error.svg b/crates/zed/assets/icons/diagnostic-summary-error.svg new file mode 100644 index 0000000000000000000000000000000000000000..0180762a9eddb2e47e9a75d7c005e0d18c3dc04f --- /dev/null +++ b/crates/zed/assets/icons/diagnostic-summary-error.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/zed/assets/icons/diagnostic-summary-warning.svg b/crates/zed/assets/icons/diagnostic-summary-warning.svg new file mode 100644 index 0000000000000000000000000000000000000000..fead4db839ec0875a6ee284b95c3e413b5fc9045 --- /dev/null +++ b/crates/zed/assets/icons/diagnostic-summary-warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/zed/assets/icons/no.svg b/crates/zed/assets/icons/no.svg deleted file mode 100644 index 799a6dcc0fc8e243bbb89bd11b5e556194c7d9be..0000000000000000000000000000000000000000 --- a/crates/zed/assets/icons/no.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/crates/zed/assets/icons/warning.svg b/crates/zed/assets/icons/warning.svg deleted file mode 100644 index 845d07a15ae8ca3abbcfaaf4feaabfc623117eff..0000000000000000000000000000000000000000 --- a/crates/zed/assets/icons/warning.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 37c878969ca4e8d3e283a726b6b2dcb1f7f87a83..42a4aa0372a9192a9a1ff9b2ef6e0c78c8e07987 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -316,6 +316,6 @@ message.highlight_text.color = "$text.3.color" background = "$surface.1" empty_message = "$text.0" status_bar_item = { extends = "$text.2", margin.right = 10 } -tab_icon_width = 9 -tab_icon_spacing = 3 +tab_icon_width = 13 +tab_icon_spacing = 4 tab_summary_spacing = 10 From e04e929010a2e2f7ff6730637a343ba28c9a6948 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Jan 2022 15:33:34 -0800 Subject: [PATCH 11/19] Render diagnostic status bar icon with icons --- crates/diagnostics/src/diagnostics.rs | 93 +++++++++++++++++---------- crates/diagnostics/src/items.rs | 25 ++++--- crates/zed/assets/themes/_base.toml | 4 +- 3 files changed, 72 insertions(+), 50 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ba3043871f3dd471d3f8727890c1bf577fa7bb52..1099330f6e82ad8b22b6269c0d3e70d5adf73c65 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -10,8 +10,9 @@ use editor::{ Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, }; use gpui::{ - action, elements::*, keymap::Binding, AnyViewHandle, AppContext, Entity, ModelHandle, - MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, + action, elements::*, fonts::TextStyle, keymap::Binding, AnyViewHandle, AppContext, Entity, + ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, + WeakViewHandle, }; use language::{ Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, @@ -131,9 +132,10 @@ impl ProjectDiagnosticsEditor { let project = model.read(cx).project.clone(); cx.subscribe(&project, |this, _, event, cx| match event { project::Event::DiskBasedDiagnosticsFinished => { + this.summary = this.model.read(cx).project.read(cx).diagnostic_summary(cx); let paths = mem::take(&mut this.paths_to_update); this.update_excerpts(paths, cx); - cx.emit(Event::TitleChanged) + cx.emit(Event::TitleChanged); } project::Event::DiagnosticsUpdated(path) => { this.paths_to_update.insert(path.clone()); @@ -557,37 +559,11 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { } fn tab_content(&self, style: &theme::Tab, _: &AppContext) -> ElementBox { - let theme = &self.settings.borrow().theme.project_diagnostics; - let icon_width = theme.tab_icon_width; - let icon_spacing = theme.tab_icon_spacing; - let summary_spacing = theme.tab_summary_spacing; - Flex::row() - .with_children([ - Svg::new("icons/diagnostic-summary-error.svg") - .with_color(style.label.text.color) - .constrained() - .with_width(icon_width) - .aligned() - .contained() - .with_margin_right(icon_spacing) - .named("no-icon"), - Label::new(self.summary.error_count.to_string(), style.label.clone()) - .aligned() - .boxed(), - Svg::new("icons/diagnostic-summary-warning.svg") - .with_color(style.label.text.color) - .constrained() - .with_width(icon_width) - .aligned() - .contained() - .with_margin_left(summary_spacing) - .with_margin_right(icon_spacing) - .named("warn-icon"), - Label::new(self.summary.warning_count.to_string(), style.label.clone()) - .aligned() - .boxed(), - ]) - .boxed() + render_summary( + &self.summary, + &style.label.text, + &self.settings.borrow().theme.project_diagnostics, + ) } fn project_path(&self, _: &AppContext) -> Option { @@ -786,6 +762,55 @@ fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock { }) } +pub(crate) fn render_summary( + summary: &DiagnosticSummary, + text_style: &TextStyle, + theme: &theme::ProjectDiagnostics, +) -> ElementBox { + let icon_width = theme.tab_icon_width; + let icon_spacing = theme.tab_icon_spacing; + let summary_spacing = theme.tab_summary_spacing; + Flex::row() + .with_children([ + Svg::new("icons/diagnostic-summary-error.svg") + .with_color(text_style.color) + .constrained() + .with_width(icon_width) + .aligned() + .contained() + .with_margin_right(icon_spacing) + .named("no-icon"), + Label::new( + summary.error_count.to_string(), + LabelStyle { + text: text_style.clone(), + highlight_text: None, + }, + ) + .aligned() + .boxed(), + Svg::new("icons/diagnostic-summary-warning.svg") + .with_color(text_style.color) + .constrained() + .with_width(icon_width) + .aligned() + .contained() + .with_margin_left(summary_spacing) + .with_margin_right(icon_spacing) + .named("warn-icon"), + Label::new( + summary.warning_count.to_string(), + LabelStyle { + text: text_style.clone(), + highlight_text: None, + }, + ) + .aligned() + .boxed(), + ]) + .boxed() +} + fn compare_diagnostics( lhs: &DiagnosticEntry, rhs: &DiagnosticEntry, diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 3390f74a849c41cbc1268e4c05e2da6d99a18113..80291cde3dc58d36dbeeb84a144f9e271ffd43e0 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -1,9 +1,9 @@ +use crate::render_summary; use gpui::{ elements::*, platform::CursorStyle, Entity, ModelHandle, RenderContext, View, ViewContext, }; use postage::watch; use project::Project; -use std::fmt::Write; use workspace::{Settings, StatusItemView}; pub struct DiagnosticSummary { @@ -20,7 +20,6 @@ impl DiagnosticSummary { ) -> Self { cx.subscribe(project, |this, project, event, cx| match event { project::Event::DiskBasedDiagnosticsUpdated => { - this.summary = project.read(cx).diagnostic_summary(cx); cx.notify(); } project::Event::DiskBasedDiagnosticsStarted => { @@ -28,6 +27,7 @@ impl DiagnosticSummary { cx.notify(); } project::Event::DiskBasedDiagnosticsFinished => { + this.summary = project.read(cx).diagnostic_summary(cx); this.in_progress = false; cx.notify(); } @@ -55,21 +55,20 @@ impl View for DiagnosticSummary { enum Tag {} let theme = &self.settings.borrow().theme.project_diagnostics; - let mut message = String::new(); - if self.in_progress { - message.push_str("Checking... "); - } - write!( - message, - "Errors: {}, Warnings: {}", - self.summary.error_count, self.summary.warning_count - ) - .unwrap(); + + let in_progress = self.in_progress; MouseEventHandler::new::(0, cx, |_, _| { - Label::new(message, theme.status_bar_item.text.clone()) + if in_progress { + Label::new( + "Checking... ".to_string(), + theme.status_bar_item.text.clone(), + ) .contained() .with_style(theme.status_bar_item.container) .boxed() + } else { + render_summary(&self.summary, &theme.status_bar_item.text, &theme) + } }) .with_cursor_style(CursorStyle::PointingHand) .on_click(|cx| cx.dispatch_action(crate::Deploy)) diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 42a4aa0372a9192a9a1ff9b2ef6e0c78c8e07987..ddd4d60110ff586e803a75177e8bb16af400d195 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -77,11 +77,9 @@ border = { width = 1, color = "$border.0", left = true } [workspace.status_bar] padding = { left = 6, right = 6 } height = 24 +item_spacing = 24 cursor_position = "$text.2" diagnostic_message = "$text.2" -diagnostic_icon_size = 18 -diagnostic_icon_spacing = 8 -diagnostic_icon_color = "$text.2.color" [panel] padding = { top = 12, left = 12, bottom = 12, right = 12 } From cefb63936d3f659fced09799cb298fba39f41eed Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Jan 2022 15:33:49 -0800 Subject: [PATCH 12/19] Tweak diagnostic header styling Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 3 ++- crates/editor/src/display_map/block_map.rs | 2 ++ crates/editor/src/element.rs | 1 + crates/editor/src/items.rs | 2 ++ crates/theme/src/theme.rs | 4 +--- crates/zed/assets/themes/_base.toml | 4 ++-- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 1099330f6e82ad8b22b6269c0d3e70d5adf73c65..ea5441b01ecc1bae4de1e49d15ffa559066bef69 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -745,7 +745,7 @@ fn diagnostic_header_renderer( })) .contained() .with_style(style.container) - .with_padding_left(cx.gutter_width - cx.gutter_padding - icon_width) + .with_padding_left(cx.gutter_padding) .expanded() .named("diagnostic header") }) @@ -1254,6 +1254,7 @@ mod tests { anchor_x: 0., gutter_padding: 0., gutter_width: 0., + line_height: 0., em_width: 0., }) .name() diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index fa39e07f1336fcf4e4c3c759310d1cb15c901b08..5f3c8dc83e9b9525ed1e0fc927cd37018fd09824 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -72,6 +72,7 @@ pub struct BlockContext<'a> { pub gutter_width: f32, pub gutter_padding: f32, pub em_width: f32, + pub line_height: f32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -951,6 +952,7 @@ mod tests { anchor_x: 0., gutter_padding: 0., gutter_width: 0., + line_height: 0., em_width: 0., }) .name() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b4175658c8c8be680c38796fd5588b67c6dc73ae..7102c546f52fa83491ec9de78bd09e56d0df38cf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -617,6 +617,7 @@ impl EditorElement { cx, anchor_x, gutter_padding, + line_height, gutter_width, em_width, }); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 3e05fcff431217315c673adc403d06f402faf802..1dafec32c6f080dfe2799513840c14ad26eff28e 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -334,6 +334,8 @@ impl View for DiagnosticMessage { diagnostic.message.lines().next().unwrap().to_string(), theme.diagnostic_message.clone(), ) + .contained() + .with_margin_left(theme.item_spacing) .boxed() } else { Empty::new().boxed() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 497e9f9458cbef2051e9a2a050e7175146be9a0c..c3d37b950c86e3dad9e21b92a1e0a538ffe9e3eb 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -109,10 +109,8 @@ pub struct StatusBar { #[serde(flatten)] pub container: ContainerStyle, pub height: f32, + pub item_spacing: f32, pub cursor_position: TextStyle, - pub diagnostic_icon_size: f32, - pub diagnostic_icon_spacing: f32, - pub diagnostic_icon_color: Color, pub diagnostic_message: TextStyle, } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index ddd4d60110ff586e803a75177e8bb16af400d195..3e3f15919ef321f8246e7ea4f914949d10036df3 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -252,13 +252,13 @@ guest_selections = "$selection.guests" error_color = "$status.bad" [editor.diagnostic_path_header] +background = "$state.active_line" filename = { extends = "$text.0", size = 14 } path = { extends = "$text.2", size = 14, margin.left = 12 } text_scale_factor = 0.857 [editor.diagnostic_header] -background = "$state.active_line" -border = { width = 1, top = true, bottom = true, color = "$border.0" } +border = { width = 1, top = true, bottom = true, color = "#ffffff1c" } code = { extends = "$text.2", size = 14, margin.left = 10 } icon_width_factor = 1.5 text_scale_factor = 0.857 From f19934096ae4107d7a4056380fa14866048537fe Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Jan 2022 16:31:14 -0800 Subject: [PATCH 13/19] Account for editor's full scroll width when laying out blocks Compute the editor's scroll width earlier in layout and store it on the layout state, rather than computing it repeatedly. --- crates/editor/src/element.rs | 50 +++++++++++++++--------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7102c546f52fa83491ec9de78bd09e56d0df38cf..fbe7183e4cf6da6c5d4fea4566b160d10880e86a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -141,17 +141,14 @@ impl EditorElement { } let font_cache = cx.font_cache.clone(); - let text_layout_cache = cx.text_layout_cache.clone(); let snapshot = self.snapshot(cx.app); let (position, overshoot) = paint.point_for_position(&snapshot, layout, position); cx.dispatch_action(Select(SelectPhase::Update { position, overshoot, - scroll_position: (snapshot.scroll_position() + scroll_delta).clamp( - Vector2F::zero(), - layout.scroll_max(&font_cache, &text_layout_cache), - ), + scroll_position: (snapshot.scroll_position() + scroll_delta) + .clamp(Vector2F::zero(), layout.scroll_max(&font_cache)), })); true } else { @@ -193,7 +190,6 @@ impl EditorElement { let snapshot = self.snapshot(cx.app); let font_cache = &cx.font_cache; - let layout_cache = &cx.text_layout_cache; let max_glyph_width = layout.em_width; if !precise { delta *= vec2f(max_glyph_width, layout.line_height); @@ -202,10 +198,7 @@ impl EditorElement { let scroll_position = snapshot.scroll_position(); let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width; let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height; - let scroll_position = vec2f(x, y).clamp( - Vector2F::zero(), - layout.scroll_max(font_cache, layout_cache), - ); + let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max(font_cache)); cx.dispatch_action(Scroll(scroll_position)); @@ -781,10 +774,21 @@ impl Element for EditorElement { } } + let style = self.settings.style.clone(); + let longest_line_width = layout_line( + snapshot.longest_row(), + &snapshot, + &style, + cx.text_layout_cache, + ) + .width(); + let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x(); + let max_glyph_width = style.text.em_width(&cx.font_cache); + let blocks = self.layout_blocks( start_row..end_row, &snapshot, - size.x(), + size.x().max(scroll_width + gutter_width), gutter_padding, gutter_width, em_width, @@ -797,13 +801,13 @@ impl Element for EditorElement { let mut layout = LayoutState { size, + scroll_width, gutter_size, gutter_padding, text_size, - overscroll, text_offset, snapshot, - style: self.settings.style.clone(), + style, active_rows, highlighted_rows, line_layouts, @@ -813,12 +817,9 @@ impl Element for EditorElement { em_width, em_advance, selections, - 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 = style.text.em_width(&cx.font_cache); + let scroll_max = layout.scroll_max(cx.font_cache).x(); self.update_view(cx.app, |view, cx| { let clamped = view.clamp_scroll_left(scroll_max); let autoscrolled; @@ -930,6 +931,7 @@ impl Element for EditorElement { pub struct LayoutState { size: Vector2F, + scroll_width: f32, gutter_size: Vector2F, gutter_padding: f32, text_size: Vector2F, @@ -944,27 +946,17 @@ pub struct LayoutState { em_width: f32, em_advance: f32, selections: HashMap>>, - overscroll: Vector2F, text_offset: Vector2F, - max_visible_line_width: f32, } impl LayoutState { - fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 { - let row = self.snapshot.longest_row(); - let longest_line_width = - layout_line(row, &self.snapshot, &self.style, 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 { + fn scroll_max(&self, font_cache: &FontCache) -> Vector2F { let text_width = self.text_size.x(); - 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( - ((scroll_width - text_width) / em_width).max(0.0), + ((self.scroll_width - text_width) / em_width).max(0.0), max_row.saturating_sub(1) as f32, ) } From a890787923636194eff0dc4d6a9d6fdd84db66f8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 25 Jan 2022 17:19:38 -0800 Subject: [PATCH 14/19] Render header blocks as horizontall fixed (like the gutter) Tweak editor layout to perform horizontal autoscroll before laying out the blocks, so that they can access the scroll position. --- crates/diagnostics/src/diagnostics.rs | 7 +- crates/editor/src/display_map/block_map.rs | 2 + crates/editor/src/element.rs | 118 ++++++++++----------- 3 files changed, 60 insertions(+), 67 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ea5441b01ecc1bae4de1e49d15ffa559066bef69..299eed77c17f488bda9871de3b46e38ad0e3fe60 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -692,7 +692,7 @@ fn path_header_renderer(buffer: ModelHandle, build_settings: BuildSettin .left() .contained() .with_style(style.container) - .with_padding_left(cx.gutter_padding) + .with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width) .expanded() .named("path header block") }) @@ -745,7 +745,7 @@ fn diagnostic_header_renderer( })) .contained() .with_style(style.container) - .with_padding_left(cx.gutter_padding) + .with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width) .expanded() .named("diagnostic header") }) @@ -757,7 +757,7 @@ fn context_header_renderer(build_settings: BuildSettings) -> RenderBlock { let text_style = settings.style.text.clone(); Label::new("…".to_string(), text_style) .contained() - .with_padding_left(cx.gutter_padding) + .with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width) .named("collapsed context") }) } @@ -1252,6 +1252,7 @@ mod tests { .render(&BlockContext { cx, anchor_x: 0., + scroll_x: 0., gutter_padding: 0., gutter_width: 0., line_height: 0., diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 5f3c8dc83e9b9525ed1e0fc927cd37018fd09824..f00dd8965186034b8cda189670c87b0fed0b9032 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -69,6 +69,7 @@ where pub struct BlockContext<'a> { pub cx: &'a AppContext, pub anchor_x: f32, + pub scroll_x: f32, pub gutter_width: f32, pub gutter_padding: f32, pub em_width: f32, @@ -951,6 +952,7 @@ mod tests { cx, anchor_x: 0., gutter_padding: 0., + scroll_x: 0., gutter_width: 0., line_height: 0., em_width: 0., diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fbe7183e4cf6da6c5d4fea4566b160d10880e86a..ffbefbf035dd47f24baca28d6bb82b53f25b79c6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -17,7 +17,7 @@ use gpui::{ json::{self, ToJson}, keymap::Keystroke, text_layout::{self, RunStyle, TextLayoutCache}, - AppContext, Axis, Border, Element, ElementBox, Event, EventContext, FontCache, LayoutContext, + AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; @@ -140,7 +140,6 @@ impl EditorElement { )) } - let font_cache = cx.font_cache.clone(); let snapshot = self.snapshot(cx.app); let (position, overshoot) = paint.point_for_position(&snapshot, layout, position); @@ -148,7 +147,7 @@ impl EditorElement { position, overshoot, scroll_position: (snapshot.scroll_position() + scroll_delta) - .clamp(Vector2F::zero(), layout.scroll_max(&font_cache)), + .clamp(Vector2F::zero(), layout.scroll_max), })); true } else { @@ -189,7 +188,6 @@ impl EditorElement { } let snapshot = self.snapshot(cx.app); - let font_cache = &cx.font_cache; let max_glyph_width = layout.em_width; if !precise { delta *= vec2f(max_glyph_width, layout.line_height); @@ -198,7 +196,7 @@ impl EditorElement { let scroll_position = snapshot.scroll_position(); let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width; let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height; - let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max(font_cache)); + let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max); cx.dispatch_action(Scroll(scroll_position)); @@ -452,7 +450,7 @@ impl EditorElement { .width() } - fn layout_rows( + fn layout_line_numbers( &self, rows: Range, active_rows: &BTreeMap, @@ -611,6 +609,7 @@ impl EditorElement { anchor_x, gutter_padding, line_height, + scroll_x: snapshot.scroll_position.x(), gutter_width, em_width, }); @@ -764,7 +763,8 @@ impl Element for EditorElement { } }); - let line_number_layouts = self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx); + let line_number_layouts = + self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); let mut max_visible_line_width = 0.0; let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); @@ -783,53 +783,23 @@ impl Element for EditorElement { ) .width(); let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x(); - let max_glyph_width = style.text.em_width(&cx.font_cache); - - let blocks = self.layout_blocks( - start_row..end_row, - &snapshot, - size.x().max(scroll_width + gutter_width), - gutter_padding, - gutter_width, - em_width, - gutter_width + text_offset.x(), - line_height, - &style, - &line_layouts, - cx, + let em_width = style.text.em_width(cx.font_cache); + let max_row = snapshot.max_point().row(); + let scroll_max = vec2f( + ((scroll_width - text_size.x()) / em_width).max(0.0), + max_row.saturating_sub(1) as f32, ); - let mut layout = LayoutState { - size, - scroll_width, - gutter_size, - gutter_padding, - text_size, - text_offset, - snapshot, - style, - active_rows, - highlighted_rows, - line_layouts, - line_number_layouts, - blocks, - line_height, - em_width, - em_advance, - selections, - }; - - let scroll_max = layout.scroll_max(cx.font_cache).x(); self.update_view(cx.app, |view, cx| { - let clamped = view.clamp_scroll_left(scroll_max); + let clamped = view.clamp_scroll_left(scroll_max.x()); let autoscrolled; if autoscroll_horizontally { autoscrolled = view.autoscroll_horizontally( start_row, - layout.text_size.x(), + text_size.x(), scroll_width, - max_glyph_width, - &layout.line_layouts, + em_width, + &line_layouts, cx, ); } else { @@ -837,11 +807,45 @@ impl Element for EditorElement { } if clamped || autoscrolled { - layout.snapshot = view.snapshot(cx); + snapshot = view.snapshot(cx); } }); - (size, Some(layout)) + let blocks = self.layout_blocks( + start_row..end_row, + &snapshot, + size.x().max(scroll_width + gutter_width), + gutter_padding, + gutter_width, + em_width, + gutter_width + text_offset.x(), + line_height, + &style, + &line_layouts, + cx, + ); + + ( + size, + Some(LayoutState { + size, + scroll_max, + gutter_size, + gutter_padding, + text_size, + text_offset, + snapshot, + active_rows, + highlighted_rows, + line_layouts, + line_number_layouts, + blocks, + line_height, + em_width, + em_advance, + selections, + }), + ) } fn paint( @@ -931,11 +935,10 @@ impl Element for EditorElement { pub struct LayoutState { size: Vector2F, - scroll_width: f32, + scroll_max: Vector2F, gutter_size: Vector2F, gutter_padding: f32, text_size: Vector2F, - style: EditorStyle, snapshot: EditorSnapshot, active_rows: BTreeMap, highlighted_rows: Option>, @@ -949,19 +952,6 @@ pub struct LayoutState { text_offset: Vector2F, } -impl LayoutState { - fn scroll_max(&self, font_cache: &FontCache) -> Vector2F { - let text_width = self.text_size.x(); - let em_width = self.style.text.em_width(font_cache); - let max_row = self.snapshot.max_point().row(); - - vec2f( - ((self.scroll_width - text_width) / em_width).max(0.0), - max_row.saturating_sub(1) as f32, - ) - } -} - fn layout_line( row: u32, snapshot: &EditorSnapshot, @@ -1191,7 +1181,7 @@ mod tests { 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_rows(0..6, &Default::default(), &snapshot, &mut layout_cx) + element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx) }); assert_eq!(layouts.len(), 6); } From 52594fe250cc98bde8c05bccaece8a345e14cee0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Jan 2022 13:07:39 +0100 Subject: [PATCH 15/19] WIP: Start on squiggly underlines --- crates/gpui/src/platform/mac/renderer.rs | 79 ++++++++++++++++++- .../gpui/src/platform/mac/shaders/shaders.h | 15 ++++ .../src/platform/mac/shaders/shaders.metal | 46 +++++++++++ crates/gpui/src/text_layout.rs | 4 +- 4 files changed, 141 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index 2a97f4820ccd3da49104bb67a08f6d62718aacdf..b5c563c9e47225c62c456455d76876a96d96c60c 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -26,6 +26,7 @@ pub struct Renderer { sprite_pipeline_state: metal::RenderPipelineState, image_pipeline_state: metal::RenderPipelineState, path_atlas_pipeline_state: metal::RenderPipelineState, + underline_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, instances: metal::Buffer, } @@ -109,6 +110,14 @@ impl Renderer { "path_atlas_fragment", MTLPixelFormat::R16Float, ); + let underline_pipeline_state = build_pipeline_state( + &device, + &library, + "underline", + "underline_vertex", + "underline_fragment", + pixel_format, + ); Self { sprite_cache, image_cache, @@ -118,6 +127,7 @@ impl Renderer { sprite_pipeline_state, image_pipeline_state, path_atlas_pipeline_state, + underline_pipeline_state, unit_vertices, instances, } @@ -339,7 +349,7 @@ impl Renderer { drawable_size, command_encoder, ); - self.render_quads( + self.render_underlines( layer.underlines(), scale_factor, offset, @@ -821,6 +831,73 @@ impl Renderer { ); *offset = next_offset; } + + fn render_underlines( + &mut self, + underlines: &[Quad], + scale_factor: f32, + offset: &mut usize, + drawable_size: Vector2F, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if underlines.is_empty() { + return; + } + align_offset(offset); + let next_offset = *offset + underlines.len() * mem::size_of::(); + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + command_encoder.set_render_pipeline_state(&self.underline_pipeline_state); + command_encoder.set_vertex_buffer( + shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexVertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUnderlines as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_vertex_bytes( + shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUniforms as u64, + mem::size_of::() as u64, + [shaders::GPUIUniforms { + viewport_size: drawable_size.to_float2(), + }] + .as_ptr() as *const c_void, + ); + + let buffer_contents = unsafe { + (self.instances.contents() as *mut u8).offset(*offset as isize) + as *mut shaders::GPUIUnderline + }; + for (ix, quad) in underlines.iter().enumerate() { + let bounds = quad.bounds * scale_factor; + let shader_quad = shaders::GPUIUnderline { + origin: bounds.origin().round().to_float2(), + size: bounds.size().round().to_float2(), + thickness: 1. * scale_factor, + color: quad + .background + .unwrap_or(Color::transparent_black()) + .to_uchar4(), + }; + unsafe { + *(buffer_contents.offset(ix as isize)) = shader_quad; + } + } + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + underlines.len() as u64, + ); + *offset = next_offset; + } } fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor { diff --git a/crates/gpui/src/platform/mac/shaders/shaders.h b/crates/gpui/src/platform/mac/shaders/shaders.h index 1b6ad3f26f98122b11afeaff02006dd2ef0e8daa..2e6133fdd51ced78560e168bd33a6590102b59a3 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.h +++ b/crates/gpui/src/platform/mac/shaders/shaders.h @@ -104,3 +104,18 @@ typedef struct vector_uchar4 border_color; float corner_radius; } GPUIImage; + +typedef enum +{ + GPUIUnderlineInputIndexVertices = 0, + GPUIUnderlineInputIndexUnderlines = 1, + GPUIUnderlineInputIndexUniforms = 2, +} GPUIUnderlineInputIndex; + +typedef struct +{ + vector_float2 origin; + vector_float2 size; + float thickness; + vector_uchar4 color; +} GPUIUnderline; diff --git a/crates/gpui/src/platform/mac/shaders/shaders.metal b/crates/gpui/src/platform/mac/shaders/shaders.metal index 0cf7d290f2f5bbd4ba6442b5d9bbfde855c1ed58..413440a24e1c6e611cf5f351e9a6f83b620007e0 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders/shaders.metal @@ -304,3 +304,49 @@ fragment float4 path_atlas_fragment( float alpha = saturate(0.5 - distance); return float4(alpha, 0., 0., 1.); } + +struct UnderlineFragmentInput { + float4 position [[position]]; + float2 origin; + float2 size; + float thickness; + float4 color; +}; + +vertex UnderlineFragmentInput underline_vertex( + uint unit_vertex_id [[vertex_id]], + uint underline_id [[instance_id]], + constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]], + constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]], + constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]] +) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + GPUIUnderline underline = underlines[underline_id]; + float2 position = unit_vertex * underline.size + underline.origin; + float4 device_position = to_device_position(position, uniforms->viewport_size); + + return UnderlineFragmentInput { + device_position, + underline.origin, + underline.size, + underline.thickness, + coloru_to_colorf(underline.color), + }; +} + +fragment float4 underline_fragment( + UnderlineFragmentInput input [[stage_in]] +) { + float half_thickness = input.thickness * 0.5; + float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5); + float frequency = M_PI_F * 0.75; + float amplitude = 0.3; + float sine = sin(st.x * frequency) * amplitude; + float dSine = cos(st.x * frequency) * amplitude * frequency; + float distance = (st.y - sine) / sqrt(1. + dSine * dSine); + float distance_in_pixels = distance * input.size.y; + float distance_from_top_border = distance_in_pixels - half_thickness; + float distance_from_bottom_border = distance_in_pixels + half_thickness; + float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border)); + return input.color * float4(1., 1., 1., alpha); +} diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 9c975ea491786f9811b74de2720024ef144786c2..f2fca6d0a6d58f135dfa41c9fc3ea914199963bd 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -290,7 +290,7 @@ impl Line { if let Some((underline_origin, underline_color)) = finished_underline { cx.scene.push_underline(scene::Quad { - bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 1.)), + bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 3.)), background: Some(underline_color), border: Default::default(), corner_radius: 0., @@ -311,7 +311,7 @@ impl Line { let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.); cx.scene.push_underline(scene::Quad { - bounds: RectF::from_points(underline_start, line_end + vec2f(0., 1.)), + bounds: RectF::from_points(underline_start, line_end + vec2f(0., 3.)), background: Some(underline_color), border: Default::default(), corner_radius: 0., From b9b255652f383eefefd83135269cc4c4cce63960 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Jan 2022 15:52:21 +0100 Subject: [PATCH 16/19] Display squiggly underlines underneath text with diagnostics Co-Authored-By: Nathan Sobo --- crates/editor/src/element.rs | 14 +++--- crates/gpui/src/fonts.rs | 41 ++++++++++++++--- crates/gpui/src/platform/mac/renderer.rs | 43 +++++++++--------- .../gpui/src/platform/mac/shaders/shaders.h | 1 + .../src/platform/mac/shaders/shaders.metal | 30 ++++++++----- crates/gpui/src/scene.rs | 17 +++++-- crates/gpui/src/text_layout.rs | 45 ++++++++++--------- 7 files changed, 119 insertions(+), 72 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ffbefbf035dd47f24baca28d6bb82b53f25b79c6..db67903e570c9f7ff2ff0fd4dba35610b3c617ec 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -8,7 +8,7 @@ use collections::{BTreeMap, HashMap}; use gpui::{ color::Color, elements::layout_highlighted_chunks, - fonts::HighlightStyle, + fonts::{HighlightStyle, Underline}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, @@ -540,12 +540,12 @@ impl EditorElement { .chunks(rows.clone(), Some(&style.syntax)) .map(|chunk| { let highlight = if let Some(severity) = chunk.diagnostic { - let underline = Some( - super::diagnostic_style(severity, true, style) - .message - .text - .color, - ); + let diagnostic_style = super::diagnostic_style(severity, true, style); + let underline = Some(Underline { + color: diagnostic_style.message.text.color, + thickness: 1.0.into(), + squiggly: true, + }); if let Some(mut highlight) = chunk.highlight_style { highlight.underline = underline; Some(highlight) diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 61082f6aaed686e631a679c815c7c9515df2f6b9..2768b9f986cf1f7b11802972445630aa0b63e5e4 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -10,6 +10,7 @@ pub use font_kit::{ metrics::Metrics, properties::{Properties, Stretch, Style, Weight}, }; +use ordered_float::OrderedFloat; use serde::{de, Deserialize}; use serde_json::Value; use std::{cell::RefCell, sync::Arc}; @@ -27,14 +28,21 @@ pub struct TextStyle { pub font_id: FontId, pub font_size: f32, pub font_properties: Properties, - pub underline: Option, + pub underline: Option, } #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct HighlightStyle { pub color: Color, pub font_properties: Properties, - pub underline: Option, + pub underline: Option, +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Underline { + pub color: Color, + pub thickness: OrderedFloat, + pub squiggly: bool, } #[allow(non_camel_case_types)] @@ -81,7 +89,14 @@ struct HighlightStyleJson { #[serde(untagged)] enum UnderlineStyleJson { Underlined(bool), - UnderlinedWithColor(Color), + UnderlinedWithProperties { + #[serde(default)] + color: Option, + #[serde(default)] + thickness: Option, + #[serde(default)] + squiggly: bool, + }, } impl TextStyle { @@ -89,7 +104,7 @@ impl TextStyle { font_family_name: impl Into>, font_size: f32, font_properties: Properties, - underline: Option, + underline: Option, color: Color, font_cache: &FontCache, ) -> anyhow::Result { @@ -276,11 +291,23 @@ impl<'de> Deserialize<'de> for HighlightStyle { } } -fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option { +fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option { match json { UnderlineStyleJson::Underlined(false) => None, - UnderlineStyleJson::Underlined(true) => Some(text_color), - UnderlineStyleJson::UnderlinedWithColor(color) => Some(color), + UnderlineStyleJson::Underlined(true) => Some(Underline { + color: text_color, + thickness: 1.0.into(), + squiggly: false, + }), + UnderlineStyleJson::UnderlinedWithProperties { + color, + thickness, + squiggly, + } => Some(Underline { + color: color.unwrap_or(text_color), + thickness: thickness.unwrap_or(1.).into(), + squiggly, + }), } } diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index b5c563c9e47225c62c456455d76876a96d96c60c..07d425af3ebd4235a178ca479a21052d0d71b043 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -6,7 +6,7 @@ use crate::{ vector::{vec2f, vec2i, Vector2F}, }, platform, - scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow}, + scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow, Underline}, }; use cocoa::foundation::NSUInteger; use metal::{MTLPixelFormat, MTLResourceOptions, NSRange}; @@ -334,23 +334,23 @@ impl Renderer { drawable_size, command_encoder, ); - self.render_sprites( - layer.glyphs(), - layer.icons(), + self.render_underlines( + layer.underlines(), scale_factor, offset, drawable_size, command_encoder, ); - self.render_images( - layer.images(), + self.render_sprites( + layer.glyphs(), + layer.icons(), scale_factor, offset, drawable_size, command_encoder, ); - self.render_underlines( - layer.underlines(), + self.render_images( + layer.images(), scale_factor, offset, drawable_size, @@ -834,7 +834,7 @@ impl Renderer { fn render_underlines( &mut self, - underlines: &[Quad], + underlines: &[Underline], scale_factor: f32, offset: &mut usize, drawable_size: Vector2F, @@ -874,19 +874,22 @@ impl Renderer { (self.instances.contents() as *mut u8).offset(*offset as isize) as *mut shaders::GPUIUnderline }; - for (ix, quad) in underlines.iter().enumerate() { - let bounds = quad.bounds * scale_factor; - let shader_quad = shaders::GPUIUnderline { - origin: bounds.origin().round().to_float2(), - size: bounds.size().round().to_float2(), - thickness: 1. * scale_factor, - color: quad - .background - .unwrap_or(Color::transparent_black()) - .to_uchar4(), + for (ix, underline) in underlines.iter().enumerate() { + let origin = underline.origin * scale_factor; + let mut height = underline.thickness; + if underline.squiggly { + height *= 3.; + } + let size = vec2f(underline.width, height) * scale_factor; + let shader_underline = shaders::GPUIUnderline { + origin: origin.round().to_float2(), + size: size.round().to_float2(), + thickness: underline.thickness * scale_factor, + color: underline.color.to_uchar4(), + squiggly: underline.squiggly as u8, }; unsafe { - *(buffer_contents.offset(ix as isize)) = shader_quad; + *(buffer_contents.offset(ix as isize)) = shader_underline; } } diff --git a/crates/gpui/src/platform/mac/shaders/shaders.h b/crates/gpui/src/platform/mac/shaders/shaders.h index 2e6133fdd51ced78560e168bd33a6590102b59a3..3f5096f37c867cfa2d52f8a8c79cda439418b785 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.h +++ b/crates/gpui/src/platform/mac/shaders/shaders.h @@ -118,4 +118,5 @@ typedef struct vector_float2 size; float thickness; vector_uchar4 color; + uint8_t squiggly; } GPUIUnderline; diff --git a/crates/gpui/src/platform/mac/shaders/shaders.metal b/crates/gpui/src/platform/mac/shaders/shaders.metal index 413440a24e1c6e611cf5f351e9a6f83b620007e0..b484739edbd921a8d4d9c1466ce6d6af973d8f03 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders/shaders.metal @@ -311,6 +311,7 @@ struct UnderlineFragmentInput { float2 size; float thickness; float4 color; + bool squiggly; }; vertex UnderlineFragmentInput underline_vertex( @@ -331,22 +332,27 @@ vertex UnderlineFragmentInput underline_vertex( underline.size, underline.thickness, coloru_to_colorf(underline.color), + underline.squiggly != 0, }; } fragment float4 underline_fragment( UnderlineFragmentInput input [[stage_in]] ) { - float half_thickness = input.thickness * 0.5; - float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5); - float frequency = M_PI_F * 0.75; - float amplitude = 0.3; - float sine = sin(st.x * frequency) * amplitude; - float dSine = cos(st.x * frequency) * amplitude * frequency; - float distance = (st.y - sine) / sqrt(1. + dSine * dSine); - float distance_in_pixels = distance * input.size.y; - float distance_from_top_border = distance_in_pixels - half_thickness; - float distance_from_bottom_border = distance_in_pixels + half_thickness; - float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border)); - return input.color * float4(1., 1., 1., alpha); + if (input.squiggly) { + float half_thickness = input.thickness * 0.5; + float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5); + float frequency = (M_PI_F * (3. * input.thickness)) / 8.; + float amplitude = 1. / (2. * input.thickness); + float sine = sin(st.x * frequency) * amplitude; + float dSine = cos(st.x * frequency) * amplitude * frequency; + float distance = (st.y - sine) / sqrt(1. + dSine * dSine); + float distance_in_pixels = distance * input.size.y; + float distance_from_top_border = distance_in_pixels - half_thickness; + float distance_from_bottom_border = distance_in_pixels + half_thickness; + float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border)); + return input.color * float4(1., 1., 1., alpha); + } else { + return input.color; + } } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index b833ffe627d07be3ef255eef9072f4a704d56825..a5b2f6c8b81aed44801a24eec5fc06aa82f49bef 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -25,7 +25,7 @@ struct StackingContext { pub struct Layer { clip_bounds: Option, quads: Vec, - underlines: Vec, + underlines: Vec, images: Vec, shadows: Vec, glyphs: Vec, @@ -76,6 +76,15 @@ pub struct Border { pub left: bool, } +#[derive(Clone, Copy, Default, Debug)] +pub struct Underline { + pub origin: Vector2F, + pub width: f32, + pub thickness: f32, + pub color: Color, + pub squiggly: bool, +} + impl<'de> Deserialize<'de> for Border { fn deserialize(deserializer: D) -> Result where @@ -183,7 +192,7 @@ impl Scene { self.active_layer().push_image(image) } - pub fn push_underline(&mut self, underline: Quad) { + pub fn push_underline(&mut self, underline: Underline) { self.active_layer().push_underline(underline) } @@ -277,11 +286,11 @@ impl Layer { self.quads.as_slice() } - fn push_underline(&mut self, underline: Quad) { + fn push_underline(&mut self, underline: Underline) { self.underlines.push(underline); } - pub fn underlines(&self) -> &[Quad] { + pub fn underlines(&self) -> &[Underline] { self.underlines.as_slice() } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index f2fca6d0a6d58f135dfa41c9fc3ea914199963bd..30a0e99502a76a772a9ce7c63de4990fc248edd2 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -1,6 +1,6 @@ use crate::{ color::Color, - fonts::{FontId, GlyphId}, + fonts::{FontId, GlyphId, Underline}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, @@ -28,7 +28,7 @@ pub struct TextLayoutCache { pub struct RunStyle { pub color: Color, pub font_id: FontId, - pub underline: Option, + pub underline: Option, } impl TextLayoutCache { @@ -167,7 +167,7 @@ impl<'a> Hash for CacheKeyRef<'a> { #[derive(Default, Debug)] pub struct Line { layout: Arc, - style_runs: SmallVec<[(u32, Color, Option); 32]>, + style_runs: SmallVec<[(u32, Color, Option); 32]>, } #[derive(Default, Debug)] @@ -265,14 +265,14 @@ impl Line { let mut finished_underline = None; if glyph.index >= run_end { - if let Some((run_len, run_color, run_underline_color)) = style_runs.next() { - if let Some((_, underline_color)) = underline { - if *run_underline_color != Some(underline_color) { + if let Some((run_len, run_color, run_underline)) = style_runs.next() { + if let Some((_, underline_style)) = underline { + if *run_underline != Some(underline_style) { finished_underline = underline.take(); } } - if let Some(run_underline_color) = run_underline_color { - underline.get_or_insert((glyph_origin, *run_underline_color)); + if let Some(run_underline) = run_underline { + underline.get_or_insert((glyph_origin, *run_underline)); } run_end += *run_len as usize; @@ -288,12 +288,13 @@ impl Line { continue; } - if let Some((underline_origin, underline_color)) = finished_underline { - cx.scene.push_underline(scene::Quad { - bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 3.)), - background: Some(underline_color), - border: Default::default(), - corner_radius: 0., + if let Some((underline_origin, underline_style)) = finished_underline { + cx.scene.push_underline(scene::Underline { + origin: underline_origin, + width: glyph_origin.x() - underline_origin.x(), + thickness: underline_style.thickness.into(), + color: underline_style.color, + squiggly: underline_style.squiggly, }); } @@ -307,14 +308,14 @@ impl Line { } } - if let Some((underline_start, underline_color)) = underline.take() { - let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.); - - cx.scene.push_underline(scene::Quad { - bounds: RectF::from_points(underline_start, line_end + vec2f(0., 3.)), - background: Some(underline_color), - border: Default::default(), - corner_radius: 0., + if let Some((underline_start, underline_style)) = underline.take() { + let line_end_x = origin.x() + self.layout.width; + cx.scene.push_underline(scene::Underline { + origin: underline_start, + width: line_end_x - underline_start.x(), + color: underline_style.color, + thickness: underline_style.thickness.into(), + squiggly: underline_style.squiggly, }); } } From 3f6960bd3436742ec97a4e2ed5b7d18f1c555f2c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Jan 2022 16:03:06 +0100 Subject: [PATCH 17/19] Position underlines at a distance proportional to the line descent Co-Authored-By: Nathan Sobo --- crates/gpui/src/text_layout.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 30a0e99502a76a772a9ce7c63de4990fc248edd2..6e371437bfdba44993d6a06d20ad85881ad6dd0a 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -272,7 +272,13 @@ impl Line { } } if let Some(run_underline) = run_underline { - underline.get_or_insert((glyph_origin, *run_underline)); + underline.get_or_insert(( + vec2f( + glyph_origin.x(), + origin.y() + baseline_offset.y() + 0.618 * self.layout.descent, + ), + *run_underline, + )); } run_end += *run_len as usize; From dedd5d63fb874e0137d27e487d746f27f4576ef7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Jan 2022 17:06:20 +0100 Subject: [PATCH 18/19] Fix blending of quad's border with background Co-Authored-By: Nathan Sobo --- .../gpui/src/platform/mac/shaders/shaders.metal | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/platform/mac/shaders/shaders.metal b/crates/gpui/src/platform/mac/shaders/shaders.metal index b484739edbd921a8d4d9c1466ce6d6af973d8f03..385c8d25d88928343e4ab0dae25d39ab29994f43 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders/shaders.metal @@ -66,21 +66,13 @@ float4 quad_sdf(QuadFragmentInput input) { border_width = vertical_border; } - float4 color; - if (border_width == 0.) { - color = input.background_color; - } else { - float4 border_color = float4(mix(float3(input.background_color), float3(input.border_color), input.border_color.a), 1.); + float4 color = input.background_color * float4(1., 1., 1., saturate(0.5 - distance)); + if (border_width != 0.) { float inset_distance = distance + border_width; - color = mix( - border_color, - input.background_color, - saturate(0.5 - inset_distance) - ); + color = mix(input.border_color, color, saturate(0.5 - inset_distance)); } - float4 coverage = float4(1., 1., 1., saturate(0.5 - distance)); - return coverage * color; + return color; } vertex QuadFragmentInput quad_vertex( From 0263ea289c0650dc328f7fa38e01e8929de3c5c8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Jan 2022 17:08:24 +0100 Subject: [PATCH 19/19] Style diagnostic header's border in light and dark theme Co-Authored-By: Nathan Sobo --- crates/zed/assets/themes/_base.toml | 2 +- crates/zed/assets/themes/black.toml | 1 + crates/zed/assets/themes/dark.toml | 1 + crates/zed/assets/themes/light.toml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 3e3f15919ef321f8246e7ea4f914949d10036df3..7f343ba1ac2f7a20261099756f88b25c9d717544 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -258,7 +258,7 @@ path = { extends = "$text.2", size = 14, margin.left = 12 } text_scale_factor = 0.857 [editor.diagnostic_header] -border = { width = 1, top = true, bottom = true, color = "#ffffff1c" } +border = { width = 1, top = true, bottom = true, color = "$border.1" } code = { extends = "$text.2", size = 14, margin.left = 10 } icon_width_factor = 1.5 text_scale_factor = 0.857 diff --git a/crates/zed/assets/themes/black.toml b/crates/zed/assets/themes/black.toml index 972ef37132c12e4b6a3f9aad2e5b78dccccf3cb2..47fce47b06b112b6a8de3d9a636231cd469c1979 100644 --- a/crates/zed/assets/themes/black.toml +++ b/crates/zed/assets/themes/black.toml @@ -7,6 +7,7 @@ extends = "_base" [border] 0 = "#000000B2" +1 = "#FFFFFF16" [text] 0 = { extends = "$text.base", color = "#ffffff" } diff --git a/crates/zed/assets/themes/dark.toml b/crates/zed/assets/themes/dark.toml index 9d65a160eb6d1a6ff19c448a19c0231176cd4178..bd6e473a7a10beb4b4ea09ded11217194c6f63fb 100644 --- a/crates/zed/assets/themes/dark.toml +++ b/crates/zed/assets/themes/dark.toml @@ -7,6 +7,7 @@ extends = "_base" [border] 0 = "#1B222B" +1 = "#FFFFFF16" [text] 0 = { extends = "$text.base", color = "#FFFFFF" } diff --git a/crates/zed/assets/themes/light.toml b/crates/zed/assets/themes/light.toml index 18134501ece21622abeb5dd5bca0a958e66f7464..3113a6911012544ace4a3aa453238aa1388e6f6b 100644 --- a/crates/zed/assets/themes/light.toml +++ b/crates/zed/assets/themes/light.toml @@ -7,6 +7,7 @@ extends = "_base" [border] 0 = "#DDDDDC" +1 = "#0000000F" [text] 0 = { extends = "$text.base", color = "#000000" }