syntax_tree_view.rs

  1use editor::{Anchor, Editor, ExcerptId, SelectionEffects, scroll::Autoscroll};
  2use gpui::{
  3    App, AppContext as _, Context, Div, Entity, EventEmitter, FocusHandle, Focusable, Hsla,
  4    InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement,
  5    Render, ScrollStrategy, SharedString, Styled, UniformListScrollHandle, WeakEntity, Window,
  6    actions, div, rems, uniform_list,
  7};
  8use language::{Buffer, OwnedSyntaxLayer};
  9use std::{mem, ops::Range};
 10use theme::ActiveTheme;
 11use tree_sitter::{Node, TreeCursor};
 12use ui::{ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex};
 13use workspace::{
 14    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 15    item::{Item, ItemHandle},
 16};
 17
 18actions!(
 19    dev,
 20    [
 21        /// Opens the syntax tree view for the current file.
 22        OpenSyntaxTreeView
 23    ]
 24);
 25
 26pub fn init(cx: &mut App) {
 27    cx.observe_new(|workspace: &mut Workspace, _, _| {
 28        workspace.register_action(|workspace, _: &OpenSyntaxTreeView, window, cx| {
 29            let active_item = workspace.active_item(cx);
 30            let workspace_handle = workspace.weak_handle();
 31            let syntax_tree_view =
 32                cx.new(|cx| SyntaxTreeView::new(workspace_handle, active_item, window, cx));
 33            workspace.split_item(
 34                SplitDirection::Right,
 35                Box::new(syntax_tree_view),
 36                window,
 37                cx,
 38            )
 39        });
 40    })
 41    .detach();
 42}
 43
 44pub struct SyntaxTreeView {
 45    workspace_handle: WeakEntity<Workspace>,
 46    editor: Option<EditorState>,
 47    list_scroll_handle: UniformListScrollHandle,
 48    selected_descendant_ix: Option<usize>,
 49    hovered_descendant_ix: Option<usize>,
 50    focus_handle: FocusHandle,
 51}
 52
 53pub struct SyntaxTreeToolbarItemView {
 54    tree_view: Option<Entity<SyntaxTreeView>>,
 55    subscription: Option<gpui::Subscription>,
 56}
 57
 58struct EditorState {
 59    editor: Entity<Editor>,
 60    active_buffer: Option<BufferState>,
 61    _subscription: gpui::Subscription,
 62}
 63
 64#[derive(Clone)]
 65struct BufferState {
 66    buffer: Entity<Buffer>,
 67    excerpt_id: ExcerptId,
 68    active_layer: Option<OwnedSyntaxLayer>,
 69}
 70
 71impl SyntaxTreeView {
 72    pub fn new(
 73        workspace_handle: WeakEntity<Workspace>,
 74        active_item: Option<Box<dyn ItemHandle>>,
 75        window: &mut Window,
 76        cx: &mut Context<Self>,
 77    ) -> Self {
 78        let mut this = Self {
 79            workspace_handle: workspace_handle.clone(),
 80            list_scroll_handle: UniformListScrollHandle::new(),
 81            editor: None,
 82            hovered_descendant_ix: None,
 83            selected_descendant_ix: None,
 84            focus_handle: cx.focus_handle(),
 85        };
 86
 87        this.workspace_updated(active_item, window, cx);
 88        cx.observe_in(
 89            &workspace_handle.upgrade().unwrap(),
 90            window,
 91            |this, workspace, window, cx| {
 92                this.workspace_updated(workspace.read(cx).active_item(cx), window, cx);
 93            },
 94        )
 95        .detach();
 96
 97        this
 98    }
 99
100    fn workspace_updated(
101        &mut self,
102        active_item: Option<Box<dyn ItemHandle>>,
103        window: &mut Window,
104        cx: &mut Context<Self>,
105    ) {
106        if let Some(item) = active_item
107            && item.item_id() != cx.entity_id()
108            && let Some(editor) = item.act_as::<Editor>(cx)
109        {
110            self.set_editor(editor, window, cx);
111        }
112    }
113
114    fn set_editor(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut Context<Self>) {
115        if let Some(state) = &self.editor {
116            if state.editor == editor {
117                return;
118            }
119            editor.update(cx, |editor, cx| {
120                editor.clear_background_highlights::<Self>(cx)
121            });
122        }
123
124        let subscription = cx.subscribe_in(&editor, window, |this, _, event, window, cx| {
125            let did_reparse = match event {
126                editor::EditorEvent::Reparsed(_) => true,
127                editor::EditorEvent::SelectionsChanged { .. } => false,
128                _ => return,
129            };
130            this.editor_updated(did_reparse, window, cx);
131        });
132
133        self.editor = Some(EditorState {
134            editor,
135            _subscription: subscription,
136            active_buffer: None,
137        });
138        self.editor_updated(true, window, cx);
139    }
140
141    fn editor_updated(
142        &mut self,
143        did_reparse: bool,
144        window: &mut Window,
145        cx: &mut Context<Self>,
146    ) -> Option<()> {
147        // Find which excerpt the cursor is in, and the position within that excerpted buffer.
148        let editor_state = self.editor.as_mut()?;
149        let snapshot = editor_state
150            .editor
151            .update(cx, |editor, cx| editor.snapshot(window, cx));
152        let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
153            let selection_range = editor.selections.last::<usize>(cx).range();
154            let multi_buffer = editor.buffer().read(cx);
155            let (buffer, range, excerpt_id) = snapshot
156                .buffer_snapshot
157                .range_to_buffer_ranges(selection_range)
158                .pop()?;
159            let buffer = multi_buffer.buffer(buffer.remote_id()).unwrap();
160            Some((buffer, range, excerpt_id))
161        })?;
162
163        // If the cursor has moved into a different excerpt, retrieve a new syntax layer
164        // from that buffer.
165        let buffer_state = editor_state
166            .active_buffer
167            .get_or_insert_with(|| BufferState {
168                buffer: buffer.clone(),
169                excerpt_id,
170                active_layer: None,
171            });
172        let mut prev_layer = None;
173        if did_reparse {
174            prev_layer = buffer_state.active_layer.take();
175        }
176        if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
177            buffer_state.buffer = buffer.clone();
178            buffer_state.excerpt_id = excerpt_id;
179            buffer_state.active_layer = None;
180        }
181
182        let layer = match &mut buffer_state.active_layer {
183            Some(layer) => layer,
184            None => {
185                let snapshot = buffer.read(cx).snapshot();
186                let layer = if let Some(prev_layer) = prev_layer {
187                    let prev_range = prev_layer.node().byte_range();
188                    snapshot
189                        .syntax_layers()
190                        .filter(|layer| layer.language == &prev_layer.language)
191                        .min_by_key(|layer| {
192                            let range = layer.node().byte_range();
193                            ((range.start as i64) - (prev_range.start as i64)).abs()
194                                + ((range.end as i64) - (prev_range.end as i64)).abs()
195                        })?
196                } else {
197                    snapshot.syntax_layers().next()?
198                };
199                buffer_state.active_layer.insert(layer.to_owned())
200            }
201        };
202
203        // Within the active layer, find the syntax node under the cursor,
204        // and scroll to it.
205        let mut cursor = layer.node().walk();
206        while cursor.goto_first_child_for_byte(range.start).is_some() {
207            if !range.is_empty() && cursor.node().end_byte() == range.start {
208                cursor.goto_next_sibling();
209            }
210        }
211
212        // Ascend to the smallest ancestor that contains the range.
213        loop {
214            let node_range = cursor.node().byte_range();
215            if node_range.start <= range.start && node_range.end >= range.end {
216                break;
217            }
218            if !cursor.goto_parent() {
219                break;
220            }
221        }
222
223        let descendant_ix = cursor.descendant_index();
224        self.selected_descendant_ix = Some(descendant_ix);
225        self.list_scroll_handle
226            .scroll_to_item(descendant_ix, ScrollStrategy::Center);
227
228        cx.notify();
229        Some(())
230    }
231
232    fn update_editor_with_range_for_descendant_ix(
233        &self,
234        descendant_ix: usize,
235        window: &mut Window,
236        cx: &mut Context<Self>,
237        mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut Window, &mut Context<Editor>),
238    ) -> Option<()> {
239        let editor_state = self.editor.as_ref()?;
240        let buffer_state = editor_state.active_buffer.as_ref()?;
241        let layer = buffer_state.active_layer.as_ref()?;
242
243        // Find the node.
244        let mut cursor = layer.node().walk();
245        cursor.goto_descendant(descendant_ix);
246        let node = cursor.node();
247        let range = node.byte_range();
248
249        // Build a text anchor range.
250        let buffer = buffer_state.buffer.read(cx);
251        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
252
253        // Build a multibuffer anchor range.
254        let multibuffer = editor_state.editor.read(cx).buffer();
255        let multibuffer = multibuffer.read(cx).snapshot(cx);
256        let excerpt_id = buffer_state.excerpt_id;
257        let range = multibuffer
258            .anchor_in_excerpt(excerpt_id, range.start)
259            .unwrap()
260            ..multibuffer
261                .anchor_in_excerpt(excerpt_id, range.end)
262                .unwrap();
263
264        // Update the editor with the anchor range.
265        editor_state.editor.update(cx, |editor, cx| {
266            f(editor, range, window, cx);
267        });
268        Some(())
269    }
270
271    fn render_node(cursor: &TreeCursor, depth: u32, selected: bool, cx: &App) -> Div {
272        let colors = cx.theme().colors();
273        let mut row = h_flex();
274        if let Some(field_name) = cursor.field_name() {
275            row = row.children([Label::new(field_name).color(Color::Info), Label::new(": ")]);
276        }
277
278        let node = cursor.node();
279        row.child(if node.is_named() {
280            Label::new(node.kind()).color(Color::Default)
281        } else {
282            Label::new(format!("\"{}\"", node.kind())).color(Color::Created)
283        })
284        .child(
285            div()
286                .child(Label::new(format_node_range(node)).color(Color::Muted))
287                .pl_1(),
288        )
289        .text_bg(if selected {
290            colors.element_selected
291        } else {
292            Hsla::default()
293        })
294        .pl(rems(depth as f32))
295        .hover(|style| style.bg(colors.element_hover))
296    }
297}
298
299impl Render for SyntaxTreeView {
300    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
301        let mut rendered = div().flex_1().bg(cx.theme().colors().editor_background);
302
303        if let Some(layer) = self
304            .editor
305            .as_ref()
306            .and_then(|editor| editor.active_buffer.as_ref())
307            .and_then(|buffer| buffer.active_layer.as_ref())
308        {
309            let layer = layer.clone();
310            rendered = rendered.child(uniform_list(
311                "SyntaxTreeView",
312                layer.node().descendant_count(),
313                cx.processor(move |this, range: Range<usize>, _, cx| {
314                    let mut items = Vec::new();
315                    let mut cursor = layer.node().walk();
316                    let mut descendant_ix = range.start;
317                    cursor.goto_descendant(descendant_ix);
318                    let mut depth = cursor.depth();
319                    let mut visited_children = false;
320                    while descendant_ix < range.end {
321                        if visited_children {
322                            if cursor.goto_next_sibling() {
323                                visited_children = false;
324                            } else if cursor.goto_parent() {
325                                depth -= 1;
326                            } else {
327                                break;
328                            }
329                        } else {
330                            items.push(
331                                Self::render_node(
332                                    &cursor,
333                                    depth,
334                                    Some(descendant_ix) == this.selected_descendant_ix,
335                                    cx,
336                                )
337                                .on_mouse_down(
338                                    MouseButton::Left,
339                                    cx.listener(move |tree_view, _: &MouseDownEvent, window, cx| {
340                                        tree_view.update_editor_with_range_for_descendant_ix(
341                                            descendant_ix,
342                                            window, cx,
343                                            |editor, mut range, window, cx| {
344                                                // Put the cursor at the beginning of the node.
345                                                mem::swap(&mut range.start, &mut range.end);
346
347                                                editor.change_selections(
348                                                    SelectionEffects::scroll(Autoscroll::newest()),
349                                                    window, cx,
350                                                    |selections| {
351                                                        selections.select_ranges(vec![range]);
352                                                    },
353                                                );
354                                            },
355                                        );
356                                    }),
357                                )
358                                .on_mouse_move(cx.listener(
359                                    move |tree_view, _: &MouseMoveEvent, window, cx| {
360                                        if tree_view.hovered_descendant_ix != Some(descendant_ix) {
361                                            tree_view.hovered_descendant_ix = Some(descendant_ix);
362                                            tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, window, cx, |editor, range, _, cx| {
363                                                editor.clear_background_highlights::<Self>( cx);
364                                                editor.highlight_background::<Self>(
365                                                    &[range],
366                                                    |theme| theme.colors().editor_document_highlight_write_background,
367                                                     cx,
368                                                );
369                                            });
370                                            cx.notify();
371                                        }
372                                    },
373                                )),
374                            );
375                            descendant_ix += 1;
376                            if cursor.goto_first_child() {
377                                depth += 1;
378                            } else {
379                                visited_children = true;
380                            }
381                        }
382                    }
383                    items
384                }),
385            )
386            .size_full()
387            .track_scroll(self.list_scroll_handle.clone())
388            .text_bg(cx.theme().colors().background).into_any_element());
389        }
390
391        rendered
392    }
393}
394
395impl EventEmitter<()> for SyntaxTreeView {}
396
397impl Focusable for SyntaxTreeView {
398    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
399        self.focus_handle.clone()
400    }
401}
402
403impl Item for SyntaxTreeView {
404    type Event = ();
405
406    fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
407
408    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
409        "Syntax Tree".into()
410    }
411
412    fn telemetry_event_text(&self) -> Option<&'static str> {
413        None
414    }
415
416    fn clone_on_split(
417        &self,
418        _: Option<workspace::WorkspaceId>,
419        window: &mut Window,
420        cx: &mut Context<Self>,
421    ) -> Option<Entity<Self>>
422    where
423        Self: Sized,
424    {
425        Some(cx.new(|cx| {
426            let mut clone = Self::new(self.workspace_handle.clone(), None, window, cx);
427            if let Some(editor) = &self.editor {
428                clone.set_editor(editor.editor.clone(), window, cx)
429            }
430            clone
431        }))
432    }
433}
434
435impl Default for SyntaxTreeToolbarItemView {
436    fn default() -> Self {
437        Self::new()
438    }
439}
440
441impl SyntaxTreeToolbarItemView {
442    pub fn new() -> Self {
443        Self {
444            tree_view: None,
445            subscription: None,
446        }
447    }
448
449    fn render_menu(&mut self, cx: &mut Context<Self>) -> Option<PopoverMenu<ContextMenu>> {
450        let tree_view = self.tree_view.as_ref()?;
451        let tree_view = tree_view.read(cx);
452
453        let editor_state = tree_view.editor.as_ref()?;
454        let buffer_state = editor_state.active_buffer.as_ref()?;
455        let active_layer = buffer_state.active_layer.clone()?;
456        let active_buffer = buffer_state.buffer.read(cx).snapshot();
457
458        let view = cx.entity();
459        Some(
460            PopoverMenu::new("Syntax Tree")
461                .trigger(Self::render_header(&active_layer))
462                .menu(move |window, cx| {
463                    ContextMenu::build(window, cx, |mut menu, window, _| {
464                        for (layer_ix, layer) in active_buffer.syntax_layers().enumerate() {
465                            menu = menu.entry(
466                                format!(
467                                    "{} {}",
468                                    layer.language.name(),
469                                    format_node_range(layer.node())
470                                ),
471                                None,
472                                window.handler_for(&view, move |view, window, cx| {
473                                    view.select_layer(layer_ix, window, cx);
474                                }),
475                            );
476                        }
477                        menu
478                    })
479                    .into()
480                }),
481        )
482    }
483
484    fn select_layer(
485        &mut self,
486        layer_ix: usize,
487        window: &mut Window,
488        cx: &mut Context<Self>,
489    ) -> Option<()> {
490        let tree_view = self.tree_view.as_ref()?;
491        tree_view.update(cx, |view, cx| {
492            let editor_state = view.editor.as_mut()?;
493            let buffer_state = editor_state.active_buffer.as_mut()?;
494            let snapshot = buffer_state.buffer.read(cx).snapshot();
495            let layer = snapshot.syntax_layers().nth(layer_ix)?;
496            buffer_state.active_layer = Some(layer.to_owned());
497            view.selected_descendant_ix = None;
498            cx.notify();
499            view.focus_handle.focus(window);
500            Some(())
501        })
502    }
503
504    fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike {
505        ButtonLike::new("syntax tree header")
506            .child(Label::new(active_layer.language.name()))
507            .child(Label::new(format_node_range(active_layer.node())))
508    }
509}
510
511fn format_node_range(node: Node) -> String {
512    let start = node.start_position();
513    let end = node.end_position();
514    format!(
515        "[{}:{} - {}:{}]",
516        start.row + 1,
517        start.column + 1,
518        end.row + 1,
519        end.column + 1,
520    )
521}
522
523impl Render for SyntaxTreeToolbarItemView {
524    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
525        self.render_menu(cx)
526            .unwrap_or_else(|| PopoverMenu::new("Empty Syntax Tree"))
527    }
528}
529
530impl EventEmitter<ToolbarItemEvent> for SyntaxTreeToolbarItemView {}
531
532impl ToolbarItemView for SyntaxTreeToolbarItemView {
533    fn set_active_pane_item(
534        &mut self,
535        active_pane_item: Option<&dyn ItemHandle>,
536        window: &mut Window,
537        cx: &mut Context<Self>,
538    ) -> ToolbarItemLocation {
539        if let Some(item) = active_pane_item
540            && let Some(view) = item.downcast::<SyntaxTreeView>()
541        {
542            self.tree_view = Some(view.clone());
543            self.subscription = Some(cx.observe_in(&view, window, |_, _, _, cx| cx.notify()));
544            return ToolbarItemLocation::PrimaryLeft;
545        }
546        self.tree_view = None;
547        self.subscription = None;
548        ToolbarItemLocation::Hidden
549    }
550}