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