From ac1eb19f8330f3a4fe84b682a4b15f82305ea247 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 9 Mar 2022 14:53:31 -0700 Subject: [PATCH 01/27] 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 053883e938..e401f85031 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 465e46af21..7b90179c8f 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 2866ae8f63..facb5ab2a7 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 91d02b1aa0..ded484e549 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 dcf716e0bb..ae3a09dc13 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 c3e9cdcbf2..8fdae59e31 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 4c301b4e48..24e917a777 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 7c983f1e6f..6814646117 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 7dc1173048..60073a64d6 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 3d79ecadd6..e624a7c316 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 75e3c8526c..74734fbc5b 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 5af18241b6..fdc8db5a74 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 dc8ba002a3..ecaf17f80e 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 a5184ea5f3..94ab2187fb 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 3e649fa4dd..dae93d2151 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 178442a4a858a93663f5870192385a75b1a6fe98 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 01:09:02 -0800 Subject: [PATCH 02/27] Add support for rendering cursors as a block and underscore --- crates/editor/src/editor.rs | 12 +++++++++- crates/editor/src/element.rs | 45 ++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 91d02b1aa0..711fee890b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -450,6 +450,7 @@ pub struct Editor { document_highlights_task: Option>, pending_rename: Option, searchable: bool, + cursor_shape: CursorShape, } pub struct EditorSnapshot { @@ -930,6 +931,7 @@ impl Editor { document_highlights_task: Default::default(), pending_rename: Default::default(), searchable: true, + cursor_shape: Default::default(), }; this.end_selection(cx); this @@ -1021,6 +1023,14 @@ impl Editor { cx.notify(); } + pub fn set_cursor_shape( + &mut self, + cursor_shape: CursorShape + ) { + self.cursor_shape = cursor_shape; + // TODO: Do we need to notify? + } + pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor) @@ -5569,7 +5579,7 @@ impl View for Editor { self.display_map.update(cx, |map, cx| { map.set_font(style.text.font_id, style.text.font_size, cx) }); - EditorElement::new(self.handle.clone(), style.clone()).boxed() + EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed() } fn ui_name() -> &'static str { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index dcf716e0bb..85e34108d7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,11 +32,12 @@ use std::{ pub struct EditorElement { view: WeakViewHandle, style: EditorStyle, + cursor_shape: CursorShape, } impl EditorElement { - pub fn new(view: WeakViewHandle, style: EditorStyle) -> Self { - Self { view, style } + pub fn new(view: WeakViewHandle, style: EditorStyle, cursor_shape: CursorShape) -> Self { + Self { view, style, cursor_shape } } fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor { @@ -362,13 +363,24 @@ impl EditorElement { if (start_row..end_row).contains(&cursor_position.row()) { let cursor_row_layout = &layout.line_layouts[(cursor_position.row() - start_row) as usize]; - let x = cursor_row_layout.x_for_index(cursor_position.column() as usize) - - scroll_left; + let cursor_column = cursor_position.column() as usize; + let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); + let mut character_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; + // TODO: Is there a better option here for the character size + // at the end of the line? + // Default to 1/3 the line height + if character_width == 0.0 { + character_width = layout.line_height / 3.0; + } + + let x = cursor_character_x - scroll_left; let y = cursor_position.row() as f32 * layout.line_height - scroll_top; cursors.push(Cursor { color: style.cursor, + character_width, origin: content_origin + vec2f(x, y), line_height: layout.line_height, + shape: self.cursor_shape, }); } } @@ -1212,16 +1224,39 @@ impl PaintState { } } +#[derive(Copy, Clone)] +pub enum CursorShape { + Bar, + Block, + Underscore +} + +impl Default for CursorShape { + fn default() -> Self { + CursorShape::Bar + } +} + struct Cursor { origin: Vector2F, + character_width: f32, line_height: f32, color: Color, + shape: CursorShape } impl Cursor { fn paint(&self, cx: &mut PaintContext) { + let bounds = match self.shape { + CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)), + CursorShape::Block => RectF::new(self.origin, vec2f(self.character_width, self.line_height)), + CursorShape::Underscore => RectF::new( + self.origin + Vector2F::new(0.0, self.line_height - 2.0), + vec2f(self.character_width, 2.0)), + }; + cx.scene.push_quad(Quad { - bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)), + bounds, background: Some(self.color), border: Border::new(0., Color::black()), corner_radius: 0., From 0d42c851959214015e736396b9bf491d0725cae6 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 01:09:25 -0800 Subject: [PATCH 03/27] fix formatting --- crates/editor/src/editor.rs | 5 +---- crates/editor/src/element.rs | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 711fee890b..32a81f4f69 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1023,10 +1023,7 @@ impl Editor { cx.notify(); } - pub fn set_cursor_shape( - &mut self, - cursor_shape: CursorShape - ) { + pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) { self.cursor_shape = cursor_shape; // TODO: Do we need to notify? } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 85e34108d7..822d3ab085 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -36,8 +36,16 @@ pub struct EditorElement { } impl EditorElement { - pub fn new(view: WeakViewHandle, style: EditorStyle, cursor_shape: CursorShape) -> Self { - Self { view, style, cursor_shape } + pub fn new( + view: WeakViewHandle, + style: EditorStyle, + cursor_shape: CursorShape, + ) -> Self { + Self { + view, + style, + cursor_shape, + } } fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor { @@ -365,7 +373,8 @@ impl EditorElement { &layout.line_layouts[(cursor_position.row() - start_row) as usize]; let cursor_column = cursor_position.column() as usize; let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); - let mut character_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; + let mut character_width = + cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; // TODO: Is there a better option here for the character size // at the end of the line? // Default to 1/3 the line height @@ -1228,7 +1237,7 @@ impl PaintState { pub enum CursorShape { Bar, Block, - Underscore + Underscore, } impl Default for CursorShape { @@ -1242,17 +1251,20 @@ struct Cursor { character_width: f32, line_height: f32, color: Color, - shape: CursorShape + shape: CursorShape, } impl Cursor { fn paint(&self, cx: &mut PaintContext) { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)), - CursorShape::Block => RectF::new(self.origin, vec2f(self.character_width, self.line_height)), + CursorShape::Block => { + RectF::new(self.origin, vec2f(self.character_width, self.line_height)) + } CursorShape::Underscore => RectF::new( self.origin + Vector2F::new(0.0, self.line_height - 2.0), - vec2f(self.character_width, 2.0)), + vec2f(self.character_width, 2.0), + ), }; cx.scene.push_quad(Quad { From e6b1fea11787760803ddb7d637d89766ede877b3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 11:32:14 -0700 Subject: [PATCH 04/27] 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 | 202 ++++++++++++++++++--- 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, 280 insertions(+), 79 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e401f85031..fe7895cba5 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 7b90179c8f..770ebc8fbe 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 facb5ab2a7..295b7710ef 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..]; + 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); - // 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]; + 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 e2239e7671..696b50577a 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 7858197902..2fab37fb30 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 ded484e549..6400944857 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 64683faa96..93f67d0cc7 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 24e917a777..1057cbe7aa 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 1de6b2f589..80143aad69 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 eddb089f2744f282c2e490c711fbf0fbf0532fc0 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 13:16:31 -0800 Subject: [PATCH 05/27] render character under block cursor --- crates/editor/src/editor.rs | 4 +-- crates/editor/src/element.rs | 61 +++++++++++++++++++++++++--------- crates/gpui/src/text_layout.rs | 20 +++++++++-- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 32a81f4f69..31a0387cce 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1023,9 +1023,9 @@ impl Editor { cx.notify(); } - pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) { + pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext) { self.cursor_shape = cursor_shape; - // TODO: Do we need to notify? + cx.notify(); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 822d3ab085..caea726700 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -16,7 +16,7 @@ use gpui::{ PathBuilder, }, json::{self, ToJson}, - text_layout::{self, RunStyle, TextLayoutCache}, + text_layout::{self, Line, RunStyle, TextLayoutCache}, AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; @@ -347,7 +347,7 @@ impl EditorElement { let mut cursors = SmallVec::<[Cursor; 32]>::new(); for (replica_id, selections) in &layout.selections { - let style = style.replica_selection_style(*replica_id); + let selection_style = style.replica_selection_style(*replica_id); let corner_radius = 0.15 * layout.line_height; for selection in selections { @@ -355,7 +355,7 @@ impl EditorElement { selection.start..selection.end, start_row, end_row, - style.selection, + selection_style.selection, corner_radius, corner_radius * 2., layout, @@ -372,24 +372,49 @@ impl EditorElement { let cursor_row_layout = &layout.line_layouts[(cursor_position.row() - start_row) as usize]; let cursor_column = cursor_position.column() as usize; + let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); - let mut character_width = + let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; - // TODO: Is there a better option here for the character size - // at the end of the line? - // Default to 1/3 the line height - if character_width == 0.0 { - character_width = layout.line_height / 3.0; + if block_width == 0.0 { + block_width = layout.em_width; } + let block_text = + if matches!(self.cursor_shape, CursorShape::Block) { + layout.snapshot.chars_at(cursor_position).next().and_then( + |character| { + let font_id = + cursor_row_layout.font_for_index(cursor_column)?; + let text = character.to_string(); + + Some(cx.text_layout_cache.layout_str( + &text, + cursor_row_layout.font_size(), + &[( + text.len(), + RunStyle { + font_id, + color: style.background, + underline: None, + }, + )], + )) + }, + ) + } else { + None + }; + let x = cursor_character_x - scroll_left; let y = cursor_position.row() as f32 * layout.line_height - scroll_top; cursors.push(Cursor { - color: style.cursor, - character_width, + color: selection_style.cursor, + block_width, origin: content_origin + vec2f(x, y), line_height: layout.line_height, shape: self.cursor_shape, + block_text, }); } } @@ -1182,6 +1207,7 @@ fn layout_line( while !line.is_char_boundary(len) { len -= 1; } + line.truncate(len); } @@ -1242,16 +1268,17 @@ pub enum CursorShape { impl Default for CursorShape { fn default() -> Self { - CursorShape::Bar + CursorShape::Block } } struct Cursor { origin: Vector2F, - character_width: f32, + block_width: f32, line_height: f32, color: Color, shape: CursorShape, + block_text: Option, } impl Cursor { @@ -1259,11 +1286,11 @@ impl Cursor { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)), CursorShape::Block => { - RectF::new(self.origin, vec2f(self.character_width, self.line_height)) + RectF::new(self.origin, vec2f(self.block_width, self.line_height)) } CursorShape::Underscore => RectF::new( self.origin + Vector2F::new(0.0, self.line_height - 2.0), - vec2f(self.character_width, 2.0), + vec2f(self.block_width, 2.0), ), }; @@ -1273,6 +1300,10 @@ impl Cursor { border: Border::new(0., Color::black()), corner_radius: 0., }); + + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin, bounds, self.line_height, cx); + } } } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 6e371437bf..25f1cb82c0 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -186,7 +186,7 @@ pub struct Run { pub glyphs: Vec, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Glyph { pub id: GlyphId, pub position: Vector2F, @@ -210,10 +210,14 @@ impl Line { self.layout.width } + pub fn font_size(&self) -> f32 { + self.layout.font_size + } + pub fn x_for_index(&self, index: usize) -> f32 { for run in &self.layout.runs { for glyph in &run.glyphs { - if glyph.index == index { + if glyph.index >= index { return glyph.position.x(); } } @@ -221,6 +225,18 @@ impl Line { self.layout.width } + pub fn font_for_index(&self, index: usize) -> Option { + for run in &self.layout.runs { + for glyph in &run.glyphs { + if glyph.index >= index { + return Some(run.font_id); + } + } + } + + None + } + pub fn index_for_x(&self, x: f32) -> Option { if x >= self.layout.width { None From 5b35c68d2e678beeeccd3969520540fe4feeca3e Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 13:20:45 -0800 Subject: [PATCH 06/27] Fix failing gpui test from missing cursor shape --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index caea726700..3238cf840f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1467,7 +1467,7 @@ 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)); + 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); From 5502c00d9a77bf4e0394a9e451bdfd43474eb5b2 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 13:28:13 -0800 Subject: [PATCH 07/27] swap default cursor shape back to bar --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 3238cf840f..b15487b54c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1268,7 +1268,7 @@ pub enum CursorShape { impl Default for CursorShape { fn default() -> Self { - CursorShape::Block + CursorShape::Bar } } From 3968b37e26ba77f9f5f537631042af69033a0280 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Mar 2022 14:55:31 -0700 Subject: [PATCH 08/27] 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 6400944857..bd12b1f5ba 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 ae3a09dc13..13161f9083 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 1057cbe7aa..f31a80a831 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 6814646117..707bad55e6 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 60073a64d6..d2fab7467d 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 c96db5aab2..d372c2fd31 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 59a488c896..97c3c55fcb 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 09/27] 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 bd12b1f5ba..6d3a17a81c 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 10/27] 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 6d3a17a81c..352d969946 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 d2fab7467d..c0995b6808 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 11/27] 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 352d969946..addece43f9 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 81fc8122210c86e56d41423b24535382af7f77d4 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Thu, 10 Mar 2022 20:03:01 -0800 Subject: [PATCH 12/27] Add global events to MutableAppContext and raise global event when new workspace is created --- crates/gpui/src/app.rs | 93 +++++++++++++++++++++++++++---- crates/workspace/src/workspace.rs | 21 ++++--- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index e91963bfa6..d19ecce152 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -740,6 +740,7 @@ type ActionCallback = type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext); type SubscriptionCallback = Box bool>; +type GlobalSubscriptionCallback = Box; type ObservationCallback = Box bool>; type ReleaseObservationCallback = Box; @@ -757,6 +758,7 @@ pub struct MutableAppContext { next_subscription_id: usize, frame_count: usize, subscriptions: Arc>>>, + global_subscriptions: Arc>>>, observations: Arc>>>, release_observations: Arc>>>, presenters_and_platform_windows: @@ -804,6 +806,7 @@ impl MutableAppContext { next_subscription_id: 0, frame_count: 0, subscriptions: Default::default(), + global_subscriptions: Default::default(), observations: Default::default(), release_observations: Default::default(), presenters_and_platform_windows: HashMap::new(), @@ -1062,6 +1065,12 @@ impl MutableAppContext { self.foreground_platform.prompt_for_new_path(directory) } + pub fn emit_global(&mut self, payload: E) { + self.pending_effects.push_back(Effect::GlobalEvent { + payload: Box::new(payload), + }); + } + pub fn subscribe(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, @@ -1075,6 +1084,31 @@ impl MutableAppContext { }) } + pub fn global_subscribe(&mut self, mut callback: F) -> Subscription + where + E: Any + Copy, + F: 'static + FnMut(&E, &mut Self), + { + let id = post_inc(&mut self.next_subscription_id); + let type_id = TypeId::of::(); + self.global_subscriptions + .lock() + .entry(type_id) + .or_default() + .insert( + id, + Box::new(move |payload, cx| { + let payload = payload.downcast_ref().expect("downcast is type safe"); + callback(payload, cx) + })); + Subscription::GlobalSubscription { + id, + type_id, + subscriptions: Some(Arc::downgrade(&self.global_subscriptions)) + } + } + + pub fn observe(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, @@ -1573,6 +1607,7 @@ impl MutableAppContext { if let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload), + Effect::GlobalEvent { payload } => self.emit_global_event(payload), Effect::ModelNotification { model_id } => { self.notify_model_observers(model_id) } @@ -1700,6 +1735,16 @@ impl MutableAppContext { } } + fn emit_global_event(&mut self, payload: Box) { + let type_id = (&*payload).type_id(); + let callbacks = self.global_subscriptions.lock().remove(&type_id); + if let Some(callbacks) = callbacks { + for (_, mut callback) in callbacks { + callback(payload.as_ref(), self) + } + } + } + fn notify_model_observers(&mut self, observed_id: usize) { let callbacks = self.observations.lock().remove(&observed_id); if let Some(callbacks) = callbacks { @@ -2071,6 +2116,9 @@ pub enum Effect { entity_id: usize, payload: Box, }, + GlobalEvent { + payload: Box, + }, ModelNotification { model_id: usize, }, @@ -2104,6 +2152,10 @@ impl Debug for Effect { .debug_struct("Effect::Event") .field("entity_id", entity_id) .finish(), + Effect::GlobalEvent { payload, .. } => f + .debug_struct("Effect::GlobalEvent") + .field("type_id", &(&*payload).type_id()) + .finish(), Effect::ModelNotification { model_id } => f .debug_struct("Effect::ModelNotification") .field("model_id", model_id) @@ -3762,6 +3814,11 @@ pub enum Subscription { entity_id: usize, subscriptions: Option>>>>, }, + GlobalSubscription { + id: usize, + type_id: TypeId, + subscriptions: Option>>>>, + }, Observation { id: usize, entity_id: usize, @@ -3781,6 +3838,9 @@ impl Subscription { Subscription::Subscription { subscriptions, .. } => { subscriptions.take(); } + Subscription::GlobalSubscription { subscriptions, .. } => { + subscriptions.take(); + } Subscription::Observation { observations, .. } => { observations.take(); } @@ -3794,6 +3854,28 @@ impl Subscription { impl Drop for Subscription { fn drop(&mut self) { match self { + Subscription::Subscription { + id, + entity_id, + subscriptions, + } => { + if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) { + if let Some(subscriptions) = subscriptions.lock().get_mut(entity_id) { + subscriptions.remove(id); + } + } + } + Subscription::GlobalSubscription { + id, + type_id, + subscriptions, + } => { + if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) { + if let Some(subscriptions) = subscriptions.lock().get_mut(type_id) { + subscriptions.remove(id); + } + } + } Subscription::Observation { id, entity_id, @@ -3816,17 +3898,6 @@ impl Drop for Subscription { } } } - Subscription::Subscription { - id, - entity_id, - subscriptions, - } => { - if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) { - if let Some(subscriptions) = subscriptions.lock().get_mut(entity_id) { - subscriptions.remove(id); - } - } - } } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f06a244c05..5680ad4cdf 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1511,6 +1511,8 @@ fn open(action: &Open, cx: &mut MutableAppContext) { .detach(); } +pub struct WorkspaceBuilt(WeakViewHandle); + pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, @@ -1537,7 +1539,7 @@ pub fn open_paths( } let workspace = existing.unwrap_or_else(|| { - cx.add_window((app_state.build_window_options)(), |cx| { + let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { let project = Project::local( app_state.client.clone(), app_state.user_store.clone(), @@ -1546,8 +1548,9 @@ pub fn open_paths( cx, ); (app_state.build_workspace)(project, &app_state, cx) - }) - .1 + }); + cx.emit_global(WorkspaceBuilt(workspace.downgrade())); + workspace }); let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); @@ -1581,12 +1584,13 @@ pub fn join_project( &mut cx, ) .await?; - let (_, workspace) = cx.update(|cx| { - cx.add_window((app_state.build_window_options)(), |cx| { + Ok(cx.update(|cx| { + let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { (app_state.build_workspace)(project, &app_state, cx) - }) - }); - Ok(workspace) + }); + cx.emit_global(WorkspaceBuilt(workspace.downgrade())); + workspace + })) }) } @@ -1601,5 +1605,6 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { ); (app_state.build_workspace)(project, &app_state, cx) }); + cx.emit_global(WorkspaceBuilt(workspace.downgrade())); cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone())); } From c67cfd7fe1142214939ceba879aff25a211f16e0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 12:41:05 +0100 Subject: [PATCH 13/27] Respect excerpt's range when comparing two anchors both belonging to it --- crates/editor/src/display_map/fold_map.rs | 8 ++++++ crates/editor/src/multi_buffer.rs | 14 ++++------ crates/editor/src/multi_buffer/anchor.rs | 32 +++++++++++------------ 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index daafbee57b..4f25f32e21 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -241,6 +241,14 @@ impl FoldMap { self.buffer.lock().len(), "transform tree does not match buffer's length" ); + + let mut folds = self.folds.iter().peekable(); + while let Some(fold) = folds.next() { + if let Some(next_fold) = folds.peek() { + let comparison = fold.0.cmp(&next_fold.0, &self.buffer.lock()).unwrap(); + assert!(comparison.is_le()); + } + } } } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 3678f8f116..fd2ed744ba 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1998,10 +1998,9 @@ impl MultiBufferSnapshot { pub fn can_resolve(&self, anchor: &Anchor) -> bool { if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { true - } else if let Some((buffer_id, buffer_snapshot)) = - self.buffer_snapshot_for_excerpt(&anchor.excerpt_id) - { - anchor.buffer_id == Some(buffer_id) && buffer_snapshot.can_resolve(&anchor.text_anchor) + } else if let Some(excerpt) = self.excerpt(&anchor.excerpt_id) { + anchor.buffer_id == Some(excerpt.buffer_id) + && excerpt.buffer.can_resolve(&anchor.text_anchor) } else { false } @@ -2231,15 +2230,12 @@ impl MultiBufferSnapshot { )) } - fn buffer_snapshot_for_excerpt<'a>( - &'a self, - excerpt_id: &'a ExcerptId, - ) -> Option<(usize, &'a BufferSnapshot)> { + fn excerpt<'a>(&'a self, excerpt_id: &'a ExcerptId) -> Option<&'a Excerpt> { let mut cursor = self.excerpts.cursor::>(); cursor.seek(&Some(excerpt_id), Bias::Left, &()); if let Some(excerpt) = cursor.item() { if excerpt.id == *excerpt_id { - return Some((excerpt.buffer_id, &excerpt.buffer)); + return Some(excerpt); } } None diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index c51eb2a4c7..3de03fb123 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -40,17 +40,19 @@ impl Anchor { if excerpt_id_cmp.is_eq() { if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() { Ok(Ordering::Equal) - } else if let Some((buffer_id, buffer_snapshot)) = - snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) - { + } else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { // Even though the anchor refers to a valid excerpt the underlying buffer might have // changed. In that case, treat the anchor as if it were at the start of that // excerpt. - if self.buffer_id == Some(buffer_id) && other.buffer_id == Some(buffer_id) { - self.text_anchor.cmp(&other.text_anchor, buffer_snapshot) - } else if self.buffer_id == Some(buffer_id) { + if self.buffer_id == Some(excerpt.buffer_id) + && other.buffer_id == Some(excerpt.buffer_id) + { + let self_anchor = excerpt.clip_anchor(self.text_anchor.clone()); + let other_anchor = excerpt.clip_anchor(other.text_anchor.clone()); + self_anchor.cmp(&other_anchor, &excerpt.buffer) + } else if self.buffer_id == Some(excerpt.buffer_id) { Ok(Ordering::Greater) - } else if other.buffer_id == Some(buffer_id) { + } else if other.buffer_id == Some(excerpt.buffer_id) { Ok(Ordering::Less) } else { Ok(Ordering::Equal) @@ -65,14 +67,12 @@ impl Anchor { pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Left { - if let Some((buffer_id, buffer_snapshot)) = - snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) - { - if self.buffer_id == Some(buffer_id) { + if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { + if self.buffer_id == Some(excerpt.buffer_id) { return Self { buffer_id: self.buffer_id, excerpt_id: self.excerpt_id.clone(), - text_anchor: self.text_anchor.bias_left(buffer_snapshot), + text_anchor: self.text_anchor.bias_left(&excerpt.buffer), }; } } @@ -82,14 +82,12 @@ impl Anchor { pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Right { - if let Some((buffer_id, buffer_snapshot)) = - snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) - { - if self.buffer_id == Some(buffer_id) { + if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { + if self.buffer_id == Some(excerpt.buffer_id) { return Self { buffer_id: self.buffer_id, excerpt_id: self.excerpt_id.clone(), - text_anchor: self.text_anchor.bias_right(buffer_snapshot), + text_anchor: self.text_anchor.bias_right(&excerpt.buffer), }; } } From 134496ce8fe262970cf8f25e34d23dcc29b6c679 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Mar 2022 08:24:42 -0700 Subject: [PATCH 14/27] Remove dead code Co-Authored-By: Antonio Scandurra --- crates/language/src/proto.rs | 44 ------------------------------------ crates/text/src/locator.rs | 5 ---- 2 files changed, 49 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index f5e797bca7..19bfc10cd0 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -4,7 +4,6 @@ use crate::{ }; use anyhow::{anyhow, Result}; use clock::ReplicaId; -use collections::HashSet; use lsp::DiagnosticSeverity; use rpc::proto; use std::{ops::Range, sync::Arc}; @@ -100,26 +99,6 @@ pub fn serialize_undo_map_entry( } } -pub fn serialize_buffer_fragment(fragment: &text::Fragment) -> proto::BufferFragment { - proto::BufferFragment { - replica_id: fragment.insertion_timestamp.replica_id as u32, - local_timestamp: fragment.insertion_timestamp.local, - lamport_timestamp: fragment.insertion_timestamp.lamport, - insertion_offset: fragment.insertion_offset as u32, - len: fragment.len as u32, - visible: fragment.visible, - deletions: fragment - .deletions - .iter() - .map(|clock| proto::VectorClockEntry { - replica_id: clock.replica_id as u32, - timestamp: clock.value, - }) - .collect(), - max_undos: serialize_version(&fragment.max_undos), - } -} - pub fn serialize_selections(selections: &Arc<[Selection]>) -> Vec { selections .iter() @@ -290,29 +269,6 @@ pub fn deserialize_undo_map_entry( ) } -pub fn deserialize_buffer_fragment( - message: proto::BufferFragment, - ix: usize, - count: usize, -) -> Fragment { - Fragment { - id: locator::Locator::from_index(ix, count), - insertion_timestamp: InsertionTimestamp { - replica_id: message.replica_id as ReplicaId, - local: message.local_timestamp, - lamport: message.lamport_timestamp, - }, - insertion_offset: message.insertion_offset as usize, - len: message.len as usize, - visible: message.visible, - deletions: HashSet::from_iter(message.deletions.into_iter().map(|entry| clock::Local { - replica_id: entry.replica_id as ReplicaId, - value: entry.timestamp, - })), - max_undos: deserialize_version(message.max_undos), - } -} - pub fn deserialize_selections(selections: Vec) -> Arc<[Selection]> { Arc::from( selections diff --git a/crates/text/src/locator.rs b/crates/text/src/locator.rs index cb1e78153c..e4feaf99ac 100644 --- a/crates/text/src/locator.rs +++ b/crates/text/src/locator.rs @@ -19,11 +19,6 @@ impl Locator { Self(smallvec![u64::MAX]) } - pub fn from_index(ix: usize, count: usize) -> Self { - let id = (1 + ix as u64) * (u64::MAX / (count as u64 + 2)); - Self(smallvec![id]) - } - pub fn assign(&mut self, other: &Self) { self.0.resize(other.0.len(), 0); self.0.copy_from_slice(&other.0); From 5407f25c941c1e0016882bd889698a81aa227ecb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Mar 2022 08:50:50 -0700 Subject: [PATCH 15/27] Don't reuse excerpt ids in MultiBuffer This prevents anchors from swapping their ordering, which was causing issues in FoldMap. Co-Authored-By: Antonio Scandurra --- crates/editor/src/multi_buffer.rs | 12 ++++++++++-- crates/text/src/locator.rs | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index fd2ed744ba..766acd717e 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -36,6 +36,7 @@ pub type ExcerptId = Locator; pub struct MultiBuffer { snapshot: RefCell, buffers: RefCell>, + used_excerpt_ids: SumTree, subscriptions: Topic, singleton: bool, replica_id: ReplicaId, @@ -155,6 +156,7 @@ impl MultiBuffer { Self { snapshot: Default::default(), buffers: Default::default(), + used_excerpt_ids: Default::default(), subscriptions: Default::default(), singleton: false, replica_id, @@ -192,6 +194,7 @@ impl MultiBuffer { Self { snapshot: RefCell::new(self.snapshot.borrow().clone()), buffers: RefCell::new(buffers), + used_excerpt_ids: Default::default(), subscriptions: Default::default(), singleton: self.singleton, replica_id: self.replica_id, @@ -759,13 +762,18 @@ impl MultiBuffer { ); let mut next_id = ExcerptId::max(); - if let Some(next_excerpt) = cursor.item() { - next_id = next_excerpt.id.clone(); + { + let mut used_cursor = self.used_excerpt_ids.cursor::(); + used_cursor.seek(&prev_id, Bias::Right, &()); + if let Some(used_id) = used_cursor.item() { + next_id = used_id.clone(); + } } let mut ids = Vec::new(); while let Some(range) = ranges.next() { let id = ExcerptId::between(&prev_id, &next_id); + self.used_excerpt_ids.insert_or_replace(id.clone(), &()); if let Err(ix) = buffer_state.excerpts.binary_search(&id) { buffer_state.excerpts.insert(ix, id.clone()); } diff --git a/crates/text/src/locator.rs b/crates/text/src/locator.rs index e4feaf99ac..f98f050339 100644 --- a/crates/text/src/locator.rs +++ b/crates/text/src/locator.rs @@ -49,6 +49,30 @@ impl Default for Locator { } } +impl sum_tree::Item for Locator { + type Summary = Locator; + + fn summary(&self) -> Self::Summary { + self.clone() + } +} + +impl sum_tree::KeyedItem for Locator { + type Key = Locator; + + fn key(&self) -> Self::Key { + self.clone() + } +} + +impl sum_tree::Summary for Locator { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.assign(summary); + } +} + #[cfg(test)] mod tests { use super::*; From a74b602f1832784e3a9170d836d2f913d428a3ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 17:03:13 +0100 Subject: [PATCH 16/27] Assume the anchor is valid if we can find an excerpt that contains it --- crates/editor/src/multi_buffer.rs | 42 ++++++++++++++---------- crates/editor/src/multi_buffer/anchor.rs | 41 +++++++---------------- 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 766acd717e..1b567d6993 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -971,10 +971,13 @@ impl MultiBuffer { ) -> Option<(ModelHandle, language::Anchor)> { let snapshot = self.read(cx); let anchor = snapshot.anchor_before(position); - Some(( - self.buffers.borrow()[&anchor.buffer_id?].buffer.clone(), - anchor.text_anchor, - )) + let buffer = self + .buffers + .borrow() + .get(&anchor.buffer_id?)? + .buffer + .clone(); + Some((buffer, anchor.text_anchor)) } fn on_buffer_event( @@ -1028,14 +1031,19 @@ impl MultiBuffer { let snapshot = self.snapshot(cx); let anchor = snapshot.anchor_before(position); - anchor.buffer_id.map_or(false, |buffer_id| { - let buffer = self.buffers.borrow()[&buffer_id].buffer.clone(); - buffer - .read(cx) - .completion_triggers() - .iter() - .any(|string| string == text) - }) + anchor + .buffer_id + .and_then(|buffer_id| { + let buffer = self.buffers.borrow().get(&buffer_id)?.buffer.clone(); + Some( + buffer + .read(cx) + .completion_triggers() + .iter() + .any(|string| string == text), + ) + }) + .unwrap_or(false) } pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc> { @@ -1765,7 +1773,7 @@ impl MultiBufferSnapshot { let mut position = D::from_text_summary(&cursor.start().text); if let Some(excerpt) = cursor.item() { - if excerpt.id == anchor.excerpt_id && Some(excerpt.buffer_id) == anchor.buffer_id { + if excerpt.id == anchor.excerpt_id { let excerpt_buffer_start = excerpt.range.start.summary::(&excerpt.buffer); let excerpt_buffer_end = excerpt.range.end.summary::(&excerpt.buffer); let buffer_position = cmp::min( @@ -1796,10 +1804,9 @@ impl MultiBufferSnapshot { let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { let excerpt_id = &anchor.excerpt_id; - let buffer_id = anchor.buffer_id; let excerpt_anchors = iter::from_fn(|| { let anchor = anchors.peek()?; - if anchor.excerpt_id == *excerpt_id && anchor.buffer_id == buffer_id { + if anchor.excerpt_id == *excerpt_id { Some(&anchors.next().unwrap().text_anchor) } else { None @@ -1813,7 +1820,7 @@ impl MultiBufferSnapshot { let position = D::from_text_summary(&cursor.start().text); if let Some(excerpt) = cursor.item() { - if excerpt.id == *excerpt_id && Some(excerpt.buffer_id) == buffer_id { + if excerpt.id == *excerpt_id { let excerpt_buffer_start = excerpt.range.start.summary::(&excerpt.buffer); let excerpt_buffer_end = excerpt.range.end.summary::(&excerpt.buffer); summaries.extend( @@ -2007,8 +2014,7 @@ impl MultiBufferSnapshot { if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { true } else if let Some(excerpt) = self.excerpt(&anchor.excerpt_id) { - anchor.buffer_id == Some(excerpt.buffer_id) - && excerpt.buffer.can_resolve(&anchor.text_anchor) + excerpt.buffer.can_resolve(&anchor.text_anchor) } else { false } diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 3de03fb123..33147ce285 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -41,22 +41,7 @@ impl Anchor { if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() { Ok(Ordering::Equal) } else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { - // Even though the anchor refers to a valid excerpt the underlying buffer might have - // changed. In that case, treat the anchor as if it were at the start of that - // excerpt. - if self.buffer_id == Some(excerpt.buffer_id) - && other.buffer_id == Some(excerpt.buffer_id) - { - let self_anchor = excerpt.clip_anchor(self.text_anchor.clone()); - let other_anchor = excerpt.clip_anchor(other.text_anchor.clone()); - self_anchor.cmp(&other_anchor, &excerpt.buffer) - } else if self.buffer_id == Some(excerpt.buffer_id) { - Ok(Ordering::Greater) - } else if other.buffer_id == Some(excerpt.buffer_id) { - Ok(Ordering::Less) - } else { - Ok(Ordering::Equal) - } + self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer) } else { Ok(Ordering::Equal) } @@ -68,13 +53,11 @@ impl Anchor { pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Left { if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { - if self.buffer_id == Some(excerpt.buffer_id) { - return Self { - buffer_id: self.buffer_id, - excerpt_id: self.excerpt_id.clone(), - text_anchor: self.text_anchor.bias_left(&excerpt.buffer), - }; - } + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_left(&excerpt.buffer), + }; } } self.clone() @@ -83,13 +66,11 @@ impl Anchor { pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Right { if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { - if self.buffer_id == Some(excerpt.buffer_id) { - return Self { - buffer_id: self.buffer_id, - excerpt_id: self.excerpt_id.clone(), - text_anchor: self.text_anchor.bias_right(&excerpt.buffer), - }; - } + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_right(&excerpt.buffer), + }; } } self.clone() From 71aa5e5360d97bd8a84b4ac136a957d4b6638ce9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Mar 2022 17:13:28 +0100 Subject: [PATCH 17/27] :lipstick: --- crates/editor/src/multi_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 1b567d6993..756d21e9b5 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -36,7 +36,7 @@ pub type ExcerptId = Locator; pub struct MultiBuffer { snapshot: RefCell, buffers: RefCell>, - used_excerpt_ids: SumTree, + used_excerpt_ids: SumTree, subscriptions: Topic, singleton: bool, replica_id: ReplicaId, From 144591d63969750721518480d8ced0b6822acbf9 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 11 Mar 2022 11:25:36 -0800 Subject: [PATCH 18/27] Minor renames for clarity --- crates/gpui/src/app.rs | 15 ++++++++------- crates/workspace/src/workspace.rs | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d19ecce152..109e52ae0b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1084,9 +1084,9 @@ impl MutableAppContext { }) } - pub fn global_subscribe(&mut self, mut callback: F) -> Subscription + pub fn subscribe_global(&mut self, mut callback: F) -> Subscription where - E: Any + Copy, + E: Any, F: 'static + FnMut(&E, &mut Self), { let id = post_inc(&mut self.next_subscription_id); @@ -1096,19 +1096,19 @@ impl MutableAppContext { .entry(type_id) .or_default() .insert( - id, + id, Box::new(move |payload, cx| { let payload = payload.downcast_ref().expect("downcast is type safe"); callback(payload, cx) - })); + }), + ); Subscription::GlobalSubscription { id, type_id, - subscriptions: Some(Arc::downgrade(&self.global_subscriptions)) + subscriptions: Some(Arc::downgrade(&self.global_subscriptions)), } } - pub fn observe(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, @@ -3817,7 +3817,8 @@ pub enum Subscription { GlobalSubscription { id: usize, type_id: TypeId, - subscriptions: Option>>>>, + subscriptions: + Option>>>>, }, Observation { id: usize, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5680ad4cdf..81633e3f2c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1511,7 +1511,7 @@ fn open(action: &Open, cx: &mut MutableAppContext) { .detach(); } -pub struct WorkspaceBuilt(WeakViewHandle); +pub struct WorkspaceCreated(WeakViewHandle); pub fn open_paths( abs_paths: &[PathBuf], @@ -1549,7 +1549,7 @@ pub fn open_paths( ); (app_state.build_workspace)(project, &app_state, cx) }); - cx.emit_global(WorkspaceBuilt(workspace.downgrade())); + cx.emit_global(WorkspaceCreated(workspace.downgrade())); workspace }); @@ -1588,7 +1588,7 @@ pub fn join_project( let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { (app_state.build_workspace)(project, &app_state, cx) }); - cx.emit_global(WorkspaceBuilt(workspace.downgrade())); + cx.emit_global(WorkspaceCreated(workspace.downgrade())); workspace })) }) @@ -1605,6 +1605,6 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { ); (app_state.build_workspace)(project, &app_state, cx) }); - cx.emit_global(WorkspaceBuilt(workspace.downgrade())); + cx.emit_global(WorkspaceCreated(workspace.downgrade())); cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone())); } From c35a96c4255a0fd4d5ca1b338127746ef46ad2f6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Mar 2022 15:54:17 -0700 Subject: [PATCH 19/27] 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 6c481eaa55..5e18ad0b64 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 0a4bd6caa8..e462bd9872 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 16afb3d5b18bd736548fbe1d3140383478d2cbef Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 11 Mar 2022 15:34:04 -0800 Subject: [PATCH 20/27] Add some tests for global events and fix potential bug in subscriptions when subscription is dropped inside of it's own callback Co-authored-by: Nathan Sobo --- crates/gpui/src/app.rs | 385 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 341 insertions(+), 44 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 109e52ae0b..3429dd4a42 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -757,9 +757,9 @@ pub struct MutableAppContext { next_window_id: usize, next_subscription_id: usize, frame_count: usize, - subscriptions: Arc>>>, - global_subscriptions: Arc>>>, - observations: Arc>>>, + subscriptions: Arc>>>>, + global_subscriptions: Arc>>>>, + observations: Arc>>>>, release_observations: Arc>>>, presenters_and_platform_windows: HashMap>, Box)>, @@ -1097,10 +1097,10 @@ impl MutableAppContext { .or_default() .insert( id, - Box::new(move |payload, cx| { + Some(Box::new(move |payload, cx| { let payload = payload.downcast_ref().expect("downcast is type safe"); callback(payload, cx) - }), + })), ); Subscription::GlobalSubscription { id, @@ -1137,14 +1137,14 @@ impl MutableAppContext { .or_default() .insert( id, - Box::new(move |payload, cx| { + Some(Box::new(move |payload, cx| { if let Some(emitter) = H::upgrade_from(&emitter, cx.as_ref()) { let payload = payload.downcast_ref().expect("downcast is type safe"); callback(emitter, payload, cx) } else { false } - }), + })), ); Subscription::Subscription { id, @@ -1168,13 +1168,13 @@ impl MutableAppContext { .or_default() .insert( id, - Box::new(move |cx| { + Some(Box::new(move |cx| { if let Some(observed) = H::upgrade_from(&observed, cx) { callback(observed, cx) } else { false } - }), + })), ); Subscription::Observation { id, @@ -1722,14 +1722,24 @@ impl MutableAppContext { fn emit_event(&mut self, entity_id: usize, payload: Box) { let callbacks = self.subscriptions.lock().remove(&entity_id); if let Some(callbacks) = callbacks { - for (id, mut callback) in callbacks { - let alive = callback(payload.as_ref(), self); - if alive { - self.subscriptions - .lock() - .entry(entity_id) - .or_default() - .insert(id, callback); + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = callback(payload.as_ref(), self); + if alive { + match self.subscriptions + .lock() + .entry(entity_id) + .or_default() + .entry(id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, + } + } } } } @@ -1739,8 +1749,23 @@ impl MutableAppContext { let type_id = (&*payload).type_id(); let callbacks = self.global_subscriptions.lock().remove(&type_id); if let Some(callbacks) = callbacks { - for (_, mut callback) in callbacks { - callback(payload.as_ref(), self) + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + callback(payload.as_ref(), self); + match self.global_subscriptions + .lock() + .entry(type_id) + .or_default() + .entry(id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, + } + } } } } @@ -1749,14 +1774,24 @@ impl MutableAppContext { let callbacks = self.observations.lock().remove(&observed_id); if let Some(callbacks) = callbacks { if self.cx.models.contains_key(&observed_id) { - for (id, mut callback) in callbacks { - let alive = callback(self); - if alive { - self.observations - .lock() - .entry(observed_id) - .or_default() - .insert(id, callback); + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = callback(self); + if alive { + match self.observations + .lock() + .entry(observed_id) + .or_default() + .entry(id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, + } + } } } } @@ -1779,14 +1814,24 @@ impl MutableAppContext { .views .contains_key(&(observed_window_id, observed_view_id)) { - for (id, mut callback) in callbacks { - let alive = callback(self); - if alive { - self.observations - .lock() - .entry(observed_view_id) - .or_default() - .insert(id, callback); + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = callback(self); + if alive { + match self.observations + .lock() + .entry(observed_view_id) + .or_default() + .entry(id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, + } + } } } } @@ -3812,18 +3857,18 @@ pub enum Subscription { Subscription { id: usize, entity_id: usize, - subscriptions: Option>>>>, + subscriptions: Option>>>>>, }, GlobalSubscription { id: usize, type_id: TypeId, subscriptions: - Option>>>>, + Option>>>>>, }, Observation { id: usize, entity_id: usize, - observations: Option>>>>, + observations: Option>>>>>, }, ReleaseObservation { id: usize, @@ -3861,8 +3906,18 @@ impl Drop for Subscription { subscriptions, } => { if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) { - if let Some(subscriptions) = subscriptions.lock().get_mut(entity_id) { - subscriptions.remove(id); + match subscriptions + .lock() + .entry(*entity_id) + .or_default() + .entry(*id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(None); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, } } } @@ -3872,8 +3927,18 @@ impl Drop for Subscription { subscriptions, } => { if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) { - if let Some(subscriptions) = subscriptions.lock().get_mut(type_id) { - subscriptions.remove(id); + match subscriptions + .lock() + .entry(*type_id) + .or_default() + .entry(*id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(None); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, } } } @@ -3883,8 +3948,18 @@ impl Drop for Subscription { observations, } => { if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) { - if let Some(observations) = observations.lock().get_mut(entity_id) { - observations.remove(id); + match observations + .lock() + .entry(*entity_id) + .or_default() + .entry(*id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(None); + }, + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + }, } } } @@ -4464,6 +4539,96 @@ mod tests { assert_eq!(handle_1.read(cx).events, vec![7, 5, 10, 9]); } + #[crate::test(self)] + fn test_global_events(cx: &mut MutableAppContext) { + #[derive(Clone, Debug, Eq, PartialEq)] + struct GlobalEvent(u64); + + let events = Rc::new(RefCell::new(Vec::new())); + let first_subscription; + let second_subscription; + + { + let events = events.clone(); + first_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| { + events.borrow_mut().push(("First", e.clone())); + }); + } + + { + let events = events.clone(); + second_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| { + events.borrow_mut().push(("Second", e.clone())); + }); + } + + cx.update(|cx| { + cx.emit_global(GlobalEvent(1)); + cx.emit_global(GlobalEvent(2)); + }); + + drop(first_subscription); + + cx.update(|cx| { + cx.emit_global(GlobalEvent(3)); + }); + + drop(second_subscription); + + cx.update(|cx| { + cx.emit_global(GlobalEvent(4)); + }); + + assert_eq!( + &*events.borrow(), + &[ + ("First", GlobalEvent(1)), + ("Second", GlobalEvent(1)), + ("First", GlobalEvent(2)), + ("Second", GlobalEvent(2)), + ("Second", GlobalEvent(3)), + ] + ); + } + + #[crate::test(self)] + fn test_global_nested_events(cx: &mut MutableAppContext) { + #[derive(Clone, Debug, Eq, PartialEq)] + struct GlobalEvent(u64); + + let events = Rc::new(RefCell::new(Vec::new())); + + { + let events = events.clone(); + cx.subscribe_global(move |e: &GlobalEvent, cx| { + events.borrow_mut().push(("Outer", e.clone())); + + let events = events.clone(); + cx.subscribe_global(move |e: &GlobalEvent, _| { + events.borrow_mut().push(("Inner", e.clone())); + }).detach(); + }).detach(); + } + + cx.update(|cx| { + cx.emit_global(GlobalEvent(1)); + cx.emit_global(GlobalEvent(2)); + cx.emit_global(GlobalEvent(3)); + }); + + assert_eq!( + &*events.borrow(), + &[ + ("Outer", GlobalEvent(1)), + ("Outer", GlobalEvent(2)), + ("Inner", GlobalEvent(2)), + ("Outer", GlobalEvent(3)), + ("Inner", GlobalEvent(3)), + ("Inner", GlobalEvent(3)), + ] + ); + } + #[crate::test(self)] fn test_dropping_subscribers(cx: &mut MutableAppContext) { struct View; @@ -4602,6 +4767,138 @@ mod tests { observed_model.update(cx, |_, cx| cx.notify()); } + #[crate::test(self)] + fn test_dropping_subscriptions_during_callback(cx: &mut MutableAppContext) { + struct Model; + + impl Entity for Model { + type Event = u64; + } + + // Events + let observing_model = cx.add_model(|_| Model); + let observed_model = cx.add_model(|_| Model); + + let events = Rc::new(RefCell::new(Vec::new())); + + observing_model.update(cx, |_, cx| { + let events = events.clone(); + let subscription = Rc::new(RefCell::new(None)); + *subscription.borrow_mut() = Some(cx.subscribe(&observed_model, { + let subscription = subscription.clone(); + move |_, _, e, _| { + subscription.borrow_mut().take(); + events.borrow_mut().push(e.clone()); + } + })); + }); + + observed_model.update(cx, |_, cx| { + cx.emit(1); + cx.emit(2); + }); + + assert_eq!(*events.borrow(), [1]); + + + // Global Events + #[derive(Clone, Debug, Eq, PartialEq)] + struct GlobalEvent(u64); + + let events = Rc::new(RefCell::new(Vec::new())); + + { + let events = events.clone(); + let subscription = Rc::new(RefCell::new(None)); + *subscription.borrow_mut() = Some(cx.subscribe_global({ + let subscription = subscription.clone(); + move |e: &GlobalEvent, _| { + subscription.borrow_mut().take(); + events.borrow_mut().push(e.clone()); + } + })); + } + + cx.update(|cx| { + cx.emit_global(GlobalEvent(1)); + cx.emit_global(GlobalEvent(2)); + }); + + assert_eq!(*events.borrow(), [GlobalEvent(1)]); + + // Model Observation + let observing_model = cx.add_model(|_| Model); + let observed_model = cx.add_model(|_| Model); + + let observation_count = Rc::new(RefCell::new(0)); + + observing_model.update(cx, |_, cx| { + let observation_count = observation_count.clone(); + let subscription = Rc::new(RefCell::new(None)); + *subscription.borrow_mut() = Some(cx.observe(&observed_model, { + let subscription = subscription.clone(); + move |_, _, _| { + subscription.borrow_mut().take(); + *observation_count.borrow_mut() += 1; + } + })); + }); + + observed_model.update(cx, |_, cx| { + cx.notify(); + }); + + observed_model.update(cx, |_, cx| { + cx.notify(); + }); + + assert_eq!(*observation_count.borrow(), 1); + + // View Observation + struct View; + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + Empty::new().boxed() + } + + fn ui_name() -> &'static str { + "View" + } + } + + let (window_id, _) = cx.add_window(Default::default(), |_| View); + let observing_view = cx.add_view(window_id, |_| View); + let observed_view = cx.add_view(window_id, |_| View); + + let observation_count = Rc::new(RefCell::new(0)); + observing_view.update(cx, |_, cx| { + let observation_count = observation_count.clone(); + let subscription = Rc::new(RefCell::new(None)); + *subscription.borrow_mut() = Some(cx.observe(&observed_view, { + let subscription = subscription.clone(); + move |_, _, _| { + subscription.borrow_mut().take(); + *observation_count.borrow_mut() += 1; + } + })); + }); + + observed_view.update(cx, |_, cx| { + cx.notify(); + }); + + observed_view.update(cx, |_, cx| { + cx.notify(); + }); + + assert_eq!(*observation_count.borrow(), 1); + } + #[crate::test(self)] fn test_focus(cx: &mut MutableAppContext) { struct View { From c50be722147687746bf0aa7f9ca411ebc90457c8 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Fri, 11 Mar 2022 15:38:01 -0800 Subject: [PATCH 21/27] Format including missing formatting changes from previous PR --- crates/editor/src/element.rs | 6 ++- crates/gpui/src/app.rs | 74 +++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b15487b54c..f798397a62 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1467,7 +1467,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); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 3429dd4a42..89cf9afba2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -758,7 +758,8 @@ pub struct MutableAppContext { next_subscription_id: usize, frame_count: usize, subscriptions: Arc>>>>, - global_subscriptions: Arc>>>>, + global_subscriptions: + Arc>>>>, observations: Arc>>>>, release_observations: Arc>>>, presenters_and_platform_windows: @@ -1726,7 +1727,8 @@ impl MutableAppContext { if let Some(mut callback) = callback { let alive = callback(payload.as_ref(), self); if alive { - match self.subscriptions + match self + .subscriptions .lock() .entry(entity_id) .or_default() @@ -1734,10 +1736,10 @@ impl MutableAppContext { { collections::btree_map::Entry::Vacant(entry) => { entry.insert(Some(callback)); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -1752,18 +1754,19 @@ impl MutableAppContext { for (id, callback) in callbacks { if let Some(mut callback) = callback { callback(payload.as_ref(), self); - match self.global_subscriptions + match self + .global_subscriptions .lock() .entry(type_id) .or_default() - .entry(id) + .entry(id) { collections::btree_map::Entry::Vacant(entry) => { entry.insert(Some(callback)); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -1778,18 +1781,19 @@ impl MutableAppContext { if let Some(mut callback) = callback { let alive = callback(self); if alive { - match self.observations + match self + .observations .lock() .entry(observed_id) .or_default() - .entry(id) + .entry(id) { collections::btree_map::Entry::Vacant(entry) => { entry.insert(Some(callback)); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -1818,18 +1822,19 @@ impl MutableAppContext { if let Some(mut callback) = callback { let alive = callback(self); if alive { - match self.observations + match self + .observations .lock() .entry(observed_view_id) .or_default() - .entry(id) + .entry(id) { collections::btree_map::Entry::Vacant(entry) => { entry.insert(Some(callback)); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -3857,18 +3862,21 @@ pub enum Subscription { Subscription { id: usize, entity_id: usize, - subscriptions: Option>>>>>, + subscriptions: + Option>>>>>, }, GlobalSubscription { id: usize, type_id: TypeId, - subscriptions: - Option>>>>>, + subscriptions: Option< + Weak>>>>, + >, }, Observation { id: usize, entity_id: usize, - observations: Option>>>>>, + observations: + Option>>>>>, }, ReleaseObservation { id: usize, @@ -3914,10 +3922,10 @@ impl Drop for Subscription { { collections::btree_map::Entry::Vacant(entry) => { entry.insert(None); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -3927,18 +3935,13 @@ impl Drop for Subscription { subscriptions, } => { if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) { - match subscriptions - .lock() - .entry(*type_id) - .or_default() - .entry(*id) - { + match subscriptions.lock().entry(*type_id).or_default().entry(*id) { collections::btree_map::Entry::Vacant(entry) => { entry.insert(None); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -3956,10 +3959,10 @@ impl Drop for Subscription { { collections::btree_map::Entry::Vacant(entry) => { entry.insert(None); - }, + } collections::btree_map::Entry::Occupied(entry) => { entry.remove(); - }, + } } } } @@ -4606,8 +4609,10 @@ mod tests { let events = events.clone(); cx.subscribe_global(move |e: &GlobalEvent, _| { events.borrow_mut().push(("Inner", e.clone())); - }).detach(); - }).detach(); + }) + .detach(); + }) + .detach(); } cx.update(|cx| { @@ -4800,7 +4805,6 @@ mod tests { assert_eq!(*events.borrow(), [1]); - // Global Events #[derive(Clone, Debug, Eq, PartialEq)] struct GlobalEvent(u64); From cd4a9f317822d10c7f2aaf268ec301c0a05c3320 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 12 Mar 2022 13:03:45 +0100 Subject: [PATCH 22/27] Fix bug in selection position maintenance while renaming symbol We were resolving the selection with the wrong buffer, which now causes a panic because we don't check the anchor's `buffer_id` anymore. --- crates/editor/src/editor.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0544355359..ba16522972 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4517,25 +4517,32 @@ impl Editor { self.remove_blocks([rename.block_id].into_iter().collect(), cx); self.clear_highlighted_ranges::(cx); - let editor = rename.editor.read(cx); - let snapshot = self.buffer.read(cx).snapshot(cx); - let selection = editor.newest_selection_with_snapshot::(&snapshot); + let selection_in_rename_editor = rename.editor.read(cx).newest_selection::(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 start = snapshot - .clip_offset(rename_range.start + selection.start, Bias::Left) + .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.end, Bias::Left) + .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.reversed, + reversed: selection_in_rename_editor.reversed, goal: SelectionGoal::None, }], None, From dd1711d53f56a29127b1c08db88df5ef6e678efe Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 12 Mar 2022 17:50:09 +0100 Subject: [PATCH 23/27] Account for all excerpts ever inserted when determining new excerpt ID --- crates/editor/src/multi_buffer.rs | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 756d21e9b5..cc4241cc6b 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -751,29 +751,31 @@ impl MultiBuffer { let mut cursor = snapshot.excerpts.cursor::>(); let mut new_excerpts = cursor.slice(&Some(prev_excerpt_id), Bias::Right, &()); - let mut prev_id = ExcerptId::min(); let edit_start = new_excerpts.summary().text.bytes; new_excerpts.update_last( |excerpt| { excerpt.has_trailing_newline = true; - prev_id = excerpt.id.clone(); }, &(), ); - let mut next_id = ExcerptId::max(); - { - let mut used_cursor = self.used_excerpt_ids.cursor::(); - used_cursor.seek(&prev_id, Bias::Right, &()); - if let Some(used_id) = used_cursor.item() { - next_id = used_id.clone(); - } - } + let mut used_cursor = self.used_excerpt_ids.cursor::(); + used_cursor.seek(prev_excerpt_id, Bias::Right, &()); + let mut prev_id = if let Some(excerpt_id) = used_cursor.prev_item() { + excerpt_id.clone() + } else { + ExcerptId::min() + }; + let next_id = if let Some(excerpt_id) = used_cursor.item() { + excerpt_id.clone() + } else { + ExcerptId::max() + }; + drop(used_cursor); let mut ids = Vec::new(); while let Some(range) = ranges.next() { let id = ExcerptId::between(&prev_id, &next_id); - self.used_excerpt_ids.insert_or_replace(id.clone(), &()); if let Err(ix) = buffer_state.excerpts.binary_search(&id) { buffer_state.excerpts.insert(ix, id.clone()); } @@ -790,6 +792,10 @@ impl MultiBuffer { prev_id = id.clone(); ids.push(id); } + self.used_excerpt_ids.edit( + ids.iter().cloned().map(sum_tree::Edit::Insert).collect(), + &(), + ); let edit_end = new_excerpts.summary().text.bytes; @@ -3223,8 +3229,8 @@ mod tests { let snapshot_2 = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP"); - // The old excerpt id has been reused. - assert_eq!(excerpt_id_2, excerpt_id_1); + // The old excerpt id doesn't get reused. + assert_ne!(excerpt_id_2, excerpt_id_1); // Resolve some anchors from the previous snapshot in the new snapshot. // Although there is still an excerpt with the same id, it is for @@ -3276,7 +3282,7 @@ mod tests { ]; assert_eq!( snapshot_3.summaries_for_anchors::(&anchors), - &[0, 2, 9, 13] + &[0, 2, 5, 13] ); let new_anchors = snapshot_3.refresh_anchors(&anchors); From 4f086b8d7af323cba8a408a80da641899225d564 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 12 Mar 2022 10:10:06 -0700 Subject: [PATCH 24/27] 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 e462bd9872..fbff6735f5 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 25/27] 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 | 79 ++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 85837613f6..764cacd264 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); + // 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, - end, - reversed: selection_in_rename_editor.reversed, - goal: SelectionGoal::None, - }], - None, - cx, - ); + 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 26/27] =?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 764cacd264..57e43f874e 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 27/27] 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 57e43f874e..1741a52623 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 7537f69670..78aa2a2232 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);