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