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 flap_map;
22mod fold_map;
23mod inlay_map;
24mod tab_map;
25mod wrap_map;
26
27use crate::{
28 hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
29};
30pub use block_map::{
31 BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
32 BlockMap, BlockPoint, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
33};
34use block_map::{BlockRow, BlockSnapshot};
35use collections::{HashMap, HashSet};
36pub use flap_map::*;
37pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
38use fold_map::{FoldMap, FoldSnapshot};
39use gpui::{
40 AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
41};
42pub(crate) use inlay_map::Inlay;
43use inlay_map::{InlayMap, InlaySnapshot};
44pub use inlay_map::{InlayOffset, InlayPoint};
45use language::{
46 language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
47 Subscription as BufferSubscription,
48};
49use lsp::DiagnosticSeverity;
50use multi_buffer::{
51 Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
52 ToOffset, ToPoint,
53};
54use serde::Deserialize;
55use std::ops::Add;
56use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
57use sum_tree::{Bias, TreeMap};
58use tab_map::{TabMap, TabSnapshot};
59use text::LineIndent;
60use ui::WindowContext;
61use wrap_map::{WrapMap, WrapSnapshot};
62
63#[derive(Copy, Clone, Debug, PartialEq, Eq)]
64pub enum FoldStatus {
65 Folded,
66 Foldable,
67}
68
69pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
70
71const UNNECESSARY_CODE_FADE: f32 = 0.3;
72
73pub trait ToDisplayPoint {
74 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
75}
76
77type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
78type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
79
80/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
81/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
82///
83/// See the [module level documentation](self) for more information.
84pub struct DisplayMap {
85 /// The buffer that we are displaying.
86 buffer: Model<MultiBuffer>,
87 buffer_subscription: BufferSubscription,
88 /// Decides where the [`Inlay`]s should be displayed.
89 inlay_map: InlayMap,
90 /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
91 fold_map: FoldMap,
92 /// Keeps track of hard tabs in a buffer.
93 tab_map: TabMap,
94 /// Handles soft wrapping.
95 wrap_map: Model<WrapMap>,
96 /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
97 block_map: BlockMap,
98 /// Regions of text that should be highlighted.
99 text_highlights: TextHighlights,
100 /// Regions of inlays that should be highlighted.
101 inlay_highlights: InlayHighlights,
102 /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
103 flap_map: FlapMap,
104 fold_placeholder: FoldPlaceholder,
105 pub clip_at_line_ends: bool,
106}
107
108impl DisplayMap {
109 #[allow(clippy::too_many_arguments)]
110 pub fn new(
111 buffer: Model<MultiBuffer>,
112 font: Font,
113 font_size: Pixels,
114 wrap_width: Option<Pixels>,
115 show_excerpt_controls: bool,
116 buffer_header_height: u8,
117 excerpt_header_height: u8,
118 excerpt_footer_height: u8,
119 fold_placeholder: FoldPlaceholder,
120 cx: &mut ModelContext<Self>,
121 ) -> Self {
122 let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
123
124 let tab_size = Self::tab_size(&buffer, cx);
125 let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
126 let (fold_map, snapshot) = FoldMap::new(snapshot);
127 let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
128 let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
129 let block_map = BlockMap::new(
130 snapshot,
131 show_excerpt_controls,
132 buffer_header_height,
133 excerpt_header_height,
134 excerpt_footer_height,
135 );
136 let flap_map = FlapMap::default();
137
138 cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
139
140 DisplayMap {
141 buffer,
142 buffer_subscription,
143 fold_map,
144 inlay_map,
145 tab_map,
146 wrap_map,
147 block_map,
148 flap_map,
149 fold_placeholder,
150 text_highlights: Default::default(),
151 inlay_highlights: Default::default(),
152 clip_at_line_ends: false,
153 }
154 }
155
156 pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
157 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
158 let edits = self.buffer_subscription.consume().into_inner();
159 let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
160 let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
161 let tab_size = Self::tab_size(&self.buffer, cx);
162 let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
163 let (wrap_snapshot, edits) = self
164 .wrap_map
165 .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
166 let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
167
168 DisplaySnapshot {
169 buffer_snapshot: self.buffer.read(cx).snapshot(cx),
170 fold_snapshot,
171 inlay_snapshot,
172 tab_snapshot,
173 wrap_snapshot,
174 block_snapshot,
175 flap_snapshot: self.flap_map.snapshot(),
176 text_highlights: self.text_highlights.clone(),
177 inlay_highlights: self.inlay_highlights.clone(),
178 clip_at_line_ends: self.clip_at_line_ends,
179 fold_placeholder: self.fold_placeholder.clone(),
180 }
181 }
182
183 pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
184 self.fold(
185 other
186 .folds_in_range(0..other.buffer_snapshot.len())
187 .map(|fold| {
188 (
189 fold.range.to_offset(&other.buffer_snapshot),
190 fold.placeholder.clone(),
191 )
192 }),
193 cx,
194 );
195 }
196
197 pub fn fold<T: ToOffset>(
198 &mut self,
199 ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
200 cx: &mut ModelContext<Self>,
201 ) {
202 let snapshot = self.buffer.read(cx).snapshot(cx);
203 let edits = self.buffer_subscription.consume().into_inner();
204 let tab_size = Self::tab_size(&self.buffer, cx);
205 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
206 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
207 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
208 let (snapshot, edits) = self
209 .wrap_map
210 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
211 self.block_map.read(snapshot, edits);
212 let (snapshot, edits) = fold_map.fold(ranges);
213 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
214 let (snapshot, edits) = self
215 .wrap_map
216 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
217 self.block_map.read(snapshot, edits);
218 }
219
220 pub fn unfold<T: ToOffset>(
221 &mut self,
222 ranges: impl IntoIterator<Item = Range<T>>,
223 inclusive: bool,
224 cx: &mut ModelContext<Self>,
225 ) {
226 let snapshot = self.buffer.read(cx).snapshot(cx);
227 let edits = self.buffer_subscription.consume().into_inner();
228 let tab_size = Self::tab_size(&self.buffer, cx);
229 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
230 let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
231 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
232 let (snapshot, edits) = self
233 .wrap_map
234 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
235 self.block_map.read(snapshot, edits);
236 let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
237 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
238 let (snapshot, edits) = self
239 .wrap_map
240 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
241 self.block_map.read(snapshot, edits);
242 }
243
244 pub fn insert_flaps(
245 &mut self,
246 flaps: impl IntoIterator<Item = Flap>,
247 cx: &mut ModelContext<Self>,
248 ) -> Vec<FlapId> {
249 let snapshot = self.buffer.read(cx).snapshot(cx);
250 self.flap_map.insert(flaps, &snapshot)
251 }
252
253 pub fn remove_flaps(
254 &mut self,
255 flap_ids: impl IntoIterator<Item = FlapId>,
256 cx: &mut ModelContext<Self>,
257 ) {
258 let snapshot = self.buffer.read(cx).snapshot(cx);
259 self.flap_map.remove(flap_ids, &snapshot)
260 }
261
262 pub fn insert_blocks(
263 &mut self,
264 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
265 cx: &mut ModelContext<Self>,
266 ) -> Vec<BlockId> {
267 let snapshot = self.buffer.read(cx).snapshot(cx);
268 let edits = self.buffer_subscription.consume().into_inner();
269 let tab_size = Self::tab_size(&self.buffer, cx);
270 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
271 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
272 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
273 let (snapshot, edits) = self
274 .wrap_map
275 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
276 let mut block_map = self.block_map.write(snapshot, edits);
277 block_map.insert(blocks)
278 }
279
280 pub fn replace_blocks(
281 &mut self,
282 heights_and_renderers: HashMap<BlockId, (Option<u8>, RenderBlock)>,
283 cx: &mut ModelContext<Self>,
284 ) {
285 //
286 // Note: previous implementation of `replace_blocks` simply called
287 // `self.block_map.replace(styles)` which just modified the render by replacing
288 // the `RenderBlock` with the new one.
289 //
290 // ```rust
291 // for block in &self.blocks {
292 // if let Some(render) = renderers.remove(&block.id) {
293 // *block.render.lock() = render;
294 // }
295 // }
296 // ```
297 //
298 // If height changes however, we need to update the tree. There's a performance
299 // cost to this, so we'll split the replace blocks into handling the old behavior
300 // directly and the new behavior separately.
301 //
302 //
303 let mut only_renderers = HashMap::<BlockId, RenderBlock>::default();
304 let mut full_replace = HashMap::<BlockId, (u8, RenderBlock)>::default();
305 for (id, (height, render)) in heights_and_renderers {
306 if let Some(height) = height {
307 full_replace.insert(id, (height, render));
308 } else {
309 only_renderers.insert(id, render);
310 }
311 }
312 self.block_map.replace_renderers(only_renderers);
313
314 if full_replace.is_empty() {
315 return;
316 }
317
318 let snapshot = self.buffer.read(cx).snapshot(cx);
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 (snapshot, edits) = self.fold_map.read(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 let mut block_map = self.block_map.write(snapshot, edits);
328 block_map.replace(full_replace);
329 }
330
331 pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
332 let snapshot = self.buffer.read(cx).snapshot(cx);
333 let edits = self.buffer_subscription.consume().into_inner();
334 let tab_size = Self::tab_size(&self.buffer, cx);
335 let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
336 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
337 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
338 let (snapshot, edits) = self
339 .wrap_map
340 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
341 let mut block_map = self.block_map.write(snapshot, edits);
342 block_map.remove(ids);
343 }
344
345 pub fn highlight_text(
346 &mut self,
347 type_id: TypeId,
348 ranges: Vec<Range<Anchor>>,
349 style: HighlightStyle,
350 ) {
351 self.text_highlights
352 .insert(Some(type_id), Arc::new((style, ranges)));
353 }
354
355 pub(crate) fn highlight_inlays(
356 &mut self,
357 type_id: TypeId,
358 highlights: Vec<InlayHighlight>,
359 style: HighlightStyle,
360 ) {
361 for highlight in highlights {
362 let update = self.inlay_highlights.update(&type_id, |highlights| {
363 highlights.insert(highlight.inlay, (style, highlight.clone()))
364 });
365 if update.is_none() {
366 self.inlay_highlights.insert(
367 type_id,
368 TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
369 );
370 }
371 }
372 }
373
374 pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
375 let highlights = self.text_highlights.get(&Some(type_id))?;
376 Some((highlights.0, &highlights.1))
377 }
378 pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
379 let mut cleared = self.text_highlights.remove(&Some(type_id)).is_some();
380 cleared |= self.inlay_highlights.remove(&type_id).is_some();
381 cleared
382 }
383
384 pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
385 self.wrap_map
386 .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
387 }
388
389 pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
390 self.wrap_map
391 .update(cx, |map, cx| map.set_wrap_width(width, cx))
392 }
393
394 pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
395 self.inlay_map.current_inlays()
396 }
397
398 pub(crate) fn splice_inlays(
399 &mut self,
400 to_remove: Vec<InlayId>,
401 to_insert: Vec<Inlay>,
402 cx: &mut ModelContext<Self>,
403 ) {
404 if to_remove.is_empty() && to_insert.is_empty() {
405 return;
406 }
407 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
408 let edits = self.buffer_subscription.consume().into_inner();
409 let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
410 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
411 let tab_size = Self::tab_size(&self.buffer, cx);
412 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
413 let (snapshot, edits) = self
414 .wrap_map
415 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
416 self.block_map.read(snapshot, edits);
417
418 let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
419 let (snapshot, edits) = self.fold_map.read(snapshot, edits);
420 let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
421 let (snapshot, edits) = self
422 .wrap_map
423 .update(cx, |map, cx| map.sync(snapshot, edits, cx));
424 self.block_map.read(snapshot, edits);
425 }
426
427 fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
428 let language = buffer
429 .read(cx)
430 .as_singleton()
431 .and_then(|buffer| buffer.read(cx).language());
432 language_settings(language, None, cx).tab_size
433 }
434
435 #[cfg(test)]
436 pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
437 self.wrap_map.read(cx).is_rewrapping()
438 }
439
440 pub fn show_excerpt_controls(&self) -> bool {
441 self.block_map.show_excerpt_controls()
442 }
443}
444
445#[derive(Debug, Default)]
446pub(crate) struct Highlights<'a> {
447 pub text_highlights: Option<&'a TextHighlights>,
448 pub inlay_highlights: Option<&'a InlayHighlights>,
449 pub styles: HighlightStyles,
450}
451
452#[derive(Default, Debug, Clone, Copy)]
453pub struct HighlightStyles {
454 pub inlay_hint: Option<HighlightStyle>,
455 pub suggestion: Option<HighlightStyle>,
456}
457
458pub struct HighlightedChunk<'a> {
459 pub text: &'a str,
460 pub style: Option<HighlightStyle>,
461 pub is_tab: bool,
462 pub renderer: Option<ChunkRenderer>,
463}
464
465#[derive(Clone)]
466pub struct DisplaySnapshot {
467 pub buffer_snapshot: MultiBufferSnapshot,
468 pub fold_snapshot: FoldSnapshot,
469 pub flap_snapshot: FlapSnapshot,
470 inlay_snapshot: InlaySnapshot,
471 tab_snapshot: TabSnapshot,
472 wrap_snapshot: WrapSnapshot,
473 block_snapshot: BlockSnapshot,
474 text_highlights: TextHighlights,
475 inlay_highlights: InlayHighlights,
476 clip_at_line_ends: bool,
477 pub(crate) fold_placeholder: FoldPlaceholder,
478}
479
480impl DisplaySnapshot {
481 #[cfg(test)]
482 pub fn fold_count(&self) -> usize {
483 self.fold_snapshot.fold_count()
484 }
485
486 pub fn is_empty(&self) -> bool {
487 self.buffer_snapshot.len() == 0
488 }
489
490 pub fn buffer_rows(
491 &self,
492 start_row: DisplayRow,
493 ) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
494 self.block_snapshot
495 .buffer_rows(BlockRow(start_row.0))
496 .map(|row| row.map(|row| MultiBufferRow(row.0)))
497 }
498
499 pub fn max_buffer_row(&self) -> MultiBufferRow {
500 self.buffer_snapshot.max_buffer_row()
501 }
502
503 pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
504 loop {
505 let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
506 let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
507 fold_point.0.column = 0;
508 inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
509 point = self.inlay_snapshot.to_buffer_point(inlay_point);
510
511 let mut display_point = self.point_to_display_point(point, Bias::Left);
512 *display_point.column_mut() = 0;
513 let next_point = self.display_point_to_point(display_point, Bias::Left);
514 if next_point == point {
515 return (point, display_point);
516 }
517 point = next_point;
518 }
519 }
520
521 pub fn next_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
522 loop {
523 let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
524 let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
525 fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
526 inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
527 point = self.inlay_snapshot.to_buffer_point(inlay_point);
528
529 let mut display_point = self.point_to_display_point(point, Bias::Right);
530 *display_point.column_mut() = self.line_len(display_point.row());
531 let next_point = self.display_point_to_point(display_point, Bias::Right);
532 if next_point == point {
533 return (point, display_point);
534 }
535 point = next_point;
536 }
537 }
538
539 // used by line_mode selections and tries to match vim behaviour
540 pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
541 let new_start = if range.start.row == 0 {
542 MultiBufferPoint::new(0, 0)
543 } else if range.start.row == self.max_buffer_row().0
544 || (range.end.column > 0 && range.end.row == self.max_buffer_row().0)
545 {
546 MultiBufferPoint::new(
547 range.start.row - 1,
548 self.buffer_snapshot
549 .line_len(MultiBufferRow(range.start.row - 1)),
550 )
551 } else {
552 self.prev_line_boundary(range.start).0
553 };
554
555 let new_end = if range.end.column == 0 {
556 range.end
557 } else if range.end.row < self.max_buffer_row().0 {
558 self.buffer_snapshot
559 .clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
560 } else {
561 self.buffer_snapshot.max_point()
562 };
563
564 new_start..new_end
565 }
566
567 fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
568 let inlay_point = self.inlay_snapshot.to_inlay_point(point);
569 let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
570 let tab_point = self.tab_snapshot.to_tab_point(fold_point);
571 let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
572 let block_point = self.block_snapshot.to_block_point(wrap_point);
573 DisplayPoint(block_point)
574 }
575
576 fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
577 self.inlay_snapshot
578 .to_buffer_point(self.display_point_to_inlay_point(point, bias))
579 }
580
581 pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
582 self.inlay_snapshot
583 .to_offset(self.display_point_to_inlay_point(point, bias))
584 }
585
586 pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
587 self.inlay_snapshot
588 .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
589 }
590
591 pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
592 self.buffer_snapshot
593 .anchor_at(point.to_offset(&self, bias), bias)
594 }
595
596 fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
597 let block_point = point.0;
598 let wrap_point = self.block_snapshot.to_wrap_point(block_point);
599 let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
600 let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
601 fold_point.to_inlay_point(&self.fold_snapshot)
602 }
603
604 pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
605 let block_point = point.0;
606 let wrap_point = self.block_snapshot.to_wrap_point(block_point);
607 let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
608 self.tab_snapshot.to_fold_point(tab_point, bias).0
609 }
610
611 pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
612 let tab_point = self.tab_snapshot.to_tab_point(fold_point);
613 let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
614 let block_point = self.block_snapshot.to_block_point(wrap_point);
615 DisplayPoint(block_point)
616 }
617
618 pub fn max_point(&self) -> DisplayPoint {
619 DisplayPoint(self.block_snapshot.max_point())
620 }
621
622 /// Returns text chunks starting at the given display row until the end of the file
623 pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
624 self.block_snapshot
625 .chunks(
626 display_row.0..self.max_point().row().next_row().0,
627 false,
628 Highlights::default(),
629 )
630 .map(|h| h.text)
631 }
632
633 /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
634 pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
635 (0..=display_row.0).rev().flat_map(|row| {
636 self.block_snapshot
637 .chunks(row..row + 1, false, Highlights::default())
638 .map(|h| h.text)
639 .collect::<Vec<_>>()
640 .into_iter()
641 .rev()
642 })
643 }
644
645 pub fn chunks(
646 &self,
647 display_rows: Range<DisplayRow>,
648 language_aware: bool,
649 highlight_styles: HighlightStyles,
650 ) -> DisplayChunks<'_> {
651 self.block_snapshot.chunks(
652 display_rows.start.0..display_rows.end.0,
653 language_aware,
654 Highlights {
655 text_highlights: Some(&self.text_highlights),
656 inlay_highlights: Some(&self.inlay_highlights),
657 styles: highlight_styles,
658 },
659 )
660 }
661
662 pub fn highlighted_chunks<'a>(
663 &'a self,
664 display_rows: Range<DisplayRow>,
665 language_aware: bool,
666 editor_style: &'a EditorStyle,
667 ) -> impl Iterator<Item = HighlightedChunk<'a>> {
668 self.chunks(
669 display_rows,
670 language_aware,
671 HighlightStyles {
672 inlay_hint: Some(editor_style.inlay_hints_style),
673 suggestion: Some(editor_style.suggestions_style),
674 },
675 )
676 .map(|chunk| {
677 let mut highlight_style = chunk
678 .syntax_highlight_id
679 .and_then(|id| id.style(&editor_style.syntax));
680
681 if let Some(chunk_highlight) = chunk.highlight_style {
682 if let Some(highlight_style) = highlight_style.as_mut() {
683 highlight_style.highlight(chunk_highlight);
684 } else {
685 highlight_style = Some(chunk_highlight);
686 }
687 }
688
689 let mut diagnostic_highlight = HighlightStyle::default();
690
691 if chunk.is_unnecessary {
692 diagnostic_highlight.fade_out = Some(UNNECESSARY_CODE_FADE);
693 }
694
695 if let Some(severity) = chunk.diagnostic_severity {
696 // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
697 if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
698 let diagnostic_color =
699 super::diagnostic_style(severity, true, &editor_style.status);
700 diagnostic_highlight.underline = Some(UnderlineStyle {
701 color: Some(diagnostic_color),
702 thickness: 1.0.into(),
703 wavy: true,
704 });
705 }
706 }
707
708 if let Some(highlight_style) = highlight_style.as_mut() {
709 highlight_style.highlight(diagnostic_highlight);
710 } else {
711 highlight_style = Some(diagnostic_highlight);
712 }
713
714 HighlightedChunk {
715 text: chunk.text,
716 style: highlight_style,
717 is_tab: chunk.is_tab,
718 renderer: chunk.renderer,
719 }
720 })
721 }
722
723 pub fn layout_row(
724 &self,
725 display_row: DisplayRow,
726 TextLayoutDetails {
727 text_system,
728 editor_style,
729 rem_size,
730 scroll_anchor: _,
731 visible_rows: _,
732 vertical_scroll_margin: _,
733 }: &TextLayoutDetails,
734 ) -> Arc<LineLayout> {
735 let mut runs = Vec::new();
736 let mut line = String::new();
737
738 let range = display_row..display_row.next_row();
739 for chunk in self.highlighted_chunks(range, false, &editor_style) {
740 line.push_str(chunk.text);
741
742 let text_style = if let Some(style) = chunk.style {
743 Cow::Owned(editor_style.text.clone().highlight(style))
744 } else {
745 Cow::Borrowed(&editor_style.text)
746 };
747
748 runs.push(text_style.to_run(chunk.text.len()))
749 }
750
751 if line.ends_with('\n') {
752 line.pop();
753 if let Some(last_run) = runs.last_mut() {
754 last_run.len -= 1;
755 if last_run.len == 0 {
756 runs.pop();
757 }
758 }
759 }
760
761 let font_size = editor_style.text.font_size.to_pixels(*rem_size);
762 text_system
763 .layout_line(&line, font_size, &runs)
764 .expect("we expect the font to be loaded because it's rendered by the editor")
765 }
766
767 pub fn x_for_display_point(
768 &self,
769 display_point: DisplayPoint,
770 text_layout_details: &TextLayoutDetails,
771 ) -> Pixels {
772 let line = self.layout_row(display_point.row(), text_layout_details);
773 line.x_for_index(display_point.column() as usize)
774 }
775
776 pub fn display_column_for_x(
777 &self,
778 display_row: DisplayRow,
779 x: Pixels,
780 details: &TextLayoutDetails,
781 ) -> u32 {
782 let layout_line = self.layout_row(display_row, details);
783 layout_line.closest_index_for_x(x) as u32
784 }
785
786 pub fn display_chars_at(
787 &self,
788 mut point: DisplayPoint,
789 ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
790 point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
791 self.text_chunks(point.row())
792 .flat_map(str::chars)
793 .skip_while({
794 let mut column = 0;
795 move |char| {
796 let at_point = column >= point.column();
797 column += char.len_utf8() as u32;
798 !at_point
799 }
800 })
801 .map(move |ch| {
802 let result = (ch, point);
803 if ch == '\n' {
804 *point.row_mut() += 1;
805 *point.column_mut() = 0;
806 } else {
807 *point.column_mut() += ch.len_utf8() as u32;
808 }
809 result
810 })
811 }
812
813 pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
814 self.buffer_snapshot.chars_at(offset).map(move |ch| {
815 let ret = (ch, offset);
816 offset += ch.len_utf8();
817 ret
818 })
819 }
820
821 pub fn reverse_buffer_chars_at(
822 &self,
823 mut offset: usize,
824 ) -> impl Iterator<Item = (char, usize)> + '_ {
825 self.buffer_snapshot
826 .reversed_chars_at(offset)
827 .map(move |ch| {
828 offset -= ch.len_utf8();
829 (ch, offset)
830 })
831 }
832
833 pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
834 let mut clipped = self.block_snapshot.clip_point(point.0, bias);
835 if self.clip_at_line_ends {
836 clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
837 }
838 DisplayPoint(clipped)
839 }
840
841 pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
842 DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
843 }
844
845 pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
846 let mut point = point.0;
847 if point.column == self.line_len(DisplayRow(point.row)) {
848 point.column = point.column.saturating_sub(1);
849 point = self.block_snapshot.clip_point(point, Bias::Left);
850 }
851 DisplayPoint(point)
852 }
853
854 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
855 where
856 T: ToOffset,
857 {
858 self.fold_snapshot.folds_in_range(range)
859 }
860
861 pub fn blocks_in_range(
862 &self,
863 rows: Range<DisplayRow>,
864 ) -> impl Iterator<Item = (DisplayRow, &TransformBlock)> {
865 self.block_snapshot
866 .blocks_in_range(rows.start.0..rows.end.0)
867 .map(|(row, block)| (DisplayRow(row), block))
868 }
869
870 pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
871 self.fold_snapshot.intersects_fold(offset)
872 }
873
874 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
875 self.fold_snapshot.is_line_folded(buffer_row)
876 }
877
878 pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
879 self.block_snapshot.is_block_line(BlockRow(display_row.0))
880 }
881
882 pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
883 let wrap_row = self
884 .block_snapshot
885 .to_wrap_point(BlockPoint::new(display_row.0, 0))
886 .row();
887 self.wrap_snapshot.soft_wrap_indent(wrap_row)
888 }
889
890 pub fn text(&self) -> String {
891 self.text_chunks(DisplayRow(0)).collect()
892 }
893
894 pub fn line(&self, display_row: DisplayRow) -> String {
895 let mut result = String::new();
896 for chunk in self.text_chunks(display_row) {
897 if let Some(ix) = chunk.find('\n') {
898 result.push_str(&chunk[0..ix]);
899 break;
900 } else {
901 result.push_str(chunk);
902 }
903 }
904 result
905 }
906
907 pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
908 let (buffer, range) = self
909 .buffer_snapshot
910 .buffer_line_for_row(buffer_row)
911 .unwrap();
912
913 buffer.line_indent_for_row(range.start.row)
914 }
915
916 pub fn line_len(&self, row: DisplayRow) -> u32 {
917 self.block_snapshot.line_len(BlockRow(row.0))
918 }
919
920 pub fn longest_row(&self) -> DisplayRow {
921 DisplayRow(self.block_snapshot.longest_row())
922 }
923
924 pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
925 let max_row = self.buffer_snapshot.max_buffer_row();
926 if buffer_row >= max_row {
927 return false;
928 }
929
930 let line_indent = self.line_indent_for_buffer_row(buffer_row);
931 if line_indent.is_line_blank() {
932 return false;
933 }
934
935 for next_row in (buffer_row.0 + 1)..=max_row.0 {
936 let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
937 if next_line_indent.raw_len() > line_indent.raw_len() {
938 return true;
939 } else if !next_line_indent.is_line_blank() {
940 break;
941 }
942 }
943
944 false
945 }
946
947 pub fn foldable_range(
948 &self,
949 buffer_row: MultiBufferRow,
950 ) -> Option<(Range<Point>, FoldPlaceholder)> {
951 let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
952 if let Some(flap) = self
953 .flap_snapshot
954 .query_row(buffer_row, &self.buffer_snapshot)
955 {
956 Some((
957 flap.range.to_point(&self.buffer_snapshot),
958 flap.placeholder.clone(),
959 ))
960 } else if self.starts_indent(MultiBufferRow(start.row))
961 && !self.is_line_folded(MultiBufferRow(start.row))
962 {
963 let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
964 let max_point = self.buffer_snapshot.max_point();
965 let mut end = None;
966
967 for row in (buffer_row.0 + 1)..=max_point.row {
968 let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
969 if !line_indent.is_line_blank()
970 && line_indent.raw_len() <= start_line_indent.raw_len()
971 {
972 let prev_row = row - 1;
973 end = Some(Point::new(
974 prev_row,
975 self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
976 ));
977 break;
978 }
979 }
980 let end = end.unwrap_or(max_point);
981 Some((start..end, self.fold_placeholder.clone()))
982 } else {
983 None
984 }
985 }
986
987 #[cfg(any(test, feature = "test-support"))]
988 pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
989 &self,
990 ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
991 let type_id = TypeId::of::<Tag>();
992 self.text_highlights.get(&Some(type_id)).cloned()
993 }
994
995 #[allow(unused)]
996 #[cfg(any(test, feature = "test-support"))]
997 pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
998 &self,
999 ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
1000 let type_id = TypeId::of::<Tag>();
1001 self.inlay_highlights.get(&type_id)
1002 }
1003}
1004
1005#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
1006pub struct DisplayPoint(BlockPoint);
1007
1008impl Debug for DisplayPoint {
1009 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1010 f.write_fmt(format_args!(
1011 "DisplayPoint({}, {})",
1012 self.row().0,
1013 self.column()
1014 ))
1015 }
1016}
1017
1018#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
1019#[serde(transparent)]
1020pub struct DisplayRow(pub u32);
1021
1022impl Add for DisplayRow {
1023 type Output = Self;
1024
1025 fn add(self, other: Self) -> Self::Output {
1026 DisplayRow(self.0 + other.0)
1027 }
1028}
1029
1030impl DisplayPoint {
1031 pub fn new(row: DisplayRow, column: u32) -> Self {
1032 Self(BlockPoint(Point::new(row.0, column)))
1033 }
1034
1035 pub fn zero() -> Self {
1036 Self::new(DisplayRow(0), 0)
1037 }
1038
1039 pub fn is_zero(&self) -> bool {
1040 self.0.is_zero()
1041 }
1042
1043 pub fn row(self) -> DisplayRow {
1044 DisplayRow(self.0.row)
1045 }
1046
1047 pub fn column(self) -> u32 {
1048 self.0.column
1049 }
1050
1051 pub fn row_mut(&mut self) -> &mut u32 {
1052 &mut self.0.row
1053 }
1054
1055 pub fn column_mut(&mut self) -> &mut u32 {
1056 &mut self.0.column
1057 }
1058
1059 pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1060 map.display_point_to_point(self, Bias::Left)
1061 }
1062
1063 pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1064 let wrap_point = map.block_snapshot.to_wrap_point(self.0);
1065 let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
1066 let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
1067 let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
1068 map.inlay_snapshot
1069 .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
1070 }
1071}
1072
1073impl ToDisplayPoint for usize {
1074 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1075 map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
1076 }
1077}
1078
1079impl ToDisplayPoint for OffsetUtf16 {
1080 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1081 self.to_offset(&map.buffer_snapshot).to_display_point(map)
1082 }
1083}
1084
1085impl ToDisplayPoint for Point {
1086 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1087 map.point_to_display_point(*self, Bias::Left)
1088 }
1089}
1090
1091impl ToDisplayPoint for Anchor {
1092 fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1093 self.to_point(&map.buffer_snapshot).to_display_point(map)
1094 }
1095}
1096
1097#[cfg(test)]
1098pub mod tests {
1099 use super::*;
1100 use crate::{
1101 movement,
1102 test::{editor_test_context::EditorTestContext, marked_display_snapshot},
1103 };
1104 use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
1105 use language::{
1106 language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
1107 Buffer, Language, LanguageConfig, LanguageMatcher, SelectionGoal,
1108 };
1109 use project::Project;
1110 use rand::{prelude::*, Rng};
1111 use settings::SettingsStore;
1112 use smol::stream::StreamExt;
1113 use std::{env, sync::Arc};
1114 use theme::{LoadThemes, SyntaxTheme};
1115 use util::test::{marked_text_ranges, sample_text};
1116 use Bias::*;
1117
1118 #[gpui::test(iterations = 100)]
1119 async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1120 cx.background_executor.set_block_on_ticks(0..=50);
1121 let operations = env::var("OPERATIONS")
1122 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1123 .unwrap_or(10);
1124
1125 let mut tab_size = rng.gen_range(1..=4);
1126 let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
1127 let excerpt_header_height = rng.gen_range(1..=5);
1128 let font_size = px(14.0);
1129 let max_wrap_width = 300.0;
1130 let mut wrap_width = if rng.gen_bool(0.1) {
1131 None
1132 } else {
1133 Some(px(rng.gen_range(0.0..=max_wrap_width)))
1134 };
1135
1136 log::info!("tab size: {}", tab_size);
1137 log::info!("wrap width: {:?}", wrap_width);
1138
1139 cx.update(|cx| {
1140 init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
1141 });
1142
1143 let buffer = cx.update(|cx| {
1144 if rng.gen() {
1145 let len = rng.gen_range(0..10);
1146 let text = util::RandomCharIter::new(&mut rng)
1147 .take(len)
1148 .collect::<String>();
1149 MultiBuffer::build_simple(&text, cx)
1150 } else {
1151 MultiBuffer::build_random(&mut rng, cx)
1152 }
1153 });
1154
1155 let map = cx.new_model(|cx| {
1156 DisplayMap::new(
1157 buffer.clone(),
1158 font("Helvetica"),
1159 font_size,
1160 wrap_width,
1161 true,
1162 buffer_start_excerpt_header_height,
1163 excerpt_header_height,
1164 0,
1165 FoldPlaceholder::test(),
1166 cx,
1167 )
1168 });
1169 let mut notifications = observe(&map, cx);
1170 let mut fold_count = 0;
1171 let mut blocks = Vec::new();
1172
1173 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1174 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1175 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1176 log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1177 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1178 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1179 log::info!("display text: {:?}", snapshot.text());
1180
1181 for _i in 0..operations {
1182 match rng.gen_range(0..100) {
1183 0..=19 => {
1184 wrap_width = if rng.gen_bool(0.2) {
1185 None
1186 } else {
1187 Some(px(rng.gen_range(0.0..=max_wrap_width)))
1188 };
1189 log::info!("setting wrap width to {:?}", wrap_width);
1190 map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1191 }
1192 20..=29 => {
1193 let mut tab_sizes = vec![1, 2, 3, 4];
1194 tab_sizes.remove((tab_size - 1) as usize);
1195 tab_size = *tab_sizes.choose(&mut rng).unwrap();
1196 log::info!("setting tab size to {:?}", tab_size);
1197 cx.update(|cx| {
1198 cx.update_global::<SettingsStore, _>(|store, cx| {
1199 store.update_user_settings::<AllLanguageSettings>(cx, |s| {
1200 s.defaults.tab_size = NonZeroU32::new(tab_size);
1201 });
1202 });
1203 });
1204 }
1205 30..=44 => {
1206 map.update(cx, |map, cx| {
1207 if rng.gen() || blocks.is_empty() {
1208 let buffer = map.snapshot(cx).buffer_snapshot;
1209 let block_properties = (0..rng.gen_range(1..=1))
1210 .map(|_| {
1211 let position =
1212 buffer.anchor_after(buffer.clip_offset(
1213 rng.gen_range(0..=buffer.len()),
1214 Bias::Left,
1215 ));
1216
1217 let disposition = if rng.gen() {
1218 BlockDisposition::Above
1219 } else {
1220 BlockDisposition::Below
1221 };
1222 let height = rng.gen_range(1..5);
1223 log::info!(
1224 "inserting block {:?} {:?} with height {}",
1225 disposition,
1226 position.to_point(&buffer),
1227 height
1228 );
1229 BlockProperties {
1230 style: BlockStyle::Fixed,
1231 position,
1232 height,
1233 disposition,
1234 render: Box::new(|_| div().into_any()),
1235 }
1236 })
1237 .collect::<Vec<_>>();
1238 blocks.extend(map.insert_blocks(block_properties, cx));
1239 } else {
1240 blocks.shuffle(&mut rng);
1241 let remove_count = rng.gen_range(1..=4.min(blocks.len()));
1242 let block_ids_to_remove = (0..remove_count)
1243 .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
1244 .collect();
1245 log::info!("removing block ids {:?}", block_ids_to_remove);
1246 map.remove_blocks(block_ids_to_remove, cx);
1247 }
1248 });
1249 }
1250 45..=79 => {
1251 let mut ranges = Vec::new();
1252 for _ in 0..rng.gen_range(1..=3) {
1253 buffer.read_with(cx, |buffer, cx| {
1254 let buffer = buffer.read(cx);
1255 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1256 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1257 ranges.push(start..end);
1258 });
1259 }
1260
1261 if rng.gen() && fold_count > 0 {
1262 log::info!("unfolding ranges: {:?}", ranges);
1263 map.update(cx, |map, cx| {
1264 map.unfold(ranges, true, cx);
1265 });
1266 } else {
1267 log::info!("folding ranges: {:?}", ranges);
1268 map.update(cx, |map, cx| {
1269 map.fold(
1270 ranges
1271 .into_iter()
1272 .map(|range| (range, FoldPlaceholder::test())),
1273 cx,
1274 );
1275 });
1276 }
1277 }
1278 _ => {
1279 buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1280 }
1281 }
1282
1283 if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1284 notifications.next().await.unwrap();
1285 }
1286
1287 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1288 fold_count = snapshot.fold_count();
1289 log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1290 log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1291 log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1292 log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1293 log::info!("block text: {:?}", snapshot.block_snapshot.text());
1294 log::info!("display text: {:?}", snapshot.text());
1295
1296 // Line boundaries
1297 let buffer = &snapshot.buffer_snapshot;
1298 for _ in 0..5 {
1299 let row = rng.gen_range(0..=buffer.max_point().row);
1300 let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
1301 let point = buffer.clip_point(Point::new(row, column), Left);
1302
1303 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1304 let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1305
1306 assert!(prev_buffer_bound <= point);
1307 assert!(next_buffer_bound >= point);
1308 assert_eq!(prev_buffer_bound.column, 0);
1309 assert_eq!(prev_display_bound.column(), 0);
1310 if next_buffer_bound < buffer.max_point() {
1311 assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1312 }
1313
1314 assert_eq!(
1315 prev_display_bound,
1316 prev_buffer_bound.to_display_point(&snapshot),
1317 "row boundary before {:?}. reported buffer row boundary: {:?}",
1318 point,
1319 prev_buffer_bound
1320 );
1321 assert_eq!(
1322 next_display_bound,
1323 next_buffer_bound.to_display_point(&snapshot),
1324 "display row boundary after {:?}. reported buffer row boundary: {:?}",
1325 point,
1326 next_buffer_bound
1327 );
1328 assert_eq!(
1329 prev_buffer_bound,
1330 prev_display_bound.to_point(&snapshot),
1331 "row boundary before {:?}. reported display row boundary: {:?}",
1332 point,
1333 prev_display_bound
1334 );
1335 assert_eq!(
1336 next_buffer_bound,
1337 next_display_bound.to_point(&snapshot),
1338 "row boundary after {:?}. reported display row boundary: {:?}",
1339 point,
1340 next_display_bound
1341 );
1342 }
1343
1344 // Movement
1345 let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1346 let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1347 for _ in 0..5 {
1348 let row = rng.gen_range(0..=snapshot.max_point().row().0);
1349 let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
1350 let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1351
1352 log::info!("Moving from point {:?}", point);
1353
1354 let moved_right = movement::right(&snapshot, point);
1355 log::info!("Right {:?}", moved_right);
1356 if point < max_point {
1357 assert!(moved_right > point);
1358 if point.column() == snapshot.line_len(point.row())
1359 || snapshot.soft_wrap_indent(point.row()).is_some()
1360 && point.column() == snapshot.line_len(point.row()) - 1
1361 {
1362 assert!(moved_right.row() > point.row());
1363 }
1364 } else {
1365 assert_eq!(moved_right, point);
1366 }
1367
1368 let moved_left = movement::left(&snapshot, point);
1369 log::info!("Left {:?}", moved_left);
1370 if point > min_point {
1371 assert!(moved_left < point);
1372 if point.column() == 0 {
1373 assert!(moved_left.row() < point.row());
1374 }
1375 } else {
1376 assert_eq!(moved_left, point);
1377 }
1378 }
1379 }
1380 }
1381
1382 #[gpui::test(retries = 5)]
1383 async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1384 cx.background_executor
1385 .set_block_on_ticks(usize::MAX..=usize::MAX);
1386 cx.update(|cx| {
1387 init_test(cx, |_| {});
1388 });
1389
1390 let mut cx = EditorTestContext::new(cx).await;
1391 let editor = cx.editor.clone();
1392 let window = cx.window;
1393
1394 _ = cx.update_window(window, |_, cx| {
1395 let text_layout_details =
1396 editor.update(cx, |editor, cx| editor.text_layout_details(cx));
1397
1398 let font_size = px(12.0);
1399 let wrap_width = Some(px(64.));
1400
1401 let text = "one two three four five\nsix seven eight";
1402 let buffer = MultiBuffer::build_simple(text, cx);
1403 let map = cx.new_model(|cx| {
1404 DisplayMap::new(
1405 buffer.clone(),
1406 font("Helvetica"),
1407 font_size,
1408 wrap_width,
1409 true,
1410 1,
1411 1,
1412 0,
1413 FoldPlaceholder::test(),
1414 cx,
1415 )
1416 });
1417
1418 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1419 assert_eq!(
1420 snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1421 "one two \nthree four \nfive\nsix seven \neight"
1422 );
1423 assert_eq!(
1424 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1425 DisplayPoint::new(DisplayRow(0), 7)
1426 );
1427 assert_eq!(
1428 snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1429 DisplayPoint::new(DisplayRow(1), 0)
1430 );
1431 assert_eq!(
1432 movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1433 DisplayPoint::new(DisplayRow(1), 0)
1434 );
1435 assert_eq!(
1436 movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1437 DisplayPoint::new(DisplayRow(0), 7)
1438 );
1439
1440 let x = snapshot
1441 .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1442 assert_eq!(
1443 movement::up(
1444 &snapshot,
1445 DisplayPoint::new(DisplayRow(1), 10),
1446 SelectionGoal::None,
1447 false,
1448 &text_layout_details,
1449 ),
1450 (
1451 DisplayPoint::new(DisplayRow(0), 7),
1452 SelectionGoal::HorizontalPosition(x.0)
1453 )
1454 );
1455 assert_eq!(
1456 movement::down(
1457 &snapshot,
1458 DisplayPoint::new(DisplayRow(0), 7),
1459 SelectionGoal::HorizontalPosition(x.0),
1460 false,
1461 &text_layout_details
1462 ),
1463 (
1464 DisplayPoint::new(DisplayRow(1), 10),
1465 SelectionGoal::HorizontalPosition(x.0)
1466 )
1467 );
1468 assert_eq!(
1469 movement::down(
1470 &snapshot,
1471 DisplayPoint::new(DisplayRow(1), 10),
1472 SelectionGoal::HorizontalPosition(x.0),
1473 false,
1474 &text_layout_details
1475 ),
1476 (
1477 DisplayPoint::new(DisplayRow(2), 4),
1478 SelectionGoal::HorizontalPosition(x.0)
1479 )
1480 );
1481
1482 let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1483 buffer.update(cx, |buffer, cx| {
1484 buffer.edit([(ix..ix, "and ")], None, cx);
1485 });
1486
1487 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1488 assert_eq!(
1489 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1490 "three four \nfive\nsix and \nseven eight"
1491 );
1492
1493 // Re-wrap on font size changes
1494 map.update(cx, |map, cx| {
1495 map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
1496 });
1497
1498 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1499 assert_eq!(
1500 snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1501 "three \nfour five\nsix and \nseven \neight"
1502 )
1503 });
1504 }
1505
1506 #[gpui::test]
1507 fn test_text_chunks(cx: &mut gpui::AppContext) {
1508 init_test(cx, |_| {});
1509
1510 let text = sample_text(6, 6, 'a');
1511 let buffer = MultiBuffer::build_simple(&text, cx);
1512
1513 let font_size = px(14.0);
1514 let map = cx.new_model(|cx| {
1515 DisplayMap::new(
1516 buffer.clone(),
1517 font("Helvetica"),
1518 font_size,
1519 None,
1520 true,
1521 1,
1522 1,
1523 0,
1524 FoldPlaceholder::test(),
1525 cx,
1526 )
1527 });
1528
1529 buffer.update(cx, |buffer, cx| {
1530 buffer.edit(
1531 vec![
1532 (
1533 MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
1534 "\t",
1535 ),
1536 (
1537 MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
1538 "\t",
1539 ),
1540 (
1541 MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
1542 "\t",
1543 ),
1544 ],
1545 None,
1546 cx,
1547 )
1548 });
1549
1550 assert_eq!(
1551 map.update(cx, |map, cx| map.snapshot(cx))
1552 .text_chunks(DisplayRow(1))
1553 .collect::<String>()
1554 .lines()
1555 .next(),
1556 Some(" b bbbbb")
1557 );
1558 assert_eq!(
1559 map.update(cx, |map, cx| map.snapshot(cx))
1560 .text_chunks(DisplayRow(2))
1561 .collect::<String>()
1562 .lines()
1563 .next(),
1564 Some("c ccccc")
1565 );
1566 }
1567
1568 #[gpui::test]
1569 async fn test_chunks(cx: &mut gpui::TestAppContext) {
1570 use unindent::Unindent as _;
1571
1572 let text = r#"
1573 fn outer() {}
1574
1575 mod module {
1576 fn inner() {}
1577 }"#
1578 .unindent();
1579
1580 let theme =
1581 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1582 let language = Arc::new(
1583 Language::new(
1584 LanguageConfig {
1585 name: "Test".into(),
1586 matcher: LanguageMatcher {
1587 path_suffixes: vec![".test".to_string()],
1588 ..Default::default()
1589 },
1590 ..Default::default()
1591 },
1592 Some(tree_sitter_rust::language()),
1593 )
1594 .with_highlights_query(
1595 r#"
1596 (mod_item name: (identifier) body: _ @mod.body)
1597 (function_item name: (identifier) @fn.name)
1598 "#,
1599 )
1600 .unwrap(),
1601 );
1602 language.set_theme(&theme);
1603
1604 cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
1605
1606 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1607 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1608 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1609
1610 let font_size = px(14.0);
1611
1612 let map = cx.new_model(|cx| {
1613 DisplayMap::new(
1614 buffer,
1615 font("Helvetica"),
1616 font_size,
1617 None,
1618 true,
1619 1,
1620 1,
1621 1,
1622 FoldPlaceholder::test(),
1623 cx,
1624 )
1625 });
1626 assert_eq!(
1627 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1628 vec![
1629 ("fn ".to_string(), None),
1630 ("outer".to_string(), Some(Hsla::blue())),
1631 ("() {}\n\nmod module ".to_string(), None),
1632 ("{\n fn ".to_string(), Some(Hsla::red())),
1633 ("inner".to_string(), Some(Hsla::blue())),
1634 ("() {}\n}".to_string(), Some(Hsla::red())),
1635 ]
1636 );
1637 assert_eq!(
1638 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1639 vec![
1640 (" fn ".to_string(), Some(Hsla::red())),
1641 ("inner".to_string(), Some(Hsla::blue())),
1642 ("() {}\n}".to_string(), Some(Hsla::red())),
1643 ]
1644 );
1645
1646 map.update(cx, |map, cx| {
1647 map.fold(
1648 vec![(
1649 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1650 FoldPlaceholder::test(),
1651 )],
1652 cx,
1653 )
1654 });
1655 assert_eq!(
1656 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
1657 vec![
1658 ("fn ".to_string(), None),
1659 ("out".to_string(), Some(Hsla::blue())),
1660 ("⋯".to_string(), None),
1661 (" fn ".to_string(), Some(Hsla::red())),
1662 ("inner".to_string(), Some(Hsla::blue())),
1663 ("() {}\n}".to_string(), Some(Hsla::red())),
1664 ]
1665 );
1666 }
1667
1668 #[gpui::test]
1669 async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1670 use unindent::Unindent as _;
1671
1672 cx.background_executor
1673 .set_block_on_ticks(usize::MAX..=usize::MAX);
1674
1675 let text = r#"
1676 fn outer() {}
1677
1678 mod module {
1679 fn inner() {}
1680 }"#
1681 .unindent();
1682
1683 let theme =
1684 SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1685 let language = Arc::new(
1686 Language::new(
1687 LanguageConfig {
1688 name: "Test".into(),
1689 matcher: LanguageMatcher {
1690 path_suffixes: vec![".test".to_string()],
1691 ..Default::default()
1692 },
1693 ..Default::default()
1694 },
1695 Some(tree_sitter_rust::language()),
1696 )
1697 .with_highlights_query(
1698 r#"
1699 (mod_item name: (identifier) body: _ @mod.body)
1700 (function_item name: (identifier) @fn.name)
1701 "#,
1702 )
1703 .unwrap(),
1704 );
1705 language.set_theme(&theme);
1706
1707 cx.update(|cx| init_test(cx, |_| {}));
1708
1709 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1710 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1711 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1712
1713 let font_size = px(16.0);
1714
1715 let map = cx.new_model(|cx| {
1716 DisplayMap::new(
1717 buffer,
1718 font("Courier"),
1719 font_size,
1720 Some(px(40.0)),
1721 true,
1722 1,
1723 1,
1724 0,
1725 FoldPlaceholder::test(),
1726 cx,
1727 )
1728 });
1729 assert_eq!(
1730 cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1731 [
1732 ("fn \n".to_string(), None),
1733 ("oute\nr".to_string(), Some(Hsla::blue())),
1734 ("() \n{}\n\n".to_string(), None),
1735 ]
1736 );
1737 assert_eq!(
1738 cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1739 [("{}\n\n".to_string(), None)]
1740 );
1741
1742 map.update(cx, |map, cx| {
1743 map.fold(
1744 vec![(
1745 MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1746 FoldPlaceholder::test(),
1747 )],
1748 cx,
1749 )
1750 });
1751 assert_eq!(
1752 cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
1753 [
1754 ("out".to_string(), Some(Hsla::blue())),
1755 ("⋯\n".to_string(), None),
1756 (" \nfn ".to_string(), Some(Hsla::red())),
1757 ("i\n".to_string(), Some(Hsla::blue()))
1758 ]
1759 );
1760 }
1761
1762 #[gpui::test]
1763 async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1764 cx.update(|cx| init_test(cx, |_| {}));
1765
1766 let theme =
1767 SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
1768 let language = Arc::new(
1769 Language::new(
1770 LanguageConfig {
1771 name: "Test".into(),
1772 matcher: LanguageMatcher {
1773 path_suffixes: vec![".test".to_string()],
1774 ..Default::default()
1775 },
1776 ..Default::default()
1777 },
1778 Some(tree_sitter_rust::language()),
1779 )
1780 .with_highlights_query(
1781 r#"
1782 ":" @operator
1783 (string_literal) @string
1784 "#,
1785 )
1786 .unwrap(),
1787 );
1788 language.set_theme(&theme);
1789
1790 let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1791
1792 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1793 cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1794
1795 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1796 let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1797
1798 let font_size = px(16.0);
1799 let map = cx.new_model(|cx| {
1800 DisplayMap::new(
1801 buffer,
1802 font("Courier"),
1803 font_size,
1804 None,
1805 true,
1806 1,
1807 1,
1808 1,
1809 FoldPlaceholder::test(),
1810 cx,
1811 )
1812 });
1813
1814 enum MyType {}
1815
1816 let style = HighlightStyle {
1817 color: Some(Hsla::blue()),
1818 ..Default::default()
1819 };
1820
1821 map.update(cx, |map, _cx| {
1822 map.highlight_text(
1823 TypeId::of::<MyType>(),
1824 highlighted_ranges
1825 .into_iter()
1826 .map(|range| {
1827 buffer_snapshot.anchor_before(range.start)
1828 ..buffer_snapshot.anchor_before(range.end)
1829 })
1830 .collect(),
1831 style,
1832 );
1833 });
1834
1835 assert_eq!(
1836 cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
1837 [
1838 ("const ".to_string(), None, None),
1839 ("a".to_string(), None, Some(Hsla::blue())),
1840 (":".to_string(), Some(Hsla::red()), None),
1841 (" B = ".to_string(), None, None),
1842 ("\"c ".to_string(), Some(Hsla::green()), None),
1843 ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
1844 ("\"".to_string(), Some(Hsla::green()), None),
1845 ]
1846 );
1847 }
1848
1849 #[gpui::test]
1850 fn test_clip_point(cx: &mut gpui::AppContext) {
1851 init_test(cx, |_| {});
1852
1853 fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
1854 let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1855
1856 match bias {
1857 Bias::Left => {
1858 if shift_right {
1859 *markers[1].column_mut() += 1;
1860 }
1861
1862 assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1863 }
1864 Bias::Right => {
1865 if shift_right {
1866 *markers[0].column_mut() += 1;
1867 }
1868
1869 assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1870 }
1871 };
1872 }
1873
1874 use Bias::{Left, Right};
1875 assert("ˇˇα", false, Left, cx);
1876 assert("ˇˇα", true, Left, cx);
1877 assert("ˇˇα", false, Right, cx);
1878 assert("ˇαˇ", true, Right, cx);
1879 assert("ˇˇ✋", false, Left, cx);
1880 assert("ˇˇ✋", true, Left, cx);
1881 assert("ˇˇ✋", false, Right, cx);
1882 assert("ˇ✋ˇ", true, Right, cx);
1883 assert("ˇˇ🍐", false, Left, cx);
1884 assert("ˇˇ🍐", true, Left, cx);
1885 assert("ˇˇ🍐", false, Right, cx);
1886 assert("ˇ🍐ˇ", true, Right, cx);
1887 assert("ˇˇ\t", false, Left, cx);
1888 assert("ˇˇ\t", true, Left, cx);
1889 assert("ˇˇ\t", false, Right, cx);
1890 assert("ˇ\tˇ", true, Right, cx);
1891 assert(" ˇˇ\t", false, Left, cx);
1892 assert(" ˇˇ\t", true, Left, cx);
1893 assert(" ˇˇ\t", false, Right, cx);
1894 assert(" ˇ\tˇ", true, Right, cx);
1895 assert(" ˇˇ\t", false, Left, cx);
1896 assert(" ˇˇ\t", false, Right, cx);
1897 }
1898
1899 #[gpui::test]
1900 fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
1901 init_test(cx, |_| {});
1902
1903 fn assert(text: &str, cx: &mut gpui::AppContext) {
1904 let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1905 unmarked_snapshot.clip_at_line_ends = true;
1906 assert_eq!(
1907 unmarked_snapshot.clip_point(markers[1], Bias::Left),
1908 markers[0]
1909 );
1910 }
1911
1912 assert("ˇˇ", cx);
1913 assert("ˇaˇ", cx);
1914 assert("aˇbˇ", cx);
1915 assert("aˇαˇ", cx);
1916 }
1917
1918 #[gpui::test]
1919 fn test_flaps(cx: &mut gpui::AppContext) {
1920 init_test(cx, |_| {});
1921
1922 let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
1923 let buffer = MultiBuffer::build_simple(text, cx);
1924 let font_size = px(14.0);
1925 cx.new_model(|cx| {
1926 let mut map = DisplayMap::new(
1927 buffer.clone(),
1928 font("Helvetica"),
1929 font_size,
1930 None,
1931 true,
1932 1,
1933 1,
1934 0,
1935 FoldPlaceholder::test(),
1936 cx,
1937 );
1938 let snapshot = map.buffer.read(cx).snapshot(cx);
1939 let range =
1940 snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
1941
1942 map.flap_map.insert(
1943 [Flap::new(
1944 range,
1945 FoldPlaceholder::test(),
1946 |_row, _status, _toggle, _cx| div(),
1947 |_row, _status, _cx| div(),
1948 )],
1949 &map.buffer.read(cx).snapshot(cx),
1950 );
1951
1952 map
1953 });
1954 }
1955
1956 #[gpui::test]
1957 fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
1958 init_test(cx, |_| {});
1959
1960 let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
1961 let buffer = MultiBuffer::build_simple(text, cx);
1962 let font_size = px(14.0);
1963
1964 let map = cx.new_model(|cx| {
1965 DisplayMap::new(
1966 buffer.clone(),
1967 font("Helvetica"),
1968 font_size,
1969 None,
1970 true,
1971 1,
1972 1,
1973 0,
1974 FoldPlaceholder::test(),
1975 cx,
1976 )
1977 });
1978 let map = map.update(cx, |map, cx| map.snapshot(cx));
1979 assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
1980 assert_eq!(
1981 map.text_chunks(DisplayRow(0)).collect::<String>(),
1982 "✅ α\nβ \n🏀β γ"
1983 );
1984 assert_eq!(
1985 map.text_chunks(DisplayRow(1)).collect::<String>(),
1986 "β \n🏀β γ"
1987 );
1988 assert_eq!(
1989 map.text_chunks(DisplayRow(2)).collect::<String>(),
1990 "🏀β γ"
1991 );
1992
1993 let point = MultiBufferPoint::new(0, "✅\t\t".len() as u32);
1994 let display_point = DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32);
1995 assert_eq!(point.to_display_point(&map), display_point);
1996 assert_eq!(display_point.to_point(&map), point);
1997
1998 let point = MultiBufferPoint::new(1, "β\t".len() as u32);
1999 let display_point = DisplayPoint::new(DisplayRow(1), "β ".len() as u32);
2000 assert_eq!(point.to_display_point(&map), display_point);
2001 assert_eq!(display_point.to_point(&map), point,);
2002
2003 let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2004 let display_point = DisplayPoint::new(DisplayRow(2), "🏀β ".len() as u32);
2005 assert_eq!(point.to_display_point(&map), display_point);
2006 assert_eq!(display_point.to_point(&map), point,);
2007
2008 // Display points inside of expanded tabs
2009 assert_eq!(
2010 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2011 MultiBufferPoint::new(0, "✅\t".len() as u32),
2012 );
2013 assert_eq!(
2014 DisplayPoint::new(DisplayRow(0), "✅ ".len() as u32).to_point(&map),
2015 MultiBufferPoint::new(0, "✅".len() as u32),
2016 );
2017
2018 // Clipping display points inside of multi-byte characters
2019 assert_eq!(
2020 map.clip_point(
2021 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2022 Left
2023 ),
2024 DisplayPoint::new(DisplayRow(0), 0)
2025 );
2026 assert_eq!(
2027 map.clip_point(
2028 DisplayPoint::new(DisplayRow(0), "✅".len() as u32 - 1),
2029 Bias::Right
2030 ),
2031 DisplayPoint::new(DisplayRow(0), "✅".len() as u32)
2032 );
2033 }
2034
2035 #[gpui::test]
2036 fn test_max_point(cx: &mut gpui::AppContext) {
2037 init_test(cx, |_| {});
2038
2039 let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2040 let font_size = px(14.0);
2041 let map = cx.new_model(|cx| {
2042 DisplayMap::new(
2043 buffer.clone(),
2044 font("Helvetica"),
2045 font_size,
2046 None,
2047 true,
2048 1,
2049 1,
2050 0,
2051 FoldPlaceholder::test(),
2052 cx,
2053 )
2054 });
2055 assert_eq!(
2056 map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2057 DisplayPoint::new(DisplayRow(1), 11)
2058 )
2059 }
2060
2061 fn syntax_chunks(
2062 rows: Range<DisplayRow>,
2063 map: &Model<DisplayMap>,
2064 theme: &SyntaxTheme,
2065 cx: &mut AppContext,
2066 ) -> Vec<(String, Option<Hsla>)> {
2067 chunks(rows, map, theme, cx)
2068 .into_iter()
2069 .map(|(text, color, _)| (text, color))
2070 .collect()
2071 }
2072
2073 fn chunks(
2074 rows: Range<DisplayRow>,
2075 map: &Model<DisplayMap>,
2076 theme: &SyntaxTheme,
2077 cx: &mut AppContext,
2078 ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
2079 let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2080 let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
2081 for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
2082 let syntax_color = chunk
2083 .syntax_highlight_id
2084 .and_then(|id| id.style(theme)?.color);
2085 let highlight_color = chunk.highlight_style.and_then(|style| style.color);
2086 if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
2087 if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
2088 last_chunk.push_str(chunk.text);
2089 continue;
2090 }
2091 }
2092 chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
2093 }
2094 chunks
2095 }
2096
2097 fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2098 let settings = SettingsStore::test(cx);
2099 cx.set_global(settings);
2100 language::init(cx);
2101 crate::init(cx);
2102 Project::init_settings(cx);
2103 theme::init(LoadThemes::JustBase, cx);
2104 cx.update_global::<SettingsStore, _>(|store, cx| {
2105 store.update_user_settings::<AllLanguageSettings>(cx, f);
2106 });
2107 }
2108}