syntax_tree_view.rs

  1use command_palette_hooks::CommandPaletteFilter;
  2use editor::{
  3    Anchor, Editor, ExcerptId, HighlightKey, MultiBufferOffset, SelectionEffects,
  4    scroll::Autoscroll,
  5};
  6use gpui::{
  7    App, AppContext as _, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
  8    Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent,
  9    ParentElement, Render, ScrollStrategy, SharedString, Styled, Task, UniformListScrollHandle,
 10    WeakEntity, Window, actions, div, rems, uniform_list,
 11};
 12use language::{Buffer, OwnedSyntaxLayer};
 13use std::{any::TypeId, mem, ops::Range};
 14use theme::ActiveTheme;
 15use tree_sitter::{Node, TreeCursor};
 16use ui::{
 17    ButtonCommon, ButtonLike, Clickable, Color, ContextMenu, FluentBuilder as _, IconButton,
 18    IconName, Label, LabelCommon, LabelSize, PopoverMenu, StyledExt, Tooltip, WithScrollbar,
 19    h_flex, v_flex,
 20};
 21use workspace::{
 22    Event as WorkspaceEvent, SplitDirection, ToolbarItemEvent, ToolbarItemLocation,
 23    ToolbarItemView, Workspace,
 24    item::{Item, ItemHandle},
 25};
 26
 27actions!(
 28    dev,
 29    [
 30        /// Opens the syntax tree view for the current file.
 31        OpenSyntaxTreeView,
 32    ]
 33);
 34
 35actions!(
 36    syntax_tree_view,
 37    [
 38        /// Update the syntax tree view to show the last focused file.
 39        UseActiveEditor
 40    ]
 41);
 42
 43pub fn init(cx: &mut App) {
 44    let syntax_tree_actions = [TypeId::of::<UseActiveEditor>()];
 45
 46    CommandPaletteFilter::update_global(cx, |this, _| {
 47        this.hide_action_types(&syntax_tree_actions);
 48    });
 49
 50    cx.observe_new(move |workspace: &mut Workspace, _, _| {
 51        workspace.register_action(move |workspace, _: &OpenSyntaxTreeView, window, cx| {
 52            CommandPaletteFilter::update_global(cx, |this, _| {
 53                this.show_action_types(&syntax_tree_actions);
 54            });
 55
 56            let active_item = workspace.active_item(cx);
 57            let workspace_handle = workspace.weak_handle();
 58            let syntax_tree_view = cx.new(|cx| {
 59                cx.on_release(move |view: &mut SyntaxTreeView, cx| {
 60                    if view
 61                        .workspace_handle
 62                        .read_with(cx, |workspace, cx| {
 63                            workspace.item_of_type::<SyntaxTreeView>(cx).is_none()
 64                        })
 65                        .unwrap_or_default()
 66                    {
 67                        CommandPaletteFilter::update_global(cx, |this, _| {
 68                            this.hide_action_types(&syntax_tree_actions);
 69                        });
 70                    }
 71                })
 72                .detach();
 73
 74                SyntaxTreeView::new(workspace_handle, active_item, window, cx)
 75            });
 76            workspace.split_item(
 77                SplitDirection::Right,
 78                Box::new(syntax_tree_view),
 79                window,
 80                cx,
 81            )
 82        });
 83        workspace.register_action(|workspace, _: &UseActiveEditor, window, cx| {
 84            if let Some(tree_view) = workspace.item_of_type::<SyntaxTreeView>(cx) {
 85                tree_view.update(cx, |view, cx| {
 86                    view.update_active_editor(&Default::default(), window, cx)
 87                })
 88            }
 89        });
 90    })
 91    .detach();
 92}
 93
 94pub struct SyntaxTreeView {
 95    workspace_handle: WeakEntity<Workspace>,
 96    editor: Option<EditorState>,
 97    list_scroll_handle: UniformListScrollHandle,
 98    /// The last active editor in the workspace. Note that this is specifically not the
 99    /// currently shown editor.
100    last_active_editor: Option<Entity<Editor>>,
101    selected_descendant_ix: Option<usize>,
102    hovered_descendant_ix: Option<usize>,
103    focus_handle: FocusHandle,
104}
105
106pub struct SyntaxTreeToolbarItemView {
107    tree_view: Option<Entity<SyntaxTreeView>>,
108    subscription: Option<gpui::Subscription>,
109}
110
111struct EditorState {
112    editor: Entity<Editor>,
113    active_buffer: Option<BufferState>,
114    _subscription: gpui::Subscription,
115}
116
117impl EditorState {
118    fn has_language(&self) -> bool {
119        self.active_buffer
120            .as_ref()
121            .is_some_and(|buffer| buffer.active_layer.is_some())
122    }
123}
124
125#[derive(Clone)]
126struct BufferState {
127    buffer: Entity<Buffer>,
128    excerpt_id: ExcerptId,
129    active_layer: Option<OwnedSyntaxLayer>,
130}
131
132impl SyntaxTreeView {
133    pub fn new(
134        workspace_handle: WeakEntity<Workspace>,
135        active_item: Option<Box<dyn ItemHandle>>,
136        window: &mut Window,
137        cx: &mut Context<Self>,
138    ) -> Self {
139        let mut this = Self {
140            workspace_handle: workspace_handle.clone(),
141            list_scroll_handle: UniformListScrollHandle::new(),
142            editor: None,
143            last_active_editor: None,
144            hovered_descendant_ix: None,
145            selected_descendant_ix: None,
146            focus_handle: cx.focus_handle(),
147        };
148
149        this.handle_item_updated(active_item, window, cx);
150
151        cx.subscribe_in(
152            &workspace_handle.upgrade().unwrap(),
153            window,
154            move |this, workspace, event, window, cx| match event {
155                WorkspaceEvent::ItemAdded { .. } | WorkspaceEvent::ActiveItemChanged => {
156                    this.handle_item_updated(workspace.read(cx).active_item(cx), window, cx)
157                }
158                WorkspaceEvent::ItemRemoved { item_id } => {
159                    this.handle_item_removed(item_id, window, cx);
160                }
161                _ => {}
162            },
163        )
164        .detach();
165
166        this
167    }
168
169    fn handle_item_updated(
170        &mut self,
171        active_item: Option<Box<dyn ItemHandle>>,
172        window: &mut Window,
173        cx: &mut Context<Self>,
174    ) {
175        let Some(editor) = active_item
176            .filter(|item| item.item_id() != cx.entity_id())
177            .and_then(|item| item.act_as::<Editor>(cx))
178        else {
179            return;
180        };
181
182        if let Some(editor_state) = self.editor.as_ref().filter(|state| state.has_language()) {
183            self.last_active_editor = (editor_state.editor != editor).then_some(editor);
184        } else {
185            self.set_editor(editor, window, cx);
186        }
187    }
188
189    fn handle_item_removed(
190        &mut self,
191        item_id: &EntityId,
192        window: &mut Window,
193        cx: &mut Context<Self>,
194    ) {
195        if self
196            .editor
197            .as_ref()
198            .is_some_and(|state| state.editor.entity_id() == *item_id)
199        {
200            self.editor = None;
201            // Try activating the last active editor if there is one
202            self.update_active_editor(&Default::default(), window, cx);
203            cx.notify();
204        }
205    }
206
207    fn update_active_editor(
208        &mut self,
209        _: &UseActiveEditor,
210        window: &mut Window,
211        cx: &mut Context<Self>,
212    ) {
213        let Some(editor) = self.last_active_editor.take() else {
214            return;
215        };
216        self.set_editor(editor, window, cx);
217    }
218
219    fn set_editor(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut Context<Self>) {
220        if let Some(state) = &self.editor {
221            if state.editor == editor {
222                return;
223            }
224            let key = HighlightKey::SyntaxTreeView(cx.entity_id().as_u64() as usize);
225            editor.update(cx, |editor, cx| editor.clear_background_highlights(key, cx));
226        }
227
228        let subscription = cx.subscribe_in(&editor, window, |this, _, event, window, cx| {
229            let did_reparse = match event {
230                editor::EditorEvent::Reparsed(_) => true,
231                editor::EditorEvent::SelectionsChanged { .. } => false,
232                _ => return,
233            };
234            this.editor_updated(did_reparse, window, cx);
235        });
236
237        self.editor = Some(EditorState {
238            editor,
239            _subscription: subscription,
240            active_buffer: None,
241        });
242        self.editor_updated(true, window, cx);
243    }
244
245    fn editor_updated(
246        &mut self,
247        did_reparse: bool,
248        window: &mut Window,
249        cx: &mut Context<Self>,
250    ) -> Option<()> {
251        // Find which excerpt the cursor is in, and the position within that excerpted buffer.
252        let editor_state = self.editor.as_mut()?;
253        let snapshot = editor_state
254            .editor
255            .update(cx, |editor, cx| editor.snapshot(window, cx));
256        let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
257            let selection_range = editor
258                .selections
259                .last::<MultiBufferOffset>(&editor.display_snapshot(cx))
260                .range();
261            let multi_buffer = editor.buffer().read(cx);
262            let (buffer, range, excerpt_id) = snapshot
263                .buffer_snapshot()
264                .range_to_buffer_ranges(selection_range.start..=selection_range.end)
265                .pop()?;
266            let buffer = multi_buffer.buffer(buffer.remote_id()).unwrap();
267            Some((buffer, range, excerpt_id))
268        })?;
269
270        // If the cursor has moved into a different excerpt, retrieve a new syntax layer
271        // from that buffer.
272        let buffer_state = editor_state
273            .active_buffer
274            .get_or_insert_with(|| BufferState {
275                buffer: buffer.clone(),
276                excerpt_id,
277                active_layer: None,
278            });
279        let mut prev_layer = None;
280        if did_reparse {
281            prev_layer = buffer_state.active_layer.take();
282        }
283        if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
284            buffer_state.buffer = buffer.clone();
285            buffer_state.excerpt_id = excerpt_id;
286            buffer_state.active_layer = None;
287        }
288
289        let layer = match &mut buffer_state.active_layer {
290            Some(layer) => layer,
291            None => {
292                let snapshot = buffer.read(cx).snapshot();
293                let layer = if let Some(prev_layer) = prev_layer {
294                    let prev_range = prev_layer.node().byte_range();
295                    snapshot
296                        .syntax_layers()
297                        .filter(|layer| layer.language == &prev_layer.language)
298                        .min_by_key(|layer| {
299                            let range = layer.node().byte_range();
300                            ((range.start as i64) - (prev_range.start as i64)).abs()
301                                + ((range.end as i64) - (prev_range.end as i64)).abs()
302                        })?
303                } else {
304                    snapshot.syntax_layers().next()?
305                };
306                buffer_state.active_layer.insert(layer.to_owned())
307            }
308        };
309
310        // Within the active layer, find the syntax node under the cursor,
311        // and scroll to it.
312        let mut cursor = layer.node().walk();
313        while cursor.goto_first_child_for_byte(range.start.0).is_some() {
314            if !range.is_empty() && cursor.node().end_byte() == range.start.0 {
315                cursor.goto_next_sibling();
316            }
317        }
318
319        // Ascend to the smallest ancestor that contains the range.
320        loop {
321            let node_range = cursor.node().byte_range();
322            if node_range.start <= range.start.0 && node_range.end >= range.end.0 {
323                break;
324            }
325            if !cursor.goto_parent() {
326                break;
327            }
328        }
329
330        let descendant_ix = cursor.descendant_index();
331        self.selected_descendant_ix = Some(descendant_ix);
332        self.list_scroll_handle
333            .scroll_to_item(descendant_ix, ScrollStrategy::Center);
334
335        cx.notify();
336        Some(())
337    }
338
339    fn update_editor_with_range_for_descendant_ix(
340        &self,
341        descendant_ix: usize,
342        window: &mut Window,
343        cx: &mut Context<Self>,
344        f: &mut dyn FnMut(&mut Editor, Range<Anchor>, usize, &mut Window, &mut Context<Editor>),
345    ) -> Option<()> {
346        let editor_state = self.editor.as_ref()?;
347        let buffer_state = editor_state.active_buffer.as_ref()?;
348        let layer = buffer_state.active_layer.as_ref()?;
349
350        // Find the node.
351        let mut cursor = layer.node().walk();
352        cursor.goto_descendant(descendant_ix);
353        let node = cursor.node();
354        let range = node.byte_range();
355
356        // Build a text anchor range.
357        let buffer = buffer_state.buffer.read(cx);
358        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
359
360        // Build a multibuffer anchor range.
361        let multibuffer = editor_state.editor.read(cx).buffer();
362        let multibuffer = multibuffer.read(cx).snapshot(cx);
363        let excerpt_id = buffer_state.excerpt_id;
364        let range = multibuffer.anchor_range_in_excerpt(excerpt_id, range)?;
365        let key = cx.entity_id().as_u64() as usize;
366
367        // Update the editor with the anchor range.
368        editor_state.editor.update(cx, |editor, cx| {
369            f(editor, range, key, window, cx);
370        });
371        Some(())
372    }
373
374    fn render_node(cursor: &TreeCursor, depth: u32, selected: bool, cx: &App) -> Div {
375        let colors = cx.theme().colors();
376        let mut row = h_flex();
377        if let Some(field_name) = cursor.field_name() {
378            row = row.children([Label::new(field_name).color(Color::Info), Label::new(": ")]);
379        }
380
381        let node = cursor.node();
382        row.child(if node.is_named() {
383            Label::new(node.kind()).color(Color::Default)
384        } else {
385            Label::new(format!("\"{}\"", node.kind())).color(Color::Created)
386        })
387        .child(
388            div()
389                .child(Label::new(format_node_range(node)).color(Color::Muted))
390                .pl_1(),
391        )
392        .text_bg(if selected {
393            colors.element_selected
394        } else {
395            Hsla::default()
396        })
397        .pl(rems(depth as f32))
398        .hover(|style| style.bg(colors.element_hover))
399    }
400
401    fn compute_items(
402        &mut self,
403        layer: &OwnedSyntaxLayer,
404        range: Range<usize>,
405        cx: &Context<Self>,
406    ) -> Vec<Div> {
407        let mut items = Vec::new();
408        let mut cursor = layer.node().walk();
409        let mut descendant_ix = range.start;
410        cursor.goto_descendant(descendant_ix);
411        let mut depth = cursor.depth();
412        let mut visited_children = false;
413        while descendant_ix < range.end {
414            if visited_children {
415                if cursor.goto_next_sibling() {
416                    visited_children = false;
417                } else if cursor.goto_parent() {
418                    depth -= 1;
419                } else {
420                    break;
421                }
422            } else {
423                items.push(
424                    Self::render_node(
425                        &cursor,
426                        depth,
427                        Some(descendant_ix) == self.selected_descendant_ix,
428                        cx,
429                    )
430                    .on_mouse_down(
431                        MouseButton::Left,
432                        cx.listener(move |tree_view, _: &MouseDownEvent, window, cx| {
433                            tree_view.update_editor_with_range_for_descendant_ix(
434                                descendant_ix,
435                                window,
436                                cx,
437                                &mut |editor, mut range, _, window, cx| {
438                                    // Put the cursor at the beginning of the node.
439                                    mem::swap(&mut range.start, &mut range.end);
440
441                                    editor.change_selections(
442                                        SelectionEffects::scroll(Autoscroll::newest()),
443                                        window,
444                                        cx,
445                                        |selections| {
446                                            selections.select_ranges([range]);
447                                        },
448                                    );
449                                },
450                            );
451                        }),
452                    )
453                    .on_mouse_move(cx.listener(
454                        move |tree_view, _: &MouseMoveEvent, window, cx| {
455                            if tree_view.hovered_descendant_ix != Some(descendant_ix) {
456                                tree_view.hovered_descendant_ix = Some(descendant_ix);
457                                tree_view.update_editor_with_range_for_descendant_ix(
458                                    descendant_ix,
459                                    window,
460                                    cx,
461                                    &mut |editor, range, key, _, cx| {
462                                        Self::set_editor_highlights(editor, key, &[range], cx);
463                                    },
464                                );
465                                cx.notify();
466                            }
467                        },
468                    )),
469                );
470                descendant_ix += 1;
471                if cursor.goto_first_child() {
472                    depth += 1;
473                } else {
474                    visited_children = true;
475                }
476            }
477        }
478        items
479    }
480
481    fn set_editor_highlights(
482        editor: &mut Editor,
483        key: usize,
484        ranges: &[Range<Anchor>],
485        cx: &mut Context<Editor>,
486    ) {
487        editor.highlight_background(
488            HighlightKey::SyntaxTreeView(key),
489            ranges,
490            |_, theme| theme.colors().editor_document_highlight_write_background,
491            cx,
492        );
493    }
494
495    fn clear_editor_highlights(editor: &Entity<Editor>, cx: &mut Context<Self>) {
496        let highlight_key = HighlightKey::SyntaxTreeView(cx.entity_id().as_u64() as usize);
497        editor.update(cx, |editor, cx| {
498            editor.clear_background_highlights(highlight_key, cx);
499        });
500    }
501}
502
503impl Render for SyntaxTreeView {
504    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
505        div()
506            .flex_1()
507            .bg(cx.theme().colors().editor_background)
508            .map(|this| {
509                let editor_state = self.editor.as_ref();
510
511                if let Some(layer) = editor_state
512                    .and_then(|editor| editor.active_buffer.as_ref())
513                    .and_then(|buffer| buffer.active_layer.as_ref())
514                {
515                    let layer = layer.clone();
516                    this.child(
517                        uniform_list(
518                            "SyntaxTreeView",
519                            layer.node().descendant_count(),
520                            cx.processor(move |this, range: Range<usize>, _, cx| {
521                                this.compute_items(&layer, range, cx)
522                            }),
523                        )
524                        .size_full()
525                        .track_scroll(&self.list_scroll_handle)
526                        .text_bg(cx.theme().colors().background)
527                        .into_any_element(),
528                    )
529                    .vertical_scrollbar_for(&self.list_scroll_handle, window, cx)
530                    .into_any_element()
531                } else {
532                    let inner_content = v_flex()
533                        .items_center()
534                        .text_center()
535                        .gap_2()
536                        .max_w_3_5()
537                        .map(|this| {
538                            if editor_state.is_some_and(|state| !state.has_language()) {
539                                this.child(Label::new("Current editor has no associated language"))
540                                    .child(
541                                        Label::new(concat!(
542                                            "Try assigning a language or",
543                                            "switching to a different buffer"
544                                        ))
545                                        .size(LabelSize::Small),
546                                    )
547                            } else {
548                                this.child(Label::new("Not attached to an editor")).child(
549                                    Label::new("Focus an editor to show a new tree view")
550                                        .size(LabelSize::Small),
551                                )
552                            }
553                        });
554
555                    this.h_flex()
556                        .size_full()
557                        .justify_center()
558                        .child(inner_content)
559                        .into_any_element()
560                }
561            })
562    }
563}
564
565impl EventEmitter<()> for SyntaxTreeView {}
566
567impl Focusable for SyntaxTreeView {
568    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
569        self.focus_handle.clone()
570    }
571}
572
573impl Item for SyntaxTreeView {
574    type Event = ();
575
576    fn to_item_events(_: &Self::Event, _: &mut dyn FnMut(workspace::item::ItemEvent)) {}
577
578    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
579        "Syntax Tree".into()
580    }
581
582    fn telemetry_event_text(&self) -> Option<&'static str> {
583        None
584    }
585
586    fn can_split(&self) -> bool {
587        true
588    }
589
590    fn clone_on_split(
591        &self,
592        _: Option<workspace::WorkspaceId>,
593        window: &mut Window,
594        cx: &mut Context<Self>,
595    ) -> Task<Option<Entity<Self>>>
596    where
597        Self: Sized,
598    {
599        Task::ready(Some(cx.new(|cx| {
600            let mut clone = Self::new(self.workspace_handle.clone(), None, window, cx);
601            if let Some(editor) = &self.editor {
602                clone.set_editor(editor.editor.clone(), window, cx)
603            }
604            clone
605        })))
606    }
607
608    fn on_removed(&self, cx: &mut Context<Self>) {
609        if let Some(state) = self.editor.as_ref() {
610            Self::clear_editor_highlights(&state.editor, cx);
611        }
612    }
613}
614
615impl Default for SyntaxTreeToolbarItemView {
616    fn default() -> Self {
617        Self::new()
618    }
619}
620
621impl SyntaxTreeToolbarItemView {
622    pub fn new() -> Self {
623        Self {
624            tree_view: None,
625            subscription: None,
626        }
627    }
628
629    fn render_menu(&mut self, cx: &mut Context<Self>) -> Option<PopoverMenu<ContextMenu>> {
630        let tree_view = self.tree_view.as_ref()?;
631        let tree_view = tree_view.read(cx);
632
633        let editor_state = tree_view.editor.as_ref()?;
634        let buffer_state = editor_state.active_buffer.as_ref()?;
635        let active_layer = buffer_state.active_layer.clone()?;
636        let active_buffer = buffer_state.buffer.read(cx).snapshot();
637
638        let view = cx.weak_entity();
639        Some(
640            PopoverMenu::new("Syntax Tree")
641                .trigger(Self::render_header(&active_layer))
642                .menu(move |window, cx| {
643                    ContextMenu::build(window, cx, |mut menu, _, _| {
644                        for (layer_ix, layer) in active_buffer.syntax_layers().enumerate() {
645                            let view = view.clone();
646                            menu = menu.entry(
647                                format!(
648                                    "{} {}",
649                                    layer.language.name(),
650                                    format_node_range(layer.node())
651                                ),
652                                None,
653                                move |window, cx| {
654                                    view.update(cx, |view, cx| {
655                                        view.select_layer(layer_ix, window, cx);
656                                    })
657                                    .ok();
658                                },
659                            );
660                        }
661                        menu
662                    })
663                    .into()
664                }),
665        )
666    }
667
668    fn select_layer(
669        &mut self,
670        layer_ix: usize,
671        window: &mut Window,
672        cx: &mut Context<Self>,
673    ) -> Option<()> {
674        let tree_view = self.tree_view.as_ref()?;
675        tree_view.update(cx, |view, cx| {
676            let editor_state = view.editor.as_mut()?;
677            let buffer_state = editor_state.active_buffer.as_mut()?;
678            let snapshot = buffer_state.buffer.read(cx).snapshot();
679            let layer = snapshot.syntax_layers().nth(layer_ix)?;
680            buffer_state.active_layer = Some(layer.to_owned());
681            view.selected_descendant_ix = None;
682            cx.notify();
683            view.focus_handle.focus(window, cx);
684            Some(())
685        })
686    }
687
688    fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike {
689        ButtonLike::new("syntax tree header")
690            .child(Label::new(active_layer.language.name()))
691            .child(Label::new(format_node_range(active_layer.node())))
692    }
693
694    fn render_update_button(&mut self, cx: &mut Context<Self>) -> Option<IconButton> {
695        self.tree_view.as_ref().and_then(|view| {
696            view.update(cx, |view, cx| {
697                view.last_active_editor.as_ref().map(|editor| {
698                    IconButton::new("syntax-view-update", IconName::RotateCw)
699                        .tooltip({
700                            let active_tab_name = editor.read_with(cx, |editor, cx| {
701                                editor.tab_content_text(Default::default(), cx)
702                            });
703
704                            Tooltip::text(format!("Update view to '{active_tab_name}'"))
705                        })
706                        .on_click(cx.listener(|this, _, window, cx| {
707                            this.update_active_editor(&Default::default(), window, cx);
708                        }))
709                })
710            })
711        })
712    }
713}
714
715fn format_node_range(node: Node) -> String {
716    let start = node.start_position();
717    let end = node.end_position();
718    format!(
719        "[{}:{} - {}:{}]",
720        start.row + 1,
721        start.column + 1,
722        end.row + 1,
723        end.column + 1,
724    )
725}
726
727impl Render for SyntaxTreeToolbarItemView {
728    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
729        h_flex()
730            .gap_1()
731            .children(self.render_menu(cx))
732            .children(self.render_update_button(cx))
733    }
734}
735
736impl EventEmitter<ToolbarItemEvent> for SyntaxTreeToolbarItemView {}
737
738impl ToolbarItemView for SyntaxTreeToolbarItemView {
739    fn set_active_pane_item(
740        &mut self,
741        active_pane_item: Option<&dyn ItemHandle>,
742        window: &mut Window,
743        cx: &mut Context<Self>,
744    ) -> ToolbarItemLocation {
745        if let Some(item) = active_pane_item
746            && let Some(view) = item.downcast::<SyntaxTreeView>()
747        {
748            self.tree_view = Some(view.clone());
749            self.subscription = Some(cx.observe_in(&view, window, |_, _, _, cx| cx.notify()));
750            return ToolbarItemLocation::PrimaryLeft;
751        }
752        self.tree_view = None;
753        self.subscription = None;
754        ToolbarItemLocation::Hidden
755    }
756}