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}