Detailed changes
@@ -7,10 +7,13 @@ use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
use block_map::{BlockMap, BlockPoint};
use collections::{HashMap, HashSet};
use fold_map::FoldMap;
-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<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
+
pub struct DisplayMap {
buffer: ModelHandle<MultiBuffer>,
buffer_subscription: BufferSubscription,
@@ -30,6 +35,7 @@ pub struct DisplayMap {
tab_map: TabMap,
wrap_map: ModelHandle<WrapMap>,
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,23 @@ impl DisplayMap {
block_map.remove(ids);
}
+ pub fn highlight_text(
+ &mut self,
+ type_id: TypeId,
+ ranges: Vec<Range<Anchor>>,
+ style: HighlightStyle,
+ ) {
+ self.text_highlights
+ .insert(Some(type_id), Arc::new((style, ranges)));
+ }
+
+ pub fn clear_text_highlights(
+ &mut self,
+ type_id: TypeId,
+ ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
+ self.text_highlights.remove(&Some(type_id))
+ }
+
pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
self.wrap_map
.update(cx, |map, cx| map.set_font(font_id, font_size, cx));
@@ -178,6 +203,7 @@ pub struct DisplaySnapshot {
tabs_snapshot: tab_map::TabSnapshot,
wraps_snapshot: wrap_map::WrapSnapshot,
blocks_snapshot: block_map::BlockSnapshot,
+ text_highlights: TextHighlights,
}
impl DisplaySnapshot {
@@ -252,7 +278,7 @@ impl DisplaySnapshot {
pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
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)
}
@@ -261,7 +287,8 @@ impl DisplaySnapshot {
display_rows: Range<u32>,
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<Item = char> + 'a {
@@ -1146,7 +1173,7 @@ mod tests {
let mut chunks: Vec<(String, Option<Color>)> = 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 {
@@ -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<u32>, language_aware: bool) -> BlockChunks<'a> {
+ pub fn chunks<'a>(
+ &'a self,
+ rows: Range<u32>,
+ 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,
@@ -807,7 +817,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,
});
}
@@ -1435,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::<String>();
assert_eq!(
@@ -1,14 +1,19 @@
+use super::TextHighlights;
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};
@@ -71,6 +76,12 @@ impl FoldPoint {
}
}
+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> {
@@ -484,7 +495,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()
}
@@ -642,20 +653,96 @@ impl FoldSnapshot {
pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
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<FoldOffset>, language_aware: bool) -> FoldChunks<'a> {
+ pub fn chunks<'a>(
+ &'a self,
+ range: Range<FoldOffset>,
+ 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
+ };
+
+ 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.seek(&range.start, Bias::Right, &());
- let overshoot = range.start.0 - transform_cursor.start().0 .0;
- let buffer_start = transform_cursor.start().1 + overshoot;
+ transform_cursor.next(&());
+ }
+ highlight_endpoints.sort();
+ transform_cursor.seek(&range.start, Bias::Right, &());
+ }
+ }
FoldChunks {
transform_cursor,
@@ -666,6 +753,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(),
}
}
@@ -954,6 +1043,8 @@ pub struct FoldChunks<'a> {
buffer_offset: usize,
output_offset: usize,
max_output_offset: usize,
+ highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
+ active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
}
impl<'a> Iterator for FoldChunks<'a> {
@@ -986,11 +1077,27 @@ 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,
});
}
+ 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();
@@ -998,20 +1105,31 @@ impl<'a> Iterator for FoldChunks<'a> {
}
// Otherwise, take a chunk from the buffer's text.
- if let Some((chunk_offset, mut chunk)) = self.buffer_chunk {
- let offset_in_chunk = self.buffer_offset - chunk_offset;
- chunk.text = &chunk.text[offset_in_chunk..];
-
- // Truncate the chunk so that it ends at the next fold.
- let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset;
- if chunk.text.len() >= region_end {
- chunk.text = &chunk.text[0..region_end];
+ if let Some((buffer_chunk_start, mut chunk)) = self.buffer_chunk {
+ let buffer_chunk_end = buffer_chunk_start + chunk.text.len();
+ let transform_end = self.transform_cursor.end(&()).1;
+ let chunk_end = buffer_chunk_end
+ .min(transform_end)
+ .min(next_highlight_endpoint);
+
+ chunk.text = &chunk.text
+ [self.buffer_offset - buffer_chunk_start..chunk_end - buffer_chunk_start];
+
+ if !self.active_highlights.is_empty() {
+ let mut highlight_style = HighlightStyle::default();
+ for active_highlight in self.active_highlights.values() {
+ highlight_style.highlight(*active_highlight);
+ }
+ chunk.highlight_style = Some(highlight_style);
+ }
+
+ if chunk_end == transform_end {
self.transform_cursor.next(&());
- } else {
+ } else if chunk_end == buffer_chunk_end {
self.buffer_chunk.take();
}
- self.buffer_offset += chunk.text.len();
+ self.buffer_offset = chunk_end;
self.output_offset += chunk.text.len();
return Some(chunk);
}
@@ -1020,9 +1138,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<TypeId>,
+ style: HighlightStyle,
+}
+
+impl PartialOrd for HighlightEndpoint {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ 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))
}
}
@@ -1079,7 +1213,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};
@@ -1284,6 +1419,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::<Vec<_>>();
+ 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::<Vec<_>>();
+
+ 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();
@@ -1408,7 +1562,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::<String>(),
text,
@@ -1,4 +1,7 @@
-use super::fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot};
+use super::{
+ fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot},
+ 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<TabPoint>, language_aware: bool) -> TabChunks<'a> {
+ pub fn chunks<'a>(
+ &'a self,
+ range: Range<TabPoint>,
+ 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::<String>(),
"chunks({:?}..{:?})",
@@ -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::<Transform>::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<Item = &str> {
- 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<u32>, language_aware: bool) -> WrapChunks<'a> {
+ pub fn chunks<'a>(
+ &'a self,
+ rows: Range<u32>,
+ 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::<String>();
assert_eq!(
@@ -134,6 +134,9 @@ action!(ConfirmCompletion, Option<usize>);
action!(ConfirmCodeAction, Option<usize>);
action!(OpenExcerpts);
+enum DocumentHighlightRead {}
+enum DocumentHighlightWrite {}
+
pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec<Box<dyn PathOpener>>) {
path_openers.push(Box::new(items::BufferOpener));
cx.add_bindings(vec![
@@ -409,6 +412,8 @@ type CompletionId = usize;
pub type GetFieldEditorTheme = fn(&theme::Theme) -> theme::FieldEditor;
+type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
+
pub struct Editor {
handle: WeakViewHandle<Self>,
buffer: ModelHandle<MultiBuffer>,
@@ -431,16 +436,18 @@ pub struct Editor {
settings: watch::Receiver<Settings>,
soft_wrap_mode_override: Option<settings::SoftWrap>,
get_field_editor_theme: Option<GetFieldEditorTheme>,
+ override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<ModelHandle<Project>>,
focused: bool,
show_local_cursors: bool,
+ show_local_selections: bool,
blink_epoch: usize,
blinking_paused: bool,
mode: EditorMode,
vertical_scroll_margin: f32,
placeholder_text: Option<Arc<str>>,
highlighted_rows: Option<Range<u32>>,
- highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
+ background_highlights: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
nav_history: Option<ItemNavHistory>,
context_menu: Option<ContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
@@ -865,7 +872,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,
@@ -915,13 +922,14 @@ impl Editor {
autoscroll_request: None,
focused: false,
show_local_cursors: false,
+ show_local_selections: true,
blink_epoch: 0,
blinking_paused: false,
mode,
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(),
@@ -931,6 +939,7 @@ impl Editor {
document_highlights_task: Default::default(),
pending_rename: Default::default(),
searchable: true,
+ override_text_style: None,
cursor_shape: Default::default(),
};
this.end_selection(cx);
@@ -984,7 +993,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(
@@ -1501,7 +1515,7 @@ impl Editor {
}
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- if self.take_rename(cx).is_some() {
+ if self.take_rename(false, cx).is_some() {
return;
}
@@ -2357,7 +2371,7 @@ impl Editor {
if let Some(editor) = editor.act_as::<Self>(cx) {
editor.update(cx, |editor, cx| {
let color = editor.style(cx).highlighted_line_background;
- editor.highlight_ranges::<Self>(ranges_to_highlight, color, cx);
+ editor.highlight_background::<Self>(ranges_to_highlight, color, cx);
});
}
});
@@ -2397,6 +2411,10 @@ impl Editor {
}
fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> 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();
@@ -2412,13 +2430,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);
@@ -2451,12 +2470,12 @@ impl Editor {
}
}
- this.highlight_ranges::<DocumentHighlightRead>(
+ this.highlight_background::<DocumentHighlightRead>(
read_ranges,
read_background,
cx,
);
- this.highlight_ranges::<DocumentHighlightWrite>(
+ this.highlight_background::<DocumentHighlightWrite>(
write_ranges,
write_background,
cx,
@@ -3363,7 +3382,7 @@ impl Editor {
}
pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
- if self.take_rename(cx).is_some() {
+ if self.take_rename(true, cx).is_some() {
return;
}
@@ -3411,7 +3430,7 @@ impl Editor {
}
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
- 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) {
@@ -4355,7 +4374,7 @@ impl Editor {
if let Some(editor) = editor.act_as::<Self>(cx) {
editor.update(cx, |editor, cx| {
let color = editor.style(cx).highlighted_line_background;
- editor.highlight_ranges::<Self>(ranges_to_highlight, color, cx);
+ editor.highlight_background::<Self>(ranges_to_highlight, color, cx);
});
}
});
@@ -4373,7 +4392,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)?;
@@ -4383,7 +4402,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)
});
@@ -4393,54 +4411,59 @@ 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);
+ 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);
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::<String>();
+ .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.
+ 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 {
+ 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));
- editor.select_ranges(
- [tail_offset_in_rename_range..cursor_offset_in_rename_range],
- None,
- cx,
- );
- editor.highlight_ranges::<Rename>(
- vec![Anchor::min()..Anchor::max()],
- style.diff_background_inserted,
- cx,
- );
+ editor.select_all(&SelectAll, cx);
editor
});
- this.highlight_ranges::<Rename>(
- vec![range.clone()],
- style.diff_background_deleted,
- cx,
- );
- this.update_selections(
- vec![Selection {
- id: selection.id,
- start: rename_end,
- end: rename_end,
- reversed: false,
- goal: SelectionGoal::None,
- }],
- None,
+
+ let ranges = this
+ .clear_background_highlights::<DocumentHighlightWrite>(cx)
+ .into_iter()
+ .flat_map(|(_, ranges)| ranges)
+ .chain(
+ this.clear_background_highlights::<DocumentHighlightRead>(cx)
+ .into_iter()
+ .flat_map(|(_, ranges)| ranges),
+ )
+ .collect();
+ this.highlight_text::<Rename>(
+ ranges,
+ HighlightStyle {
+ fade_out: Some(style.rename_fade),
+ ..Default::default()
+ },
cx,
);
cx.focus(&rename_editor);
@@ -4482,7 +4505,7 @@ impl Editor {
let editor = workspace.active_item(cx)?.act_as::<Editor>(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)?;
@@ -4506,55 +4529,59 @@ 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(())
}))
}
- fn take_rename(&mut self, cx: &mut ViewContext<Self>) -> Option<RenameState> {
+ fn take_rename(
+ &mut self,
+ moving_cursor: bool,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<RenameState> {
let rename = self.pending_rename.take()?;
self.remove_blocks([rename.block_id].into_iter().collect(), cx);
- self.clear_highlighted_ranges::<Rename>(cx);
+ self.clear_text_highlights::<Rename>(cx);
+ self.show_local_selections = true;
- let selection_in_rename_editor = rename.editor.read(cx).newest_selection::<usize>(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_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);
+ if moving_cursor {
+ let cursor_in_rename_editor =
+ rename.editor.read(cx).newest_selection::<usize>(cx).head();
- self.update_selections(
- vec![Selection {
- id: self.newest_anchor_selection().id,
- start,
- end,
- reversed: selection_in_rename_editor.reversed,
- goal: SelectionGoal::None,
- }],
- None,
- cx,
- );
+ // Update the selection to match the position of the selection inside
+ // the rename editor.
+ let snapshot = self.buffer.read(cx).read(cx);
+ let rename_range = rename.range.to_offset(&snapshot);
+ let cursor_in_editor = snapshot
+ .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
+ .min(rename_range.end);
+ drop(snapshot);
+
+ self.update_selections(
+ vec![Selection {
+ id: self.newest_anchor_selection().id,
+ start: cursor_in_editor,
+ end: cursor_in_editor,
+ reversed: false,
+ goal: SelectionGoal::None,
+ }],
+ None,
+ cx,
+ );
+ }
Some(rename)
}
@@ -4574,7 +4601,7 @@ impl Editor {
}
let rename = self.pending_rename.take().unwrap();
self.remove_blocks([rename.block_id].into_iter().collect(), cx);
- self.clear_highlighted_ranges::<Rename>(cx);
+ self.clear_background_highlights::<Rename>(cx);
}
}
@@ -5294,7 +5321,7 @@ impl Editor {
.update(cx, |map, cx| map.set_wrap_width(width, cx))
}
- pub fn set_highlighted_rows(&mut self, rows: Option<Range<u32>>) {
+ pub fn highlight_rows(&mut self, rows: Option<Range<u32>>) {
self.highlighted_rows = rows;
}
@@ -5302,27 +5329,27 @@ impl Editor {
self.highlighted_rows.clone()
}
- pub fn highlight_ranges<T: 'static>(
+ pub fn highlight_background<T: 'static>(
&mut self,
ranges: Vec<Range<Anchor>>,
color: Color,
cx: &mut ViewContext<Self>,
) {
- self.highlighted_ranges
+ self.background_highlights
.insert(TypeId::of::<T>(), (color, ranges));
cx.notify();
}
- pub fn clear_highlighted_ranges<T: 'static>(
+ pub fn clear_background_highlights<T: 'static>(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<(Color, Vec<Range<Anchor>>)> {
cx.notify();
- self.highlighted_ranges.remove(&TypeId::of::<T>())
+ self.background_highlights.remove(&TypeId::of::<T>())
}
#[cfg(feature = "test-support")]
- pub fn all_highlighted_ranges(
+ pub fn all_background_highlights(
&mut self,
cx: &mut ViewContext<Self>,
) -> Vec<(Range<DisplayPoint>, Color)> {
@@ -5330,23 +5357,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<T: 'static>(&self) -> Option<(Color, &[Range<Anchor>])> {
- self.highlighted_ranges
+ pub fn background_highlights_for_type<T: 'static>(&self) -> Option<(Color, &[Range<Anchor>])> {
+ self.background_highlights
.get(&TypeId::of::<T>())
.map(|(color, ranges)| (*color, ranges.as_slice()))
}
- pub fn highlighted_ranges_in_range(
+ pub fn background_highlights_in_range(
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
) -> Vec<(Range<DisplayPoint>, 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() {
@@ -5375,6 +5402,27 @@ impl Editor {
results
}
+ pub fn highlight_text<T: 'static>(
+ &mut self,
+ ranges: Vec<Range<Anchor>>,
+ style: HighlightStyle,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.display_map.update(cx, |map, _| {
+ map.highlight_text(TypeId::of::<T>(), ranges, style)
+ });
+ cx.notify();
+ }
+
+ pub fn clear_text_highlights<T: 'static>(
+ &mut self,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
+ cx.notify();
+ self.display_map
+ .update(cx, |map, _| map.clear_text_highlights(TypeId::of::<T>()))
+ }
+
fn next_blink_epoch(&mut self) -> usize {
self.blink_epoch += 1;
self.blink_epoch
@@ -5606,17 +5654,20 @@ impl View for Editor {
}
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
- 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<Self>) {
self.focused = false;
- self.show_local_cursors = false;
self.buffer
.update(cx, |buffer, cx| buffer.remove_active_selections(cx));
self.hide_context_menu(cx);
@@ -5651,14 +5702,14 @@ impl View for Editor {
fn build_style(
settings: &Settings,
get_field_editor_theme: Option<GetFieldEditorTheme>,
+ 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 {
@@ -5667,7 +5718,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();
@@ -5688,7 +5738,20 @@ fn build_style(
placeholder_text: None,
theme,
}
+ };
+
+ if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) {
+ if let Some(highlighted) = style
+ .text
+ .clone()
+ .highlight(highlight_style, font_cache)
+ .log_err()
+ {
+ style.text = highlighted;
+ }
}
+
+ style
}
impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
@@ -8912,7 +8975,7 @@ mod tests {
buffer.anchor_after(range.start)..buffer.anchor_after(range.end)
};
- editor.highlight_ranges::<Type1>(
+ editor.highlight_background::<Type1>(
vec![
anchor_range(Point::new(2, 1)..Point::new(2, 3)),
anchor_range(Point::new(4, 2)..Point::new(4, 4)),
@@ -8922,7 +8985,7 @@ mod tests {
Color::red(),
cx,
);
- editor.highlight_ranges::<Type2>(
+ editor.highlight_background::<Type2>(
vec![
anchor_range(Point::new(3, 2)..Point::new(3, 5)),
anchor_range(Point::new(5, 3)..Point::new(5, 6)),
@@ -8934,7 +8997,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,
);
@@ -8963,7 +9026,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,
),
@@ -652,30 +652,37 @@ 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 = chunk
+ .syntax_highlight_id
+ .and_then(|id| id.style(&style.syntax));
+
+ 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);
- let 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)
+ 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 {
- Some(HighlightStyle {
- underline,
- color: style.text.color,
- font_properties: style.text.font_properties,
- })
+ highlight_style = Some(diagnostic_highlight);
}
- } else {
- highlight_style
- };
- (chunk.text, highlight)
+ }
+
+ (chunk.text, highlight_style)
});
layout_highlighted_chunks(
chunks,
@@ -898,37 +905,42 @@ 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,
);
- 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
@@ -1467,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);
@@ -216,25 +216,6 @@ impl MultiBuffer {
this
}
- #[cfg(any(test, feature = "test-support"))]
- pub fn build_simple(text: &str, cx: &mut gpui::MutableAppContext) -> ModelHandle<Self> {
- 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<Self> {
- 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
}
@@ -1192,6 +1173,23 @@ impl MultiBuffer {
#[cfg(any(test, feature = "test-support"))]
impl MultiBuffer {
+ pub fn build_simple(text: &str, cx: &mut gpui::MutableAppContext) -> ModelHandle<Self> {
+ 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<Self> {
+ 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,
@@ -2316,6 +2314,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<usize> {
+ 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<TransactionId> {
self.transaction_depth += 1;
@@ -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);
}
@@ -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,30 @@ impl Color {
pub fn from_u32(rgba: u32) -> Self {
Self(ColorU::from_u32(rgba))
}
+
+ pub fn blend(source: Color, dest: Color) -> Color {
+ // 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();
+ 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, fade: f32) {
+ let fade = fade.clamp(0., 1.);
+ self.0.a = (self.0.a as f32 * (1. - fade)) as u8;
+ }
}
impl<'de> Deserialize<'de> for Color {
@@ -67,12 +67,12 @@ 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));
offset = range.start;
} else {
- result = Some((&self.text[range.clone()], Some(*highlight)));
+ result = Some((&self.text[range.clone()], Some(*highlight_style)));
highlight_ranges.next();
offset = range.end;
}
@@ -198,23 +198,23 @@ impl Element for Text {
/// Perform text layout on a series of highlighted chunks of text.
pub fn layout_highlighted_chunks<'a>(
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
- style: &'a TextStyle,
+ text_style: &'a TextStyle,
text_layout_cache: &'a TextLayoutCache,
font_cache: &'a Arc<FontCache>,
max_line_len: usize,
max_line_count: usize,
) -> Vec<Line> {
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 +225,30 @@ 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());
+ 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(style.font_family_id, &highlight_style.font_properties)
- .unwrap_or(style.font_id)
+ .select_font(text_style.font_family_id, &font_properties)
+ .unwrap_or(text_style.font_id)
};
if line.len() + line_chunk.len() > max_line_len {
@@ -250,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;
}
}
}
@@ -31,13 +31,16 @@ pub struct TextStyle {
pub underline: Option<Underline>,
}
-#[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<Underline>,
+ pub fade_out: Option<f32>,
}
+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<f32>,
}
#[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(style.color, self.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<TextStyle> 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,26 @@ 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);
+ 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() {
+ self.underline = other.underline;
}
}
}
@@ -256,6 +291,7 @@ impl From<Color> for HighlightStyle {
color,
font_properties: Default::default(),
underline: None,
+ fade_out: None,
}
}
}
@@ -295,6 +331,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,
})
}
}
@@ -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};
@@ -246,7 +246,8 @@ pub struct BufferChunks<'a> {
#[derive(Clone, Copy, Debug, Default)]
pub struct Chunk<'a> {
pub text: &'a str,
- pub highlight_id: Option<HighlightId>,
+ pub syntax_highlight_id: Option<HighlightId>,
+ pub highlight_style: Option<HighlightStyle>,
pub diagnostic: Option<DiagnosticSeverity>,
}
@@ -1728,7 +1729,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 {
@@ -2102,7 +2103,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 {
@@ -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
}
}
@@ -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;
@@ -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);
}
@@ -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::<Self>(cx));
+ editor.update(cx, |editor, cx| {
+ editor.clear_background_highlights::<Self>(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::<Self>(cx));
+ editor.update(cx, |editor, cx| {
+ editor.clear_background_highlights::<Self>(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::<Self>(cx));
+ editor.update(cx, |editor, cx| {
+ editor.clear_background_highlights::<Self>(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::<Self>(
+ editor.highlight_background::<Self>(
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),
@@ -565,7 +565,7 @@ impl ProjectSearchView {
if reset_selections {
editor.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit), cx);
}
- editor.highlight_ranges::<Self>(match_ranges, theme.match_background, cx);
+ editor.highlight_background::<Self>(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),
@@ -31,6 +31,10 @@ impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
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::<MapKeyRef<'_, K>>();
cursor.seek(&MapKeyRef(Some(key)), Bias::Left, &());
@@ -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,
@@ -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"