1use std::cmp;
2
3use crate::InlineCompletion;
4use gpui::{
5 point, prelude::*, quad, size, AnyElement, AppContext, Bounds, Corners, Edges, HighlightStyle,
6 Hsla, StyledText, TextLayout, TextStyle,
7};
8use language::OffsetRangeExt;
9use settings::Settings;
10use theme::ThemeSettings;
11use ui::prelude::*;
12
13pub struct CompletionDiffElement {
14 element: AnyElement,
15 text_layout: TextLayout,
16 cursor_offset: usize,
17}
18
19impl CompletionDiffElement {
20 pub fn new(completion: &InlineCompletion, cx: &AppContext) -> Self {
21 let mut diff = completion
22 .snapshot
23 .text_for_range(completion.excerpt_range.clone())
24 .collect::<String>();
25
26 let mut cursor_offset_in_diff = None;
27 let mut delta = 0;
28 let mut diff_highlights = Vec::new();
29 for (old_range, new_text) in completion.edits.iter() {
30 let old_range = old_range.to_offset(&completion.snapshot);
31
32 if cursor_offset_in_diff.is_none() && completion.cursor_offset <= old_range.end {
33 cursor_offset_in_diff =
34 Some(completion.cursor_offset - completion.excerpt_range.start + delta);
35 }
36
37 let old_start_in_diff = old_range.start - completion.excerpt_range.start + delta;
38 let old_end_in_diff = old_range.end - completion.excerpt_range.start + delta;
39 if old_start_in_diff < old_end_in_diff {
40 diff_highlights.push((
41 old_start_in_diff..old_end_in_diff,
42 HighlightStyle {
43 background_color: Some(cx.theme().status().deleted_background),
44 strikethrough: Some(gpui::StrikethroughStyle {
45 thickness: px(1.),
46 color: Some(cx.theme().colors().text_muted),
47 }),
48 ..Default::default()
49 },
50 ));
51 }
52
53 if !new_text.is_empty() {
54 diff.insert_str(old_end_in_diff, new_text);
55 diff_highlights.push((
56 old_end_in_diff..old_end_in_diff + new_text.len(),
57 HighlightStyle {
58 background_color: Some(cx.theme().status().created_background),
59 ..Default::default()
60 },
61 ));
62 delta += new_text.len();
63 }
64 }
65
66 let cursor_offset_in_diff = cursor_offset_in_diff
67 .unwrap_or_else(|| completion.cursor_offset - completion.excerpt_range.start + delta);
68
69 let settings = ThemeSettings::get_global(cx).clone();
70 let text_style = TextStyle {
71 color: cx.theme().colors().editor_foreground,
72 font_size: settings.buffer_font_size().into(),
73 font_family: settings.buffer_font.family,
74 font_features: settings.buffer_font.features,
75 font_fallbacks: settings.buffer_font.fallbacks,
76 line_height: relative(settings.buffer_line_height.value()),
77 font_weight: settings.buffer_font.weight,
78 font_style: settings.buffer_font.style,
79 ..Default::default()
80 };
81 let element = StyledText::new(diff).with_highlights(&text_style, diff_highlights);
82 let text_layout = element.layout().clone();
83
84 CompletionDiffElement {
85 element: element.into_any_element(),
86 text_layout,
87 cursor_offset: cursor_offset_in_diff,
88 }
89 }
90}
91
92impl IntoElement for CompletionDiffElement {
93 type Element = Self;
94
95 fn into_element(self) -> Self {
96 self
97 }
98}
99
100impl Element for CompletionDiffElement {
101 type RequestLayoutState = ();
102 type PrepaintState = ();
103
104 fn id(&self) -> Option<ElementId> {
105 None
106 }
107
108 fn request_layout(
109 &mut self,
110 _id: Option<&gpui::GlobalElementId>,
111 cx: &mut WindowContext,
112 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
113 (self.element.request_layout(cx), ())
114 }
115
116 fn prepaint(
117 &mut self,
118 _id: Option<&gpui::GlobalElementId>,
119 _bounds: gpui::Bounds<Pixels>,
120 _request_layout: &mut Self::RequestLayoutState,
121 cx: &mut WindowContext,
122 ) -> Self::PrepaintState {
123 self.element.prepaint(cx);
124 }
125
126 fn paint(
127 &mut self,
128 _id: Option<&gpui::GlobalElementId>,
129 _bounds: gpui::Bounds<Pixels>,
130 _request_layout: &mut Self::RequestLayoutState,
131 _prepaint: &mut Self::PrepaintState,
132 cx: &mut WindowContext,
133 ) {
134 if let Some(position) = self.text_layout.position_for_index(self.cursor_offset) {
135 let bounds = self.text_layout.bounds();
136 let line_height = self.text_layout.line_height();
137 let line_width = self
138 .text_layout
139 .line_layout_for_index(self.cursor_offset)
140 .map_or(bounds.size.width, |layout| layout.width());
141 cx.paint_quad(quad(
142 Bounds::new(
143 point(bounds.origin.x, position.y),
144 size(cmp::max(bounds.size.width, line_width), line_height),
145 ),
146 Corners::default(),
147 cx.theme().colors().editor_active_line_background,
148 Edges::default(),
149 Hsla::transparent_black(),
150 ));
151 self.element.paint(cx);
152 cx.paint_quad(quad(
153 Bounds::new(position, size(px(2.), line_height)),
154 Corners::default(),
155 cx.theme().players().local().cursor,
156 Edges::default(),
157 Hsla::transparent_black(),
158 ));
159 }
160 }
161}