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