Merge branch 'main' into settings-file

This commit is contained in:
Max Brunsfeld 2022-03-13 10:43:23 -07:00
commit b33a049958
27 changed files with 1304 additions and 448 deletions

View file

@ -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 {

View file

@ -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!(

View file

@ -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> {
@ -241,6 +252,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());
}
}
}
}
@ -476,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()
}
@ -634,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
};
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,
@ -658,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(),
}
}
@ -946,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> {
@ -978,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();
@ -990,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..];
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);
}
@ -1012,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))
}
}
@ -1071,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};
@ -1276,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();
@ -1400,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,

View file

@ -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({:?}..{:?})",

View file

@ -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!(

View file

@ -133,6 +133,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![
@ -408,6 +411,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>,
@ -429,16 +434,18 @@ pub struct Editor {
autoscroll_request: Option<Autoscroll>,
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<()>>)>,
@ -448,6 +455,7 @@ pub struct Editor {
document_highlights_task: Option<Task<()>>,
pending_rename: Option<RenameState>,
searchable: bool,
cursor_shape: CursorShape,
}
pub struct EditorSnapshot {
@ -849,7 +857,7 @@ impl Editor {
) -> Self {
let display_map = cx.add_model(|cx| {
let settings = cx.app_state::<Settings>();
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,
@ -898,13 +906,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(),
@ -914,6 +923,8 @@ 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);
this
@ -966,7 +977,12 @@ impl Editor {
}
fn style(&self, cx: &AppContext) -> EditorStyle {
build_style(cx.app_state::<Settings>(), self.get_field_editor_theme, cx)
build_style(
cx.app_state::<Settings>(),
self.get_field_editor_theme,
self.override_text_style.as_deref(),
cx,
)
}
pub fn set_placeholder_text(
@ -1005,6 +1021,11 @@ impl Editor {
cx.notify();
}
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
self.cursor_shape = cursor_shape;
cx.notify();
}
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> 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)
@ -1478,7 +1499,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;
}
@ -2334,7 +2355,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);
});
}
});
@ -2374,6 +2395,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();
@ -2389,13 +2414,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);
@ -2428,12 +2454,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,
@ -3340,7 +3366,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;
}
@ -3388,7 +3414,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) {
@ -4332,7 +4358,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);
});
}
});
@ -4350,7 +4376,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)?;
@ -4360,7 +4386,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)
});
@ -4370,54 +4395,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(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);
@ -4459,7 +4489,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)?;
@ -4483,48 +4513,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 editor = rename.editor.read(cx);
let snapshot = self.buffer.read(cx).snapshot(cx);
let selection = editor.newest_selection_with_snapshot::<usize>(&snapshot);
if moving_cursor {
let cursor_in_rename_editor =
rename.editor.read(cx).newest_selection::<usize>(cx).head();
// Update the selection to match the position of the selection inside
// the rename editor.
let rename_range = rename.range.to_offset(&snapshot);
let start = snapshot
.clip_offset(rename_range.start + selection.start, Bias::Left)
.min(rename_range.end);
let end = snapshot
.clip_offset(rename_range.start + selection.end, Bias::Left)
.min(rename_range.end);
self.update_selections(
vec![Selection {
id: self.newest_anchor_selection().id,
start,
end,
reversed: selection.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)
}
@ -4544,7 +4585,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);
}
}
@ -5256,7 +5297,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;
}
@ -5264,27 +5305,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)> {
@ -5292,23 +5333,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() {
@ -5337,6 +5378,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
@ -5560,7 +5622,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 {
@ -5568,17 +5630,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);
@ -5613,14 +5678,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 {
@ -5629,7 +5694,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();
@ -5650,7 +5714,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> {
@ -8841,7 +8918,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)),
@ -8851,7 +8928,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)),
@ -8863,7 +8940,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,
);
@ -8892,7 +8969,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,
),

View file

@ -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,
};
@ -32,11 +32,20 @@ use std::{
pub struct EditorElement {
view: WeakViewHandle<Editor>,
style: EditorStyle,
cursor_shape: CursorShape,
}
impl EditorElement {
pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
Self { view, style }
pub fn new(
view: WeakViewHandle<Editor>,
style: EditorStyle,
cursor_shape: CursorShape,
) -> Self {
Self {
view,
style,
cursor_shape,
}
}
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
@ -338,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 {
@ -346,7 +355,7 @@ impl EditorElement {
selection.start..selection.end,
start_row,
end_row,
style.selection,
selection_style.selection,
corner_radius,
corner_radius * 2.,
layout,
@ -362,13 +371,50 @@ 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 block_width =
cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
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,
color: selection_style.cursor,
block_width,
origin: content_origin + vec2f(x, y),
line_height: layout.line_height,
shape: self.cursor_shape,
block_text,
});
}
}
@ -606,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 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 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 {
Some(HighlightStyle {
underline,
color: style.text.color,
font_properties: style.text.font_properties,
})
highlight_style = Some(chunk_highlight);
}
} else {
highlight_style
};
(chunk.text, highlight)
}
if let Some(severity) = chunk.diagnostic {
let diagnostic_style = super::diagnostic_style(severity, true, style);
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)
});
layout_highlighted_chunks(
chunks,
@ -852,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
@ -1161,6 +1219,7 @@ fn layout_line(
while !line.is_char_boundary(len) {
len -= 1;
}
line.truncate(len);
}
@ -1212,20 +1271,51 @@ impl PaintState {
}
}
#[derive(Copy, Clone)]
pub enum CursorShape {
Bar,
Block,
Underscore,
}
impl Default for CursorShape {
fn default() -> Self {
CursorShape::Bar
}
}
struct Cursor {
origin: Vector2F,
block_width: f32,
line_height: f32,
color: Color,
shape: CursorShape,
block_text: Option<Line>,
}
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.block_width, self.line_height))
}
CursorShape::Underscore => RectF::new(
self.origin + Vector2F::new(0.0, self.line_height - 2.0),
vec2f(self.block_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.,
});
if let Some(block_text) = &self.block_text {
block_text.paint(self.origin, bounds, self.line_height, cx);
}
}
}
@ -1388,7 +1478,11 @@ mod tests {
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
Editor::new(EditorMode::Full, buffer, None, 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);

View file

@ -36,6 +36,7 @@ pub type ExcerptId = Locator;
pub struct MultiBuffer {
snapshot: RefCell<MultiBufferSnapshot>,
buffers: RefCell<HashMap<usize, BufferState>>,
used_excerpt_ids: SumTree<ExcerptId>,
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,
@ -213,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
}
@ -748,20 +732,27 @@ impl MultiBuffer {
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
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();
if let Some(next_excerpt) = cursor.item() {
next_id = next_excerpt.id.clone();
}
let mut used_cursor = self.used_excerpt_ids.cursor::<Locator>();
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() {
@ -782,6 +773,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;
@ -963,10 +958,13 @@ impl MultiBuffer {
) -> Option<(ModelHandle<Buffer>, 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(
@ -1020,14 +1018,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<Language>> {
@ -1170,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,
@ -1757,7 +1777,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::<D>(&excerpt.buffer);
let excerpt_buffer_end = excerpt.range.end.summary::<D>(&excerpt.buffer);
let buffer_position = cmp::min(
@ -1788,10 +1808,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
@ -1805,7 +1824,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::<D>(&excerpt.buffer);
let excerpt_buffer_end = excerpt.range.end.summary::<D>(&excerpt.buffer);
summaries.extend(
@ -1998,10 +2017,8 @@ 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) {
excerpt.buffer.can_resolve(&anchor.text_anchor)
} else {
false
}
@ -2231,15 +2248,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::<Option<&ExcerptId>>();
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
@ -2300,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;
@ -3213,8 +3236,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
@ -3266,7 +3289,7 @@ mod tests {
];
assert_eq!(
snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
&[0, 2, 9, 13]
&[0, 2, 5, 13]
);
let new_anchors = snapshot_3.refresh_anchors(&anchors);

View file

@ -40,21 +40,8 @@ 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)
{
// 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) {
Ok(Ordering::Greater)
} else if other.buffer_id == Some(buffer_id) {
Ok(Ordering::Less)
} else {
Ok(Ordering::Equal)
}
} else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
} else {
Ok(Ordering::Equal)
}
@ -65,16 +52,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) {
return Self {
buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id.clone(),
text_anchor: self.text_anchor.bias_left(buffer_snapshot),
};
}
if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
return Self {
buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id.clone(),
text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
};
}
}
self.clone()
@ -82,16 +65,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) {
return Self {
buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id.clone(),
text_anchor: self.text_anchor.bias_right(buffer_snapshot),
};
}
if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
return Self {
buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id.clone(),
text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
};
}
}
self.clone()