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