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