1use std::cmp;
2
3use crate::EditPrediction;
4use gpui::{
5 AnyElement, App, BorderStyle, Bounds, Corners, Edges, HighlightStyle, Hsla, StyledText,
6 TextLayout, TextStyle, point, prelude::*, quad, size,
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: &EditPrediction, cx: &App) -> 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(cx).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_default_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 source_location(&self) -> Option<&'static core::panic::Location<'static>> {
109 None
110 }
111
112 fn request_layout(
113 &mut self,
114 _id: Option<&gpui::GlobalElementId>,
115 _inspector_id: Option<&gpui::InspectorElementId>,
116 window: &mut Window,
117 cx: &mut App,
118 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
119 (self.element.request_layout(window, cx), ())
120 }
121
122 fn prepaint(
123 &mut self,
124 _id: Option<&gpui::GlobalElementId>,
125 _inspector_id: Option<&gpui::InspectorElementId>,
126 _bounds: gpui::Bounds<Pixels>,
127 _request_layout: &mut Self::RequestLayoutState,
128 window: &mut Window,
129 cx: &mut App,
130 ) -> Self::PrepaintState {
131 self.element.prepaint(window, cx);
132 }
133
134 fn paint(
135 &mut self,
136 _id: Option<&gpui::GlobalElementId>,
137 _inspector_id: Option<&gpui::InspectorElementId>,
138 _bounds: gpui::Bounds<Pixels>,
139 _request_layout: &mut Self::RequestLayoutState,
140 _prepaint: &mut Self::PrepaintState,
141 window: &mut Window,
142 cx: &mut App,
143 ) {
144 if let Some(position) = self.text_layout.position_for_index(self.cursor_offset) {
145 let bounds = self.text_layout.bounds();
146 let line_height = self.text_layout.line_height();
147 let line_width = self
148 .text_layout
149 .line_layout_for_index(self.cursor_offset)
150 .map_or(bounds.size.width, |layout| layout.width());
151 window.paint_quad(quad(
152 Bounds::new(
153 point(bounds.origin.x, position.y),
154 size(cmp::max(bounds.size.width, line_width), line_height),
155 ),
156 Corners::default(),
157 cx.theme().colors().editor_active_line_background,
158 Edges::default(),
159 Hsla::transparent_black(),
160 BorderStyle::default(),
161 ));
162 self.element.paint(window, cx);
163 window.paint_quad(quad(
164 Bounds::new(position, size(px(2.), line_height)),
165 Corners::default(),
166 cx.theme().players().local().cursor,
167 Edges::default(),
168 Hsla::transparent_black(),
169 BorderStyle::default(),
170 ));
171 }
172 }
173}