Skip to main content

gpui_component/diff/
decoration.rs

1use std::collections::HashMap;
2
3use gpui::{ElementId, Hsla, SharedString};
4
5use super::{DiffLinePosition, DiffLineRange, DiffSide};
6
7/// A background that marks source lines in place, without changing the
8/// selection, copying or layout.
9///
10/// Decorations paint above the change background and below inline emphasis,
11/// search highlights and text; a selected line shows the selection instead.
12/// Where several decorations cover a line, the last supplied one is shown.
13#[derive(Clone)]
14pub struct DiffDecoration {
15    range: DiffLineRange,
16    background: Option<Hsla>,
17}
18
19impl DiffDecoration {
20    /// Marks the lines of `range`.
21    ///
22    /// A range that ends on the other side covers its start side from
23    /// `start` onwards and its end side up to `end`.
24    pub fn new(range: DiffLineRange) -> Self {
25        Self {
26            range,
27            background: None,
28        }
29    }
30    /// Sets the background, which defaults to a translucent warning color.
31    pub fn with_background(mut self, background: impl Into<Hsla>) -> Self {
32        self.background = Some(background.into());
33        self
34    }
35    pub fn range(&self) -> &DiffLineRange {
36        &self.range
37    }
38    /// The background set with [`Self::with_background`].
39    pub fn background(&self) -> Option<Hsla> {
40        self.background
41    }
42
43    fn contains(&self, side: DiffSide, line: usize) -> bool {
44        let range = &self.range;
45        if range.is_single_side() {
46            side == range.side() && (range.start()..=range.end()).contains(&line)
47        } else if side == range.side() {
48            line >= range.start()
49        } else {
50            line <= range.end()
51        }
52    }
53}
54
55/// An interactive marker in the lane between the line numbers and the code,
56/// such as a comment or diagnostic indicator that opens a details panel.
57///
58/// The application renders it with [`super::Diff::render_gutter_marker`],
59/// usually as a small button, and owns the panel and its focus.
60#[derive(Clone)]
61pub struct DiffGutterMarker {
62    id: ElementId,
63    position: DiffLinePosition,
64    label: SharedString,
65}
66
67impl DiffGutterMarker {
68    /// Places a marker beside a source line.
69    pub fn new(id: impl Into<ElementId>, position: DiffLinePosition) -> Self {
70        Self {
71            id: id.into(),
72            position,
73            label: SharedString::default(),
74        }
75    }
76    /// Sets the accessible label that the rendered marker should announce.
77    pub fn with_label(mut self, label: impl Into<SharedString>) -> Self {
78        self.label = label.into();
79        self
80    }
81    pub fn id(&self) -> &ElementId {
82        &self.id
83    }
84    pub fn position(&self) -> &DiffLinePosition {
85        &self.position
86    }
87    pub fn label(&self) -> &SharedString {
88        &self.label
89    }
90}
91
92/// Decorations by file path, in supplied order.
93#[derive(Default)]
94pub(super) struct Decorations {
95    files: HashMap<SharedString, Vec<DiffDecoration>>,
96}
97
98impl Decorations {
99    pub(super) fn new(decorations: impl IntoIterator<Item = DiffDecoration>) -> Self {
100        let mut index = Self::default();
101        for decoration in decorations {
102            index
103                .files
104                .entry(decoration.range.path().clone())
105                .or_default()
106                .push(decoration);
107        }
108        index
109    }
110
111    /// The decoration shown on a line: the last supplied one covering it.
112    pub(super) fn at(&self, position: &DiffLinePosition) -> Option<&DiffDecoration> {
113        self.files
114            .get(position.path())?
115            .iter()
116            .rev()
117            .find(|decoration| decoration.contains(position.side(), position.line()))
118    }
119}
120
121/// Gutter markers by line, keeping the first supplied for each line.
122#[derive(Default)]
123pub(super) struct GutterMarkers {
124    lines: HashMap<DiffLinePosition, DiffGutterMarker>,
125}
126
127impl GutterMarkers {
128    pub(super) fn new(markers: impl IntoIterator<Item = DiffGutterMarker>) -> Self {
129        let mut index = Self::default();
130        for marker in markers {
131            index.lines.entry(marker.position.clone()).or_insert(marker);
132        }
133        index
134    }
135
136    pub(super) fn is_empty(&self) -> bool {
137        self.lines.is_empty()
138    }
139
140    pub(super) fn at(&self, position: &DiffLinePosition) -> Option<&DiffGutterMarker> {
141        self.lines.get(position)
142    }
143}