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