1//! This module defines where the text should be displayed in an [`Editor`][Editor].
2//!
3//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
4//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
5//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
6//! we display as spaces and where to display custom blocks (like diagnostics).
7//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
8//! of several smaller structures that form a hierarchy (starting at the bottom):
9//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
10//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
11//! - [`TabMap`] that keeps track of hard tabs in a buffer.
12//! - [`WrapMap`] that handles soft wrapping.
13//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
14//! - [`DisplayMap`] that adds background highlights to the regions of text.
15//! Each one of those builds on top of preceding map.
16//!
17//! [Editor]: crate::Editor
18//! [EditorElement]: crate::element::EditorElement
19
20mod block_map;
21mod crease_map;
22mod fold_map;
23mod inlay_map;
24pub(crate) mod invisibles;
25mod tab_map;
26mod wrap_map;
27
28use crate::{
29 hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
30};
31pub use block_map::{
32 Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
33 BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
34};
35use block_map::{BlockRow, BlockSnapshot};
36use collections::{HashMap, HashSet};
37pub use crease_map::*;
38pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
39use fold_map::{FoldMap, FoldSnapshot};
40use gpui::{
41 AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
42};
43pub(crate) use inlay_map::Inlay;
44use inlay_map::{InlayMap, InlaySnapshot};
45pub use inlay_map::{InlayOffset, InlayPoint};
46use invisibles::{is_invisible, replacement};
47use language::{
48 language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
49 Subscription as BufferSubscription,
50};
51use lsp::DiagnosticSeverity;
52use multi_buffer::{
53 Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
54 ToOffset, ToPoint,
55};
56use serde::Deserialize;
57use std::{
58 any::TypeId,
59 borrow::Cow,
60 fmt::Debug,
61 iter,
62 num::NonZeroU32,
63 ops::{Add, Range, Sub},
64 sync::Arc,
65};
66use sum_tree::{Bias, TreeMap};
67use tab_map::{TabMap, TabSnapshot};
68use text::LineIndent;
69use ui::{px, SharedString, WindowContext};
70use unicode_segmentation::UnicodeSegmentation;
71use wrap_map::{WrapMap, WrapSnapshot};
72
73#[derive(Copy, Clone, Debug, PartialEq, Eq)]
74pub enum FoldStatus {
75 Folded,
76 Foldable,
77}
78
79pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
80
81pub trait ToDisplayPoint {
82 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
83}
84
85type TextHighlights = TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
86type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
87
88/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
89/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
90///
91/// See the [module level documentation](self) for more information.
92pub struct DisplayMap {
93 /// The buffer that we are displaying.
94 buffer: Model<MultiBuffer>,
95 buffer_subscription: BufferSubscription,
96 /// Decides where the [`Inlay`]s should be displayed.
97 inlay_map: InlayMap,
98 /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
99 fold_map: FoldMap,
100 /// Keeps track of hard tabs in a buffer.
101 tab_map: TabMap,
102 /// Handles soft wrapping.
103 wrap_map: Model<WrapMap>,
104 /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
105 block_map: BlockMap,
106 /// Regions of text that should be highlighted.
107 text_highlights: TextHighlights,
108 /// Regions of inlays that should be highlighted.
109 inlay_highlights: InlayHighlights,
110 /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
111 crease_map: CreaseMap,
112 pub(crate) fold_placeholder: FoldPlaceholder,
113 pub clip_at_line_ends: bool,
114 pub(crate) masked: bool,
115}
116
117impl DisplayMap {
118 #[allow(clippy::too_many_arguments)]
119 pub fn new(
120 buffer: Model<MultiBuffer>,
121 font: Font,
122 font_size: Pixels,
123 wrap_width: Option<Pixels>,
124 show_excerpt_controls: bool,
125 buffer_header_height: u32,
126 excerpt_header_height: u32,
127 excerpt_footer_height: u32,
128 fold_placeholder: FoldPlaceholder,
129 cx: &mut ModelContext<Self>,
130 ) -> Self {
131 let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
132
133 let tab_size = Self::tab_size(&buffer, cx);
134 let buffer_snapshot = buffer.read(cx).snapshot(cx);
135 let crease_map = CreaseMap::new(&buffer_snapshot);
136 let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
137 let (fold_map, snapshot) = FoldMap::new(snapshot);
138 let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
139 let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
140 let block_map = BlockMap::new(
141 snapshot,
142 show_excerpt_controls,
143 buffer_header_height,
144 excerpt_header_height,
145 excerpt_footer_height,
146 );
147
148 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
149
150 DisplayMap {
151 buffer,
152 buffer_subscription,
153 fold_map,
154 inlay_map,
155 tab_map,
156 wrap_map,
157 block_map,
158 crease_map,
159 fold_placeholder,
160 text_highlights: Default::default(),
161 inlay_highlights: Default::default(),
162 clip_at_line_ends: false,
163 masked: false,
164 }
165 }
166
167 pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
168 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
169 let edits = self.buffer_subscription.consume().into_inner();
170 let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
171 let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
172 let tab_size = Self::tab_size(&self.buffer, cx);
173 let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
174 let (wrap_snapshot, edits) = self
175 .wrap_map
176 .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
177 let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
178
179 DisplaySnapshot {
180 buffer_snapshot: self.buffer.read(cx).snapshot(cx),
181 fold_snapshot,
182 inlay_snapshot,
183 tab_snapshot,
184 wrap_snapshot,
185 block_snapshot,
186 crease_snapshot: self.crease_map.snapshot(),
187 text_highlights: self.text_highlights.clone(),
188 inlay_highlights: self.inlay_highlights.clone(),
189 clip_at_line_ends: self.clip_at_line_ends,
190 masked: self.masked,
191 fold_placeholder: self.fold_placeholder.clone(),
192 }
193 }
194
195 pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
196 self.fold(
197 other
198 .folds_in_range(0..other.buffer_snapshot.len())
199 .map(|fold| {
200 Crease::simple(
201 fold.range.to_offset(&other.buffer_snapshot),
202 fold.placeholder.clone(),
203 )
204 })
205 .collect(),
206 cx,
207 );
208 }
209
210 /// Creates folds for the given creases.
211 pub fn fold<T: Clone + ToOffset>(
212 &mut self,
213 creases: Vec<Crease<T>>,
214 cx: &mut ModelContext<Self>,
215 ) {
216 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
217 let edits = self.buffer_subscription.consume().into_inner();
218 let tab_size = Self::tab_size(&self.buffer, cx);
219 let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
220 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
221 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
222 let (snapshot, edits) = self
223 .wrap_map
224 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
225 self.block_map.read(snapshot, edits);
226
227 let inline = creases.iter().filter_map(|crease| {
228 if let Crease::Inline {
229 range, placeholder, ..
230 } = crease
231 {
232 Some((range.clone(), placeholder.clone()))
233 } else {
234 None
235 }
236 });
237 let (snapshot, edits) = fold_map.fold(inline);
238
239 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
240 let (snapshot, edits) = self
241 .wrap_map
242 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
243 let mut block_map = self.block_map.write(snapshot, edits);
244 let blocks = creases.into_iter().filter_map(|crease| {
245 if let Crease::Block {
246 range,
247 block_height,
248 render_block,
249 block_style,
250 block_priority,
251 ..
252 } = crease
253 {
254 Some((
255 range,
256 render_block,
257 block_height,
258 block_style,
259 block_priority,
260 ))
261 } else {
262 None
263 }
264 });
265 block_map.insert(
266 blocks
267 .into_iter()
268 .map(|(range, render, height, style, priority)| {
269 let start = buffer_snapshot.anchor_before(range.start);
270 let end = buffer_snapshot.anchor_after(range.end);
271 BlockProperties {
272 placement: BlockPlacement::Replace(start..=end),
273 render,
274 height,
275 style,
276 priority,
277 }
278 }),
279 );
280 }
281
282 /// Removes any folds with the given ranges.
283 pub fn remove_folds_with_type<T: ToOffset>(
284 &mut self,
285 ranges: impl IntoIterator<Item = Range<T>>,
286 type_id: TypeId,
287 cx: &mut ModelContext<Self>,
288 ) {
289 let snapshot = self.buffer.read(cx).snapshot(cx);
290 let edits = self.buffer_subscription.consume().into_inner();
291 let tab_size = Self::tab_size(&self.buffer, cx);
292 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
293 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
294 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
295 let (snapshot, edits) = self
296 .wrap_map
297 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
298 self.block_map.read(snapshot, edits);
299 let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
300 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
301 let (snapshot, edits) = self
302 .wrap_map
303 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
304 self.block_map.write(snapshot, edits);
305 }
306
307 /// Removes any folds whose ranges intersect any of the given ranges.
308 pub fn unfold_intersecting<T: ToOffset>(
309 &mut self,
310 ranges: impl IntoIterator<Item = Range<T>>,
311 inclusive: bool,
312 cx: &mut ModelContext<Self>,
313 ) {
314 let snapshot = self.buffer.read(cx).snapshot(cx);
315 let offset_ranges = ranges
316 .into_iter()
317 .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
318 .collect::<Vec<_>>();
319 let edits = self.buffer_subscription.consume().into_inner();
320 let tab_size = Self::tab_size(&self.buffer, cx);
321 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
322 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
323 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
324 let (snapshot, edits) = self
325 .wrap_map
326 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
327 self.block_map.read(snapshot, edits);
328
329 let (snapshot, edits) =
330 fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
331 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
332 let (snapshot, edits) = self
333 .wrap_map
334 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
335 let mut block_map = self.block_map.write(snapshot, edits);
336 block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
337 }
338
339 pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut ModelContext<Self>) {
340 let snapshot = self.buffer.read(cx).snapshot(cx);
341 let edits = self.buffer_subscription.consume().into_inner();
342 let tab_size = Self::tab_size(&self.buffer, cx);
343 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
344 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
345 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
346 let (snapshot, edits) = self
347 .wrap_map
348 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
349 let mut block_map = self.block_map.write(snapshot, edits);
350 block_map.fold_buffer(buffer_id, self.buffer.read(cx), cx)
351 }
352
353 pub fn unfold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut ModelContext<Self>) {
354 let snapshot = self.buffer.read(cx).snapshot(cx);
355 let edits = self.buffer_subscription.consume().into_inner();
356 let tab_size = Self::tab_size(&self.buffer, cx);
357 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
358 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
359 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
360 let (snapshot, edits) = self
361 .wrap_map
362 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
363 let mut block_map = self.block_map.write(snapshot, edits);
364 block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx)
365 }
366
367 pub(crate) fn buffer_folded(&self, buffer_id: language::BufferId) -> bool {
368 self.block_map.folded_buffers.contains(&buffer_id)
369 }
370
371 pub fn insert_creases(
372 &mut self,
373 creases: impl IntoIterator<Item = Crease<Anchor>>,
374 cx: &mut ModelContext<Self>,
375 ) -> Vec<CreaseId> {
376 let snapshot = self.buffer.read(cx).snapshot(cx);
377 self.crease_map.insert(creases, &snapshot)
378 }
379
380 pub fn remove_creases(
381 &mut self,
382 crease_ids: impl IntoIterator<Item = CreaseId>,
383 cx: &mut ModelContext<Self>,
384 ) {
385 let snapshot = self.buffer.read(cx).snapshot(cx);
386 self.crease_map.remove(crease_ids, &snapshot)
387 }
388
389 pub fn insert_blocks(
390 &mut self,
391 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
392 cx: &mut ModelContext<Self>,
393 ) -> Vec<CustomBlockId> {
394 let snapshot = self.buffer.read(cx).snapshot(cx);
395 let edits = self.buffer_subscription.consume().into_inner();
396 let tab_size = Self::tab_size(&self.buffer, cx);
397 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
398 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
399 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
400 let (snapshot, edits) = self
401 .wrap_map
402 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
403 let mut block_map = self.block_map.write(snapshot, edits);
404 block_map.insert(blocks)
405 }
406
407 pub fn resize_blocks(
408 &mut self,
409 heights: HashMap<CustomBlockId, u32>,
410 cx: &mut ModelContext<Self>,
411 ) {
412 let snapshot = self.buffer.read(cx).snapshot(cx);
413 let edits = self.buffer_subscription.consume().into_inner();
414 let tab_size = Self::tab_size(&self.buffer, cx);
415 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
416 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
417 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
418 let (snapshot, edits) = self
419 .wrap_map
420 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
421 let mut block_map = self.block_map.write(snapshot, edits);
422 block_map.resize(heights);
423 }
424
425 pub fn replace_blocks(&mut self, renderers: HashMap<CustomBlockId, RenderBlock>) {
426 self.block_map.replace_blocks(renderers);
427 }
428
429 pub fn remove_blocks(&mut self, ids: HashSet<CustomBlockId>, cx: &mut ModelContext<Self>) {
430 let snapshot = self.buffer.read(cx).snapshot(cx);
431 let edits = self.buffer_subscription.consume().into_inner();
432 let tab_size = Self::tab_size(&self.buffer, cx);
433 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
434 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
435 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
436 let (snapshot, edits) = self
437 .wrap_map
438 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
439 let mut block_map = self.block_map.write(snapshot, edits);
440 block_map.remove(ids);
441 }
442
443 pub fn row_for_block(
444 &mut self,
445 block_id: CustomBlockId,
446 cx: &mut ModelContext<Self>,
447 ) -> Option<DisplayRow> {
448 let snapshot = self.buffer.read(cx).snapshot(cx);
449 let edits = self.buffer_subscription.consume().into_inner();
450 let tab_size = Self::tab_size(&self.buffer, cx);
451 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
452 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
453 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
454 let (snapshot, edits) = self
455 .wrap_map
456 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
457 let block_map = self.block_map.read(snapshot, edits);
458 let block_row = block_map.row_for_block(block_id)?;
459 Some(DisplayRow(block_row.0))
460 }
461
462 pub fn highlight_text(
463 &mut self,
464 type_id: TypeId,
465 ranges: Vec<Range<Anchor>>,
466 style: HighlightStyle,
467 ) {
468 self.text_highlights
469 .insert(type_id, Arc::new((style, ranges)));
470 }
471
472 pub(crate) fn highlight_inlays(
473 &mut self,
474 type_id: TypeId,
475 highlights: Vec<InlayHighlight>,
476 style: HighlightStyle,
477 ) {
478 for highlight in highlights {
479 let update = self.inlay_highlights.update(&type_id, |highlights| {
480 highlights.insert(highlight.inlay, (style, highlight.clone()))
481 });
482 if update.is_none() {
483 self.inlay_highlights.insert(
484 type_id,
485 TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
486 );
487 }
488 }
489 }
490
491 pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
492 let highlights = self.text_highlights.get(&type_id)?;
493 Some((highlights.0, &highlights.1))
494 }
495 pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
496 let mut cleared = self.text_highlights.remove(&type_id).is_some();
497 cleared |= self.inlay_highlights.remove(&type_id).is_some();
498 cleared
499 }
500
501 pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
502 self.wrap_map
503 .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
504 }
505
506 pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
507 self.wrap_map
508 .update(cx, |map, cx| map.set_wrap_width(width, cx))
509 }
510
511 pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
512 self.inlay_map.current_inlays()
513 }
514
515 pub(crate) fn splice_inlays(
516 &mut self,
517 to_remove: Vec<InlayId>,
518 to_insert: Vec<Inlay>,
519 cx: &mut ModelContext<Self>,
520 ) {
521 if to_remove.is_empty() && to_insert.is_empty() {
522 return;
523 }
524 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
525 let edits = self.buffer_subscription.consume().into_inner();
526 let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
527 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
528 let tab_size = Self::tab_size(&self.buffer, cx);
529 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
530 let (snapshot, edits) = self
531 .wrap_map
532 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
533 self.block_map.read(snapshot, edits);
534
535 let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
536 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
537 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
538 let (snapshot, edits) = self
539 .wrap_map
540 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
541 self.block_map.read(snapshot, edits);
542 }
543
544 fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
545 let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
546 let language = buffer
547 .and_then(|buffer| buffer.language())
548 .map(|l| l.name());
549 let file = buffer.and_then(|buffer| buffer.file());
550 language_settings(language, file, cx).tab_size
551 }
552
553 #[cfg(test)]
554 pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
555 self.wrap_map.read(cx).is_rewrapping()
556 }
557
558 pub fn show_excerpt_controls(&self) -> bool {
559 self.block_map.show_excerpt_controls()
560 }
561}
562
563#[derive(Debug, Default)]
564pub(crate) struct Highlights<'a> {
565 pub text_highlights: Option<&'a TextHighlights>,
566 pub inlay_highlights: Option<&'a InlayHighlights>,
567 pub styles: HighlightStyles,
568}
569
570#[derive(Clone, Copy, Debug)]
571pub struct InlineCompletionStyles {
572 pub insertion: HighlightStyle,
573 pub whitespace: HighlightStyle,
574}
575
576#[derive(Default, Debug, Clone, Copy)]
577pub struct HighlightStyles {
578 pub inlay_hint: Option<HighlightStyle>,
579 pub inline_completion: Option<InlineCompletionStyles>,
580}
581
582#[derive(Clone)]
583pub enum ChunkReplacement {
584 Renderer(ChunkRenderer),
585 Str(SharedString),
586}
587
588pub struct HighlightedChunk<'a> {
589 pub text: &'a str,
590 pub style: Option<HighlightStyle>,
591 pub is_tab: bool,
592 pub replacement: Option<ChunkReplacement>,
593}
594
595impl<'a> HighlightedChunk<'a> {
596 fn highlight_invisibles(
597 self,
598 editor_style: &'a EditorStyle,
599 ) -> impl Iterator<Item = Self> + 'a {
600 let mut chars = self.text.chars().peekable();
601 let mut text = self.text;
602 let style = self.style;
603 let is_tab = self.is_tab;
604 let renderer = self.replacement;
605 iter::from_fn(move || {
606 let mut prefix_len = 0;
607 while let Some(&ch) = chars.peek() {
608 if !is_invisible(ch) {
609 prefix_len += ch.len_utf8();
610 chars.next();
611 continue;
612 }
613 if prefix_len > 0 {
614 let (prefix, suffix) = text.split_at(prefix_len);
615 text = suffix;
616 return Some(HighlightedChunk {
617 text: prefix,
618 style,
619 is_tab,
620 replacement: renderer.clone(),
621 });
622 }
623 chars.next();
624 let (prefix, suffix) = text.split_at(ch.len_utf8());
625 text = suffix;
626 if let Some(replacement) = replacement(ch) {
627 let invisible_highlight = HighlightStyle {
628 background_color: Some(editor_style.status.hint_background),
629 underline: Some(UnderlineStyle {
630 color: Some(editor_style.status.hint),
631 thickness: px(1.),
632 wavy: false,
633 }),
634 ..Default::default()
635 };
636 let invisible_style = if let Some(mut style) = style {
637 style.highlight(invisible_highlight);
638 style
639 } else {
640 invisible_highlight
641 };
642 return Some(HighlightedChunk {
643 text: prefix,
644 style: Some(invisible_style),
645 is_tab: false,
646 replacement: Some(ChunkReplacement::Str(replacement.into())),
647 });
648 } else {
649 let invisible_highlight = HighlightStyle {
650 background_color: Some(editor_style.status.hint_background),
651 underline: Some(UnderlineStyle {
652 color: Some(editor_style.status.hint),
653 thickness: px(1.),
654 wavy: false,
655 }),
656 ..Default::default()
657 };
658 let invisible_style = if let Some(mut style) = style {
659 style.highlight(invisible_highlight);
660 style
661 } else {
662 invisible_highlight
663 };
664
665 return Some(HighlightedChunk {
666 text: prefix,
667 style: Some(invisible_style),
668 is_tab: false,
669 replacement: renderer.clone(),
670 });
671 }
672 }
673
674 if !text.is_empty() {
675 let remainder = text;
676 text = "";
677 Some(HighlightedChunk {
678 text: remainder,
679 style,
680 is_tab,
681 replacement: renderer.clone(),
682 })
683 } else {
684 None
685 }
686 })
687 }
688}
689
690#[derive(Clone)]
691pub struct DisplaySnapshot {
692 pub buffer_snapshot: MultiBufferSnapshot,
693 pub fold_snapshot: FoldSnapshot,
694 pub crease_snapshot: CreaseSnapshot,
695 inlay_snapshot: InlaySnapshot,
696 tab_snapshot: TabSnapshot,
697 wrap_snapshot: WrapSnapshot,
698 block_snapshot: BlockSnapshot,
699 text_highlights: TextHighlights,
700 inlay_highlights: InlayHighlights,
701 clip_at_line_ends: bool,
702 masked: bool,
703 pub(crate) fold_placeholder: FoldPlaceholder,
704}
705
706impl DisplaySnapshot {
707 #[cfg(test)]
708 pub fn fold_count(&self) -> usize {
709 self.fold_snapshot.fold_count()
710 }
711
712 pub fn is_empty(&self) -> bool {
713 self.buffer_snapshot.len() == 0
714 }
715
716 pub fn buffer_rows(
717 &self,
718 start_row: DisplayRow,
719 ) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
720 self.block_snapshot
721 .buffer_rows(BlockRow(start_row.0))
722 .map(|row| row.map(MultiBufferRow))
723 }
724
725 pub fn widest_line_number(&self) -> u32 {
726 self.buffer_snapshot.widest_line_number()
727 }
728
729 pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
730 loop {
731 let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
732 let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
733 fold_point.0.column = 0;
734 inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
735 point = self.inlay_snapshot.to_buffer_point(inlay_point);
736
737 let mut display_point = self.point_to_display_point(point, Bias::Left);
738 *display_point.column_mut() = 0;
739 let next_point = self.display_point_to_point(display_point, Bias::Left);
740 if next_point == point {
741 return (point, display_point);
742 }
743 point = next_point;
744 }
745 }
746
747 pub fn next_line_boundary(
748 &self,
749 mut point: MultiBufferPoint,
750 ) -> (MultiBufferPoint, DisplayPoint) {
751 let original_point = point;
752 loop {
753 let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
754 let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
755 fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
756 inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
757 point = self.inlay_snapshot.to_buffer_point(inlay_point);
758
759 let mut display_point = self.point_to_display_point(point, Bias::Right);
760 *display_point.column_mut() = self.line_len(display_point.row());
761 let next_point = self.display_point_to_point(display_point, Bias::Right);
762 if next_point == point || original_point == point || original_point == next_point {
763 return (point, display_point);
764 }
765 point = next_point;
766 }
767 }
768
769 // used by line_mode selections and tries to match vim behavior
770 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
771 let max_row = self.buffer_snapshot.max_row().0;
772 let new_start = if range.start.row == 0 {
773 MultiBufferPoint::new(0, 0)
774 } else if range.start.row == max_row || (range.end.column > 0 && range.end.row == max_row) {
775 MultiBufferPoint::new(
776 range.start.row - 1,
777 self.buffer_snapshot
778 .line_len(MultiBufferRow(range.start.row - 1)),
779 )
780 } else {
781 self.prev_line_boundary(range.start).0
782 };
783
784 let new_end = if range.end.column == 0 {
785 range.end
786 } else if range.end.row < max_row {
787 self.buffer_snapshot
788 .clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
789 } else {
790 self.buffer_snapshot.max_point()
791 };
792
793 new_start..new_end
794 }
795
796 pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
797 let inlay_point = self.inlay_snapshot.to_inlay_point(point);
798 let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
799 let tab_point = self.tab_snapshot.to_tab_point(fold_point);
800 let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
801 let block_point = self.block_snapshot.to_block_point(wrap_point);
802 DisplayPoint(block_point)
803 }
804
805 pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
806 self.inlay_snapshot
807 .to_buffer_point(self.display_point_to_inlay_point(point, bias))
808 }
809
810 pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
811 self.inlay_snapshot
812 .to_offset(self.display_point_to_inlay_point(point, bias))
813 }
814
815 pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
816 self.inlay_snapshot
817 .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
818 }
819
820 pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
821 self.buffer_snapshot
822 .anchor_at(point.to_offset(self, bias), bias)
823 }
824
825 fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
826 let block_point = point.0;
827 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
828 let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
829 let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
830 fold_point.to_inlay_point(&self.fold_snapshot)
831 }
832
833 pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
834 let block_point = point.0;
835 let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
836 let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
837 self.tab_snapshot.to_fold_point(tab_point, bias).0
838 }
839
840 pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
841 let tab_point = self.tab_snapshot.to_tab_point(fold_point);
842 let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
843 let block_point = self.block_snapshot.to_block_point(wrap_point);
844 DisplayPoint(block_point)
845 }
846
847 pub fn max_point(&self) -> DisplayPoint {
848 DisplayPoint(self.block_snapshot.max_point())
849 }
850
851 /// Returns text chunks starting at the given display row until the end of the file
852 pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
853 self.block_snapshot
854 .chunks(
855 display_row.0..self.max_point().row().next_row().0,
856 false,
857 self.masked,
858 Highlights::default(),
859 )
860 .map(|h| h.text)
861 }
862
863 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
864 pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
865 (0..=display_row.0).rev().flat_map(move |row| {
866 self.block_snapshot
867 .chunks(row..row + 1, false, self.masked, Highlights::default())
868 .map(|h| h.text)
869 .collect::<Vec<_>>()
870 .into_iter()
871 .rev()
872 })
873 }
874
875 pub fn chunks(
876 &self,
877 display_rows: Range<DisplayRow>,
878 language_aware: bool,
879 highlight_styles: HighlightStyles,
880 ) -> DisplayChunks<'_> {
881 self.block_snapshot.chunks(
882 display_rows.start.0..display_rows.end.0,
883 language_aware,
884 self.masked,
885 Highlights {
886 text_highlights: Some(&self.text_highlights),
887 inlay_highlights: Some(&self.inlay_highlights),
888 styles: highlight_styles,
889 },
890 )
891 }
892
893 pub fn highlighted_chunks<'a>(
894 &'a self,
895 display_rows: Range<DisplayRow>,
896 language_aware: bool,
897 editor_style: &'a EditorStyle,
898 ) -> impl Iterator<Item = HighlightedChunk<'a>> {
899 self.chunks(
900 display_rows,
901 language_aware,
902 HighlightStyles {
903 inlay_hint: Some(editor_style.inlay_hints_style),
904 inline_completion: Some(editor_style.inline_completion_styles),
905 },
906 )
907 .flat_map(|chunk| {
908 let mut highlight_style = chunk
909 .syntax_highlight_id
910 .and_then(|id| id.style(&editor_style.syntax));
911
912 if let Some(chunk_highlight) = chunk.highlight_style {
913 if let Some(highlight_style) = highlight_style.as_mut() {
914 highlight_style.highlight(chunk_highlight);
915 } else {
916 highlight_style = Some(chunk_highlight);
917 }
918 }
919
920 let mut diagnostic_highlight = HighlightStyle::default();
921
922 if chunk.is_unnecessary {
923 diagnostic_highlight.fade_out = Some(editor_style.unnecessary_code_fade);
924 }
925
926 if let Some(severity) = chunk.diagnostic_severity {
927 // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
928 if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
929 let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
930 diagnostic_highlight.underline = Some(UnderlineStyle {
931 color: Some(diagnostic_color),
932 thickness: 1.0.into(),
933 wavy: true,
934 });
935 }
936 }
937
938 if let Some(highlight_style) = highlight_style.as_mut() {
939 highlight_style.highlight(diagnostic_highlight);
940 } else {
941 highlight_style = Some(diagnostic_highlight);
942 }
943
944 HighlightedChunk {
945 text: chunk.text,
946 style: highlight_style,
947 is_tab: chunk.is_tab,
948 replacement: chunk.renderer.map(ChunkReplacement::Renderer),
949 }
950 .highlight_invisibles(editor_style)
951 })
952 }
953
954 pub fn layout_row(
955 &self,
956 display_row: DisplayRow,
957 TextLayoutDetails {
958 text_system,
959 editor_style,
960 rem_size,
961 scroll_anchor: _,
962 visible_rows: _,
963 vertical_scroll_margin: _,
964 }: &TextLayoutDetails,
965 ) -> Arc<LineLayout> {
966 let mut runs = Vec::new();
967 let mut line = String::new();
968
969 let range = display_row..display_row.next_row();
970 for chunk in self.highlighted_chunks(range, false, editor_style) {
971 line.push_str(chunk.text);
972
973 let text_style = if let Some(style) = chunk.style {
974 Cow::Owned(editor_style.text.clone().highlight(style))
975 } else {
976 Cow::Borrowed(&editor_style.text)
977 };
978
979 runs.push(text_style.to_run(chunk.text.len()))
980 }
981
982 if line.ends_with('\n') {
983 line.pop();
984 if let Some(last_run) = runs.last_mut() {
985 last_run.len -= 1;
986 if last_run.len == 0 {
987 runs.pop();
988 }
989 }
990 }
991
992 let font_size = editor_style.text.font_size.to_pixels(*rem_size);
993 text_system
994 .layout_line(&line, font_size, &runs)
995 .expect("we expect the font to be loaded because it's rendered by the editor")
996 }
997
998 pub fn x_for_display_point(
999 &self,
1000 display_point: DisplayPoint,
1001 text_layout_details: &TextLayoutDetails,
1002 ) -> Pixels {
1003 let line = self.layout_row(display_point.row(), text_layout_details);
1004 line.x_for_index(display_point.column() as usize)
1005 }
1006
1007 pub fn display_column_for_x(
1008 &self,
1009 display_row: DisplayRow,
1010 x: Pixels,
1011 details: &TextLayoutDetails,
1012 ) -> u32 {
1013 let layout_line = self.layout_row(display_row, details);
1014 layout_line.closest_index_for_x(x) as u32
1015 }
1016
1017 pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
1018 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
1019 let chars = self
1020 .text_chunks(point.row())
1021 .flat_map(str::chars)
1022 .skip_while({
1023 let mut column = 0;
1024 move |char| {
1025 let at_point = column >= point.column();
1026 column += char.len_utf8() as u32;
1027 !at_point
1028 }
1029 })
1030 .take_while({
1031 let mut prev = false;
1032 move |char| {
1033 let now = char.is_ascii();
1034 let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
1035 prev = now;
1036 !end
1037 }
1038 });
1039 chars.collect::<String>().graphemes(true).next().map(|s| {
1040 if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
1041 replacement(invisible).unwrap_or(s).to_owned().into()
1042 } else if s == "\n" {
1043 " ".into()
1044 } else {
1045 s.to_owned().into()
1046 }
1047 })
1048 }
1049
1050 pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
1051 self.buffer_snapshot.chars_at(offset).map(move |ch| {
1052 let ret = (ch, offset);
1053 offset += ch.len_utf8();
1054 ret
1055 })
1056 }
1057
1058 pub fn reverse_buffer_chars_at(
1059 &self,
1060 mut offset: usize,
1061 ) -> impl Iterator<Item = (char, usize)> + '_ {
1062 self.buffer_snapshot
1063 .reversed_chars_at(offset)
1064 .map(move |ch| {
1065 offset -= ch.len_utf8();
1066 (ch, offset)
1067 })
1068 }
1069
1070 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
1071 let mut clipped = self.block_snapshot.clip_point(point.0, bias);
1072 if self.clip_at_line_ends {
1073 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
1074 }
1075 DisplayPoint(clipped)
1076 }
1077
1078 pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
1079 DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
1080 }
1081
1082 pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
1083 let mut point = point.0;
1084 if point.column == self.line_len(DisplayRow(point.row)) {
1085 point.column = point.column.saturating_sub(1);
1086 point = self.block_snapshot.clip_point(point, Bias::Left);
1087 }
1088 DisplayPoint(point)
1089 }
1090
1091 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
1092 where
1093 T: ToOffset,
1094 {
1095 self.fold_snapshot.folds_in_range(range)
1096 }
1097
1098 pub fn blocks_in_range(
1099 &self,
1100 rows: Range<DisplayRow>,
1101 ) -> impl Iterator<Item = (DisplayRow, &Block)> {
1102 self.block_snapshot
1103 .blocks_in_range(rows.start.0..rows.end.0)
1104 .map(|(row, block)| (DisplayRow(row), block))
1105 }
1106
1107 pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
1108 self.block_snapshot.block_for_id(id)
1109 }
1110
1111 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
1112 self.fold_snapshot.intersects_fold(offset)
1113 }
1114
1115 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
1116 self.block_snapshot.is_line_replaced(buffer_row)
1117 || self.fold_snapshot.is_line_folded(buffer_row)
1118 }
1119
1120 pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
1121 self.block_snapshot.is_block_line(BlockRow(display_row.0))
1122 }
1123
1124 pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
1125 let wrap_row = self
1126 .block_snapshot
1127 .to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
1128 .row();
1129 self.wrap_snapshot.soft_wrap_indent(wrap_row)
1130 }
1131
1132 pub fn text(&self) -> String {
1133 self.text_chunks(DisplayRow(0)).collect()
1134 }
1135
1136 pub fn line(&self, display_row: DisplayRow) -> String {
1137 let mut result = String::new();
1138 for chunk in self.text_chunks(display_row) {
1139 if let Some(ix) = chunk.find('\n') {
1140 result.push_str(&chunk[0..ix]);
1141 break;
1142 } else {
1143 result.push_str(chunk);
1144 }
1145 }
1146 result
1147 }
1148
1149 pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
1150 let (buffer, range) = self
1151 .buffer_snapshot
1152 .buffer_line_for_row(buffer_row)
1153 .unwrap();
1154
1155 buffer.line_indent_for_row(range.start.row)
1156 }
1157
1158 pub fn line_len(&self, row: DisplayRow) -> u32 {
1159 self.block_snapshot.line_len(BlockRow(row.0))
1160 }
1161
1162 pub fn longest_row(&self) -> DisplayRow {
1163 DisplayRow(self.block_snapshot.longest_row())
1164 }
1165
1166 pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
1167 let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
1168 let longest_row = self.block_snapshot.longest_row_in_range(block_range);
1169 DisplayRow(longest_row.0)
1170 }
1171
1172 pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
1173 let max_row = self.buffer_snapshot.max_row();
1174 if buffer_row >= max_row {
1175 return false;
1176 }
1177
1178 let line_indent = self.line_indent_for_buffer_row(buffer_row);
1179 if line_indent.is_line_blank() {
1180 return false;
1181 }
1182
1183 (buffer_row.0 + 1..=max_row.0)
1184 .find_map(|next_row| {
1185 let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
1186 if next_line_indent.raw_len() > line_indent.raw_len() {
1187 Some(true)
1188 } else if !next_line_indent.is_line_blank() {
1189 Some(false)
1190 } else {
1191 None
1192 }
1193 })
1194 .unwrap_or(false)
1195 }
1196
1197 pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
1198 let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
1199 if let Some(crease) = self
1200 .crease_snapshot
1201 .query_row(buffer_row, &self.buffer_snapshot)
1202 {
1203 match crease {
1204 Crease::Inline {
1205 range,
1206 placeholder,
1207 render_toggle,
1208 render_trailer,
1209 metadata,
1210 } => Some(Crease::Inline {
1211 range: range.to_point(&self.buffer_snapshot),
1212 placeholder: placeholder.clone(),
1213 render_toggle: render_toggle.clone(),
1214 render_trailer: render_trailer.clone(),
1215 metadata: metadata.clone(),
1216 }),
1217 Crease::Block {
1218 range,
1219 block_height,
1220 block_style,
1221 render_block,
1222 block_priority,
1223 render_toggle,
1224 } => Some(Crease::Block {
1225 range: range.to_point(&self.buffer_snapshot),
1226 block_height: *block_height,
1227 block_style: *block_style,
1228 render_block: render_block.clone(),
1229 block_priority: *block_priority,
1230 render_toggle: render_toggle.clone(),
1231 }),
1232 }
1233 } else if self.starts_indent(MultiBufferRow(start.row))
1234 && !self.is_line_folded(MultiBufferRow(start.row))
1235 {
1236 let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
1237 let max_point = self.buffer_snapshot.max_point();
1238 let mut end = None;
1239
1240 for row in (buffer_row.0 + 1)..=max_point.row {
1241 let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
1242 if !line_indent.is_line_blank()
1243 && line_indent.raw_len() <= start_line_indent.raw_len()
1244 {
1245 let prev_row = row - 1;
1246 end = Some(Point::new(
1247 prev_row,
1248 self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
1249 ));
1250 break;
1251 }
1252 }
1253
1254 let mut row_before_line_breaks = end.unwrap_or(max_point);
1255 while row_before_line_breaks.row > start.row
1256 && self
1257 .buffer_snapshot
1258 .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
1259 {
1260 row_before_line_breaks.row -= 1;
1261 }
1262
1263 row_before_line_breaks = Point::new(
1264 row_before_line_breaks.row,
1265 self.buffer_snapshot
1266 .line_len(MultiBufferRow(row_before_line_breaks.row)),
1267 );
1268
1269 Some(Crease::Inline {
1270 range: start..row_before_line_breaks,
1271 placeholder: self.fold_placeholder.clone(),
1272 render_toggle: None,
1273 render_trailer: None,
1274 metadata: None,
1275 })
1276 } else {
1277 None
1278 }
1279 }
1280
1281 #[cfg(any(test, feature = "test-support"))]
1282 pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
1283 &self,
1284 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
1285 let type_id = TypeId::of::<Tag>();
1286 self.text_highlights.get(&type_id).cloned()
1287 }
1288
1289 #[allow(unused)]
1290 #[cfg(any(test, feature = "test-support"))]
1291 pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
1292 &self,
1293 ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
1294 let type_id = TypeId::of::<Tag>();
1295 self.inlay_highlights.get(&type_id)
1296 }
1297
1298 pub fn buffer_header_height(&self) -> u32 {
1299 self.block_snapshot.buffer_header_height
1300 }
1301
1302 pub fn excerpt_footer_height(&self) -> u32 {
1303 self.block_snapshot.excerpt_footer_height
1304 }
1305
1306 pub fn excerpt_header_height(&self) -> u32 {
1307 self.block_snapshot.excerpt_header_height
1308 }
1309}
1310
1311#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
1312pub struct DisplayPoint(BlockPoint);
1313
1314impl Debug for DisplayPoint {
1315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1316 f.write_fmt(format_args!(
1317 "DisplayPoint({}, {})",
1318 self.row().0,
1319 self.column()
1320 ))
1321 }
1322}
1323
1324impl Add for DisplayPoint {
1325 type Output = Self;
1326
1327 fn add(self, other: Self) -> Self::Output {
1328 DisplayPoint(BlockPoint(self.0 .0 + other.0 .0))
1329 }
1330}
1331
1332impl Sub for DisplayPoint {
1333 type Output = Self;
1334
1335 fn sub(self, other: Self) -> Self::Output {
1336 DisplayPoint(BlockPoint(self.0 .0 - other.0 .0))
1337 }
1338}
1339
1340#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
1341#[serde(transparent)]
1342pub struct DisplayRow(pub u32);
1343
1344impl Add<DisplayRow> for DisplayRow {
1345 type Output = Self;
1346
1347 fn add(self, other: Self) -> Self::Output {
1348 DisplayRow(self.0 + other.0)
1349 }
1350}
1351
1352impl Add<u32> for DisplayRow {
1353 type Output = Self;
1354
1355 fn add(self, other: u32) -> Self::Output {
1356 DisplayRow(self.0 + other)
1357 }
1358}
1359
1360impl Sub<DisplayRow> for DisplayRow {
1361 type Output = Self;
1362
1363 fn sub(self, other: Self) -> Self::Output {
1364 DisplayRow(self.0 - other.0)
1365 }
1366}
1367
1368impl Sub<u32> for DisplayRow {
1369 type Output = Self;
1370
1371 fn sub(self, other: u32) -> Self::Output {
1372 DisplayRow(self.0 - other)
1373 }
1374}
1375
1376impl DisplayPoint {
1377 pub fn new(row: DisplayRow, column: u32) -> Self {
1378 Self(BlockPoint(Point::new(row.0, column)))
1379 }
1380
1381 pub fn zero() -> Self {
1382 Self::new(DisplayRow(0), 0)
1383 }
1384
1385 pub fn is_zero(&self) -> bool {
1386 self.0.is_zero()
1387 }
1388
1389 pub fn row(self) -> DisplayRow {
1390 DisplayRow(self.0.row)
1391 }
1392
1393 pub fn column(self) -> u32 {
1394 self.0.column
1395 }
1396
1397 pub fn row_mut(&mut self) -> &mut u32 {
1398 &mut self.0.row
1399 }
1400
1401 pub fn column_mut(&mut self) -> &mut u32 {
1402 &mut self.0.column
1403 }
1404
1405 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1406 map.display_point_to_point(self, Bias::Left)
1407 }
1408
1409 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1410 let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
1411 let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
1412 let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
1413 let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
1414 map.inlay_snapshot
1415 .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
1416 }
1417}
1418
1419impl ToDisplayPoint for usize {
1420 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1421 map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
1422 }
1423}
1424
1425impl ToDisplayPoint for OffsetUtf16 {
1426 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1427 self.to_offset(&map.buffer_snapshot).to_display_point(map)
1428 }
1429}
1430
1431impl ToDisplayPoint for Point {
1432 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1433 map.point_to_display_point(*self, Bias::Left)
1434 }
1435}
1436
1437impl ToDisplayPoint for Anchor {
1438 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1439 self.to_point(&map.buffer_snapshot).to_display_point(map)
1440 }
1441}
1442
1443#[cfg(test)]
1444pub mod tests {
1445 use super::*;
1446 use crate::{movement, test::marked_display_snapshot};
1447 use block_map::BlockPlacement;
1448 use gpui::{
1449 div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla, Rgba,
1450 };
1451 use language::{
1452 language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
1453 Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
1454 LanguageMatcher,
1455 };
1456 use lsp::LanguageServerId;
1457 use project::Project;
1458 use rand::{prelude::*, Rng};
1459 use settings::SettingsStore;
1460 use smol::stream::StreamExt;
1461 use std::{env, sync::Arc};
1462 use text::PointUtf16;
1463 use theme::{LoadThemes, SyntaxTheme};
1464 use unindent::Unindent as _;
1465 use util::test::{marked_text_ranges, sample_text};
1466 use Bias::*;
1467
1468 #[gpui::test(iterations = 100)]
1469 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1470 cx.background_executor.set_block_on_ticks(0..=50);
1471 let operations = env::var("OPERATIONS")
1472 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1473 .unwrap_or(10);
1474
1475 let mut tab_size = rng.gen_range(1..=4);
1476 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
1477 let excerpt_header_height = rng.gen_range(1..=5);
1478 let font_size = px(14.0);
1479 let max_wrap_width = 300.0;
1480 let mut wrap_width = if rng.gen_bool(0.1) {
1481 None
1482 } else {
1483 Some(px(rng.gen_range(0.0..=max_wrap_width)))
1484 };
1485
1486 log::info!("tab size: {}", tab_size);
1487 log::info!("wrap width: {:?}", wrap_width);
1488
1489 cx.update(|cx| {
1490 init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
1491 });
1492
1493 let buffer = cx.update(|cx| {
1494 if rng.gen() {
1495 let len = rng.gen_range(0..10);
1496 let text = util::RandomCharIter::new(&mut rng)
1497 .take(len)
1498 .collect::<String>();
1499 MultiBuffer::build_simple(&text, cx)
1500 } else {
1501 MultiBuffer::build_random(&mut rng, cx)
1502 }
1503 });
1504
1505 let map = cx.new_model(|cx| {
1506 DisplayMap::new(
1507 buffer.clone(),
1508 font("Helvetica"),
1509 font_size,
1510 wrap_width,
1511 true,
1512 buffer_start_excerpt_header_height,
1513 excerpt_header_height,
1514 0,
1515 FoldPlaceholder::test(),
1516 cx,
1517 )
1518 });
1519 let mut notifications = observe(&map, cx);
1520 let mut fold_count = 0;
1521 let mut blocks = Vec::new();
1522
1523 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1524 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1525 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1526 log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1527 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1528 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1529 log::info!("display text: {:?}", snapshot.text());
1530
1531 for _i in 0..operations {
1532 match rng.gen_range(0..100) {
1533 0..=19 => {
1534 wrap_width = if rng.gen_bool(0.2) {
1535 None
1536 } else {
1537 Some(px(rng.gen_range(0.0..=max_wrap_width)))
1538 };
1539 log::info!("setting wrap width to {:?}", wrap_width);
1540 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1541 }
1542 20..=29 => {
1543 let mut tab_sizes = vec![1, 2, 3, 4];
1544 tab_sizes.remove((tab_size - 1) as usize);
1545 tab_size = *tab_sizes.choose(&mut rng).unwrap();
1546 log::info!("setting tab size to {:?}", tab_size);
1547 cx.update(|cx| {
1548 cx.update_global::<SettingsStore, _>(|store, cx| {
1549 store.update_user_settings::<AllLanguageSettings>(cx, |s| {
1550 s.defaults.tab_size = NonZeroU32::new(tab_size);
1551 });
1552 });
1553 });
1554 }
1555 30..=44 => {
1556 map.update(cx, |map, cx| {
1557 if rng.gen() || blocks.is_empty() {
1558 let buffer = map.snapshot(cx).buffer_snapshot;
1559 let block_properties = (0..rng.gen_range(1..=1))
1560 .map(|_| {
1561 let position =
1562 buffer.anchor_after(buffer.clip_offset(
1563 rng.gen_range(0..=buffer.len()),
1564 Bias::Left,
1565 ));
1566
1567 let placement = if rng.gen() {
1568 BlockPlacement::Above(position)
1569 } else {
1570 BlockPlacement::Below(position)
1571 };
1572 let height = rng.gen_range(1..5);
1573 log::info!(
1574 "inserting block {:?} with height {}",
1575 placement.as_ref().map(|p| p.to_point(&buffer)),
1576 height
1577 );
1578 let priority = rng.gen_range(1..100);
1579 BlockProperties {
1580 placement,
1581 style: BlockStyle::Fixed,
1582 height,
1583 render: Arc::new(|_| div().into_any()),
1584 priority,
1585 }
1586 })
1587 .collect::<Vec<_>>();
1588 blocks.extend(map.insert_blocks(block_properties, cx));
1589 } else {
1590 blocks.shuffle(&mut rng);
1591 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
1592 let block_ids_to_remove = (0..remove_count)
1593 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
1594 .collect();
1595 log::info!("removing block ids {:?}", block_ids_to_remove);
1596 map.remove_blocks(block_ids_to_remove, cx);
1597 }
1598 });
1599 }
1600 45..=79 => {
1601 let mut ranges = Vec::new();
1602 for _ in 0..rng.gen_range(1..=3) {
1603 buffer.read_with(cx, |buffer, cx| {
1604 let buffer = buffer.read(cx);
1605 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1606 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1607 ranges.push(start..end);
1608 });
1609 }
1610
1611 if rng.gen() && fold_count > 0 {
1612 log::info!("unfolding ranges: {:?}", ranges);
1613 map.update(cx, |map, cx| {
1614 map.unfold_intersecting(ranges, true, cx);
1615 });
1616 } else {
1617 log::info!("folding ranges: {:?}", ranges);
1618 map.update(cx, |map, cx| {
1619 map.fold(
1620 ranges
1621 .into_iter()
1622 .map(|range| Crease::simple(range, FoldPlaceholder::test()))
1623 .collect(),
1624 cx,
1625 );
1626 });
1627 }
1628 }
1629 _ => {
1630 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1631 }
1632 }
1633
1634 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1635 notifications.next().await.unwrap();
1636 }
1637
1638 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1639 fold_count = snapshot.fold_count();
1640 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1641 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1642 log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1643 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1644 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1645 log::info!("display text: {:?}", snapshot.text());
1646
1647 // Line boundaries
1648 let buffer = &snapshot.buffer_snapshot;
1649 for _ in 0..5 {
1650 let row = rng.gen_range(0..=buffer.max_point().row);
1651 let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
1652 let point = buffer.clip_point(Point::new(row, column), Left);
1653
1654 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1655 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1656
1657 assert!(prev_buffer_bound <= point);
1658 assert!(next_buffer_bound >= point);
1659 assert_eq!(prev_buffer_bound.column, 0);
1660 assert_eq!(prev_display_bound.column(), 0);
1661 if next_buffer_bound < buffer.max_point() {
1662 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1663 }
1664
1665 assert_eq!(
1666 prev_display_bound,
1667 prev_buffer_bound.to_display_point(&snapshot),
1668 "row boundary before {:?}. reported buffer row boundary: {:?}",
1669 point,
1670 prev_buffer_bound
1671 );
1672 assert_eq!(
1673 next_display_bound,
1674 next_buffer_bound.to_display_point(&snapshot),
1675 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1676 point,
1677 next_buffer_bound
1678 );
1679 assert_eq!(
1680 prev_buffer_bound,
1681 prev_display_bound.to_point(&snapshot),
1682 "row boundary before {:?}. reported display row boundary: {:?}",
1683 point,
1684 prev_display_bound
1685 );
1686 assert_eq!(
1687 next_buffer_bound,
1688 next_display_bound.to_point(&snapshot),
1689 "row boundary after {:?}. reported display row boundary: {:?}",
1690 point,
1691 next_display_bound
1692 );
1693 }
1694
1695 // Movement
1696 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1697 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1698 for _ in 0..5 {
1699 let row = rng.gen_range(0..=snapshot.max_point().row().0);
1700 let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
1701 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1702
1703 log::info!("Moving from point {:?}", point);
1704
1705 let moved_right = movement::right(&snapshot, point);
1706 log::info!("Right {:?}", moved_right);
1707 if point < max_point {
1708 assert!(moved_right > point);
1709 if point.column() == snapshot.line_len(point.row())
1710 || snapshot.soft_wrap_indent(point.row()).is_some()
1711 && point.column() == snapshot.line_len(point.row()) - 1
1712 {
1713 assert!(moved_right.row() > point.row());
1714 }
1715 } else {
1716 assert_eq!(moved_right, point);
1717 }
1718
1719 let moved_left = movement::left(&snapshot, point);
1720 log::info!("Left {:?}", moved_left);
1721 if point > min_point {
1722 assert!(moved_left < point);
1723 if point.column() == 0 {
1724 assert!(moved_left.row() < point.row());
1725 }
1726 } else {
1727 assert_eq!(moved_left, point);
1728 }
1729 }
1730 }
1731 }
1732
1733 #[cfg(target_os = "macos")]
1734 #[gpui::test(retries = 5)]
1735 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1736 cx.background_executor
1737 .set_block_on_ticks(usize::MAX..=usize::MAX);
1738 cx.update(|cx| {
1739 init_test(cx, |_| {});
1740 });
1741
1742 let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
1743 let editor = cx.editor.clone();
1744 let window = cx.window;
1745
1746 _ = cx.update_window(window, |_, cx| {
1747 let text_layout_details =
1748 editor.update(cx, |editor, cx| editor.text_layout_details(cx));
1749
1750 let font_size = px(12.0);
1751 let wrap_width = Some(px(64.));
1752
1753 let text = "one two three four five\nsix seven eight";
1754 let buffer = MultiBuffer::build_simple(text, cx);
1755 let map = cx.new_model(|cx| {
1756 DisplayMap::new(
1757 buffer.clone(),
1758 font("Helvetica"),
1759 font_size,
1760 wrap_width,
1761 true,
1762 1,
1763 1,
1764 0,
1765 FoldPlaceholder::test(),
1766 cx,
1767 )
1768 });
1769
1770 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1771 assert_eq!(
1772 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1773 "one two \nthree four \nfive\nsix seven \neight"
1774 );
1775 assert_eq!(
1776 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1777 DisplayPoint::new(DisplayRow(0), 7)
1778 );
1779 assert_eq!(
1780 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1781 DisplayPoint::new(DisplayRow(1), 0)
1782 );
1783 assert_eq!(
1784 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1785 DisplayPoint::new(DisplayRow(1), 0)
1786 );
1787 assert_eq!(
1788 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1789 DisplayPoint::new(DisplayRow(0), 7)
1790 );
1791
1792 let x = snapshot
1793 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1794 assert_eq!(
1795 movement::up(
1796 &snapshot,
1797 DisplayPoint::new(DisplayRow(1), 10),
1798 language::SelectionGoal::None,
1799 false,
1800 &text_layout_details,
1801 ),
1802 (
1803 DisplayPoint::new(DisplayRow(0), 7),
1804 language::SelectionGoal::HorizontalPosition(x.0)
1805 )
1806 );
1807 assert_eq!(
1808 movement::down(
1809 &snapshot,
1810 DisplayPoint::new(DisplayRow(0), 7),
1811 language::SelectionGoal::HorizontalPosition(x.0),
1812 false,
1813 &text_layout_details
1814 ),
1815 (
1816 DisplayPoint::new(DisplayRow(1), 10),
1817 language::SelectionGoal::HorizontalPosition(x.0)
1818 )
1819 );
1820 assert_eq!(
1821 movement::down(
1822 &snapshot,
1823 DisplayPoint::new(DisplayRow(1), 10),
1824 language::SelectionGoal::HorizontalPosition(x.0),
1825 false,
1826 &text_layout_details
1827 ),
1828 (
1829 DisplayPoint::new(DisplayRow(2), 4),
1830 language::SelectionGoal::HorizontalPosition(x.0)
1831 )
1832 );
1833
1834 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1835 buffer.update(cx, |buffer, cx| {
1836 buffer.edit([(ix..ix, "and ")], None, cx);
1837 });
1838
1839 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1840 assert_eq!(
1841 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1842 "three four \nfive\nsix and \nseven eight"
1843 );
1844
1845 // Re-wrap on font size changes
1846 map.update(cx, |map, cx| {
1847 map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
1848 });
1849
1850 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1851 assert_eq!(
1852 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1853 "three \nfour five\nsix and \nseven \neight"
1854 )
1855 });
1856 }
1857
1858 #[gpui::test]
1859 fn test_text_chunks(cx: &mut gpui::AppContext) {
1860 init_test(cx, |_| {});
1861
1862 let text = sample_text(6, 6, 'a');
1863 let buffer = MultiBuffer::build_simple(&text, cx);
1864
1865 let font_size = px(14.0);
1866 let map = cx.new_model(|cx| {
1867 DisplayMap::new(
1868 buffer.clone(),
1869 font("Helvetica"),
1870 font_size,
1871 None,
1872 true,
1873 1,
1874 1,
1875 0,
1876 FoldPlaceholder::test(),
1877 cx,
1878 )
1879 });
1880
1881 buffer.update(cx, |buffer, cx| {
1882 buffer.edit(
1883 vec![
1884 (
1885 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
1886 "\t",
1887 ),
1888 (
1889 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
1890 "\t",
1891 ),
1892 (
1893 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
1894 "\t",
1895 ),
1896 ],
1897 None,
1898 cx,
1899 )
1900 });
1901
1902 assert_eq!(
1903 map.update(cx, |map, cx| map.snapshot(cx))
1904 .text_chunks(DisplayRow(1))
1905 .collect::<String>()
1906 .lines()
1907 .next(),
1908 Some(" b bbbbb")
1909 );
1910 assert_eq!(
1911 map.update(cx, |map, cx| map.snapshot(cx))
1912 .text_chunks(DisplayRow(2))
1913 .collect::<String>()
1914 .lines()
1915 .next(),
1916 Some("c ccccc")
1917 );
1918 }
1919
1920 #[gpui::test]
1921 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1922 let text = r#"
1923 fn outer() {}
1924
1925 mod module {
1926 fn inner() {}
1927 }"#
1928 .unindent();
1929
1930 let theme =
1931 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1932 let language = Arc::new(
1933 Language::new(
1934 LanguageConfig {
1935 name: "Test".into(),
1936 matcher: LanguageMatcher {
1937 path_suffixes: vec![".test".to_string()],
1938 ..Default::default()
1939 },
1940 ..Default::default()
1941 },
1942 Some(tree_sitter_rust::LANGUAGE.into()),
1943 )
1944 .with_highlights_query(
1945 r#"
1946 (mod_item name: (identifier) body: _ @mod.body)
1947 (function_item name: (identifier) @fn.name)
1948 "#,
1949 )
1950 .unwrap(),
1951 );
1952 language.set_theme(&theme);
1953
1954 cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
1955
1956 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1957 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1958 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1959
1960 let font_size = px(14.0);
1961
1962 let map = cx.new_model(|cx| {
1963 DisplayMap::new(
1964 buffer,
1965 font("Helvetica"),
1966 font_size,
1967 None,
1968 true,
1969 1,
1970 1,
1971 1,
1972 FoldPlaceholder::test(),
1973 cx,
1974 )
1975 });
1976 assert_eq!(
1977 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1978 vec![
1979 ("fn ".to_string(), None),
1980 ("outer".to_string(), Some(Hsla::blue())),
1981 ("() {}\n\nmod module ".to_string(), None),
1982 ("{\n fn ".to_string(), Some(Hsla::red())),
1983 ("inner".to_string(), Some(Hsla::blue())),
1984 ("() {}\n}".to_string(), Some(Hsla::red())),
1985 ]
1986 );
1987 assert_eq!(
1988 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1989 vec![
1990 (" fn ".to_string(), Some(Hsla::red())),
1991 ("inner".to_string(), Some(Hsla::blue())),
1992 ("() {}\n}".to_string(), Some(Hsla::red())),
1993 ]
1994 );
1995
1996 map.update(cx, |map, cx| {
1997 map.fold(
1998 vec![Crease::simple(
1999 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2000 FoldPlaceholder::test(),
2001 )],
2002 cx,
2003 )
2004 });
2005 assert_eq!(
2006 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
2007 vec![
2008 ("fn ".to_string(), None),
2009 ("out".to_string(), Some(Hsla::blue())),
2010 ("⋯".to_string(), None),
2011 (" fn ".to_string(), Some(Hsla::red())),
2012 ("inner".to_string(), Some(Hsla::blue())),
2013 ("() {}\n}".to_string(), Some(Hsla::red())),
2014 ]
2015 );
2016 }
2017
2018 #[gpui::test]
2019 async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
2020 cx.background_executor
2021 .set_block_on_ticks(usize::MAX..=usize::MAX);
2022
2023 let text = r#"
2024 const A: &str = "
2025 one
2026 two
2027 three
2028 ";
2029 const B: &str = "four";
2030 "#
2031 .unindent();
2032
2033 let theme = SyntaxTheme::new_test(vec![
2034 ("string", Hsla::red()),
2035 ("punctuation", Hsla::blue()),
2036 ("keyword", Hsla::green()),
2037 ]);
2038 let language = Arc::new(
2039 Language::new(
2040 LanguageConfig {
2041 name: "Rust".into(),
2042 ..Default::default()
2043 },
2044 Some(tree_sitter_rust::LANGUAGE.into()),
2045 )
2046 .with_highlights_query(
2047 r#"
2048 (string_literal) @string
2049 "const" @keyword
2050 [":" ";"] @punctuation
2051 "#,
2052 )
2053 .unwrap(),
2054 );
2055 language.set_theme(&theme);
2056
2057 cx.update(|cx| init_test(cx, |_| {}));
2058
2059 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
2060 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2061 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
2062 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2063
2064 let map = cx.new_model(|cx| {
2065 DisplayMap::new(
2066 buffer,
2067 font("Courier"),
2068 px(16.0),
2069 None,
2070 true,
2071 1,
2072 1,
2073 0,
2074 FoldPlaceholder::test(),
2075 cx,
2076 )
2077 });
2078
2079 // Insert a block in the middle of a multi-line string literal
2080 map.update(cx, |map, cx| {
2081 map.insert_blocks(
2082 [BlockProperties {
2083 placement: BlockPlacement::Below(
2084 buffer_snapshot.anchor_before(Point::new(1, 0)),
2085 ),
2086 height: 1,
2087 style: BlockStyle::Sticky,
2088 render: Arc::new(|_| div().into_any()),
2089 priority: 0,
2090 }],
2091 cx,
2092 )
2093 });
2094
2095 pretty_assertions::assert_eq!(
2096 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
2097 [
2098 ("const".into(), Some(Hsla::green())),
2099 (" A".into(), None),
2100 (":".into(), Some(Hsla::blue())),
2101 (" &str = ".into(), None),
2102 ("\"\n one\n".into(), Some(Hsla::red())),
2103 ("\n".into(), None),
2104 (" two\n three\n\"".into(), Some(Hsla::red())),
2105 (";".into(), Some(Hsla::blue())),
2106 ("\n".into(), None),
2107 ("const".into(), Some(Hsla::green())),
2108 (" B".into(), None),
2109 (":".into(), Some(Hsla::blue())),
2110 (" &str = ".into(), None),
2111 ("\"four\"".into(), Some(Hsla::red())),
2112 (";".into(), Some(Hsla::blue())),
2113 ("\n".into(), None),
2114 ]
2115 );
2116 }
2117
2118 #[gpui::test]
2119 async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
2120 cx.background_executor
2121 .set_block_on_ticks(usize::MAX..=usize::MAX);
2122
2123 let text = r#"
2124 struct A {
2125 b: usize;
2126 }
2127 const c: usize = 1;
2128 "#
2129 .unindent();
2130
2131 cx.update(|cx| init_test(cx, |_| {}));
2132
2133 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
2134
2135 buffer.update(cx, |buffer, cx| {
2136 buffer.update_diagnostics(
2137 LanguageServerId(0),
2138 DiagnosticSet::new(
2139 [DiagnosticEntry {
2140 range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
2141 diagnostic: Diagnostic {
2142 severity: DiagnosticSeverity::ERROR,
2143 group_id: 1,
2144 message: "hi".into(),
2145 ..Default::default()
2146 },
2147 }],
2148 buffer,
2149 ),
2150 cx,
2151 )
2152 });
2153
2154 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
2155 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2156
2157 let map = cx.new_model(|cx| {
2158 DisplayMap::new(
2159 buffer,
2160 font("Courier"),
2161 px(16.0),
2162 None,
2163 true,
2164 1,
2165 1,
2166 0,
2167 FoldPlaceholder::test(),
2168 cx,
2169 )
2170 });
2171
2172 let black = gpui::black().to_rgb();
2173 let red = gpui::red().to_rgb();
2174
2175 // Insert a block in the middle of a multi-line diagnostic.
2176 map.update(cx, |map, cx| {
2177 map.highlight_text(
2178 TypeId::of::<usize>(),
2179 vec![
2180 buffer_snapshot.anchor_before(Point::new(3, 9))
2181 ..buffer_snapshot.anchor_after(Point::new(3, 14)),
2182 buffer_snapshot.anchor_before(Point::new(3, 17))
2183 ..buffer_snapshot.anchor_after(Point::new(3, 18)),
2184 ],
2185 red.into(),
2186 );
2187 map.insert_blocks(
2188 [BlockProperties {
2189 placement: BlockPlacement::Below(
2190 buffer_snapshot.anchor_before(Point::new(1, 0)),
2191 ),
2192 height: 1,
2193 style: BlockStyle::Sticky,
2194 render: Arc::new(|_| div().into_any()),
2195 priority: 0,
2196 }],
2197 cx,
2198 )
2199 });
2200
2201 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2202 let mut chunks = Vec::<(String, Option<DiagnosticSeverity>, Rgba)>::new();
2203 for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
2204 let color = chunk
2205 .highlight_style
2206 .and_then(|style| style.color)
2207 .map_or(black, |color| color.to_rgb());
2208 if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut() {
2209 if *last_severity == chunk.diagnostic_severity && *last_color == color {
2210 last_chunk.push_str(chunk.text);
2211 continue;
2212 }
2213 }
2214
2215 chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
2216 }
2217
2218 assert_eq!(
2219 chunks,
2220 [
2221 (
2222 "struct A {\n b: usize;\n".into(),
2223 Some(DiagnosticSeverity::ERROR),
2224 black
2225 ),
2226 ("\n".into(), None, black),
2227 ("}".into(), Some(DiagnosticSeverity::ERROR), black),
2228 ("\nconst c: ".into(), None, black),
2229 ("usize".into(), None, red),
2230 (" = ".into(), None, black),
2231 ("1".into(), None, red),
2232 (";\n".into(), None, black),
2233 ]
2234 );
2235 }
2236
2237 #[gpui::test]
2238 async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
2239 cx.background_executor
2240 .set_block_on_ticks(usize::MAX..=usize::MAX);
2241
2242 cx.update(|cx| init_test(cx, |_| {}));
2243
2244 let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
2245 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2246 let map = cx.new_model(|cx| {
2247 DisplayMap::new(
2248 buffer.clone(),
2249 font("Courier"),
2250 px(16.0),
2251 None,
2252 true,
2253 1,
2254 1,
2255 0,
2256 FoldPlaceholder::test(),
2257 cx,
2258 )
2259 });
2260
2261 let snapshot = map.update(cx, |map, cx| {
2262 map.insert_blocks(
2263 [BlockProperties {
2264 placement: BlockPlacement::Replace(
2265 buffer_snapshot.anchor_before(Point::new(1, 2))
2266 ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
2267 ),
2268 height: 4,
2269 style: BlockStyle::Fixed,
2270 render: Arc::new(|_| div().into_any()),
2271 priority: 0,
2272 }],
2273 cx,
2274 );
2275 map.snapshot(cx)
2276 });
2277
2278 assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
2279
2280 let point_to_display_points = [
2281 (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
2282 (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
2283 (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
2284 ];
2285 for (buffer_point, display_point) in point_to_display_points {
2286 assert_eq!(
2287 snapshot.point_to_display_point(buffer_point, Bias::Left),
2288 display_point,
2289 "point_to_display_point({:?}, Bias::Left)",
2290 buffer_point
2291 );
2292 assert_eq!(
2293 snapshot.point_to_display_point(buffer_point, Bias::Right),
2294 display_point,
2295 "point_to_display_point({:?}, Bias::Right)",
2296 buffer_point
2297 );
2298 }
2299
2300 let display_points_to_points = [
2301 (
2302 DisplayPoint::new(DisplayRow(1), 0),
2303 Point::new(1, 0),
2304 Point::new(2, 5),
2305 ),
2306 (
2307 DisplayPoint::new(DisplayRow(2), 0),
2308 Point::new(1, 0),
2309 Point::new(2, 5),
2310 ),
2311 (
2312 DisplayPoint::new(DisplayRow(3), 0),
2313 Point::new(1, 0),
2314 Point::new(2, 5),
2315 ),
2316 (
2317 DisplayPoint::new(DisplayRow(4), 0),
2318 Point::new(1, 0),
2319 Point::new(2, 5),
2320 ),
2321 (
2322 DisplayPoint::new(DisplayRow(5), 0),
2323 Point::new(3, 0),
2324 Point::new(3, 0),
2325 ),
2326 ];
2327 for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
2328 assert_eq!(
2329 snapshot.display_point_to_point(display_point, Bias::Left),
2330 left_buffer_point,
2331 "display_point_to_point({:?}, Bias::Left)",
2332 display_point
2333 );
2334 assert_eq!(
2335 snapshot.display_point_to_point(display_point, Bias::Right),
2336 right_buffer_point,
2337 "display_point_to_point({:?}, Bias::Right)",
2338 display_point
2339 );
2340 }
2341 }
2342
2343 // todo(linux) fails due to pixel differences in text rendering
2344 #[cfg(target_os = "macos")]
2345 #[gpui::test]
2346 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
2347 cx.background_executor
2348 .set_block_on_ticks(usize::MAX..=usize::MAX);
2349
2350 let text = r#"
2351 fn outer() {}
2352
2353 mod module {
2354 fn inner() {}
2355 }"#
2356 .unindent();
2357
2358 let theme =
2359 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2360 let language = Arc::new(
2361 Language::new(
2362 LanguageConfig {
2363 name: "Test".into(),
2364 matcher: LanguageMatcher {
2365 path_suffixes: vec![".test".to_string()],
2366 ..Default::default()
2367 },
2368 ..Default::default()
2369 },
2370 Some(tree_sitter_rust::LANGUAGE.into()),
2371 )
2372 .with_highlights_query(
2373 r#"
2374 (mod_item name: (identifier) body: _ @mod.body)
2375 (function_item name: (identifier) @fn.name)
2376 "#,
2377 )
2378 .unwrap(),
2379 );
2380 language.set_theme(&theme);
2381
2382 cx.update(|cx| init_test(cx, |_| {}));
2383
2384 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
2385 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2386 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
2387
2388 let font_size = px(16.0);
2389
2390 let map = cx.new_model(|cx| {
2391 DisplayMap::new(
2392 buffer,
2393 font("Courier"),
2394 font_size,
2395 Some(px(40.0)),
2396 true,
2397 1,
2398 1,
2399 0,
2400 FoldPlaceholder::test(),
2401 cx,
2402 )
2403 });
2404 assert_eq!(
2405 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2406 [
2407 ("fn \n".to_string(), None),
2408 ("oute\nr".to_string(), Some(Hsla::blue())),
2409 ("() \n{}\n\n".to_string(), None),
2410 ]
2411 );
2412 assert_eq!(
2413 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2414 [("{}\n\n".to_string(), None)]
2415 );
2416
2417 map.update(cx, |map, cx| {
2418 map.fold(
2419 vec![Crease::simple(
2420 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2421 FoldPlaceholder::test(),
2422 )],
2423 cx,
2424 )
2425 });
2426 assert_eq!(
2427 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
2428 [
2429 ("out".to_string(), Some(Hsla::blue())),
2430 ("⋯\n".to_string(), None),
2431 (" \nfn ".to_string(), Some(Hsla::red())),
2432 ("i\n".to_string(), Some(Hsla::blue()))
2433 ]
2434 );
2435 }
2436
2437 #[gpui::test]
2438 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
2439 cx.update(|cx| init_test(cx, |_| {}));
2440
2441 let theme =
2442 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
2443 let language = Arc::new(
2444 Language::new(
2445 LanguageConfig {
2446 name: "Test".into(),
2447 matcher: LanguageMatcher {
2448 path_suffixes: vec![".test".to_string()],
2449 ..Default::default()
2450 },
2451 ..Default::default()
2452 },
2453 Some(tree_sitter_rust::LANGUAGE.into()),
2454 )
2455 .with_highlights_query(
2456 r#"
2457 ":" @operator
2458 (string_literal) @string
2459 "#,
2460 )
2461 .unwrap(),
2462 );
2463 language.set_theme(&theme);
2464
2465 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
2466
2467 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
2468 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2469
2470 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
2471 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2472
2473 let font_size = px(16.0);
2474 let map = cx.new_model(|cx| {
2475 DisplayMap::new(
2476 buffer,
2477 font("Courier"),
2478 font_size,
2479 None,
2480 true,
2481 1,
2482 1,
2483 1,
2484 FoldPlaceholder::test(),
2485 cx,
2486 )
2487 });
2488
2489 enum MyType {}
2490
2491 let style = HighlightStyle {
2492 color: Some(Hsla::blue()),
2493 ..Default::default()
2494 };
2495
2496 map.update(cx, |map, _cx| {
2497 map.highlight_text(
2498 TypeId::of::<MyType>(),
2499 highlighted_ranges
2500 .into_iter()
2501 .map(|range| {
2502 buffer_snapshot.anchor_before(range.start)
2503 ..buffer_snapshot.anchor_before(range.end)
2504 })
2505 .collect(),
2506 style,
2507 );
2508 });
2509
2510 assert_eq!(
2511 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
2512 [
2513 ("const ".to_string(), None, None),
2514 ("a".to_string(), None, Some(Hsla::blue())),
2515 (":".to_string(), Some(Hsla::red()), None),
2516 (" B = ".to_string(), None, None),
2517 ("\"c ".to_string(), Some(Hsla::green()), None),
2518 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
2519 ("\"".to_string(), Some(Hsla::green()), None),
2520 ]
2521 );
2522 }
2523
2524 #[gpui::test]
2525 fn test_clip_point(cx: &mut gpui::AppContext) {
2526 init_test(cx, |_| {});
2527
2528 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
2529 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
2530
2531 match bias {
2532 Bias::Left => {
2533 if shift_right {
2534 *markers[1].column_mut() += 1;
2535 }
2536
2537 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
2538 }
2539 Bias::Right => {
2540 if shift_right {
2541 *markers[0].column_mut() += 1;
2542 }
2543
2544 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
2545 }
2546 };
2547 }
2548
2549 use Bias::{Left, Right};
2550 assert("ˇˇα", false, Left, cx);
2551 assert("ˇˇα", true, Left, cx);
2552 assert("ˇˇα", false, Right, cx);
2553 assert("ˇαˇ", true, Right, cx);
2554 assert("ˇˇ✋", false, Left, cx);
2555 assert("ˇˇ✋", true, Left, cx);
2556 assert("ˇˇ✋", false, Right, cx);
2557 assert("ˇ✋ˇ", true, Right, cx);
2558 assert("ˇˇ🍐", false, Left, cx);
2559 assert("ˇˇ🍐", true, Left, cx);
2560 assert("ˇˇ🍐", false, Right, cx);
2561 assert("ˇ🍐ˇ", true, Right, cx);
2562 assert("ˇˇ\t", false, Left, cx);
2563 assert("ˇˇ\t", true, Left, cx);
2564 assert("ˇˇ\t", false, Right, cx);
2565 assert("ˇ\tˇ", true, Right, cx);
2566 assert(" ˇˇ\t", false, Left, cx);
2567 assert(" ˇˇ\t", true, Left, cx);
2568 assert(" ˇˇ\t", false, Right, cx);
2569 assert(" ˇ\tˇ", true, Right, cx);
2570 assert(" ˇˇ\t", false, Left, cx);
2571 assert(" ˇˇ\t", false, Right, cx);
2572 }
2573
2574 #[gpui::test]
2575 fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
2576 init_test(cx, |_| {});
2577
2578 fn assert(text: &str, cx: &mut gpui::AppContext) {
2579 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
2580 unmarked_snapshot.clip_at_line_ends = true;
2581 assert_eq!(
2582 unmarked_snapshot.clip_point(markers[1], Bias::Left),
2583 markers[0]
2584 );
2585 }
2586
2587 assert("ˇˇ", cx);
2588 assert("ˇaˇ", cx);
2589 assert("aˇbˇ", cx);
2590 assert("aˇαˇ", cx);
2591 }
2592
2593 #[gpui::test]
2594 fn test_creases(cx: &mut gpui::AppContext) {
2595 init_test(cx, |_| {});
2596
2597 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
2598 let buffer = MultiBuffer::build_simple(text, cx);
2599 let font_size = px(14.0);
2600 cx.new_model(|cx| {
2601 let mut map = DisplayMap::new(
2602 buffer.clone(),
2603 font("Helvetica"),
2604 font_size,
2605 None,
2606 true,
2607 1,
2608 1,
2609 0,
2610 FoldPlaceholder::test(),
2611 cx,
2612 );
2613 let snapshot = map.buffer.read(cx).snapshot(cx);
2614 let range =
2615 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
2616
2617 map.crease_map.insert(
2618 [Crease::inline(
2619 range,
2620 FoldPlaceholder::test(),
2621 |_row, _status, _toggle, _cx| div(),
2622 |_row, _status, _cx| div(),
2623 )],
2624 &map.buffer.read(cx).snapshot(cx),
2625 );
2626
2627 map
2628 });
2629 }
2630
2631 #[gpui::test]
2632 fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
2633 init_test(cx, |_| {});
2634
2635 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
2636 let buffer = MultiBuffer::build_simple(text, cx);
2637 let font_size = px(14.0);
2638
2639 let map = cx.new_model(|cx| {
2640 DisplayMap::new(
2641 buffer.clone(),
2642 font("Helvetica"),
2643 font_size,
2644 None,
2645 true,
2646 1,
2647 1,
2648 0,
2649 FoldPlaceholder::test(),
2650 cx,
2651 )
2652 });
2653 let map = map.update(cx, |map, cx| map.snapshot(cx));
2654 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
2655 assert_eq!(
2656 map.text_chunks(DisplayRow(0)).collect::<String>(),
2657 "✅ α\nβ \n🏀β γ"
2658 );
2659 assert_eq!(
2660 map.text_chunks(DisplayRow(1)).collect::<String>(),
2661 "β \n🏀β γ"
2662 );
2663 assert_eq!(
2664 map.text_chunks(DisplayRow(2)).collect::<String>(),
2665 "🏀β γ"
2666 );
2667
2668 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
2669 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
2670 assert_eq!(point.to_display_point(&map), display_point);
2671 assert_eq!(display_point.to_point(&map), point);
2672
2673 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
2674 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
2675 assert_eq!(point.to_display_point(&map), display_point);
2676 assert_eq!(display_point.to_point(&map), point,);
2677
2678 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2679 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
2680 assert_eq!(point.to_display_point(&map), display_point);
2681 assert_eq!(display_point.to_point(&map), point,);
2682
2683 // Display points inside of expanded tabs
2684 assert_eq!(
2685 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2686 MultiBufferPoint::new(0, "✅\t".len() as u32),
2687 );
2688 assert_eq!(
2689 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2690 MultiBufferPoint::new(0, "✅".len() as u32),
2691 );
2692
2693 // Clipping display points inside of multi-byte characters
2694 assert_eq!(
2695 map.clip_point(
2696 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2697 Left
2698 ),
2699 DisplayPoint::new(DisplayRow(0), 0)
2700 );
2701 assert_eq!(
2702 map.clip_point(
2703 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2704 Bias::Right
2705 ),
2706 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
2707 );
2708 }
2709
2710 #[gpui::test]
2711 fn test_max_point(cx: &mut gpui::AppContext) {
2712 init_test(cx, |_| {});
2713
2714 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2715 let font_size = px(14.0);
2716 let map = cx.new_model(|cx| {
2717 DisplayMap::new(
2718 buffer.clone(),
2719 font("Helvetica"),
2720 font_size,
2721 None,
2722 true,
2723 1,
2724 1,
2725 0,
2726 FoldPlaceholder::test(),
2727 cx,
2728 )
2729 });
2730 assert_eq!(
2731 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2732 DisplayPoint::new(DisplayRow(1), 11)
2733 )
2734 }
2735
2736 fn syntax_chunks(
2737 rows: Range<DisplayRow>,
2738 map: &Model<DisplayMap>,
2739 theme: &SyntaxTheme,
2740 cx: &mut AppContext,
2741 ) -> Vec<(String, Option<Hsla>)> {
2742 chunks(rows, map, theme, cx)
2743 .into_iter()
2744 .map(|(text, color, _)| (text, color))
2745 .collect()
2746 }
2747
2748 fn chunks(
2749 rows: Range<DisplayRow>,
2750 map: &Model<DisplayMap>,
2751 theme: &SyntaxTheme,
2752 cx: &mut AppContext,
2753 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
2754 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2755 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
2756 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
2757 let syntax_color = chunk
2758 .syntax_highlight_id
2759 .and_then(|id| id.style(theme)?.color);
2760 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
2761 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
2762 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
2763 last_chunk.push_str(chunk.text);
2764 continue;
2765 }
2766 }
2767 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
2768 }
2769 chunks
2770 }
2771
2772 fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2773 let settings = SettingsStore::test(cx);
2774 cx.set_global(settings);
2775 language::init(cx);
2776 crate::init(cx);
2777 Project::init_settings(cx);
2778 theme::init(LoadThemes::JustBase, cx);
2779 cx.update_global::<SettingsStore, _>(|store, cx| {
2780 store.update_user_settings::<AllLanguageSettings>(cx, f);
2781 });
2782 }
2783}