buffer.rs

  1use std::marker::PhantomData;
  2
  3use gpui2::{Hsla, WindowContext};
  4
  5use crate::prelude::*;
  6use crate::{h_stack, v_stack, Icon, IconElement};
  7
  8#[derive(Default, PartialEq, Copy, Clone)]
  9pub struct PlayerCursor {
 10    color: Hsla,
 11    index: usize,
 12}
 13
 14#[derive(Default, PartialEq, Clone)]
 15pub struct HighlightedText {
 16    pub text: String,
 17    pub color: Hsla,
 18}
 19
 20#[derive(Default, PartialEq, Clone)]
 21pub struct HighlightedLine {
 22    pub highlighted_texts: Vec<HighlightedText>,
 23}
 24
 25#[derive(Default, PartialEq, Clone)]
 26pub struct BufferRow {
 27    pub line_number: usize,
 28    pub code_action: bool,
 29    pub current: bool,
 30    pub line: Option<HighlightedLine>,
 31    pub cursors: Option<Vec<PlayerCursor>>,
 32    pub status: GitStatus,
 33    pub show_line_number: bool,
 34}
 35
 36#[derive(Clone)]
 37pub struct BufferRows {
 38    pub show_line_numbers: bool,
 39    pub rows: Vec<BufferRow>,
 40}
 41
 42impl Default for BufferRows {
 43    fn default() -> Self {
 44        Self {
 45            show_line_numbers: true,
 46            rows: vec![BufferRow {
 47                line_number: 1,
 48                code_action: false,
 49                current: true,
 50                line: None,
 51                cursors: None,
 52                status: GitStatus::None,
 53                show_line_number: true,
 54            }],
 55        }
 56    }
 57}
 58
 59impl BufferRow {
 60    pub fn new(line_number: usize) -> Self {
 61        Self {
 62            line_number,
 63            code_action: false,
 64            current: false,
 65            line: None,
 66            cursors: None,
 67            status: GitStatus::None,
 68            show_line_number: true,
 69        }
 70    }
 71
 72    pub fn set_line(mut self, line: Option<HighlightedLine>) -> Self {
 73        self.line = line;
 74        self
 75    }
 76
 77    pub fn set_cursors(mut self, cursors: Option<Vec<PlayerCursor>>) -> Self {
 78        self.cursors = cursors;
 79        self
 80    }
 81
 82    pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self {
 83        if let Some(cursors) = &mut self.cursors {
 84            cursors.push(cursor);
 85        } else {
 86            self.cursors = Some(vec![cursor]);
 87        }
 88        self
 89    }
 90
 91    pub fn set_status(mut self, status: GitStatus) -> Self {
 92        self.status = status;
 93        self
 94    }
 95
 96    pub fn set_show_line_number(mut self, show_line_number: bool) -> Self {
 97        self.show_line_number = show_line_number;
 98        self
 99    }
100
101    pub fn set_code_action(mut self, code_action: bool) -> Self {
102        self.code_action = code_action;
103        self
104    }
105
106    pub fn set_current(mut self, current: bool) -> Self {
107        self.current = current;
108        self
109    }
110}
111
112#[derive(Element, Clone)]
113pub struct Buffer<S: 'static + Send + Sync + Clone> {
114    id: ElementId,
115    state_type: PhantomData<S>,
116    rows: Option<BufferRows>,
117    readonly: bool,
118    language: Option<String>,
119    title: Option<String>,
120    path: Option<String>,
121}
122
123impl<S: 'static + Send + Sync + Clone> Buffer<S> {
124    pub fn new(id: impl Into<ElementId>) -> Self {
125        Self {
126            id: id.into(),
127            state_type: PhantomData,
128            rows: Some(BufferRows::default()),
129            readonly: false,
130            language: None,
131            title: Some("untitled".to_string()),
132            path: None,
133        }
134    }
135
136    pub fn set_title<T: Into<Option<String>>>(mut self, title: T) -> Self {
137        self.title = title.into();
138        self
139    }
140
141    pub fn set_path<P: Into<Option<String>>>(mut self, path: P) -> Self {
142        self.path = path.into();
143        self
144    }
145
146    pub fn set_readonly(mut self, readonly: bool) -> Self {
147        self.readonly = readonly;
148        self
149    }
150
151    pub fn set_rows<R: Into<Option<BufferRows>>>(mut self, rows: R) -> Self {
152        self.rows = rows.into();
153        self
154    }
155
156    pub fn set_language<L: Into<Option<String>>>(mut self, language: L) -> Self {
157        self.language = language.into();
158        self
159    }
160
161    fn render_row(row: BufferRow, cx: &WindowContext) -> impl Element<ViewState = S> {
162        let theme = theme(cx);
163
164        let line_background = if row.current {
165            theme.editor_active_line
166        } else {
167            theme.transparent
168        };
169
170        let line_number_color = if row.current {
171            theme.text
172        } else {
173            theme.syntax.comment
174        };
175
176        h_stack()
177            .bg(line_background)
178            .w_full()
179            .gap_2()
180            .px_1()
181            .child(
182                h_stack()
183                    .w_4()
184                    .h_full()
185                    .px_0p5()
186                    .when(row.code_action, |c| {
187                        div().child(IconElement::new(Icon::Bolt))
188                    }),
189            )
190            .when(row.show_line_number, |this| {
191                this.child(
192                    h_stack().justify_end().px_0p5().w_3().child(
193                        div()
194                            .text_color(line_number_color)
195                            .child(row.line_number.to_string()),
196                    ),
197                )
198            })
199            .child(div().mx_0p5().w_1().h_full().bg(row.status.hsla(cx)))
200            .children(row.line.map(|line| {
201                div()
202                    .flex()
203                    .children(line.highlighted_texts.iter().map(|highlighted_text| {
204                        div()
205                            .text_color(highlighted_text.color)
206                            .child(highlighted_text.text.clone())
207                    }))
208            }))
209    }
210
211    fn render_rows(&self, cx: &WindowContext) -> Vec<impl Element<ViewState = S>> {
212        match &self.rows {
213            Some(rows) => rows
214                .rows
215                .iter()
216                .map(|row| Self::render_row(row.clone(), cx))
217                .collect(),
218            None => vec![],
219        }
220    }
221
222    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
223        let theme = theme(cx);
224        let rows = self.render_rows(cx);
225
226        v_stack()
227            .flex_1()
228            .w_full()
229            .h_full()
230            .bg(theme.editor)
231            .children(rows)
232    }
233}
234
235#[cfg(feature = "stories")]
236pub use stories::*;
237
238#[cfg(feature = "stories")]
239mod stories {
240    use gpui2::rems;
241
242    use crate::{
243        empty_buffer_example, hello_world_rust_buffer_example,
244        hello_world_rust_buffer_with_status_example, Story,
245    };
246
247    use super::*;
248
249    #[derive(Element)]
250    pub struct BufferStory<S: 'static + Send + Sync + Clone> {
251        state_type: PhantomData<S>,
252    }
253
254    impl<S: 'static + Send + Sync + Clone> BufferStory<S> {
255        pub fn new() -> Self {
256            Self {
257                state_type: PhantomData,
258            }
259        }
260
261        fn render(
262            &mut self,
263            _view: &mut S,
264            cx: &mut ViewContext<S>,
265        ) -> impl Element<ViewState = S> {
266            let theme = theme(cx);
267
268            Story::container(cx)
269                .child(Story::title_for::<_, Buffer<S>>(cx))
270                .child(Story::label(cx, "Default"))
271                .child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
272                .child(Story::label(cx, "Hello World (Rust)"))
273                .child(
274                    div()
275                        .w(rems(64.))
276                        .h_96()
277                        .child(hello_world_rust_buffer_example(&theme)),
278                )
279                .child(Story::label(cx, "Hello World (Rust) with Status"))
280                .child(
281                    div()
282                        .w(rems(64.))
283                        .h_96()
284                        .child(hello_world_rust_buffer_with_status_example(&theme)),
285                )
286        }
287    }
288}