buffer.rs

  1use std::marker::PhantomData;
  2
  3use gpui3::{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    state_type: PhantomData<S>,
115    scroll_state: ScrollState,
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() -> Self {
125        Self {
126            state_type: PhantomData,
127            scroll_state: ScrollState::default(),
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 bind_scroll_state(&mut self, scroll_state: ScrollState) {
137        self.scroll_state = scroll_state;
138    }
139
140    pub fn set_title<T: Into<Option<String>>>(mut self, title: T) -> Self {
141        self.title = title.into();
142        self
143    }
144
145    pub fn set_path<P: Into<Option<String>>>(mut self, path: P) -> Self {
146        self.path = path.into();
147        self
148    }
149
150    pub fn set_readonly(mut self, readonly: bool) -> Self {
151        self.readonly = readonly;
152        self
153    }
154
155    pub fn set_rows<R: Into<Option<BufferRows>>>(mut self, rows: R) -> Self {
156        self.rows = rows.into();
157        self
158    }
159
160    pub fn set_language<L: Into<Option<String>>>(mut self, language: L) -> Self {
161        self.language = language.into();
162        self
163    }
164
165    fn render_row(row: BufferRow, cx: &WindowContext) -> impl Element<ViewState = S> {
166        let system_color = SystemColor::new();
167        let color = ThemeColor::new(cx);
168
169        let line_background = if row.current {
170            color.editor_active_line
171        } else {
172            system_color.transparent
173        };
174
175        let line_number_color = if row.current {
176            color.text
177        } else {
178            color.syntax.comment
179        };
180
181        h_stack()
182            .bg(line_background)
183            .w_full()
184            .gap_2()
185            .px_1()
186            .child(
187                h_stack()
188                    .w_4()
189                    .h_full()
190                    .px_0p5()
191                    .when(row.code_action, |c| {
192                        div().child(IconElement::new(Icon::Bolt))
193                    }),
194            )
195            .when(row.show_line_number, |this| {
196                this.child(
197                    h_stack().justify_end().px_0p5().w_3().child(
198                        div()
199                            .text_color(line_number_color)
200                            .child(row.line_number.to_string()),
201                    ),
202                )
203            })
204            .child(div().mx_0p5().w_1().h_full().bg(row.status.hsla(cx)))
205            .children(row.line.map(|line| {
206                div()
207                    .flex()
208                    .children(line.highlighted_texts.iter().map(|highlighted_text| {
209                        div()
210                            .text_color(highlighted_text.color)
211                            .child(highlighted_text.text.clone())
212                    }))
213            }))
214    }
215
216    fn render_rows(&self, cx: &WindowContext) -> Vec<impl Element<ViewState = S>> {
217        match &self.rows {
218            Some(rows) => rows
219                .rows
220                .iter()
221                .map(|row| Self::render_row(row.clone(), cx))
222                .collect(),
223            None => vec![],
224        }
225    }
226
227    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
228        let color = ThemeColor::new(cx);
229        let rows = self.render_rows(cx);
230
231        v_stack()
232            .flex_1()
233            .w_full()
234            .h_full()
235            .bg(color.editor)
236            .children(rows)
237    }
238}
239
240#[cfg(feature = "stories")]
241pub use stories::*;
242
243#[cfg(feature = "stories")]
244mod stories {
245    use gpui3::rems;
246
247    use crate::{
248        empty_buffer_example, hello_world_rust_buffer_example,
249        hello_world_rust_buffer_with_status_example, Story,
250    };
251
252    use super::*;
253
254    #[derive(Element)]
255    pub struct BufferStory<S: 'static + Send + Sync + Clone> {
256        state_type: PhantomData<S>,
257    }
258
259    impl<S: 'static + Send + Sync + Clone> BufferStory<S> {
260        pub fn new() -> Self {
261            Self {
262                state_type: PhantomData,
263            }
264        }
265
266        fn render(
267            &mut self,
268            _view: &mut S,
269            cx: &mut ViewContext<S>,
270        ) -> impl Element<ViewState = S> {
271            let color = ThemeColor::new(cx);
272
273            Story::container(cx)
274                .child(Story::title_for::<_, Buffer<S>>(cx))
275                .child(Story::label(cx, "Default"))
276                .child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
277                .child(Story::label(cx, "Hello World (Rust)"))
278                .child(
279                    div()
280                        .w(rems(64.))
281                        .h_96()
282                        .child(hello_world_rust_buffer_example(&color)),
283                )
284                .child(Story::label(cx, "Hello World (Rust) with Status"))
285                .child(
286                    div()
287                        .w(rems(64.))
288                        .h_96()
289                        .child(hello_world_rust_buffer_with_status_example(&color)),
290                )
291        }
292    }
293}