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