completion_diff_element.rs

  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}