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