buffer.rs

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