syntax_tree_view.rs

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