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