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<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<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<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<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}