syntax_tree_view.rs

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