completion_diff_element.rs

  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}