1use std::ops::Range;
4
5use gpui::{
6 App, BorderStyle, Bounds, Font, Global, Hsla, PaintQuad, Pixels, TextRun, Window,
7 WrappedLineLayout, fill, outline, px,
8};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum CaretShape {
13 #[default]
14 Bar,
15 Block,
16 Underline,
17}
18
19impl Global for CaretShape {}
20
21pub(crate) fn caret_shape(cx: &App) -> CaretShape {
22 cx.try_global::<CaretShape>().copied().unwrap_or_default()
23}
24
25pub(crate) fn set_caret_shape(shape: CaretShape, cx: &mut App) {
27 cx.set_global(shape);
28 cx.refresh_windows();
29}
30
31impl CaretShape {
32 pub fn quad(
36 self,
37 mut bar: Bounds<Pixels>,
38 line_height: Pixels,
39 width: Pixels,
40 color: Hsla,
41 hollow: bool,
42 ) -> PaintQuad {
43 if self != Self::Bar {
44 bar.size.width = width;
45 }
46 match self {
47 Self::Bar => fill(bar, color),
48 Self::Block => {
49 bar.origin.y -= (line_height - bar.size.height) / 2.0;
50 bar.size.height = line_height;
51 match hollow {
52 true => outline(bar, color, BorderStyle::Solid),
53 false => fill(bar, color),
54 }
55 }
56 Self::Underline => {
57 let thickness = px(2.0).min(bar.size.height);
58 bar.origin.y += bar.size.height - thickness;
59 bar.size.height = thickness;
60 fill(bar, color)
61 }
62 }
63 }
64
65 pub fn cuts_out(self, hollow: bool) -> bool {
68 self == Self::Block && !hollow
69 }
70}
71
72pub fn zero_width(font: Font, font_size: Pixels, window: &Window) -> Pixels {
75 let run = TextRun {
76 len: 1,
77 font,
78 color: Hsla::default(),
79 background_color: None,
80 underline: None,
81 strikethrough: None,
82 };
83 window
84 .text_system()
85 .shape_line("0".into(), font_size, &[run], None)
86 .width
87}
88
89pub fn recoloured(runs: Vec<TextRun>, range: &Range<usize>, color: Hsla) -> Vec<TextRun> {
91 let mut out = Vec::with_capacity(runs.len() + 2);
92 let mut at = 0;
93 for run in runs {
94 let end = at + run.len;
95 for (start, stop, inside) in [
96 (at, end.min(range.start), false),
97 (at.max(range.start), end.min(range.end), true),
98 (at.max(range.end), end, false),
99 ] {
100 if stop <= start {
101 continue;
102 }
103 out.push(TextRun {
104 len: stop - start,
105 color: if inside { color } else { run.color },
106 ..run.clone()
107 });
108 }
109 at = end;
110 }
111 out
112}
113
114pub fn character_advance(line: &WrappedLineLayout, text: &str, offset: usize) -> Option<Pixels> {
116 if offset >= text.len() || !text.is_char_boundary(offset) {
117 return None;
118 }
119 if line
120 .wrap_boundaries()
121 .iter()
122 .any(|boundary| line.runs()[boundary.run_ix].glyphs[boundary.glyph_ix].index == offset)
123 {
124 return None;
125 }
126 let end = super::next_boundary(text, offset);
127 Some((line.unwrapped_layout.x_for_index(end) - line.unwrapped_layout.x_for_index(offset)).abs())
128}