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}