diff --git a/zed/assets/themes/dark.toml b/zed/assets/themes/dark.toml index 96ba65bd6371fc34604163977fcfc4d5085062f5..e4b064ca97a74f91ed0cca7ab2d1460dd7a93cd7 100644 --- a/zed/assets/themes/dark.toml +++ b/zed/assets/themes/dark.toml @@ -15,8 +15,9 @@ modal_match_text = 0xcccccc modal_match_text_highlight = 0x18a3ff [editor] -background = 0x1c1d1e -gutter_background = 0x1c1d1e +background = 0x131415 +gutter_background = 0x131415 +active_line_background = 0x1c1d1e line_number = 0x5a5a5b line_number_active = 0xffffff default_text = 0xd4d4d4 diff --git a/zed/src/editor.rs b/zed/src/editor.rs index bd4fb7baedd0e6b80052a6b59654671e1917ee96..3911970249c95bf3afaa646399b242aec9a4801b 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -27,7 +27,7 @@ use smallvec::SmallVec; use smol::Timer; use std::{ cmp::{self, Ordering}, - collections::HashSet, + collections::BTreeMap, fmt::Write, iter::FromIterator, mem, @@ -2357,7 +2357,7 @@ impl Snapshot { pub fn layout_line_numbers( &self, rows: Range, - active_rows: &HashSet, + active_rows: &BTreeMap, font_cache: &FontCache, layout_cache: &TextLayoutCache, theme: &Theme, @@ -2373,7 +2373,7 @@ impl Snapshot { .enumerate() { let display_row = rows.start + ix as u32; - let color = if active_rows.contains(&display_row) { + let color = if active_rows.contains_key(&display_row) { theme.editor.line_number_active.0 } else { theme.editor.line_number.0 diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index ad9aac701a08d019c85c5571b516e0e9ee3f6193..c2a486afdd884cc73019f5b40bb02f33dc9b8c30 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -14,10 +14,10 @@ use gpui::{ }; use json::json; use smallvec::SmallVec; -use std::{cmp::Ordering, collections::HashSet, ops::Range}; use std::{ - cmp::{self}, - collections::HashMap, + cmp::{self, Ordering}, + collections::{BTreeMap, HashMap}, + ops::Range, }; pub struct EditorElement { @@ -182,19 +182,64 @@ impl EditorElement { true } - fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, cx: &mut PaintContext) { - let settings = self.view(cx.app).settings.borrow(); - let theme = &settings.theme; + fn paint_background( + &self, + gutter_bounds: RectF, + text_bounds: RectF, + layout: &LayoutState, + cx: &mut PaintContext, + ) { + let bounds = gutter_bounds.union_rect(text_bounds); let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; - - cx.scene.push_layer(Some(rect)); + let editor = self.view(cx.app); + let settings = editor.settings.borrow(); + let theme = &settings.theme; cx.scene.push_quad(Quad { - bounds: rect, + bounds: gutter_bounds, background: Some(theme.editor.gutter_background.0), border: Border::new(0., ColorU::transparent_black()), corner_radius: 0., }); + cx.scene.push_quad(Quad { + bounds: text_bounds, + background: Some(theme.editor.background.0), + border: Border::new(0., ColorU::transparent_black()), + corner_radius: 0., + }); + if !editor.single_line { + let mut active_rows = layout.active_rows.iter().peekable(); + while let Some((start_row, contains_non_empty_selection)) = active_rows.next() { + let mut end_row = *start_row; + while active_rows.peek().map_or(false, |r| { + *r.0 == end_row + 1 && r.1 == contains_non_empty_selection + }) { + active_rows.next().unwrap(); + end_row += 1; + } + + if !contains_non_empty_selection { + let origin = vec2f( + bounds.origin_x(), + bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top, + ); + let size = vec2f( + bounds.width(), + layout.line_height * (end_row - start_row + 1) as f32, + ); + cx.scene.push_quad(Quad { + bounds: RectF::new(origin, size), + background: Some(theme.editor.active_line_background.0), + border: Border::default(), + corner_radius: 0., + }); + } + } + } + } + + fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, cx: &mut PaintContext) { + let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; for (ix, line) in layout.line_number_layouts.iter().enumerate() { if let Some(line) = line { let line_origin = rect.origin() @@ -209,8 +254,6 @@ impl EditorElement { ); } } - - cx.scene.pop_layer(); } fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, cx: &mut PaintContext) { @@ -225,12 +268,6 @@ impl EditorElement { let scroll_left = scroll_position.x() * max_glyph_width; cx.scene.push_layer(Some(bounds)); - cx.scene.push_quad(Quad { - bounds, - background: Some(theme.background.0), - border: Border::new(0., ColorU::transparent_black()), - corner_radius: 0., - }); // Draw selections let corner_radius = 2.5; @@ -393,7 +430,7 @@ impl Element for EditorElement { let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen let mut selections = HashMap::new(); - let mut active_rows = HashSet::new(); + let mut active_rows = BTreeMap::new(); self.update_view(cx.app, |view, cx| { let replica_id = view.replica_id(cx); for selection_set_id in view.active_selection_sets(cx).collect::>() { @@ -405,6 +442,7 @@ impl Element for EditorElement { ) { set.push(selection.clone()); if selection_set_id.replica_id == replica_id { + let is_empty = selection.start == selection.end; let mut selection_start; let mut selection_end; if selection.start < selection.end { @@ -416,10 +454,13 @@ impl Element for EditorElement { }; selection_start = snapshot.prev_row_boundary(selection_start).0; selection_end = snapshot.next_row_boundary(selection_end).0; - active_rows.extend( - cmp::max(selection_start.row(), start_row) - ..=cmp::min(selection_end.row(), end_row), - ); + for row in cmp::max(selection_start.row(), start_row) + ..=cmp::min(selection_end.row(), end_row) + { + let contains_non_empty_selection = + active_rows.entry(row).or_insert(!is_empty); + *contains_non_empty_selection |= !is_empty; + } } } @@ -478,6 +519,7 @@ impl Element for EditorElement { overscroll, text_offset, snapshot, + active_rows, line_layouts, line_number_layouts, line_height, @@ -520,17 +562,22 @@ impl Element for EditorElement { cx: &mut PaintContext, ) -> Self::PaintState { if let Some(layout) = layout { + cx.scene.push_layer(Some(bounds)); + let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size); let text_bounds = RectF::new( bounds.origin() + vec2f(layout.gutter_size.x(), 0.0), layout.text_size, ); + self.paint_background(gutter_bounds, text_bounds, layout, cx); if layout.gutter_size.x() > 0. { self.paint_gutter(gutter_bounds, layout, cx); } self.paint_text(text_bounds, layout, cx); + cx.scene.pop_layer(); + Some(PaintState { bounds, text_bounds, @@ -590,6 +637,7 @@ pub struct LayoutState { gutter_padding: f32, text_size: Vector2F, snapshot: Snapshot, + active_rows: BTreeMap, line_layouts: Vec, line_number_layouts: Vec>, line_height: f32, diff --git a/zed/src/settings.rs b/zed/src/settings.rs index 85f31873e3597f626af7362f2d24adddb21e83c0..54621b905b59812306a1475ce36ce5935d52568a 100644 --- a/zed/src/settings.rs +++ b/zed/src/settings.rs @@ -57,6 +57,7 @@ pub struct UiTheme { pub struct EditorTheme { pub background: Color, pub gutter_background: Color, + pub active_line_background: Color, pub line_number: Color, pub line_number_active: Color, pub default_text: Color,