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