From ac1eb19f8330f3a4fe84b682a4b15f82305ea247 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 9 Mar 2022 14:53:31 -0700 Subject: [PATCH 01/11] Start on text highlight support --- crates/editor/src/display_map.rs | 31 ++++++++-- crates/editor/src/display_map/block_map.rs | 3 +- crates/editor/src/display_map/fold_map.rs | 3 +- crates/editor/src/editor.rs | 68 ++++++++++++++-------- crates/editor/src/element.rs | 43 +++++++------- crates/go_to_line/src/go_to_line.rs | 4 +- crates/gpui/src/color.rs | 25 +++++++- crates/gpui/src/elements/text.rs | 29 ++++----- crates/gpui/src/fonts.rs | 33 ++++++++++- crates/language/src/buffer.rs | 10 ++-- crates/language/src/highlight_map.rs | 8 +-- crates/language/src/language.rs | 2 +- crates/outline/src/outline.rs | 4 +- crates/search/src/buffer_search.rs | 22 ++++--- crates/search/src/project_search.rs | 4 +- 15 files changed, 198 insertions(+), 91 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 053883e93888914bbb8277f65fe54c5850ccb44a..e401f850319375e6e6c841b665a40d3c2be1bea9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -7,10 +7,13 @@ use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::{FoldMap, ToFoldPoint as _}; -use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; +use gpui::{ + fonts::{FontId, HighlightStyle}, + Entity, ModelContext, ModelHandle, +}; use language::{Point, Subscription as BufferSubscription}; -use std::ops::Range; -use sum_tree::Bias; +use std::{any::TypeId, ops::Range, sync::Arc}; +use sum_tree::{Bias, TreeMap}; use tab_map::TabMap; use wrap_map::WrapMap; @@ -23,6 +26,8 @@ pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint; } +type TextHighlights = TreeMap, Arc<(HighlightStyle, Vec>)>>; + pub struct DisplayMap { buffer: ModelHandle, buffer_subscription: BufferSubscription, @@ -30,6 +35,7 @@ pub struct DisplayMap { tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, + text_highlights: TextHighlights, } impl Entity for DisplayMap { @@ -60,6 +66,7 @@ impl DisplayMap { tab_map, wrap_map, block_map, + text_highlights: Default::default(), } } @@ -79,6 +86,7 @@ impl DisplayMap { tabs_snapshot, wraps_snapshot, blocks_snapshot, + text_highlights: self.text_highlights.clone(), } } @@ -156,6 +164,20 @@ impl DisplayMap { block_map.remove(ids); } + pub fn highlight_text( + &mut self, + type_id: TypeId, + ranges: Vec>, + style: HighlightStyle, + ) { + self.text_highlights + .insert(Some(type_id), Arc::new((style, ranges))); + } + + pub fn clear_text_highlights(&mut self, type_id: TypeId) { + self.text_highlights.remove(&Some(type_id)); + } + pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { self.wrap_map .update(cx, |map, cx| map.set_font(font_id, font_size, cx)); @@ -178,6 +200,7 @@ pub struct DisplaySnapshot { tabs_snapshot: tab_map::TabSnapshot, wraps_snapshot: wrap_map::WrapSnapshot, blocks_snapshot: block_map::BlockSnapshot, + text_highlights: TextHighlights, } impl DisplaySnapshot { @@ -1146,7 +1169,7 @@ mod tests { let mut chunks: Vec<(String, Option)> = Vec::new(); for chunk in snapshot.chunks(rows, true) { let color = chunk - .highlight_id + .syntax_highlight_id .and_then(|id| id.style(theme).map(|s| s.color)); if let Some((last_chunk, last_color)) = chunks.last_mut() { if color == *last_color { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 465e46af213aa42db30508ed956e3192b47773ad..7b90179c8ffbfbf1903ec0097708e891c3a94f9a 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -807,7 +807,8 @@ impl<'a> Iterator for BlockChunks<'a> { return Some(Chunk { text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) }, - highlight_id: None, + syntax_highlight_id: None, + highlight_style: None, diagnostic: None, }); } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 2866ae8f636122ef0cda82bbd8d8cdf52ea176d7..facb5ab2a7f1ee86d67e69129d024ab011fe2b2c 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -984,7 +984,8 @@ impl<'a> Iterator for FoldChunks<'a> { self.output_offset += output_text.len(); return Some(Chunk { text: output_text, - highlight_id: None, + syntax_highlight_id: None, + highlight_style: None, diagnostic: None, }); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 91d02b1aa0c99af374759a037e90882ef7c6d51d..ded484e549675de172764b453bc1bfc8b35d608b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -440,7 +440,7 @@ pub struct Editor { vertical_scroll_margin: f32, placeholder_text: Option>, highlighted_rows: Option>, - highlighted_ranges: BTreeMap>)>, + background_highlights: BTreeMap>)>, nav_history: Option, context_menu: Option, completion_tasks: Vec<(CompletionId, Task>)>, @@ -920,7 +920,7 @@ impl Editor { vertical_scroll_margin: 3.0, placeholder_text: None, highlighted_rows: None, - highlighted_ranges: Default::default(), + background_highlights: Default::default(), nav_history: None, context_menu: None, completion_tasks: Default::default(), @@ -2350,7 +2350,7 @@ impl Editor { if let Some(editor) = editor.act_as::(cx) { editor.update(cx, |editor, cx| { let color = editor.style(cx).highlighted_line_background; - editor.highlight_ranges::(ranges_to_highlight, color, cx); + editor.highlight_background::(ranges_to_highlight, color, cx); }); } }); @@ -2444,12 +2444,12 @@ impl Editor { } } - this.highlight_ranges::( + this.highlight_background::( read_ranges, read_background, cx, ); - this.highlight_ranges::( + this.highlight_background::( write_ranges, write_background, cx, @@ -4333,7 +4333,7 @@ impl Editor { if let Some(editor) = editor.act_as::(cx) { editor.update(cx, |editor, cx| { let color = editor.style(cx).highlighted_line_background; - editor.highlight_ranges::(ranges_to_highlight, color, cx); + editor.highlight_background::(ranges_to_highlight, color, cx); }); } }); @@ -4398,14 +4398,14 @@ impl Editor { None, cx, ); - editor.highlight_ranges::( + editor.highlight_background::( vec![Anchor::min()..Anchor::max()], style.diff_background_inserted, cx, ); editor }); - this.highlight_ranges::( + this.highlight_background::( vec![range.clone()], style.diff_background_deleted, cx, @@ -4500,7 +4500,7 @@ impl Editor { fn take_rename(&mut self, cx: &mut ViewContext) -> Option { let rename = self.pending_rename.take()?; self.remove_blocks([rename.block_id].into_iter().collect(), cx); - self.clear_highlighted_ranges::(cx); + self.clear_background_highlights::(cx); let editor = rename.editor.read(cx); let snapshot = self.buffer.read(cx).snapshot(cx); @@ -4545,7 +4545,7 @@ impl Editor { } let rename = self.pending_rename.take().unwrap(); self.remove_blocks([rename.block_id].into_iter().collect(), cx); - self.clear_highlighted_ranges::(cx); + self.clear_background_highlights::(cx); } } @@ -5265,7 +5265,7 @@ impl Editor { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - pub fn set_highlighted_rows(&mut self, rows: Option>) { + pub fn highlight_rows(&mut self, rows: Option>) { self.highlighted_rows = rows; } @@ -5273,27 +5273,27 @@ impl Editor { self.highlighted_rows.clone() } - pub fn highlight_ranges( + pub fn highlight_background( &mut self, ranges: Vec>, color: Color, cx: &mut ViewContext, ) { - self.highlighted_ranges + self.background_highlights .insert(TypeId::of::(), (color, ranges)); cx.notify(); } - pub fn clear_highlighted_ranges( + pub fn clear_background_highlights( &mut self, cx: &mut ViewContext, ) -> Option<(Color, Vec>)> { cx.notify(); - self.highlighted_ranges.remove(&TypeId::of::()) + self.background_highlights.remove(&TypeId::of::()) } #[cfg(feature = "test-support")] - pub fn all_highlighted_ranges( + pub fn all_background_highlights( &mut self, cx: &mut ViewContext, ) -> Vec<(Range, Color)> { @@ -5301,23 +5301,23 @@ impl Editor { let buffer = &snapshot.buffer_snapshot; let start = buffer.anchor_before(0); let end = buffer.anchor_after(buffer.len()); - self.highlighted_ranges_in_range(start..end, &snapshot) + self.background_highlights_in_range(start..end, &snapshot) } - pub fn highlighted_ranges_for_type(&self) -> Option<(Color, &[Range])> { - self.highlighted_ranges + pub fn background_highlights_for_type(&self) -> Option<(Color, &[Range])> { + self.background_highlights .get(&TypeId::of::()) .map(|(color, ranges)| (*color, ranges.as_slice())) } - pub fn highlighted_ranges_in_range( + pub fn background_highlights_in_range( &self, search_range: Range, display_snapshot: &DisplaySnapshot, ) -> Vec<(Range, Color)> { let mut results = Vec::new(); let buffer = &display_snapshot.buffer_snapshot; - for (color, ranges) in self.highlighted_ranges.values() { + for (color, ranges) in self.background_highlights.values() { let start_ix = match ranges.binary_search_by(|probe| { let cmp = probe.end.cmp(&search_range.start, &buffer).unwrap(); if cmp.is_gt() { @@ -5346,6 +5346,24 @@ impl Editor { results } + pub fn highlight_text( + &mut self, + ranges: Vec>, + style: HighlightStyle, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |map, _| { + map.highlight_text(TypeId::of::(), ranges, style) + }); + cx.notify(); + } + + pub fn clear_text_highlights(&mut self, cx: &mut ViewContext) { + self.display_map + .update(cx, |map, _| map.clear_text_highlights(TypeId::of::())); + cx.notify(); + } + fn next_blink_epoch(&mut self) -> usize { self.blink_epoch += 1; self.blink_epoch @@ -8868,7 +8886,7 @@ mod tests { buffer.anchor_after(range.start)..buffer.anchor_after(range.end) }; - editor.highlight_ranges::( + editor.highlight_background::( vec![ anchor_range(Point::new(2, 1)..Point::new(2, 3)), anchor_range(Point::new(4, 2)..Point::new(4, 4)), @@ -8878,7 +8896,7 @@ mod tests { Color::red(), cx, ); - editor.highlight_ranges::( + editor.highlight_background::( vec![ anchor_range(Point::new(3, 2)..Point::new(3, 5)), anchor_range(Point::new(5, 3)..Point::new(5, 6)), @@ -8890,7 +8908,7 @@ mod tests { ); let snapshot = editor.snapshot(cx); - let mut highlighted_ranges = editor.highlighted_ranges_in_range( + let mut highlighted_ranges = editor.background_highlights_in_range( anchor_range(Point::new(3, 4)..Point::new(7, 4)), &snapshot, ); @@ -8919,7 +8937,7 @@ mod tests { ] ); assert_eq!( - editor.highlighted_ranges_in_range( + editor.background_highlights_in_range( anchor_range(Point::new(5, 6)..Point::new(6, 4)), &snapshot, ), diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index dcf716e0bb91b6fc55aba4bf4cc7db55ac81a031..ae3a09dc13615c175d2053cffa2f8c5905bc99d0 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -606,30 +606,33 @@ impl EditorElement { } else { let style = &self.style; let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| { - let highlight_style = chunk - .highlight_id - .and_then(|highlight_id| highlight_id.style(&style.syntax)); - let highlight = if let Some(severity) = chunk.diagnostic { + let mut highlight_style = HighlightStyle { + color: style.text.color, + font_properties: style.text.font_properties, + ..Default::default() + }; + + if let Some(syntax_highlight_style) = chunk + .syntax_highlight_id + .and_then(|id| id.style(&style.syntax)) + { + highlight_style.highlight(syntax_highlight_style); + } + + if let Some(style) = chunk.highlight_style { + highlight_style.highlight(style); + } + + if let Some(severity) = chunk.diagnostic { let diagnostic_style = super::diagnostic_style(severity, true, style); - let underline = Some(Underline { + highlight_style.underline = Some(Underline { color: diagnostic_style.message.text.color, thickness: 1.0.into(), squiggly: true, }); - if let Some(mut highlight) = highlight_style { - highlight.underline = underline; - Some(highlight) - } else { - Some(HighlightStyle { - underline, - color: style.text.color, - font_properties: style.text.font_properties, - }) - } - } else { - highlight_style - }; - (chunk.text, highlight) + } + + (chunk.text, highlight_style) }); layout_highlighted_chunks( chunks, @@ -852,7 +855,7 @@ impl Element for EditorElement { let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx)); highlighted_rows = view.highlighted_rows(); - highlighted_ranges = view.highlighted_ranges_in_range( + highlighted_ranges = view.background_highlights_in_range( start_anchor.clone()..end_anchor.clone(), &display_map, ); diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index c3e9cdcbf257cc12aba8302d3865587bbdd167e6..8fdae59e31fea936fdd3c376031c5b286c80bfaa 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -126,7 +126,7 @@ impl GoToLine { let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); let display_point = point.to_display_point(&snapshot); let row = display_point.row(); - active_editor.set_highlighted_rows(Some(row..row + 1)); + active_editor.highlight_rows(Some(row..row + 1)); active_editor.request_autoscroll(Autoscroll::Center, cx); }); cx.notify(); @@ -143,7 +143,7 @@ impl Entity for GoToLine { fn release(&mut self, cx: &mut MutableAppContext) { let scroll_position = self.prev_scroll_position.take(); self.active_editor.update(cx, |editor, cx| { - editor.set_highlighted_rows(None); + editor.highlight_rows(None); if let Some(scroll_position) = scroll_position { editor.set_scroll_position(scroll_position, cx); } diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 4c301b4e48bea2bc512e7e7e6f1df4aefcabfd91..24e917a7779c21b5a50d11ed8a127eee8e064297 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::json::ToJson; -use pathfinder_color::ColorU; +use pathfinder_color::{ColorF, ColorU}; use serde::{ de::{self, Unexpected}, Deserialize, Deserializer, @@ -48,6 +48,29 @@ impl Color { pub fn from_u32(rgba: u32) -> Self { Self(ColorU::from_u32(rgba)) } + + pub fn blend(source: Color, dest: Color) -> Color { + if dest.a == 255 { + return dest; + } + + let source = source.0.to_f32(); + let dest = dest.0.to_f32(); + + let a = source.a() + (dest.a() * (1. - source.a())); + let r = ((source.r() * source.a()) + (dest.r() * dest.a() * (1. - source.a()))) / a; + let g = ((source.g() * source.a()) + (dest.g() * dest.a() * (1. - source.a()))) / a; + let b = ((source.b() * source.a()) + (dest.b() * dest.a() * (1. - source.a()))) / a; + + Self(ColorF::new(r, g, b, a).to_u8()) + } + + pub fn fade_out(&mut self, factor: f32) { + let source_alpha = 1. - factor.clamp(0., 1.); + let dest_alpha = self.0.a as f32 / 255.; + let dest_alpha = source_alpha + (dest_alpha * (1. - source_alpha)); + self.0.a = (dest_alpha * (1. / 255.)) as u8; + } } impl<'de> Deserialize<'de> for Color { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 7c983f1e6fb7b99659e9053feffb4f21f4535ffc..681464611721da812572297d0edb1d828271348d 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -67,17 +67,20 @@ impl Element for Text { let mut highlight_ranges = self.highlights.iter().peekable(); let chunks = std::iter::from_fn(|| { let result; - if let Some((range, highlight)) = highlight_ranges.peek() { + if let Some((range, highlight_style)) = highlight_ranges.peek() { if offset < range.start { - result = Some((&self.text[offset..range.start], None)); + result = Some(( + &self.text[offset..range.start], + HighlightStyle::from(&self.style), + )); offset = range.start; } else { - result = Some((&self.text[range.clone()], Some(*highlight))); + result = Some((&self.text[range.clone()], *highlight_style)); highlight_ranges.next(); offset = range.end; } } else if offset < self.text.len() { - result = Some((&self.text[offset..], None)); + result = Some((&self.text[offset..], HighlightStyle::from(&self.style))); offset = self.text.len(); } else { result = None; @@ -197,24 +200,24 @@ impl Element for Text { /// Perform text layout on a series of highlighted chunks of text. pub fn layout_highlighted_chunks<'a>( - chunks: impl Iterator)>, - style: &'a TextStyle, + chunks: impl Iterator, + text_style: &'a TextStyle, text_layout_cache: &'a TextLayoutCache, font_cache: &'a Arc, max_line_len: usize, max_line_count: usize, ) -> Vec { let mut layouts = Vec::with_capacity(max_line_count); - let mut prev_font_properties = style.font_properties.clone(); - let mut prev_font_id = style.font_id; + let mut prev_font_properties = text_style.font_properties.clone(); + let mut prev_font_id = text_style.font_id; let mut line = String::new(); let mut styles = Vec::new(); let mut row = 0; let mut line_exceeded_max_len = false; - for (chunk, highlight_style) in chunks.chain([("\n", None)]) { + for (chunk, highlight_style) in chunks.chain([("\n", Default::default())]) { for (ix, mut line_chunk) in chunk.split('\n').enumerate() { if ix > 0 { - layouts.push(text_layout_cache.layout_str(&line, style.font_size, &styles)); + layouts.push(text_layout_cache.layout_str(&line, text_style.font_size, &styles)); line.clear(); styles.clear(); row += 1; @@ -225,15 +228,13 @@ pub fn layout_highlighted_chunks<'a>( } if !line_chunk.is_empty() && !line_exceeded_max_len { - let highlight_style = highlight_style.unwrap_or(style.clone().into()); - // Avoid a lookup if the font properties match the previous ones. let font_id = if highlight_style.font_properties == prev_font_properties { prev_font_id } else { font_cache - .select_font(style.font_family_id, &highlight_style.font_properties) - .unwrap_or(style.font_id) + .select_font(text_style.font_family_id, &highlight_style.font_properties) + .unwrap_or(text_style.font_id) }; if line.len() + line_chunk.len() > max_line_len { diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 7dc11730488964fdb13405f4e47dccc2d4c90266..60073a64d63d1cd6202dc3213061c607abb2f631 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -31,13 +31,16 @@ pub struct TextStyle { pub underline: Option, } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct HighlightStyle { pub color: Color, pub font_properties: Properties, pub underline: Option, + pub fade_out: Option, } +impl Eq for HighlightStyle {} + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct Underline { pub color: Color, @@ -83,6 +86,8 @@ struct HighlightStyleJson { italic: bool, #[serde(default)] underline: UnderlineStyleJson, + #[serde(default)] + fade_out: Option, } #[derive(Deserialize)] @@ -131,7 +136,10 @@ impl TextStyle { if self.font_properties != style.font_properties { self.font_id = font_cache.select_font(self.font_family_id, &style.font_properties)?; } - self.color = style.color; + self.color = Color::blend(self.color, style.color); + if let Some(factor) = style.fade_out { + self.color.fade_out(factor); + } self.underline = style.underline; Ok(self) } @@ -199,10 +207,17 @@ impl TextStyle { impl From for HighlightStyle { fn from(other: TextStyle) -> Self { + Self::from(&other) + } +} + +impl From<&TextStyle> for HighlightStyle { + fn from(other: &TextStyle) -> Self { Self { color: other.color, font_properties: other.font_properties, underline: other.underline, + fade_out: None, } } } @@ -246,6 +261,18 @@ impl HighlightStyle { color: json.color, font_properties, underline: underline_from_json(json.underline, json.color), + fade_out: json.fade_out, + } + } + + pub fn highlight(&mut self, other: HighlightStyle) { + self.color = Color::blend(other.color, self.color); + if let Some(factor) = other.fade_out { + self.color.fade_out(factor); + } + self.font_properties = other.font_properties; + if other.underline.is_some() { + self.underline = other.underline; } } } @@ -256,6 +283,7 @@ impl From for HighlightStyle { color, font_properties: Default::default(), underline: None, + fade_out: None, } } } @@ -295,6 +323,7 @@ impl<'de> Deserialize<'de> for HighlightStyle { color: serde_json::from_value(json).map_err(de::Error::custom)?, font_properties: Properties::new(), underline: None, + fade_out: None, }) } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 3d79ecadd649d587200facd035b7e33c084629cf..e624a7c316938088db24cf1091d3177715c53b8a 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -12,7 +12,7 @@ use crate::{ use anyhow::{anyhow, Result}; use clock::ReplicaId; use futures::FutureExt as _; -use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; +use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; use similar::{ChangeTag, TextDiff}; @@ -248,7 +248,8 @@ pub struct BufferChunks<'a> { #[derive(Clone, Copy, Debug, Default)] pub struct Chunk<'a> { pub text: &'a str, - pub highlight_id: Option, + pub syntax_highlight_id: Option, + pub highlight_style: Option, pub diagnostic: Option, } @@ -1716,7 +1717,7 @@ impl BufferSnapshot { offset += chunk.text.len(); } let style = chunk - .highlight_id + .syntax_highlight_id .zip(theme) .and_then(|(highlight, theme)| highlight.style(theme)); if let Some(style) = style { @@ -2086,7 +2087,8 @@ impl<'a> Iterator for BufferChunks<'a> { Some(Chunk { text: slice, - highlight_id, + syntax_highlight_id: highlight_id, + highlight_style: None, diagnostic: self.current_diagnostic_severity(), }) } else { diff --git a/crates/language/src/highlight_map.rs b/crates/language/src/highlight_map.rs index 75e3c8526c5969ecabfe49b24929025a13848f3e..74734fbc5b376255eff644cd1930c5cf1780f761 100644 --- a/crates/language/src/highlight_map.rs +++ b/crates/language/src/highlight_map.rs @@ -8,7 +8,7 @@ pub struct HighlightMap(Arc<[HighlightId]>); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HighlightId(pub u32); -const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); +const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self { @@ -36,7 +36,7 @@ impl HighlightMap { Some((i, len)) }) .max_by_key(|(_, len)| *len) - .map_or(DEFAULT_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32)) + .map_or(DEFAULT_SYNTAX_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32)) }) .collect(), ) @@ -46,7 +46,7 @@ impl HighlightMap { self.0 .get(capture_id as usize) .copied() - .unwrap_or(DEFAULT_HIGHLIGHT_ID) + .unwrap_or(DEFAULT_SYNTAX_HIGHLIGHT_ID) } } @@ -72,7 +72,7 @@ impl Default for HighlightMap { impl Default for HighlightId { fn default() -> Self { - DEFAULT_HIGHLIGHT_ID + DEFAULT_SYNTAX_HIGHLIGHT_ID } } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 5af18241b6d942f2abba949ced7a25128132ac5c..fdc8db5a74b31498c87ba7861a09d29ec19f4020 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -515,7 +515,7 @@ impl Language { for chunk in BufferChunks::new(text, range, Some(&tree), self.grammar.as_ref(), vec![]) { let end_offset = offset + chunk.text.len(); - if let Some(highlight_id) = chunk.highlight_id { + if let Some(highlight_id) = chunk.syntax_highlight_id { result.push((offset..end_offset, highlight_id)); } offset = end_offset; diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index dc8ba002a38e9490e2e8980a42a24883277b92d3..ecaf17f80e39a28ea26e97c095ec95a1eda4d016 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -186,7 +186,7 @@ impl OutlineView { let end = outline_item.range.end.to_point(&buffer_snapshot); let display_rows = start.to_display_point(&snapshot).row() ..end.to_display_point(&snapshot).row() + 1; - active_editor.set_highlighted_rows(Some(display_rows)); + active_editor.highlight_rows(Some(display_rows)); active_editor.request_autoscroll(Autoscroll::Center, cx); }); } @@ -207,7 +207,7 @@ impl OutlineView { fn restore_active_editor(&mut self, cx: &mut MutableAppContext) { self.active_editor.update(cx, |editor, cx| { - editor.set_highlighted_rows(None); + editor.highlight_rows(None); if let Some(scroll_position) = self.prev_scroll_position { editor.set_scroll_position(scroll_position, cx); } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a5184ea5f30d4221ab5a2524e5ee439f31da590f..94ab2187fbff6aa578f1131ea5e12c26a129243d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -151,7 +151,9 @@ impl Toolbar for SearchBar { self.dismissed = true; for (editor, _) in &self.editors_with_matches { if let Some(editor) = editor.upgrade(cx) { - editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::(cx)); + editor.update(cx, |editor, cx| { + editor.clear_background_highlights::(cx) + }); } } } @@ -397,7 +399,9 @@ impl SearchBar { if Some(&editor) == self.active_editor.as_ref() { active_editor_matches = Some((editor.downgrade(), ranges)); } else { - editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::(cx)); + editor.update(cx, |editor, cx| { + editor.clear_background_highlights::(cx) + }); } } } @@ -410,7 +414,9 @@ impl SearchBar { if let Some(editor) = self.active_editor.as_ref() { if query.is_empty() { self.active_match_index.take(); - editor.update(cx, |editor, cx| editor.clear_highlighted_ranges::(cx)); + editor.update(cx, |editor, cx| { + editor.clear_background_highlights::(cx) + }); } else { let buffer = editor.read(cx).buffer().read(cx).snapshot(cx); let query = if self.regex { @@ -480,7 +486,7 @@ impl SearchBar { } } - editor.highlight_ranges::( + editor.highlight_background::( ranges, theme.match_background, cx, @@ -557,7 +563,7 @@ mod tests { editor.next_notification(&cx).await; editor.update(cx, |editor, cx| { assert_eq!( - editor.all_highlighted_ranges(cx), + editor.all_background_highlights(cx), &[ ( DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19), @@ -578,7 +584,7 @@ mod tests { editor.next_notification(&cx).await; editor.update(cx, |editor, cx| { assert_eq!( - editor.all_highlighted_ranges(cx), + editor.all_background_highlights(cx), &[( DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45), Color::red(), @@ -594,7 +600,7 @@ mod tests { editor.next_notification(&cx).await; editor.update(cx, |editor, cx| { assert_eq!( - editor.all_highlighted_ranges(cx), + editor.all_background_highlights(cx), &[ ( DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26), @@ -635,7 +641,7 @@ mod tests { editor.next_notification(&cx).await; editor.update(cx, |editor, cx| { assert_eq!( - editor.all_highlighted_ranges(cx), + editor.all_background_highlights(cx), &[ ( DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43), diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 3e649fa4dd547ec2fb4be7ebaced4be504a6fbf9..dae93d2151380b148b7f953acf38669aa3697801 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -565,7 +565,7 @@ impl ProjectSearchView { if reset_selections { editor.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit), cx); } - editor.highlight_ranges::(match_ranges, theme.match_background, cx); + editor.highlight_background::(match_ranges, theme.match_background, cx); }); if self.query_editor.is_focused(cx) { self.focus_results_editor(cx); @@ -764,7 +764,7 @@ mod tests { assert_eq!( search_view .results_editor - .update(cx, |editor, cx| editor.all_highlighted_ranges(cx)), + .update(cx, |editor, cx| editor.all_background_highlights(cx)), &[ ( DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35), From e6b1fea11787760803ddb7d637d89766ede877b3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 11:32:14 -0700 Subject: [PATCH 02/11] WIP --- crates/editor/src/display_map.rs | 5 +- crates/editor/src/display_map/block_map.rs | 28 ++- crates/editor/src/display_map/fold_map.rs | 204 ++++++++++++++++++--- crates/editor/src/display_map/tab_map.rs | 35 ++-- crates/editor/src/display_map/wrap_map.rs | 21 ++- crates/editor/src/editor.rs | 14 +- crates/editor/src/multi_buffer.rs | 45 +++-- crates/gpui/src/color.rs | 5 +- crates/sum_tree/src/tree_map.rs | 4 + 9 files changed, 281 insertions(+), 80 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e401f850319375e6e6c841b665a40d3c2be1bea9..fe7895cba556895b168bce2e125438853617e0dd 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -275,7 +275,7 @@ impl DisplaySnapshot { pub fn text_chunks(&self, display_row: u32) -> impl Iterator { self.blocks_snapshot - .chunks(display_row..self.max_point().row() + 1, false) + .chunks(display_row..self.max_point().row() + 1, false, None) .map(|h| h.text) } @@ -284,7 +284,8 @@ impl DisplaySnapshot { display_rows: Range, language_aware: bool, ) -> DisplayChunks<'a> { - self.blocks_snapshot.chunks(display_rows, language_aware) + self.blocks_snapshot + .chunks(display_rows, language_aware, Some(&self.text_highlights)) } pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 7b90179c8ffbfbf1903ec0097708e891c3a94f9a..770ebc8fbef6ce852400e18632419e03717a9b93 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,4 +1,7 @@ -use super::wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}; +use super::{ + wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, + TextHighlights, +}; use crate::{Anchor, ToPoint as _}; use collections::{Bound, HashMap, HashSet}; use gpui::{AppContext, ElementBox}; @@ -555,12 +558,17 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(0..self.transforms.summary().output_rows, false) + self.chunks(0..self.transforms.summary().output_rows, false, None) .map(|chunk| chunk.text) .collect() } - pub fn chunks<'a>(&'a self, rows: Range, language_aware: bool) -> BlockChunks<'a> { + pub fn chunks<'a>( + &'a self, + rows: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + ) -> BlockChunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); let input_end = { @@ -588,9 +596,11 @@ impl BlockSnapshot { cursor.start().1 .0 + overshoot }; BlockChunks { - input_chunks: self - .wrap_snapshot - .chunks(input_start..input_end, language_aware), + input_chunks: self.wrap_snapshot.chunks( + input_start..input_end, + language_aware, + text_highlights, + ), input_chunk: Default::default(), transforms: cursor, output_row: rows.start, @@ -1436,7 +1446,11 @@ mod tests { for start_row in 0..expected_row_count { let expected_text = expected_lines[start_row..].join("\n"); let actual_text = blocks_snapshot - .chunks(start_row as u32..blocks_snapshot.max_point().row + 1, false) + .chunks( + start_row as u32..blocks_snapshot.max_point().row + 1, + false, + None, + ) .map(|chunk| chunk.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index facb5ab2a7f1ee86d67e69129d024ab011fe2b2c..295b7710efd186f7048e1dc4a55dbb86ec268322 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -2,16 +2,22 @@ use crate::{ multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, ToOffset, }; +use collections::BTreeMap; +use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, PointUtf16, TextSummary}; use parking_lot::Mutex; use std::{ + any::TypeId, cmp::{self, Ordering}, - iter, + iter::{self, Peekable}, ops::{Range, Sub}, sync::atomic::{AtomicUsize, Ordering::SeqCst}, + vec, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; +use super::TextHighlights; + pub trait ToFoldPoint { fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint; } @@ -95,6 +101,12 @@ impl ToFoldPoint for Point { } } +impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.lines; + } +} + pub struct FoldMapWriter<'a>(&'a mut FoldMap); impl<'a> FoldMapWriter<'a> { @@ -500,7 +512,7 @@ impl FoldSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(FoldOffset(0)..self.len(), false) + self.chunks(FoldOffset(0)..self.len(), false, None) .map(|c| c.text) .collect() } @@ -640,20 +652,96 @@ impl FoldSnapshot { pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { let start = start.to_offset(self); - self.chunks(start..self.len(), false) + self.chunks(start..self.len(), false, None) .flat_map(|chunk| chunk.text.chars()) } - pub fn chunks<'a>(&'a self, range: Range, language_aware: bool) -> FoldChunks<'a> { + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + ) -> FoldChunks<'a> { + let mut highlight_endpoints = Vec::new(); let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); - transform_cursor.seek(&range.end, Bias::Right, &()); - let overshoot = range.end.0 - transform_cursor.start().0 .0; - let buffer_end = transform_cursor.start().1 + overshoot; + let buffer_end = { + transform_cursor.seek(&range.end, Bias::Right, &()); + let overshoot = range.end.0 - transform_cursor.start().0 .0; + transform_cursor.start().1 + overshoot + }; - transform_cursor.seek(&range.start, Bias::Right, &()); - let overshoot = range.start.0 - transform_cursor.start().0 .0; - let buffer_start = transform_cursor.start().1 + overshoot; + let buffer_start = { + transform_cursor.seek(&range.start, Bias::Right, &()); + let overshoot = range.start.0 - transform_cursor.start().0 .0; + transform_cursor.start().1 + overshoot + }; + + if let Some(text_highlights) = text_highlights { + if !text_highlights.is_empty() { + while transform_cursor.start().0 < range.end { + if !transform_cursor.item().unwrap().is_fold() { + let transform_start = self + .buffer_snapshot + .anchor_after(cmp::max(buffer_start, transform_cursor.start().1)); + + let transform_end = { + let overshoot = range.end.0 - transform_cursor.start().0 .0; + self.buffer_snapshot.anchor_before(cmp::min( + transform_cursor.end(&()).1, + transform_cursor.start().1 + overshoot, + )) + }; + + for (tag, highlights) in text_highlights.iter() { + let style = highlights.0; + let ranges = &highlights.1; + + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe + .end + .cmp(&transform_start, &self.buffer_snapshot()) + .unwrap(); + if cmp.is_gt() { + Ordering::Greater + } else { + Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range + .start + .cmp(&transform_end, &self.buffer_snapshot) + .unwrap() + .is_ge() + { + break; + } + + highlight_endpoints.push(HighlightEndpoint { + offset: range.start.to_offset(&self.buffer_snapshot), + is_start: true, + tag: *tag, + style, + }); + highlight_endpoints.push(HighlightEndpoint { + offset: range.end.to_offset(&self.buffer_snapshot), + is_start: false, + tag: *tag, + style, + }); + } + } + } + + transform_cursor.next(&()); + } + highlight_endpoints.sort(); + transform_cursor.seek(&range.start, Bias::Right, &()); + } + } FoldChunks { transform_cursor, @@ -664,6 +752,8 @@ impl FoldSnapshot { buffer_offset: buffer_start, output_offset: range.start.0, max_output_offset: range.end.0, + highlight_endpoints: highlight_endpoints.into_iter().peekable(), + active_highlights: Default::default(), } } @@ -952,6 +1042,8 @@ pub struct FoldChunks<'a> { buffer_offset: usize, output_offset: usize, max_output_offset: usize, + highlight_endpoints: Peekable>, + active_highlights: BTreeMap, HighlightStyle>, } impl<'a> Iterator for FoldChunks<'a> { @@ -990,6 +1082,21 @@ impl<'a> Iterator for FoldChunks<'a> { }); } + let mut next_highlight_endpoint = usize::MAX; + while let Some(endpoint) = self.highlight_endpoints.peek().copied() { + if endpoint.offset <= self.buffer_offset { + if endpoint.is_start { + self.active_highlights.insert(endpoint.tag, endpoint.style); + } else { + self.active_highlights.remove(&endpoint.tag); + } + self.highlight_endpoints.next(); + } else { + next_highlight_endpoint = endpoint.offset; + break; + } + } + // Retrieve a chunk from the current location in the buffer. if self.buffer_chunk.is_none() { let chunk_offset = self.buffer_chunks.offset(); @@ -997,20 +1104,31 @@ impl<'a> Iterator for FoldChunks<'a> { } // Otherwise, take a chunk from the buffer's text. - if let Some((chunk_offset, mut chunk)) = self.buffer_chunk { - let offset_in_chunk = self.buffer_offset - chunk_offset; - chunk.text = &chunk.text[offset_in_chunk..]; - - // Truncate the chunk so that it ends at the next fold. - let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset; - if chunk.text.len() >= region_end { - chunk.text = &chunk.text[0..region_end]; + if let Some((buffer_chunk_start, mut chunk)) = self.buffer_chunk { + let buffer_chunk_end = buffer_chunk_start + chunk.text.len(); + let transform_end = self.transform_cursor.end(&()).1; + let chunk_end = buffer_chunk_end + .min(transform_end) + .min(next_highlight_endpoint); + + chunk.text = &chunk.text + [self.buffer_offset - buffer_chunk_start..chunk_end - buffer_chunk_start]; + + if !self.active_highlights.is_empty() { + let mut highlight_style = HighlightStyle::default(); + for active_highlight in self.active_highlights.values() { + highlight_style.highlight(*active_highlight); + } + chunk.highlight_style = Some(highlight_style); + } + + if chunk_end == transform_end { self.transform_cursor.next(&()); - } else { + } else if chunk_end == buffer_chunk_end { self.buffer_chunk.take(); } - self.buffer_offset += chunk.text.len(); + self.buffer_offset = chunk_end; self.output_offset += chunk.text.len(); return Some(chunk); } @@ -1019,9 +1137,25 @@ impl<'a> Iterator for FoldChunks<'a> { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += &summary.output.lines; +#[derive(Copy, Clone, Eq, PartialEq)] +struct HighlightEndpoint { + offset: usize, + is_start: bool, + tag: Option, + style: HighlightStyle, +} + +impl PartialOrd for HighlightEndpoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HighlightEndpoint { + fn cmp(&self, other: &Self) -> Ordering { + self.offset + .cmp(&other.offset) + .then_with(|| self.is_start.cmp(&other.is_start)) } } @@ -1078,7 +1212,8 @@ mod tests { use super::*; use crate::{MultiBuffer, ToPoint}; use rand::prelude::*; - use std::{env, mem}; + use std::{cmp::Reverse, env, mem, sync::Arc}; + use sum_tree::TreeMap; use text::RandomCharIter; use util::test::sample_text; use Bias::{Left, Right}; @@ -1283,6 +1418,25 @@ mod tests { let (mut initial_snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); + let mut highlights = TreeMap::default(); + let highlight_count = rng.gen_range(0_usize..10); + let mut highlight_ranges = (0..highlight_count) + .map(|_| buffer_snapshot.random_byte_range(0, &mut rng)) + .collect::>(); + highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end))); + log::info!("highlighting ranges {:?}", highlight_ranges); + let highlight_ranges = highlight_ranges + .into_iter() + .map(|range| { + buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end) + }) + .collect::>(); + + highlights.insert( + Some(TypeId::of::<()>()), + Arc::new((HighlightStyle::default(), highlight_ranges)), + ); + for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); let mut buffer_edits = Vec::new(); @@ -1407,7 +1561,7 @@ mod tests { let text = &expected_text[start.0..end.0]; assert_eq!( snapshot - .chunks(start..end, false) + .chunks(start..end, false, Some(&highlights)) .map(|c| c.text) .collect::(), text, diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index e2239e76715278ba84e0ade5d3b56c7bc9e0f082..696b50577aacee56a2482e95e7ca168729b0a401 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,4 +1,7 @@ -use super::fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot, ToFoldPoint}; +use super::{ + fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot, ToFoldPoint}, + TextHighlights, +}; use crate::MultiBufferSnapshot; use language::{rope, Chunk}; use parking_lot::Mutex; @@ -32,9 +35,10 @@ impl TabMap { let mut tab_edits = Vec::with_capacity(fold_edits.len()); for fold_edit in &mut fold_edits { let mut delta = 0; - for chunk in old_snapshot - .fold_snapshot - .chunks(fold_edit.old.end..max_offset, false) + for chunk in + old_snapshot + .fold_snapshot + .chunks(fold_edit.old.end..max_offset, false, None) { let patterns: &[_] = &['\t', '\n']; if let Some(ix) = chunk.text.find(patterns) { @@ -109,7 +113,7 @@ impl TabSnapshot { self.max_point() }; for c in self - .chunks(range.start..line_end, false) + .chunks(range.start..line_end, false, None) .flat_map(|chunk| chunk.text.chars()) { if c == '\n' { @@ -123,7 +127,7 @@ impl TabSnapshot { last_line_chars = first_line_chars; } else { for _ in self - .chunks(TabPoint::new(range.end.row(), 0)..range.end, false) + .chunks(TabPoint::new(range.end.row(), 0)..range.end, false, None) .flat_map(|chunk| chunk.text.chars()) { last_line_chars += 1; @@ -143,7 +147,12 @@ impl TabSnapshot { self.fold_snapshot.version } - pub fn chunks<'a>(&'a self, range: Range, language_aware: bool) -> TabChunks<'a> { + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = self.to_fold_point(range.start, Bias::Left); let input_start = input_start.to_offset(&self.fold_snapshot); @@ -158,9 +167,11 @@ impl TabSnapshot { }; TabChunks { - fold_chunks: self - .fold_snapshot - .chunks(input_start..input_end, language_aware), + fold_chunks: self.fold_snapshot.chunks( + input_start..input_end, + language_aware, + text_highlights, + ), column: expanded_char_column, output_position: range.start.0, max_output_position: range.end.0, @@ -179,7 +190,7 @@ impl TabSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(TabPoint::zero()..self.max_point(), false) + self.chunks(TabPoint::zero()..self.max_point(), false, None) .map(|chunk| chunk.text) .collect() } @@ -492,7 +503,7 @@ mod tests { assert_eq!( expected_text, tabs_snapshot - .chunks(start..end, false) + .chunks(start..end, false, None) .map(|c| c.text) .collect::(), "chunks({:?}..{:?})", diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 7858197902dcd90a6f98e7739991744ed8639af6..2fab37fb30997ce867994b922f89add56e47d5d9 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,6 +1,7 @@ use super::{ fold_map, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, + TextHighlights, }; use crate::{MultiBufferSnapshot, Point}; use gpui::{ @@ -433,6 +434,7 @@ impl WrapSnapshot { let mut chunks = new_tab_snapshot.chunks( TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(), false, + None, ); let mut edit_transforms = Vec::::new(); for _ in edit.new_rows.start..edit.new_rows.end { @@ -558,11 +560,16 @@ impl WrapSnapshot { } pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { - self.chunks(wrap_row..self.max_point().row() + 1, false) + self.chunks(wrap_row..self.max_point().row() + 1, false, None) .map(|h| h.text) } - pub fn chunks<'a>(&'a self, rows: Range, language_aware: bool) -> WrapChunks<'a> { + pub fn chunks<'a>( + &'a self, + rows: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + ) -> WrapChunks<'a> { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(); @@ -575,9 +582,11 @@ impl WrapSnapshot { .to_tab_point(output_end) .min(self.tab_snapshot.max_point()); WrapChunks { - input_chunks: self - .tab_snapshot - .chunks(input_start..input_end, language_aware), + input_chunks: self.tab_snapshot.chunks( + input_start..input_end, + language_aware, + text_highlights, + ), input_chunk: Default::default(), output_position: output_start, max_output_row: rows.end, @@ -1280,7 +1289,7 @@ mod tests { } let actual_text = self - .chunks(start_row..end_row, true) + .chunks(start_row..end_row, true, None) .map(|c| c.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ded484e549675de172764b453bc1bfc8b35d608b..64009448570bb56f60fdd053ca9e3188b2f69e25 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4398,16 +4398,16 @@ impl Editor { None, cx, ); - editor.highlight_background::( - vec![Anchor::min()..Anchor::max()], - style.diff_background_inserted, - cx, - ); editor }); - this.highlight_background::( + this.highlight_text::( vec![range.clone()], - style.diff_background_deleted, + HighlightStyle { + color: Color::transparent_black(), + font_properties: todo!(), + underline: todo!(), + fade_out: todo!(), + }, cx, ); this.update_selections( diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 64683faa962229a77ba6384881eed7ca4ea87d1d..93f67d0cc7ee5dc8a7e771fa6efbd866cb6e542e 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -213,25 +213,6 @@ impl MultiBuffer { this } - #[cfg(any(test, feature = "test-support"))] - pub fn build_simple(text: &str, cx: &mut gpui::MutableAppContext) -> ModelHandle { - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - cx.add_model(|cx| Self::singleton(buffer, cx)) - } - - #[cfg(any(test, feature = "test-support"))] - pub fn build_random( - rng: &mut impl rand::Rng, - cx: &mut gpui::MutableAppContext, - ) -> ModelHandle { - cx.add_model(|cx| { - let mut multibuffer = MultiBuffer::new(0); - let mutation_count = rng.gen_range(1..=5); - multibuffer.randomly_edit_excerpts(rng, mutation_count, cx); - multibuffer - }) - } - pub fn replica_id(&self) -> ReplicaId { self.replica_id } @@ -1170,6 +1151,23 @@ impl MultiBuffer { #[cfg(any(test, feature = "test-support"))] impl MultiBuffer { + pub fn build_simple(text: &str, cx: &mut gpui::MutableAppContext) -> ModelHandle { + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); + cx.add_model(|cx| Self::singleton(buffer, cx)) + } + + pub fn build_random( + rng: &mut impl rand::Rng, + cx: &mut gpui::MutableAppContext, + ) -> ModelHandle { + cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + let mutation_count = rng.gen_range(1..=5); + multibuffer.randomly_edit_excerpts(rng, mutation_count, cx); + multibuffer + }) + } + pub fn randomly_edit( &mut self, rng: &mut impl rand::Rng, @@ -2300,6 +2298,15 @@ impl MultiBufferSnapshot { } } +#[cfg(any(test, feature = "test-support"))] +impl MultiBufferSnapshot { + pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { + let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); + let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); + start..end + } +} + impl History { fn start_transaction(&mut self, now: Instant) -> Option { self.transaction_depth += 1; diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 24e917a7779c21b5a50d11ed8a127eee8e064297..1057cbe7aa3dc690c1539fb6f91ad342b0198217 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -50,8 +50,9 @@ impl Color { } pub fn blend(source: Color, dest: Color) -> Color { - if dest.a == 255 { - return dest; + // If source is fully opaque, don't blend. + if source.a == 255 { + return source; } let source = source.0.to_f32(); diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 1de6b2f589470e11fbd436d76c77604ca1219a34..80143aad6971d9cde4164236e77d142060a24b14 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -31,6 +31,10 @@ impl TreeMap { Self(tree) } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn get<'a>(&self, key: &'a K) -> Option<&V> { let mut cursor = self.0.cursor::>(); cursor.seek(&MapKeyRef(Some(key)), Bias::Left, &()); From 3968b37e26ba77f9f5f537631042af69033a0280 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 14:55:31 -0700 Subject: [PATCH 03/11] When renaming, fade out the old name and select all --- crates/editor/src/editor.rs | 14 +++------- crates/editor/src/element.rs | 40 ++++++++++++++++------------- crates/gpui/src/color.rs | 12 ++++----- crates/gpui/src/elements/text.rs | 38 ++++++++++++++++++--------- crates/gpui/src/fonts.rs | 12 +++++++-- crates/theme/src/theme.rs | 1 + crates/zed/assets/themes/_base.toml | 1 + 7 files changed, 70 insertions(+), 48 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 64009448570bb56f60fdd053ca9e3188b2f69e25..bd12b1f5ba589a0470b5d988e9aaeb361e9c3598 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4393,20 +4393,14 @@ impl Editor { editor .buffer .update(cx, |buffer, cx| buffer.edit([0..0], &old_name, cx)); - editor.select_ranges( - [tail_offset_in_rename_range..cursor_offset_in_rename_range], - None, - cx, - ); + editor.select_all(&SelectAll, cx); editor }); this.highlight_text::( vec![range.clone()], HighlightStyle { - color: Color::transparent_black(), - font_properties: todo!(), - underline: todo!(), - fade_out: todo!(), + fade_out: Some(style.rename_fade), + ..Default::default() }, cx, ); @@ -4500,7 +4494,7 @@ impl Editor { fn take_rename(&mut self, cx: &mut ViewContext) -> Option { let rename = self.pending_rename.take()?; self.remove_blocks([rename.block_id].into_iter().collect(), cx); - self.clear_background_highlights::(cx); + self.clear_text_highlights::(cx); let editor = rename.editor.read(cx); let snapshot = self.buffer.read(cx).snapshot(cx); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ae3a09dc13615c175d2053cffa2f8c5905bc99d0..13161f90834cb5821743453b17a4c38a8e102f8d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -606,30 +606,34 @@ impl EditorElement { } else { let style = &self.style; let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| { - let mut highlight_style = HighlightStyle { - color: style.text.color, - font_properties: style.text.font_properties, - ..Default::default() - }; - - if let Some(syntax_highlight_style) = chunk + let mut highlight_style = chunk .syntax_highlight_id - .and_then(|id| id.style(&style.syntax)) - { - highlight_style.highlight(syntax_highlight_style); - } + .and_then(|id| id.style(&style.syntax)); - if let Some(style) = chunk.highlight_style { - highlight_style.highlight(style); + if let Some(chunk_highlight) = chunk.highlight_style { + if let Some(highlight_style) = highlight_style.as_mut() { + highlight_style.highlight(chunk_highlight); + } else { + highlight_style = Some(chunk_highlight); + } } if let Some(severity) = chunk.diagnostic { let diagnostic_style = super::diagnostic_style(severity, true, style); - highlight_style.underline = Some(Underline { - color: diagnostic_style.message.text.color, - thickness: 1.0.into(), - squiggly: true, - }); + let diagnostic_highlight = HighlightStyle { + underline: Some(Underline { + color: diagnostic_style.message.text.color, + thickness: 1.0.into(), + squiggly: true, + }), + ..Default::default() + }; + + if let Some(highlight_style) = highlight_style.as_mut() { + highlight_style.highlight(diagnostic_highlight); + } else { + highlight_style = Some(diagnostic_highlight); + } } (chunk.text, highlight_style) diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 1057cbe7aa3dc690c1539fb6f91ad342b0198217..f31a80a831d43812307833312b03e3b670a111b9 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -50,9 +50,11 @@ impl Color { } pub fn blend(source: Color, dest: Color) -> Color { - // If source is fully opaque, don't blend. + // Skip blending if we don't need it. if source.a == 255 { return source; + } else if source.a == 0 { + return dest; } let source = source.0.to_f32(); @@ -66,11 +68,9 @@ impl Color { Self(ColorF::new(r, g, b, a).to_u8()) } - pub fn fade_out(&mut self, factor: f32) { - let source_alpha = 1. - factor.clamp(0., 1.); - let dest_alpha = self.0.a as f32 / 255.; - let dest_alpha = source_alpha + (dest_alpha * (1. - source_alpha)); - self.0.a = (dest_alpha * (1. / 255.)) as u8; + pub fn fade_out(&mut self, fade: f32) { + let fade = fade.clamp(0., 1.); + self.0.a = (self.0.a as f32 * (1. - fade)) as u8; } } diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 681464611721da812572297d0edb1d828271348d..707bad55e699f412a5a7e1e05b5289289ad0d934 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -69,18 +69,15 @@ impl Element for Text { let result; if let Some((range, highlight_style)) = highlight_ranges.peek() { if offset < range.start { - result = Some(( - &self.text[offset..range.start], - HighlightStyle::from(&self.style), - )); + result = Some((&self.text[offset..range.start], None)); offset = range.start; } else { - result = Some((&self.text[range.clone()], *highlight_style)); + result = Some((&self.text[range.clone()], Some(*highlight_style))); highlight_ranges.next(); offset = range.end; } } else if offset < self.text.len() { - result = Some((&self.text[offset..], HighlightStyle::from(&self.style))); + result = Some((&self.text[offset..], None)); offset = self.text.len(); } else { result = None; @@ -200,7 +197,7 @@ impl Element for Text { /// Perform text layout on a series of highlighted chunks of text. pub fn layout_highlighted_chunks<'a>( - chunks: impl Iterator, + chunks: impl Iterator)>, text_style: &'a TextStyle, text_layout_cache: &'a TextLayoutCache, font_cache: &'a Arc, @@ -228,12 +225,29 @@ pub fn layout_highlighted_chunks<'a>( } if !line_chunk.is_empty() && !line_exceeded_max_len { + let font_properties; + let mut color; + let underline; + + if let Some(highlight_style) = highlight_style { + font_properties = highlight_style.font_properties; + color = Color::blend(highlight_style.color, text_style.color); + if let Some(fade) = highlight_style.fade_out { + color.fade_out(fade); + } + underline = highlight_style.underline; + } else { + font_properties = text_style.font_properties; + color = text_style.color; + underline = None; + } + // Avoid a lookup if the font properties match the previous ones. - let font_id = if highlight_style.font_properties == prev_font_properties { + let font_id = if font_properties == prev_font_properties { prev_font_id } else { font_cache - .select_font(text_style.font_family_id, &highlight_style.font_properties) + .select_font(text_style.font_family_id, &font_properties) .unwrap_or(text_style.font_id) }; @@ -251,12 +265,12 @@ pub fn layout_highlighted_chunks<'a>( line_chunk.len(), RunStyle { font_id, - color: highlight_style.color, - underline: highlight_style.underline, + color, + underline, }, )); prev_font_id = font_id; - prev_font_properties = highlight_style.font_properties; + prev_font_properties = font_properties; } } } diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 60073a64d63d1cd6202dc3213061c607abb2f631..d2fab7467d0d586034099ef386e03689b5ae1c28 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -267,8 +267,16 @@ impl HighlightStyle { pub fn highlight(&mut self, other: HighlightStyle) { self.color = Color::blend(other.color, self.color); - if let Some(factor) = other.fade_out { - self.color.fade_out(factor); + match (other.fade_out, self.fade_out) { + (Some(source_fade), None) => self.fade_out = Some(source_fade), + (Some(source_fade), Some(dest_fade)) => { + let source_alpha = 1. - source_fade; + let dest_alpha = 1. - dest_fade; + let blended_alpha = source_alpha + (dest_alpha * source_fade); + let blended_fade = 1. - blended_alpha; + self.fade_out = Some(blended_fade); + } + _ => {} } self.font_properties = other.font_properties; if other.underline.is_some() { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c96db5aab2ad3082520f8b3b96f7a93d4928c5a4..d372c2fd31b682fb03d090cce77f295adb9f48cd 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -282,6 +282,7 @@ pub struct Editor { pub gutter_padding_factor: f32, pub active_line_background: Color, pub highlighted_line_background: Color, + pub rename_fade: f32, pub document_highlight_read_background: Color, pub document_highlight_write_background: Color, pub diff_background_deleted: Color, diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 59a488c896b4791e66514a2f0b76f409e8483342..97c3c55fcb83442c9e44f7f8afb8def796862fc3 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -249,6 +249,7 @@ gutter_background = "$surface.1" gutter_padding_factor = 2.5 active_line_background = "$state.active_line" highlighted_line_background = "$state.highlighted_line" +rename_fade = 0.6 document_highlight_read_background = "#99999920" document_highlight_write_background = "#99999916" diff_background_deleted = "$state.deleted_line" From 69ce021f32d14052f832928a9f4779b71943ba46 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 15:27:29 -0700 Subject: [PATCH 04/11] Remove unused variable --- crates/editor/src/editor.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bd12b1f5ba589a0470b5d988e9aaeb361e9c3598..6d3a17a81ce421a4df4d57432b4cc001921c2640 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4351,7 +4351,7 @@ impl Editor { .buffer .read(cx) .text_anchor_for_position(selection.head(), cx)?; - let (tail_buffer, tail_buffer_position) = self + let (tail_buffer, _) = self .buffer .read(cx) .text_anchor_for_position(selection.tail(), cx)?; @@ -4361,7 +4361,6 @@ impl Editor { let snapshot = cursor_buffer.read(cx).snapshot(); let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot); - let tail_buffer_offset = tail_buffer_position.to_offset(&snapshot); let prepare_rename = project.update(cx, |project, cx| { project.prepare_rename(cursor_buffer, cursor_buffer_offset, cx) }); @@ -4371,8 +4370,6 @@ impl Editor { let rename_buffer_range = rename_range.to_offset(&snapshot); let cursor_offset_in_rename_range = cursor_buffer_offset.saturating_sub(rename_buffer_range.start); - let tail_offset_in_rename_range = - tail_buffer_offset.saturating_sub(rename_buffer_range.start); this.update(&mut cx, |this, cx| { this.take_rename(cx); From a498cd32c851c7ca864c3ce84d353c61baff8d4f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 16:38:45 -0700 Subject: [PATCH 05/11] When renaming, match the color of the renamed token in the rename editor --- crates/editor/src/editor.rs | 54 ++++++++++++++++++++++++++++++------- crates/gpui/src/fonts.rs | 2 +- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6d3a17a81ce421a4df4d57432b4cc001921c2640..352d9699460408004f2dc2e00930ceaa20ed8d1e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -409,6 +409,8 @@ type CompletionId = usize; pub type GetFieldEditorTheme = fn(&theme::Theme) -> theme::FieldEditor; +type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option; + pub struct Editor { handle: WeakViewHandle, buffer: ModelHandle, @@ -431,6 +433,7 @@ pub struct Editor { settings: watch::Receiver, soft_wrap_mode_override: Option, get_field_editor_theme: Option, + override_text_style: Option>, project: Option>, focused: bool, show_local_cursors: bool, @@ -864,7 +867,7 @@ impl Editor { ) -> Self { let display_map = cx.add_model(|cx| { let settings = settings.borrow(); - let style = build_style(&*settings, get_field_editor_theme, cx); + let style = build_style(&*settings, get_field_editor_theme, None, cx); DisplayMap::new( buffer.clone(), settings.tab_size, @@ -930,6 +933,7 @@ impl Editor { document_highlights_task: Default::default(), pending_rename: Default::default(), searchable: true, + override_text_style: None, }; this.end_selection(cx); this @@ -982,7 +986,12 @@ impl Editor { } fn style(&self, cx: &AppContext) -> EditorStyle { - build_style(&*self.settings.borrow(), self.get_field_editor_theme, cx) + build_style( + &*self.settings.borrow(), + self.get_field_editor_theme, + self.override_text_style.as_deref(), + cx, + ) } pub fn set_placeholder_text( @@ -4379,14 +4388,26 @@ impl Editor { let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range); let rename_end = rename_start + rename_buffer_range.len(); let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end); + let mut old_highlight_id = None; let old_name = buffer - .text_for_range(rename_start..rename_end) - .collect::(); + .chunks(rename_start..rename_end, true) + .map(|chunk| { + if old_highlight_id.is_none() { + old_highlight_id = chunk.syntax_highlight_id; + } + chunk.text + }) + .collect(); + drop(buffer); // Position the selection in the rename editor so that it matches the current selection. let rename_editor = cx.add_view(|cx| { let mut editor = Editor::single_line(this.settings.clone(), None, cx); + if let Some(old_highlight_id) = old_highlight_id { + editor.override_text_style = + Some(Box::new(move |style| old_highlight_id.style(&style.syntax))); + } editor .buffer .update(cx, |buffer, cx| buffer.edit([0..0], &old_name, cx)); @@ -5631,14 +5652,14 @@ impl View for Editor { fn build_style( settings: &Settings, get_field_editor_theme: Option, + override_text_style: Option<&OverrideTextStyle>, cx: &AppContext, ) -> EditorStyle { + let font_cache = cx.font_cache(); + let mut theme = settings.theme.editor.clone(); - if let Some(get_field_editor_theme) = get_field_editor_theme { + let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme { let field_editor_theme = get_field_editor_theme(&settings.theme); - if let Some(background) = field_editor_theme.container.background_color { - theme.background = background; - } theme.text_color = field_editor_theme.text.color; theme.selection = field_editor_theme.selection; EditorStyle { @@ -5647,7 +5668,6 @@ fn build_style( theme, } } else { - let font_cache = cx.font_cache(); let font_family_id = settings.buffer_font_family; let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); let font_properties = Default::default(); @@ -5668,7 +5688,23 @@ fn build_style( placeholder_text: None, theme, } + }; + + if let Some(highlight_style) = + override_text_style.and_then(|build_style| dbg!(build_style(&style))) + { + if let Some(highlighted) = style + .text + .clone() + .highlight(highlight_style, font_cache) + .log_err() + { + style.text = highlighted; + dbg!(&style.text); + } } + + style } impl SelectionExt for Selection { diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index d2fab7467d0d586034099ef386e03689b5ae1c28..c0995b68087f5c3e4f49eb68c7fb74cda99c6027 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -136,7 +136,7 @@ impl TextStyle { if self.font_properties != style.font_properties { self.font_id = font_cache.select_font(self.font_family_id, &style.font_properties)?; } - self.color = Color::blend(self.color, style.color); + self.color = Color::blend(style.color, self.color); if let Some(factor) = style.fade_out { self.color.fade_out(factor); } From 94bf3366f6a8f68de7e3219e97624bac38f64f1a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 16:42:20 -0700 Subject: [PATCH 06/11] Focus in-progress rename editor when editor is focused --- crates/editor/src/editor.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 352d9699460408004f2dc2e00930ceaa20ed8d1e..addece43f950785629aca7be67ab937cb811a356 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5607,12 +5607,16 @@ impl View for Editor { } fn on_focus(&mut self, cx: &mut ViewContext) { - self.focused = true; - self.blink_cursors(self.blink_epoch, cx); - self.buffer.update(cx, |buffer, cx| { - buffer.finalize_last_transaction(cx); - buffer.set_active_selections(&self.selections, cx) - }); + if let Some(rename) = self.pending_rename.as_ref() { + cx.focus(&rename.editor); + } else { + self.focused = true; + self.blink_cursors(self.blink_epoch, cx); + self.buffer.update(cx, |buffer, cx| { + buffer.finalize_last_transaction(cx); + buffer.set_active_selections(&self.selections, cx) + }); + } } fn on_blur(&mut self, cx: &mut ViewContext) { From c35a96c4255a0fd4d5ca1b338127746ef46ad2f6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Mar 2022 15:54:17 -0700 Subject: [PATCH 07/11] On rename, replace background highlights with transparent text highlights --- crates/editor/src/display_map.rs | 7 +++++-- crates/editor/src/editor.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 6c481eaa5574b5f3daf3f6fb9f4630c080104571..5e18ad0b640f2023c1bc65b8f3eb3b6858929d42 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -174,8 +174,11 @@ impl DisplayMap { .insert(Some(type_id), Arc::new((style, ranges))); } - pub fn clear_text_highlights(&mut self, type_id: TypeId) { - self.text_highlights.remove(&Some(type_id)); + pub fn clear_text_highlights( + &mut self, + type_id: TypeId, + ) -> Option>)>> { + self.text_highlights.remove(&Some(type_id)) } pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0a4bd6caa825b157c7485f50bf9d2f3cc2209bd6..e462bd987268b8428a9bcfd36da3e5f0d8e1aafb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -134,6 +134,9 @@ action!(ConfirmCompletion, Option); action!(ConfirmCodeAction, Option); action!(OpenExcerpts); +enum DocumentHighlightRead {} +enum DocumentHighlightWrite {} + pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec>) { path_openers.push(Box::new(items::BufferOpener)); cx.add_bindings(vec![ @@ -2421,13 +2424,14 @@ impl Editor { project.document_highlights(&cursor_buffer, cursor_buffer_position, cx) }); - enum DocumentHighlightRead {} - enum DocumentHighlightWrite {} - self.document_highlights_task = Some(cx.spawn_weak(|this, mut cx| async move { let highlights = highlights.log_err().await; if let Some((this, highlights)) = this.upgrade(&cx).zip(highlights) { this.update(&mut cx, |this, cx| { + if this.pending_rename.is_some() { + return; + } + let buffer_id = cursor_position.buffer_id; let excerpt_id = cursor_position.excerpt_id.clone(); let style = this.style(cx); @@ -4436,8 +4440,19 @@ impl Editor { editor.select_all(&SelectAll, cx); editor }); + + let ranges = this + .clear_background_highlights::(cx) + .into_iter() + .flat_map(|(_, ranges)| ranges) + .chain( + this.clear_background_highlights::(cx) + .into_iter() + .flat_map(|(_, ranges)| ranges), + ) + .collect(); this.highlight_text::( - vec![range.clone()], + ranges, HighlightStyle { fade_out: Some(style.rename_fade), ..Default::default() @@ -5392,10 +5407,13 @@ impl Editor { cx.notify(); } - pub fn clear_text_highlights(&mut self, cx: &mut ViewContext) { - self.display_map - .update(cx, |map, _| map.clear_text_highlights(TypeId::of::())); + pub fn clear_text_highlights( + &mut self, + cx: &mut ViewContext, + ) -> Option>)>> { cx.notify(); + self.display_map + .update(cx, |map, _| map.clear_text_highlights(TypeId::of::())) } fn next_blink_epoch(&mut self) -> usize { From 4f086b8d7af323cba8a408a80da641899225d564 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 12 Mar 2022 10:10:06 -0700 Subject: [PATCH 08/11] Refresh document highlight after rename, but not during Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e462bd987268b8428a9bcfd36da3e5f0d8e1aafb..fbff6735f560ecc518b16d73a07b12186616ab46 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2409,6 +2409,10 @@ impl Editor { } fn refresh_document_highlights(&mut self, cx: &mut ViewContext) -> Option<()> { + if self.pending_rename.is_some() { + return None; + } + let project = self.project.as_ref()?; let buffer = self.buffer.read(cx); let newest_selection = self.newest_anchor_selection().clone(); @@ -4533,16 +4537,21 @@ impl Editor { ) }); - Some(cx.spawn(|workspace, cx| async move { + Some(cx.spawn(|workspace, mut cx| async move { let project_transaction = rename.await?; Self::open_project_transaction( - editor, + editor.clone(), workspace, project_transaction, format!("Rename: {} → {}", old_name, new_name), - cx, + cx.clone(), ) - .await + .await?; + + editor.update(&mut cx, |editor, cx| { + editor.refresh_document_highlights(cx); + }); + Ok(()) })) } From 9f629fa307ac66998d66788bb9d18d904155c51f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 12 Mar 2022 10:30:57 -0700 Subject: [PATCH 09/11] Improve selection handling when pending rename is taken - Set selection to the intuitive cursor position when moving up rather than restoring the full selection of the rename editor. - When cancelling, restore the original selection. Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 81 ++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 85837613f610435b61c58a9b1d44fcb3223712bb..764cacd26438000ad08be6d58b44223da0e8a1db 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1513,7 +1513,7 @@ impl Editor { } pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - if self.take_rename(cx).is_some() { + if self.take_rename(false, cx).is_some() { return; } @@ -3380,7 +3380,7 @@ impl Editor { } pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext) { - if self.take_rename(cx).is_some() { + if self.take_rename(true, cx).is_some() { return; } @@ -3428,7 +3428,7 @@ impl Editor { } pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext) { - self.take_rename(cx); + self.take_rename(true, cx); if let Some(context_menu) = self.context_menu.as_mut() { if context_menu.select_next(cx) { @@ -4411,7 +4411,7 @@ impl Editor { cursor_buffer_offset.saturating_sub(rename_buffer_range.start); this.update(&mut cx, |this, cx| { - this.take_rename(cx); + this.take_rename(false, cx); let style = this.style(cx); let buffer = this.buffer.read(cx).read(cx); let cursor_offset = selection.head().to_offset(&buffer); @@ -4463,17 +4463,6 @@ impl Editor { }, cx, ); - this.update_selections( - vec![Selection { - id: selection.id, - start: rename_end, - end: rename_end, - reversed: false, - goal: SelectionGoal::None, - }], - None, - cx, - ); cx.focus(&rename_editor); let block_id = this.insert_blocks( [BlockProperties { @@ -4513,7 +4502,7 @@ impl Editor { let editor = workspace.active_item(cx)?.act_as::(cx)?; let (buffer, range, old_name, new_name) = editor.update(cx, |editor, cx| { - let rename = editor.take_rename(cx)?; + let rename = editor.take_rename(false, cx)?; let buffer = editor.buffer.read(cx); let (start_buffer, start) = buffer.text_anchor_for_position(rename.range.start.clone(), cx)?; @@ -4555,42 +4544,40 @@ impl Editor { })) } - fn take_rename(&mut self, cx: &mut ViewContext) -> Option { + fn take_rename( + &mut self, + moving_cursor: bool, + cx: &mut ViewContext, + ) -> Option { let rename = self.pending_rename.take()?; self.remove_blocks([rename.block_id].into_iter().collect(), cx); self.clear_text_highlights::(cx); - let selection_in_rename_editor = rename.editor.read(cx).newest_selection::(cx); + if moving_cursor { + let cursor_in_rename_editor = + rename.editor.read(cx).newest_selection::(cx).head(); - // Update the selection to match the position of the selection inside - // the rename editor. - let snapshot = self.buffer.read(cx).read(cx); - let rename_range = rename.range.to_offset(&snapshot); - let start = snapshot - .clip_offset( - rename_range.start + selection_in_rename_editor.start, - Bias::Left, - ) - .min(rename_range.end); - let end = snapshot - .clip_offset( - rename_range.start + selection_in_rename_editor.end, - Bias::Left, - ) - .min(rename_range.end); - drop(snapshot); - - self.update_selections( - vec![Selection { - id: self.newest_anchor_selection().id, - start, - end, - reversed: selection_in_rename_editor.reversed, - goal: SelectionGoal::None, - }], - None, - cx, - ); + // Update the selection to match the position of the selection inside + // the rename editor. + let snapshot = self.buffer.read(cx).read(cx); + let rename_range = rename.range.to_offset(&snapshot); + let cursor_in_editor = snapshot + .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left) + .min(rename_range.end); + drop(snapshot); + + self.update_selections( + vec![Selection { + id: self.newest_anchor_selection().id, + start: cursor_in_editor, + end: cursor_in_editor, + reversed: false, + goal: SelectionGoal::None, + }], + None, + cx, + ); + } Some(rename) } From 308cead8a8e7e189d811cc97e324d9676a598c6c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 12 Mar 2022 10:44:11 -0700 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=99=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 764cacd26438000ad08be6d58b44223da0e8a1db..57e43f874ef7b8d5cafa7be234a1effff667c3e8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5737,9 +5737,7 @@ fn build_style( } }; - if let Some(highlight_style) = - override_text_style.and_then(|build_style| dbg!(build_style(&style))) - { + if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) { if let Some(highlighted) = style .text .clone() @@ -5747,7 +5745,6 @@ fn build_style( .log_err() { style.text = highlighted; - dbg!(&style.text); } } From 992fc071334dd22b2c8ee7cbecab7cf8b4048e05 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 12 Mar 2022 10:45:11 -0700 Subject: [PATCH 11/11] Hide selections in original editor when renaming Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 5 ++- crates/editor/src/element.rs | 59 +++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 57e43f874ef7b8d5cafa7be234a1effff667c3e8..1741a52623898ec01d1a20d67bd2e21a1cc28ca5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -440,6 +440,7 @@ pub struct Editor { project: Option>, focused: bool, show_local_cursors: bool, + show_local_selections: bool, blink_epoch: usize, blinking_paused: bool, mode: EditorMode, @@ -921,6 +922,7 @@ impl Editor { autoscroll_request: None, focused: false, show_local_cursors: false, + show_local_selections: true, blink_epoch: 0, blinking_paused: false, mode, @@ -4432,6 +4434,7 @@ impl Editor { drop(buffer); // Position the selection in the rename editor so that it matches the current selection. + this.show_local_selections = false; let rename_editor = cx.add_view(|cx| { let mut editor = Editor::single_line(this.settings.clone(), None, cx); if let Some(old_highlight_id) = old_highlight_id { @@ -4552,6 +4555,7 @@ impl Editor { let rename = self.pending_rename.take()?; self.remove_blocks([rename.block_id].into_iter().collect(), cx); self.clear_text_highlights::(cx); + self.show_local_selections = true; if moving_cursor { let cursor_in_rename_editor = @@ -5664,7 +5668,6 @@ impl View for Editor { fn on_blur(&mut self, cx: &mut ViewContext) { self.focused = false; - self.show_local_cursors = false; self.buffer .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); self.hide_context_menu(cx); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7537f696700acbc1f63a3b2fac9830227dfd6e43..78aa2a22323f4a6c514fa92b77df8a318b38eb96 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -910,32 +910,37 @@ impl Element for EditorElement { &display_map, ); - let local_selections = view - .local_selections_in_range(start_anchor.clone()..end_anchor.clone(), &display_map); - for selection in &local_selections { - let is_empty = selection.start == selection.end; - let selection_start = snapshot.prev_line_boundary(selection.start).1; - let selection_end = snapshot.next_line_boundary(selection.end).1; - 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; + if view.show_local_selections { + let local_selections = view.local_selections_in_range( + start_anchor.clone()..end_anchor.clone(), + &display_map, + ); + for selection in &local_selections { + let is_empty = selection.start == selection.end; + let selection_start = snapshot.prev_line_boundary(selection.start).1; + let selection_end = snapshot.next_line_boundary(selection.end).1; + 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; + } } + selections.insert( + view.replica_id(cx), + local_selections + .into_iter() + .map(|selection| crate::Selection { + id: selection.id, + goal: selection.goal, + reversed: selection.reversed, + start: selection.start.to_display_point(&display_map), + end: selection.end.to_display_point(&display_map), + }) + .collect(), + ); } - selections.insert( - view.replica_id(cx), - local_selections - .into_iter() - .map(|selection| crate::Selection { - id: selection.id, - goal: selection.goal, - reversed: selection.reversed, - start: selection.start.to_display_point(&display_map), - end: selection.end.to_display_point(&display_map), - }) - .collect(), - ); for (replica_id, selection) in display_map .buffer_snapshot @@ -1474,7 +1479,11 @@ mod tests { let (window_id, editor) = cx.add_window(Default::default(), |cx| { Editor::new(EditorMode::Full, buffer, None, settings.1, None, cx) }); - let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx), CursorShape::Bar); + let element = EditorElement::new( + editor.downgrade(), + editor.read(cx).style(cx), + CursorShape::Bar, + ); let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx);