syntax_tree_view.rs

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