Merge branch 'main' into settings-file
This commit is contained in:
commit
b33a049958
27 changed files with 1304 additions and 448 deletions
|
@ -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> {
|
||||
|
@ -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,
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -115,7 +115,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();
|
||||
|
@ -132,7 +132,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);
|
||||
}
|
||||
|
|
|
@ -740,6 +740,7 @@ type ActionCallback =
|
|||
type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext);
|
||||
|
||||
type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext) -> bool>;
|
||||
type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
||||
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||
|
||||
|
@ -756,8 +757,10 @@ pub struct MutableAppContext {
|
|||
next_window_id: usize,
|
||||
next_subscription_id: usize,
|
||||
frame_count: usize,
|
||||
subscriptions: Arc<Mutex<HashMap<usize, BTreeMap<usize, SubscriptionCallback>>>>,
|
||||
observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ObservationCallback>>>>,
|
||||
subscriptions: Arc<Mutex<HashMap<usize, BTreeMap<usize, Option<SubscriptionCallback>>>>>,
|
||||
global_subscriptions:
|
||||
Arc<Mutex<HashMap<TypeId, BTreeMap<usize, Option<GlobalSubscriptionCallback>>>>>,
|
||||
observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, Option<ObservationCallback>>>>>,
|
||||
release_observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ReleaseObservationCallback>>>>,
|
||||
presenters_and_platform_windows:
|
||||
HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
|
||||
|
@ -804,6 +807,7 @@ impl MutableAppContext {
|
|||
next_subscription_id: 0,
|
||||
frame_count: 0,
|
||||
subscriptions: Default::default(),
|
||||
global_subscriptions: Default::default(),
|
||||
observations: Default::default(),
|
||||
release_observations: Default::default(),
|
||||
presenters_and_platform_windows: HashMap::new(),
|
||||
|
@ -1062,6 +1066,12 @@ impl MutableAppContext {
|
|||
self.foreground_platform.prompt_for_new_path(directory)
|
||||
}
|
||||
|
||||
pub fn emit_global<E: Any>(&mut self, payload: E) {
|
||||
self.pending_effects.push_back(Effect::GlobalEvent {
|
||||
payload: Box::new(payload),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||
where
|
||||
E: Entity,
|
||||
|
@ -1075,6 +1085,31 @@ impl MutableAppContext {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn subscribe_global<E, F>(&mut self, mut callback: F) -> Subscription
|
||||
where
|
||||
E: Any,
|
||||
F: 'static + FnMut(&E, &mut Self),
|
||||
{
|
||||
let id = post_inc(&mut self.next_subscription_id);
|
||||
let type_id = TypeId::of::<E>();
|
||||
self.global_subscriptions
|
||||
.lock()
|
||||
.entry(type_id)
|
||||
.or_default()
|
||||
.insert(
|
||||
id,
|
||||
Some(Box::new(move |payload, cx| {
|
||||
let payload = payload.downcast_ref().expect("downcast is type safe");
|
||||
callback(payload, cx)
|
||||
})),
|
||||
);
|
||||
Subscription::GlobalSubscription {
|
||||
id,
|
||||
type_id,
|
||||
subscriptions: Some(Arc::downgrade(&self.global_subscriptions)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||
where
|
||||
E: Entity,
|
||||
|
@ -1103,14 +1138,14 @@ impl MutableAppContext {
|
|||
.or_default()
|
||||
.insert(
|
||||
id,
|
||||
Box::new(move |payload, cx| {
|
||||
Some(Box::new(move |payload, cx| {
|
||||
if let Some(emitter) = H::upgrade_from(&emitter, cx.as_ref()) {
|
||||
let payload = payload.downcast_ref().expect("downcast is type safe");
|
||||
callback(emitter, payload, cx)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
})),
|
||||
);
|
||||
Subscription::Subscription {
|
||||
id,
|
||||
|
@ -1134,13 +1169,13 @@ impl MutableAppContext {
|
|||
.or_default()
|
||||
.insert(
|
||||
id,
|
||||
Box::new(move |cx| {
|
||||
Some(Box::new(move |cx| {
|
||||
if let Some(observed) = H::upgrade_from(&observed, cx) {
|
||||
callback(observed, cx)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
})),
|
||||
);
|
||||
Subscription::Observation {
|
||||
id,
|
||||
|
@ -1573,6 +1608,7 @@ impl MutableAppContext {
|
|||
if let Some(effect) = self.pending_effects.pop_front() {
|
||||
match effect {
|
||||
Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload),
|
||||
Effect::GlobalEvent { payload } => self.emit_global_event(payload),
|
||||
Effect::ModelNotification { model_id } => {
|
||||
self.notify_model_observers(model_id)
|
||||
}
|
||||
|
@ -1687,14 +1723,51 @@ impl MutableAppContext {
|
|||
fn emit_event(&mut self, entity_id: usize, payload: Box<dyn Any>) {
|
||||
let callbacks = self.subscriptions.lock().remove(&entity_id);
|
||||
if let Some(callbacks) = callbacks {
|
||||
for (id, mut callback) in callbacks {
|
||||
let alive = callback(payload.as_ref(), self);
|
||||
if alive {
|
||||
self.subscriptions
|
||||
for (id, callback) in callbacks {
|
||||
if let Some(mut callback) = callback {
|
||||
let alive = callback(payload.as_ref(), self);
|
||||
if alive {
|
||||
match self
|
||||
.subscriptions
|
||||
.lock()
|
||||
.entry(entity_id)
|
||||
.or_default()
|
||||
.entry(id)
|
||||
{
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Some(callback));
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_global_event(&mut self, payload: Box<dyn Any>) {
|
||||
let type_id = (&*payload).type_id();
|
||||
let callbacks = self.global_subscriptions.lock().remove(&type_id);
|
||||
if let Some(callbacks) = callbacks {
|
||||
for (id, callback) in callbacks {
|
||||
if let Some(mut callback) = callback {
|
||||
callback(payload.as_ref(), self);
|
||||
match self
|
||||
.global_subscriptions
|
||||
.lock()
|
||||
.entry(entity_id)
|
||||
.entry(type_id)
|
||||
.or_default()
|
||||
.insert(id, callback);
|
||||
.entry(id)
|
||||
{
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Some(callback));
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1704,14 +1777,25 @@ impl MutableAppContext {
|
|||
let callbacks = self.observations.lock().remove(&observed_id);
|
||||
if let Some(callbacks) = callbacks {
|
||||
if self.cx.models.contains_key(&observed_id) {
|
||||
for (id, mut callback) in callbacks {
|
||||
let alive = callback(self);
|
||||
if alive {
|
||||
self.observations
|
||||
.lock()
|
||||
.entry(observed_id)
|
||||
.or_default()
|
||||
.insert(id, callback);
|
||||
for (id, callback) in callbacks {
|
||||
if let Some(mut callback) = callback {
|
||||
let alive = callback(self);
|
||||
if alive {
|
||||
match self
|
||||
.observations
|
||||
.lock()
|
||||
.entry(observed_id)
|
||||
.or_default()
|
||||
.entry(id)
|
||||
{
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Some(callback));
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1734,14 +1818,25 @@ impl MutableAppContext {
|
|||
.views
|
||||
.contains_key(&(observed_window_id, observed_view_id))
|
||||
{
|
||||
for (id, mut callback) in callbacks {
|
||||
let alive = callback(self);
|
||||
if alive {
|
||||
self.observations
|
||||
.lock()
|
||||
.entry(observed_view_id)
|
||||
.or_default()
|
||||
.insert(id, callback);
|
||||
for (id, callback) in callbacks {
|
||||
if let Some(mut callback) = callback {
|
||||
let alive = callback(self);
|
||||
if alive {
|
||||
match self
|
||||
.observations
|
||||
.lock()
|
||||
.entry(observed_view_id)
|
||||
.or_default()
|
||||
.entry(id)
|
||||
{
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Some(callback));
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2071,6 +2166,9 @@ pub enum Effect {
|
|||
entity_id: usize,
|
||||
payload: Box<dyn Any>,
|
||||
},
|
||||
GlobalEvent {
|
||||
payload: Box<dyn Any>,
|
||||
},
|
||||
ModelNotification {
|
||||
model_id: usize,
|
||||
},
|
||||
|
@ -2104,6 +2202,10 @@ impl Debug for Effect {
|
|||
.debug_struct("Effect::Event")
|
||||
.field("entity_id", entity_id)
|
||||
.finish(),
|
||||
Effect::GlobalEvent { payload, .. } => f
|
||||
.debug_struct("Effect::GlobalEvent")
|
||||
.field("type_id", &(&*payload).type_id())
|
||||
.finish(),
|
||||
Effect::ModelNotification { model_id } => f
|
||||
.debug_struct("Effect::ModelNotification")
|
||||
.field("model_id", model_id)
|
||||
|
@ -3760,12 +3862,21 @@ pub enum Subscription {
|
|||
Subscription {
|
||||
id: usize,
|
||||
entity_id: usize,
|
||||
subscriptions: Option<Weak<Mutex<HashMap<usize, BTreeMap<usize, SubscriptionCallback>>>>>,
|
||||
subscriptions:
|
||||
Option<Weak<Mutex<HashMap<usize, BTreeMap<usize, Option<SubscriptionCallback>>>>>>,
|
||||
},
|
||||
GlobalSubscription {
|
||||
id: usize,
|
||||
type_id: TypeId,
|
||||
subscriptions: Option<
|
||||
Weak<Mutex<HashMap<TypeId, BTreeMap<usize, Option<GlobalSubscriptionCallback>>>>>,
|
||||
>,
|
||||
},
|
||||
Observation {
|
||||
id: usize,
|
||||
entity_id: usize,
|
||||
observations: Option<Weak<Mutex<HashMap<usize, BTreeMap<usize, ObservationCallback>>>>>,
|
||||
observations:
|
||||
Option<Weak<Mutex<HashMap<usize, BTreeMap<usize, Option<ObservationCallback>>>>>>,
|
||||
},
|
||||
ReleaseObservation {
|
||||
id: usize,
|
||||
|
@ -3781,6 +3892,9 @@ impl Subscription {
|
|||
Subscription::Subscription { subscriptions, .. } => {
|
||||
subscriptions.take();
|
||||
}
|
||||
Subscription::GlobalSubscription { subscriptions, .. } => {
|
||||
subscriptions.take();
|
||||
}
|
||||
Subscription::Observation { observations, .. } => {
|
||||
observations.take();
|
||||
}
|
||||
|
@ -3794,14 +3908,61 @@ impl Subscription {
|
|||
impl Drop for Subscription {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
Subscription::Subscription {
|
||||
id,
|
||||
entity_id,
|
||||
subscriptions,
|
||||
} => {
|
||||
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
||||
match subscriptions
|
||||
.lock()
|
||||
.entry(*entity_id)
|
||||
.or_default()
|
||||
.entry(*id)
|
||||
{
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(None);
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Subscription::GlobalSubscription {
|
||||
id,
|
||||
type_id,
|
||||
subscriptions,
|
||||
} => {
|
||||
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
||||
match subscriptions.lock().entry(*type_id).or_default().entry(*id) {
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(None);
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Subscription::Observation {
|
||||
id,
|
||||
entity_id,
|
||||
observations,
|
||||
} => {
|
||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
||||
if let Some(observations) = observations.lock().get_mut(entity_id) {
|
||||
observations.remove(id);
|
||||
match observations
|
||||
.lock()
|
||||
.entry(*entity_id)
|
||||
.or_default()
|
||||
.entry(*id)
|
||||
{
|
||||
collections::btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(None);
|
||||
}
|
||||
collections::btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3816,17 +3977,6 @@ impl Drop for Subscription {
|
|||
}
|
||||
}
|
||||
}
|
||||
Subscription::Subscription {
|
||||
id,
|
||||
entity_id,
|
||||
subscriptions,
|
||||
} => {
|
||||
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
||||
if let Some(subscriptions) = subscriptions.lock().get_mut(entity_id) {
|
||||
subscriptions.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4392,6 +4542,98 @@ mod tests {
|
|||
assert_eq!(handle_1.read(cx).events, vec![7, 5, 10, 9]);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_global_events(cx: &mut MutableAppContext) {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct GlobalEvent(u64);
|
||||
|
||||
let events = Rc::new(RefCell::new(Vec::new()));
|
||||
let first_subscription;
|
||||
let second_subscription;
|
||||
|
||||
{
|
||||
let events = events.clone();
|
||||
first_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| {
|
||||
events.borrow_mut().push(("First", e.clone()));
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let events = events.clone();
|
||||
second_subscription = cx.subscribe_global(move |e: &GlobalEvent, _| {
|
||||
events.borrow_mut().push(("Second", e.clone()));
|
||||
});
|
||||
}
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.emit_global(GlobalEvent(1));
|
||||
cx.emit_global(GlobalEvent(2));
|
||||
});
|
||||
|
||||
drop(first_subscription);
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.emit_global(GlobalEvent(3));
|
||||
});
|
||||
|
||||
drop(second_subscription);
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.emit_global(GlobalEvent(4));
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
&*events.borrow(),
|
||||
&[
|
||||
("First", GlobalEvent(1)),
|
||||
("Second", GlobalEvent(1)),
|
||||
("First", GlobalEvent(2)),
|
||||
("Second", GlobalEvent(2)),
|
||||
("Second", GlobalEvent(3)),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_global_nested_events(cx: &mut MutableAppContext) {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct GlobalEvent(u64);
|
||||
|
||||
let events = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
{
|
||||
let events = events.clone();
|
||||
cx.subscribe_global(move |e: &GlobalEvent, cx| {
|
||||
events.borrow_mut().push(("Outer", e.clone()));
|
||||
|
||||
let events = events.clone();
|
||||
cx.subscribe_global(move |e: &GlobalEvent, _| {
|
||||
events.borrow_mut().push(("Inner", e.clone()));
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.emit_global(GlobalEvent(1));
|
||||
cx.emit_global(GlobalEvent(2));
|
||||
cx.emit_global(GlobalEvent(3));
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
&*events.borrow(),
|
||||
&[
|
||||
("Outer", GlobalEvent(1)),
|
||||
("Outer", GlobalEvent(2)),
|
||||
("Inner", GlobalEvent(2)),
|
||||
("Outer", GlobalEvent(3)),
|
||||
("Inner", GlobalEvent(3)),
|
||||
("Inner", GlobalEvent(3)),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_dropping_subscribers(cx: &mut MutableAppContext) {
|
||||
struct View;
|
||||
|
@ -4530,6 +4772,137 @@ mod tests {
|
|||
observed_model.update(cx, |_, cx| cx.notify());
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_dropping_subscriptions_during_callback(cx: &mut MutableAppContext) {
|
||||
struct Model;
|
||||
|
||||
impl Entity for Model {
|
||||
type Event = u64;
|
||||
}
|
||||
|
||||
// Events
|
||||
let observing_model = cx.add_model(|_| Model);
|
||||
let observed_model = cx.add_model(|_| Model);
|
||||
|
||||
let events = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
observing_model.update(cx, |_, cx| {
|
||||
let events = events.clone();
|
||||
let subscription = Rc::new(RefCell::new(None));
|
||||
*subscription.borrow_mut() = Some(cx.subscribe(&observed_model, {
|
||||
let subscription = subscription.clone();
|
||||
move |_, _, e, _| {
|
||||
subscription.borrow_mut().take();
|
||||
events.borrow_mut().push(e.clone());
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
observed_model.update(cx, |_, cx| {
|
||||
cx.emit(1);
|
||||
cx.emit(2);
|
||||
});
|
||||
|
||||
assert_eq!(*events.borrow(), [1]);
|
||||
|
||||
// Global Events
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct GlobalEvent(u64);
|
||||
|
||||
let events = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
{
|
||||
let events = events.clone();
|
||||
let subscription = Rc::new(RefCell::new(None));
|
||||
*subscription.borrow_mut() = Some(cx.subscribe_global({
|
||||
let subscription = subscription.clone();
|
||||
move |e: &GlobalEvent, _| {
|
||||
subscription.borrow_mut().take();
|
||||
events.borrow_mut().push(e.clone());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.emit_global(GlobalEvent(1));
|
||||
cx.emit_global(GlobalEvent(2));
|
||||
});
|
||||
|
||||
assert_eq!(*events.borrow(), [GlobalEvent(1)]);
|
||||
|
||||
// Model Observation
|
||||
let observing_model = cx.add_model(|_| Model);
|
||||
let observed_model = cx.add_model(|_| Model);
|
||||
|
||||
let observation_count = Rc::new(RefCell::new(0));
|
||||
|
||||
observing_model.update(cx, |_, cx| {
|
||||
let observation_count = observation_count.clone();
|
||||
let subscription = Rc::new(RefCell::new(None));
|
||||
*subscription.borrow_mut() = Some(cx.observe(&observed_model, {
|
||||
let subscription = subscription.clone();
|
||||
move |_, _, _| {
|
||||
subscription.borrow_mut().take();
|
||||
*observation_count.borrow_mut() += 1;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
observed_model.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
observed_model.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
assert_eq!(*observation_count.borrow(), 1);
|
||||
|
||||
// View Observation
|
||||
struct View;
|
||||
|
||||
impl Entity for View {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl super::View for View {
|
||||
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
|
||||
fn ui_name() -> &'static str {
|
||||
"View"
|
||||
}
|
||||
}
|
||||
|
||||
let (window_id, _) = cx.add_window(Default::default(), |_| View);
|
||||
let observing_view = cx.add_view(window_id, |_| View);
|
||||
let observed_view = cx.add_view(window_id, |_| View);
|
||||
|
||||
let observation_count = Rc::new(RefCell::new(0));
|
||||
observing_view.update(cx, |_, cx| {
|
||||
let observation_count = observation_count.clone();
|
||||
let subscription = Rc::new(RefCell::new(None));
|
||||
*subscription.borrow_mut() = Some(cx.observe(&observed_view, {
|
||||
let subscription = subscription.clone();
|
||||
move |_, _, _| {
|
||||
subscription.borrow_mut().take();
|
||||
*observation_count.borrow_mut() += 1;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
observed_view.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
observed_view.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
assert_eq!(*observation_count.borrow(), 1);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_focus(cx: &mut MutableAppContext) {
|
||||
struct View {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ pub struct Run {
|
|||
pub glyphs: Vec<Glyph>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Glyph {
|
||||
pub id: GlyphId,
|
||||
pub position: Vector2F,
|
||||
|
@ -210,10 +210,14 @@ impl Line {
|
|||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> f32 {
|
||||
self.layout.font_size
|
||||
}
|
||||
|
||||
pub fn x_for_index(&self, index: usize) -> f32 {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index == index {
|
||||
if glyph.index >= index {
|
||||
return glyph.position.x();
|
||||
}
|
||||
}
|
||||
|
@ -221,6 +225,18 @@ impl Line {
|
|||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return Some(run.font_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn index_for_x(&self, x: f32) -> Option<usize> {
|
||||
if x >= self.layout.width {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -516,7 +516,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;
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::{
|
|||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clock::ReplicaId;
|
||||
use collections::HashSet;
|
||||
use lsp::DiagnosticSeverity;
|
||||
use rpc::proto;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
@ -100,26 +99,6 @@ pub fn serialize_undo_map_entry(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn serialize_buffer_fragment(fragment: &text::Fragment) -> proto::BufferFragment {
|
||||
proto::BufferFragment {
|
||||
replica_id: fragment.insertion_timestamp.replica_id as u32,
|
||||
local_timestamp: fragment.insertion_timestamp.local,
|
||||
lamport_timestamp: fragment.insertion_timestamp.lamport,
|
||||
insertion_offset: fragment.insertion_offset as u32,
|
||||
len: fragment.len as u32,
|
||||
visible: fragment.visible,
|
||||
deletions: fragment
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|clock| proto::VectorClockEntry {
|
||||
replica_id: clock.replica_id as u32,
|
||||
timestamp: clock.value,
|
||||
})
|
||||
.collect(),
|
||||
max_undos: serialize_version(&fragment.max_undos),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
|
||||
selections
|
||||
.iter()
|
||||
|
@ -290,29 +269,6 @@ pub fn deserialize_undo_map_entry(
|
|||
)
|
||||
}
|
||||
|
||||
pub fn deserialize_buffer_fragment(
|
||||
message: proto::BufferFragment,
|
||||
ix: usize,
|
||||
count: usize,
|
||||
) -> Fragment {
|
||||
Fragment {
|
||||
id: locator::Locator::from_index(ix, count),
|
||||
insertion_timestamp: InsertionTimestamp {
|
||||
replica_id: message.replica_id as ReplicaId,
|
||||
local: message.local_timestamp,
|
||||
lamport: message.lamport_timestamp,
|
||||
},
|
||||
insertion_offset: message.insertion_offset as usize,
|
||||
len: message.len as usize,
|
||||
visible: message.visible,
|
||||
deletions: HashSet::from_iter(message.deletions.into_iter().map(|entry| clock::Local {
|
||||
replica_id: entry.replica_id as ReplicaId,
|
||||
value: entry.timestamp,
|
||||
})),
|
||||
max_undos: deserialize_version(message.max_undos),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selection<Anchor>]> {
|
||||
Arc::from(
|
||||
selections
|
||||
|
|
|
@ -174,7 +174,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);
|
||||
});
|
||||
}
|
||||
|
@ -195,7 +195,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);
|
||||
}
|
||||
|
|
|
@ -149,7 +149,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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -388,7 +390,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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -401,7 +405,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 {
|
||||
|
@ -470,7 +476,7 @@ impl SearchBar {
|
|||
}
|
||||
|
||||
let theme = &cx.app_state::<Settings>().theme.search;
|
||||
editor.highlight_ranges::<Self>(
|
||||
editor.highlight_background::<Self>(
|
||||
ranges,
|
||||
theme.match_background,
|
||||
cx,
|
||||
|
@ -547,7 +553,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),
|
||||
|
@ -568,7 +574,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(),
|
||||
|
@ -584,7 +590,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),
|
||||
|
@ -625,7 +631,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),
|
||||
|
|
|
@ -553,7 +553,7 @@ impl ProjectSearchView {
|
|||
editor.select_ranges(match_ranges.first().cloned(), Some(Autoscroll::Fit), cx);
|
||||
}
|
||||
let theme = &cx.app_state::<Settings>().theme.search;
|
||||
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);
|
||||
|
@ -752,7 +752,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, &());
|
||||
|
|
|
@ -19,11 +19,6 @@ impl Locator {
|
|||
Self(smallvec![u64::MAX])
|
||||
}
|
||||
|
||||
pub fn from_index(ix: usize, count: usize) -> Self {
|
||||
let id = (1 + ix as u64) * (u64::MAX / (count as u64 + 2));
|
||||
Self(smallvec![id])
|
||||
}
|
||||
|
||||
pub fn assign(&mut self, other: &Self) {
|
||||
self.0.resize(other.0.len(), 0);
|
||||
self.0.copy_from_slice(&other.0);
|
||||
|
@ -54,6 +49,30 @@ impl Default for Locator {
|
|||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for Locator {
|
||||
type Summary = Locator;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::KeyedItem for Locator {
|
||||
type Key = Locator;
|
||||
|
||||
fn key(&self) -> Self::Key {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for Locator {
|
||||
type Context = ();
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
self.assign(summary);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1494,6 +1494,8 @@ fn open(action: &Open, cx: &mut MutableAppContext) {
|
|||
.detach();
|
||||
}
|
||||
|
||||
pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
|
||||
|
||||
pub fn open_paths(
|
||||
abs_paths: &[PathBuf],
|
||||
app_state: &Arc<AppState>,
|
||||
|
@ -1520,7 +1522,7 @@ pub fn open_paths(
|
|||
}
|
||||
|
||||
let workspace = existing.unwrap_or_else(|| {
|
||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
let project = Project::local(
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
|
@ -1529,8 +1531,9 @@ pub fn open_paths(
|
|||
cx,
|
||||
);
|
||||
(app_state.build_workspace)(project, &app_state, cx)
|
||||
})
|
||||
.1
|
||||
});
|
||||
cx.emit_global(WorkspaceCreated(workspace.downgrade()));
|
||||
workspace
|
||||
});
|
||||
|
||||
let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
|
||||
|
@ -1564,12 +1567,13 @@ pub fn join_project(
|
|||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
let (_, workspace) = cx.update(|cx| {
|
||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
Ok(cx.update(|cx| {
|
||||
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
(app_state.build_workspace)(project, &app_state, cx)
|
||||
})
|
||||
});
|
||||
Ok(workspace)
|
||||
});
|
||||
cx.emit_global(WorkspaceCreated(workspace.downgrade()));
|
||||
workspace
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1584,5 +1588,6 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
|||
);
|
||||
(app_state.build_workspace)(project, &app_state, cx)
|
||||
});
|
||||
cx.emit_global(WorkspaceCreated(workspace.downgrade()));
|
||||
cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue