1use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};
2use gpui::{
3 actions, div, overlay, red, uniform_list, AnyElement, AppContext, CursorStyle, Div,
4 EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
5 MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled, TextStyle,
6 UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
7};
8use language::{Buffer, OwnedSyntaxLayerInfo, SyntaxLayerInfo};
9use settings::Settings;
10use std::{mem, ops::Range};
11use theme::{Theme, ThemeSettings};
12use tree_sitter::{Node, TreeCursor};
13use ui::{h_stack, Label};
14use workspace::{
15 item::{Item, ItemHandle},
16 ui::v_stack,
17 ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
18};
19
20actions!(debug, [OpenSyntaxTreeView]);
21
22pub fn init(cx: &mut AppContext) {
23 cx.observe_new_views(|workspace: &mut Workspace, _| {
24 workspace.register_action(|workspace, _: &OpenSyntaxTreeView, cx| {
25 let active_item = workspace.active_item(cx);
26 let workspace_handle = workspace.weak_handle();
27 let syntax_tree_view =
28 cx.build_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx));
29 workspace.add_item(Box::new(syntax_tree_view), cx);
30 });
31 })
32 .detach();
33}
34
35pub struct SyntaxTreeView {
36 workspace_handle: WeakView<Workspace>,
37 editor: Option<EditorState>,
38 mouse_y: Option<Pixels>,
39 line_height: Option<Pixels>,
40 list_scroll_handle: UniformListScrollHandle,
41 selected_descendant_ix: Option<usize>,
42 hovered_descendant_ix: Option<usize>,
43 focus_handle: FocusHandle,
44}
45
46pub struct SyntaxTreeToolbarItemView {
47 tree_view: Option<View<SyntaxTreeView>>,
48 subscription: Option<gpui::Subscription>,
49 menu_open: bool,
50}
51
52struct EditorState {
53 editor: View<Editor>,
54 active_buffer: Option<BufferState>,
55 _subscription: gpui::Subscription,
56}
57
58#[derive(Clone)]
59struct BufferState {
60 buffer: Model<Buffer>,
61 excerpt_id: ExcerptId,
62 active_layer: Option<OwnedSyntaxLayerInfo>,
63}
64
65impl SyntaxTreeView {
66 pub fn new(
67 workspace_handle: WeakView<Workspace>,
68 active_item: Option<Box<dyn ItemHandle>>,
69 cx: &mut ViewContext<Self>,
70 ) -> Self {
71 let mut this = Self {
72 workspace_handle: workspace_handle.clone(),
73 list_scroll_handle: UniformListScrollHandle::new(),
74 editor: None,
75 mouse_y: None,
76 line_height: None,
77 hovered_descendant_ix: None,
78 selected_descendant_ix: None,
79 focus_handle: cx.focus_handle(),
80 };
81
82 this.workspace_updated(active_item, cx);
83 cx.observe(
84 &workspace_handle.upgrade().unwrap(),
85 |this, workspace, cx| {
86 this.workspace_updated(workspace.read(cx).active_item(cx), cx);
87 },
88 )
89 .detach();
90
91 this
92 }
93
94 fn workspace_updated(
95 &mut self,
96 active_item: Option<Box<dyn ItemHandle>>,
97 cx: &mut ViewContext<Self>,
98 ) {
99 if let Some(item) = active_item {
100 if item.item_id() != cx.entity_id() {
101 if let Some(editor) = item.act_as::<Editor>(cx) {
102 self.set_editor(editor, cx);
103 }
104 }
105 }
106 }
107
108 fn set_editor(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
109 if let Some(state) = &self.editor {
110 if state.editor == editor {
111 return;
112 }
113 editor.update(cx, |editor, cx| {
114 editor.clear_background_highlights::<Self>(cx)
115 });
116 }
117
118 let subscription = cx.subscribe(&editor, |this, _, event, cx| {
119 let did_reparse = match event {
120 editor::EditorEvent::Reparsed => true,
121 editor::EditorEvent::SelectionsChanged { .. } => false,
122 _ => return,
123 };
124 this.editor_updated(did_reparse, cx);
125 });
126
127 self.editor = Some(EditorState {
128 editor,
129 _subscription: subscription,
130 active_buffer: None,
131 });
132 self.editor_updated(true, cx);
133 }
134
135 fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
136 // Find which excerpt the cursor is in, and the position within that excerpted buffer.
137 let editor_state = self.editor.as_mut()?;
138 let editor = &editor_state.editor.read(cx);
139 let selection_range = editor.selections.last::<usize>(cx).range();
140 let multibuffer = editor.buffer().read(cx);
141 let (buffer, range, excerpt_id) = multibuffer
142 .range_to_buffer_ranges(selection_range, cx)
143 .pop()?;
144
145 // If the cursor has moved into a different excerpt, retrieve a new syntax layer
146 // from that buffer.
147 let buffer_state = editor_state
148 .active_buffer
149 .get_or_insert_with(|| BufferState {
150 buffer: buffer.clone(),
151 excerpt_id,
152 active_layer: None,
153 });
154 let mut prev_layer = None;
155 if did_reparse {
156 prev_layer = buffer_state.active_layer.take();
157 }
158 if buffer_state.buffer != buffer || buffer_state.excerpt_id != buffer_state.excerpt_id {
159 buffer_state.buffer = buffer.clone();
160 buffer_state.excerpt_id = excerpt_id;
161 buffer_state.active_layer = None;
162 }
163
164 let layer = match &mut buffer_state.active_layer {
165 Some(layer) => layer,
166 None => {
167 let snapshot = buffer.read(cx).snapshot();
168 let layer = if let Some(prev_layer) = prev_layer {
169 let prev_range = prev_layer.node().byte_range();
170 snapshot
171 .syntax_layers()
172 .filter(|layer| layer.language == &prev_layer.language)
173 .min_by_key(|layer| {
174 let range = layer.node().byte_range();
175 ((range.start as i64) - (prev_range.start as i64)).abs()
176 + ((range.end as i64) - (prev_range.end as i64)).abs()
177 })?
178 } else {
179 snapshot.syntax_layers().next()?
180 };
181 buffer_state.active_layer.insert(layer.to_owned())
182 }
183 };
184
185 // Within the active layer, find the syntax node under the cursor,
186 // and scroll to it.
187 let mut cursor = layer.node().walk();
188 while cursor.goto_first_child_for_byte(range.start).is_some() {
189 if !range.is_empty() && cursor.node().end_byte() == range.start {
190 cursor.goto_next_sibling();
191 }
192 }
193
194 // Ascend to the smallest ancestor that contains the range.
195 loop {
196 let node_range = cursor.node().byte_range();
197 if node_range.start <= range.start && node_range.end >= range.end {
198 break;
199 }
200 if !cursor.goto_parent() {
201 break;
202 }
203 }
204
205 let descendant_ix = cursor.descendant_index();
206 self.selected_descendant_ix = Some(descendant_ix);
207 self.list_scroll_handle.scroll_to_item(descendant_ix);
208
209 cx.notify();
210 Some(())
211 }
212
213 fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> {
214 let line_height = self.line_height?;
215 let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
216
217 self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| {
218 // Put the cursor at the beginning of the node.
219 mem::swap(&mut range.start, &mut range.end);
220
221 editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
222 selections.select_ranges(vec![range]);
223 });
224 });
225 Some(())
226 }
227
228 fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) {
229 if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) {
230 let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
231 if self.hovered_descendant_ix != Some(ix) {
232 self.hovered_descendant_ix = Some(ix);
233 self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| {
234 editor.clear_background_highlights::<Self>(cx);
235 editor.highlight_background::<Self>(
236 vec![range],
237 |theme| theme.editor_document_highlight_write_background,
238 cx,
239 );
240 });
241 cx.notify();
242 }
243 }
244 }
245
246 fn update_editor_with_range_for_descendant_ix(
247 &self,
248 descendant_ix: usize,
249 cx: &mut ViewContext<Self>,
250 mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>),
251 ) -> Option<()> {
252 let editor_state = self.editor.as_ref()?;
253 let buffer_state = editor_state.active_buffer.as_ref()?;
254 let layer = buffer_state.active_layer.as_ref()?;
255
256 // Find the node.
257 let mut cursor = layer.node().walk();
258 cursor.goto_descendant(descendant_ix);
259 let node = cursor.node();
260 let range = node.byte_range();
261
262 // Build a text anchor range.
263 let buffer = buffer_state.buffer.read(cx);
264 let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
265
266 // Build a multibuffer anchor range.
267 let multibuffer = editor_state.editor.read(cx).buffer();
268 let multibuffer = multibuffer.read(cx).snapshot(cx);
269 let excerpt_id = buffer_state.excerpt_id;
270 let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start)
271 ..multibuffer.anchor_in_excerpt(excerpt_id, range.end);
272
273 // Update the editor with the anchor range.
274 editor_state.editor.update(cx, |editor, cx| {
275 f(editor, range, cx);
276 });
277 Some(())
278 }
279
280 fn render_node(
281 cursor: &TreeCursor,
282 _depth: u32,
283 selected: bool,
284 hovered: bool,
285 list_hovered: bool,
286 style: &TextStyle,
287 editor_theme: &Theme,
288 _cx: &AppContext,
289 ) -> Div {
290 let editor_colors = editor_theme.colors();
291 let node = cursor.node();
292 let mut range_style = style.clone();
293 // todo!() styling
294 // let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
295 // let font_size = style.text.font_size.to_pixels(cx.rem_size());
296 // let line_height = style.text.line_height_in_pixels(cx.rem_size());
297 // let em_width = cx
298 // .text_system()
299 // .typographic_bounds(font_id, font_size, 'm')
300 // .unwrap()
301 // .size
302 // .width;
303 // let gutter_padding = (em_width * editor_theme.gutter_padding_factor).round();
304
305 range_style.color = editor_colors.editor_line_number;
306
307 let mut anonymous_node_style = style.clone();
308 let string_color = editor_theme
309 .syntax()
310 .highlights
311 .iter()
312 .find_map(|(name, style)| (name == "string").then(|| style.color)?);
313 let property_color = editor_theme
314 .syntax()
315 .highlights
316 .iter()
317 .find_map(|(name, style)| (name == "property").then(|| style.color)?);
318 if let Some(color) = string_color {
319 anonymous_node_style.color = color;
320 }
321
322 let mut row = h_stack();
323 if let Some(field_name) = cursor.field_name() {
324 let mut field_style = style.clone();
325 if let Some(color) = property_color {
326 field_style.color = color;
327 }
328
329 row = row.children([Label::new(field_name), Label::new(": ")]);
330 }
331
332 return row
333 .child(
334 if node.is_named() {
335 Label::new(node.kind())
336 } else {
337 Label::new(format!("\"{}\"", node.kind()))
338 }, // todo!()
339 // .margin(em_width),
340 )
341 .child(Label::new(format_node_range(node)))
342 .text_bg(if selected {
343 editor_colors.element_selected
344 } else if hovered && list_hovered {
345 editor_colors.element_active
346 } else {
347 Hsla::default()
348 })
349 // todo!()
350 // .padding(gutter_padding + depth as f32 * 18.0)
351 .border_1()
352 .border_color(red());
353 }
354}
355
356impl Render for SyntaxTreeView {
357 type Element = Div;
358
359 fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> Div {
360 let settings = ThemeSettings::get_global(cx);
361 let font = settings.buffer_font.clone();
362 let font_size = settings.buffer_font_size(cx);
363
364 let editor_theme = settings.active_theme.clone();
365 let editor_colors = editor_theme.colors();
366 let style = TextStyle {
367 color: editor_colors.text,
368 font_family: font.family,
369 font_features: font.features,
370 font_weight: font.weight,
371 font_style: font.style,
372 ..Default::default()
373 };
374
375 let line_height = cx.text_style().line_height_in_pixels(font_size);
376 if Some(line_height) != self.line_height {
377 self.line_height = Some(line_height);
378 self.hover_state_changed(cx);
379 }
380
381 if let Some(layer) = self
382 .editor
383 .as_ref()
384 .and_then(|editor| editor.active_buffer.as_ref())
385 .and_then(|buffer| buffer.active_layer.as_ref())
386 {
387 let layer = layer.clone();
388 let theme = editor_theme.clone();
389
390 // todo!()
391 // let list_hovered = state.hovered();
392 let list_hovered = false;
393 uniform_list(
394 cx.view().clone(),
395 "SyntaxTreeView",
396 layer.node().descendant_count(),
397 move |this, range, cx| {
398 let mut items = Vec::new();
399 let mut cursor = layer.node().walk();
400 let mut descendant_ix = range.start as usize;
401 cursor.goto_descendant(descendant_ix);
402 let mut depth = cursor.depth();
403 let mut visited_children = false;
404 while descendant_ix < range.end {
405 if visited_children {
406 if cursor.goto_next_sibling() {
407 visited_children = false;
408 } else if cursor.goto_parent() {
409 depth -= 1;
410 } else {
411 break;
412 }
413 } else {
414 items.push(Self::render_node(
415 &cursor,
416 depth,
417 Some(descendant_ix) == this.selected_descendant_ix,
418 Some(descendant_ix) == this.hovered_descendant_ix,
419 list_hovered,
420 &style,
421 &theme,
422 cx,
423 ));
424 descendant_ix += 1;
425 if cursor.goto_first_child() {
426 depth += 1;
427 } else {
428 visited_children = true;
429 }
430 }
431 }
432 items
433 },
434 )
435 .track_scroll(self.list_scroll_handle.clone())
436 .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| {
437 tree_view.mouse_y = Some(event.position.y);
438 tree_view.hover_state_changed(cx);
439 }))
440 .on_mouse_down(
441 MouseButton::Left,
442 cx.listener(move |tree_view, event: &MouseDownEvent, cx| {
443 tree_view.handle_click(event.position.y, cx);
444 }),
445 )
446 .text_bg(editor_colors.background);
447 }
448
449 div()
450 }
451}
452
453impl EventEmitter<()> for SyntaxTreeView {}
454
455impl FocusableView for SyntaxTreeView {
456 fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
457 self.focus_handle.clone()
458 }
459}
460
461impl Item for SyntaxTreeView {
462 type Event = ();
463
464 fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
465
466 fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
467 Label::new("Syntax Tree").into_any_element()
468 }
469
470 fn clone_on_split(
471 &self,
472 _: workspace::WorkspaceId,
473 cx: &mut ViewContext<Self>,
474 ) -> Option<View<Self>>
475 where
476 Self: Sized,
477 {
478 Some(cx.build_view(|cx| {
479 let mut clone = Self::new(self.workspace_handle.clone(), None, cx);
480 if let Some(editor) = &self.editor {
481 clone.set_editor(editor.editor.clone(), cx)
482 }
483 clone
484 }))
485 }
486}
487
488impl SyntaxTreeToolbarItemView {
489 pub fn new() -> Self {
490 Self {
491 menu_open: false,
492 tree_view: None,
493 subscription: None,
494 }
495 }
496
497 fn render_menu(&mut self, cx: &mut ViewContext<'_, Self>) -> Option<Div> {
498 let tree_view = self.tree_view.as_ref()?;
499 let tree_view = tree_view.read(cx);
500
501 let editor_state = tree_view.editor.as_ref()?;
502 let buffer_state = editor_state.active_buffer.as_ref()?;
503 let active_layer = buffer_state.active_layer.clone()?;
504 let active_buffer = buffer_state.buffer.read(cx).snapshot();
505
506 Some(
507 v_stack()
508 .child(Self::render_header(&active_layer, cx))
509 .children(self.menu_open.then(|| {
510 overlay().child(
511 v_stack()
512 .children(active_buffer.syntax_layers().enumerate().map(
513 |(ix, layer)| Self::render_menu_item(&active_layer, layer, ix, cx),
514 ))
515 .on_mouse_down_out(cx.listener(|this, e: &MouseDownEvent, cx| {
516 if e.button == MouseButton::Left {
517 this.menu_open = false;
518 cx.notify()
519 }
520 })),
521 )
522 })),
523 )
524 }
525
526 fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
527 self.menu_open = !self.menu_open;
528 cx.notify();
529 }
530
531 fn select_layer(&mut self, layer_ix: usize, cx: &mut ViewContext<Self>) -> Option<()> {
532 let tree_view = self.tree_view.as_ref()?;
533 tree_view.update(cx, |view, cx| {
534 let editor_state = view.editor.as_mut()?;
535 let buffer_state = editor_state.active_buffer.as_mut()?;
536 let snapshot = buffer_state.buffer.read(cx).snapshot();
537 let layer = snapshot.syntax_layers().nth(layer_ix)?;
538 buffer_state.active_layer = Some(layer.to_owned());
539 view.selected_descendant_ix = None;
540 self.menu_open = false;
541 cx.notify();
542 Some(())
543 })
544 }
545
546 fn render_header(active_layer: &OwnedSyntaxLayerInfo, cx: &mut ViewContext<Self>) -> Div {
547 h_stack()
548 .child(Label::new(active_layer.language.name()))
549 .child(Label::new(format_node_range(active_layer.node())))
550 .on_mouse_down(
551 MouseButton::Left,
552 cx.listener(move |view, _, cx| {
553 view.toggle_menu(cx);
554 }),
555 )
556 .cursor(CursorStyle::PointingHand)
557 .border_1()
558 .border_color(red())
559 }
560
561 fn render_menu_item(
562 active_layer: &OwnedSyntaxLayerInfo,
563 layer: SyntaxLayerInfo,
564 layer_ix: usize,
565 cx: &mut ViewContext<Self>,
566 ) -> Div {
567 // todo!() styling
568 let _is_selected = layer.node() == active_layer.node();
569 h_stack()
570 .child(Label::new(layer.language.name().to_string()))
571 .child(Label::new(format_node_range(layer.node())))
572 .cursor(CursorStyle::PointingHand)
573 .on_mouse_down(
574 MouseButton::Left,
575 cx.listener(move |view, _, cx| {
576 view.select_layer(layer_ix, cx);
577 }),
578 )
579 .border_1()
580 .border_color(red())
581 }
582}
583
584fn format_node_range(node: Node) -> String {
585 let start = node.start_position();
586 let end = node.end_position();
587 format!(
588 "[{}:{} - {}:{}]",
589 start.row + 1,
590 start.column + 1,
591 end.row + 1,
592 end.column + 1,
593 )
594}
595
596impl Render for SyntaxTreeToolbarItemView {
597 type Element = Div;
598
599 // todo!()
600 // fn ui_name() -> &'static str {
601 // "SyntaxTreeToolbarItemView"
602 // }
603
604 fn render(&mut self, cx: &mut ViewContext<'_, Self>) -> Div {
605 self.render_menu(cx).unwrap_or_else(|| div())
606 }
607}
608
609impl EventEmitter<ToolbarItemEvent> for SyntaxTreeToolbarItemView {}
610
611impl ToolbarItemView for SyntaxTreeToolbarItemView {
612 fn set_active_pane_item(
613 &mut self,
614 active_pane_item: Option<&dyn ItemHandle>,
615 cx: &mut ViewContext<Self>,
616 ) -> ToolbarItemLocation {
617 self.menu_open = false;
618 if let Some(item) = active_pane_item {
619 if let Some(view) = item.downcast::<SyntaxTreeView>() {
620 self.tree_view = Some(view.clone());
621 self.subscription = Some(cx.observe(&view, |_, _, cx| cx.notify()));
622 return ToolbarItemLocation::PrimaryLeft;
623 }
624 }
625 self.tree_view = None;
626 self.subscription = None;
627 ToolbarItemLocation::Hidden
628 }
629}