syntax_tree_view.rs

  1use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};
  2use gpui::{
  3    actions, div, overlay, red, uniform_list, AnyElement, AppContext, CursorStyle, 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, SyntaxLayerInfo};
  9use settings::Settings;
 10use std::{mem, ops::Range};
 11use theme::{Theme, ThemeSettings};
 12use tree_sitter::{Node, TreeCursor};
 13use ui::{h_stack, Label};
 14use workspace::{
 15    item::{Item, ItemHandle},
 16    ui::v_stack,
 17    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 18};
 19
 20actions!(debug, [OpenSyntaxTreeView]);
 21
 22pub fn init(cx: &mut AppContext) {
 23    cx.observe_new_views(|workspace: &mut Workspace, _| {
 24        workspace.register_action(|workspace, _: &OpenSyntaxTreeView, cx| {
 25            let active_item = workspace.active_item(cx);
 26            let workspace_handle = workspace.weak_handle();
 27            let syntax_tree_view =
 28                cx.build_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx));
 29            workspace.add_item(Box::new(syntax_tree_view), cx);
 30        });
 31    })
 32    .detach();
 33}
 34
 35pub struct SyntaxTreeView {
 36    workspace_handle: WeakView<Workspace>,
 37    editor: Option<EditorState>,
 38    mouse_y: Option<Pixels>,
 39    line_height: Option<Pixels>,
 40    list_scroll_handle: UniformListScrollHandle,
 41    selected_descendant_ix: Option<usize>,
 42    hovered_descendant_ix: Option<usize>,
 43    focus_handle: FocusHandle,
 44}
 45
 46pub struct SyntaxTreeToolbarItemView {
 47    tree_view: Option<View<SyntaxTreeView>>,
 48    subscription: Option<gpui::Subscription>,
 49    menu_open: bool,
 50}
 51
 52struct EditorState {
 53    editor: View<Editor>,
 54    active_buffer: Option<BufferState>,
 55    _subscription: gpui::Subscription,
 56}
 57
 58#[derive(Clone)]
 59struct BufferState {
 60    buffer: Model<Buffer>,
 61    excerpt_id: ExcerptId,
 62    active_layer: Option<OwnedSyntaxLayerInfo>,
 63}
 64
 65impl SyntaxTreeView {
 66    pub fn new(
 67        workspace_handle: WeakView<Workspace>,
 68        active_item: Option<Box<dyn ItemHandle>>,
 69        cx: &mut ViewContext<Self>,
 70    ) -> Self {
 71        let mut this = Self {
 72            workspace_handle: workspace_handle.clone(),
 73            list_scroll_handle: UniformListScrollHandle::new(),
 74            editor: None,
 75            mouse_y: None,
 76            line_height: None,
 77            hovered_descendant_ix: None,
 78            selected_descendant_ix: None,
 79            focus_handle: cx.focus_handle(),
 80        };
 81
 82        this.workspace_updated(active_item, cx);
 83        cx.observe(
 84            &workspace_handle.upgrade().unwrap(),
 85            |this, workspace, cx| {
 86                this.workspace_updated(workspace.read(cx).active_item(cx), cx);
 87            },
 88        )
 89        .detach();
 90
 91        this
 92    }
 93
 94    fn workspace_updated(
 95        &mut self,
 96        active_item: Option<Box<dyn ItemHandle>>,
 97        cx: &mut ViewContext<Self>,
 98    ) {
 99        if let Some(item) = active_item {
100            if item.item_id() != cx.entity_id() {
101                if let Some(editor) = item.act_as::<Editor>(cx) {
102                    self.set_editor(editor, cx);
103                }
104            }
105        }
106    }
107
108    fn set_editor(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
109        if let Some(state) = &self.editor {
110            if state.editor == editor {
111                return;
112            }
113            editor.update(cx, |editor, cx| {
114                editor.clear_background_highlights::<Self>(cx)
115            });
116        }
117
118        let subscription = cx.subscribe(&editor, |this, _, event, cx| {
119            let did_reparse = match event {
120                editor::EditorEvent::Reparsed => true,
121                editor::EditorEvent::SelectionsChanged { .. } => false,
122                _ => return,
123            };
124            this.editor_updated(did_reparse, cx);
125        });
126
127        self.editor = Some(EditorState {
128            editor,
129            _subscription: subscription,
130            active_buffer: None,
131        });
132        self.editor_updated(true, cx);
133    }
134
135    fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
136        // Find which excerpt the cursor is in, and the position within that excerpted buffer.
137        let editor_state = self.editor.as_mut()?;
138        let editor = &editor_state.editor.read(cx);
139        let selection_range = editor.selections.last::<usize>(cx).range();
140        let multibuffer = editor.buffer().read(cx);
141        let (buffer, range, excerpt_id) = multibuffer
142            .range_to_buffer_ranges(selection_range, cx)
143            .pop()?;
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,
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 != buffer_state.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.scroll_to_item(descendant_ix);
208
209        cx.notify();
210        Some(())
211    }
212
213    fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> {
214        let line_height = self.line_height?;
215        let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
216
217        self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| {
218            // Put the cursor at the beginning of the node.
219            mem::swap(&mut range.start, &mut range.end);
220
221            editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
222                selections.select_ranges(vec![range]);
223            });
224        });
225        Some(())
226    }
227
228    fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) {
229        if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) {
230            let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize;
231            if self.hovered_descendant_ix != Some(ix) {
232                self.hovered_descendant_ix = Some(ix);
233                self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| {
234                    editor.clear_background_highlights::<Self>(cx);
235                    editor.highlight_background::<Self>(
236                        vec![range],
237                        |theme| theme.editor_document_highlight_write_background,
238                        cx,
239                    );
240                });
241                cx.notify();
242            }
243        }
244    }
245
246    fn update_editor_with_range_for_descendant_ix(
247        &self,
248        descendant_ix: usize,
249        cx: &mut ViewContext<Self>,
250        mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>),
251    ) -> Option<()> {
252        let editor_state = self.editor.as_ref()?;
253        let buffer_state = editor_state.active_buffer.as_ref()?;
254        let layer = buffer_state.active_layer.as_ref()?;
255
256        // Find the node.
257        let mut cursor = layer.node().walk();
258        cursor.goto_descendant(descendant_ix);
259        let node = cursor.node();
260        let range = node.byte_range();
261
262        // Build a text anchor range.
263        let buffer = buffer_state.buffer.read(cx);
264        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
265
266        // Build a multibuffer anchor range.
267        let multibuffer = editor_state.editor.read(cx).buffer();
268        let multibuffer = multibuffer.read(cx).snapshot(cx);
269        let excerpt_id = buffer_state.excerpt_id;
270        let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start)
271            ..multibuffer.anchor_in_excerpt(excerpt_id, range.end);
272
273        // Update the editor with the anchor range.
274        editor_state.editor.update(cx, |editor, cx| {
275            f(editor, range, cx);
276        });
277        Some(())
278    }
279
280    fn render_node(
281        cursor: &TreeCursor,
282        _depth: u32,
283        selected: bool,
284        hovered: bool,
285        list_hovered: bool,
286        style: &TextStyle,
287        editor_theme: &Theme,
288        _cx: &AppContext,
289    ) -> Div {
290        let editor_colors = editor_theme.colors();
291        let node = cursor.node();
292        let mut range_style = style.clone();
293        // todo!() styling
294        // let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
295        // let font_size = style.text.font_size.to_pixels(cx.rem_size());
296        // let line_height = style.text.line_height_in_pixels(cx.rem_size());
297        // let em_width = cx
298        //     .text_system()
299        //     .typographic_bounds(font_id, font_size, 'm')
300        //     .unwrap()
301        //     .size
302        //     .width;
303        // let gutter_padding = (em_width * editor_theme.gutter_padding_factor).round();
304
305        range_style.color = editor_colors.editor_line_number;
306
307        let mut anonymous_node_style = style.clone();
308        let string_color = editor_theme
309            .syntax()
310            .highlights
311            .iter()
312            .find_map(|(name, style)| (name == "string").then(|| style.color)?);
313        let property_color = editor_theme
314            .syntax()
315            .highlights
316            .iter()
317            .find_map(|(name, style)| (name == "property").then(|| style.color)?);
318        if let Some(color) = string_color {
319            anonymous_node_style.color = color;
320        }
321
322        let mut row = h_stack().size_full();
323        if let Some(field_name) = cursor.field_name() {
324            let mut field_style = style.clone();
325            if let Some(color) = property_color {
326                field_style.color = color;
327            }
328
329            row = row.children([Label::new(field_name), Label::new(": ")]);
330        }
331
332        return row
333            .child(
334                if node.is_named() {
335                    Label::new(node.kind())
336                } else {
337                    Label::new(format!("\"{}\"", node.kind()))
338                }, // todo!()
339                   // .margin(em_width),
340            )
341            .child(Label::new(format_node_range(node)))
342            .text_bg(if selected {
343                editor_colors.element_selected
344            } else if hovered && list_hovered {
345                editor_colors.element_active
346            } else {
347                Hsla::default()
348            })
349            // todo!()
350            // .padding(gutter_padding + depth as f32 * 18.0)
351            .border_1()
352            .border_color(red());
353    }
354}
355
356impl Render for SyntaxTreeView {
357    type Element = Div;
358
359    fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> Div {
360        let settings = ThemeSettings::get_global(cx);
361        let font = settings.buffer_font.clone();
362        let font_size = settings.buffer_font_size(cx);
363
364        let editor_theme = settings.active_theme.clone();
365        let editor_colors = editor_theme.colors();
366        let style = TextStyle {
367            color: editor_colors.text,
368            font_family: font.family,
369            font_features: font.features,
370            font_weight: font.weight,
371            font_style: font.style,
372            ..Default::default()
373        };
374
375        let line_height = cx.text_style().line_height_in_pixels(font_size);
376        if Some(line_height) != self.line_height {
377            self.line_height = Some(line_height);
378            self.hover_state_changed(cx);
379        }
380
381        let mut rendered = div();
382
383        if let Some(layer) = self
384            .editor
385            .as_ref()
386            .and_then(|editor| editor.active_buffer.as_ref())
387            .and_then(|buffer| buffer.active_layer.as_ref())
388        {
389            let layer = layer.clone();
390            let theme = editor_theme.clone();
391
392            // todo!()
393            // let list_hovered = state.hovered();
394            let list_hovered = false;
395            let list = uniform_list(
396                cx.view().clone(),
397                "SyntaxTreeView",
398                layer.node().descendant_count(),
399                move |this, range, cx| {
400                    let mut items = Vec::new();
401                    let mut cursor = layer.node().walk();
402                    let mut descendant_ix = range.start as usize;
403                    cursor.goto_descendant(descendant_ix);
404                    let mut depth = cursor.depth();
405                    let mut visited_children = false;
406                    while descendant_ix < range.end {
407                        if visited_children {
408                            if cursor.goto_next_sibling() {
409                                visited_children = false;
410                            } else if cursor.goto_parent() {
411                                depth -= 1;
412                            } else {
413                                break;
414                            }
415                        } else {
416                            items.push(Self::render_node(
417                                &cursor,
418                                depth,
419                                Some(descendant_ix) == this.selected_descendant_ix,
420                                Some(descendant_ix) == this.hovered_descendant_ix,
421                                list_hovered,
422                                &style,
423                                &theme,
424                                cx,
425                            ));
426                            descendant_ix += 1;
427                            if cursor.goto_first_child() {
428                                depth += 1;
429                            } else {
430                                visited_children = true;
431                            }
432                        }
433                    }
434                    items
435                },
436            )
437            .track_scroll(self.list_scroll_handle.clone())
438            .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| {
439                tree_view.mouse_y = Some(event.position.y);
440                tree_view.hover_state_changed(cx);
441            }))
442            .on_mouse_down(
443                MouseButton::Left,
444                cx.listener(move |tree_view, event: &MouseDownEvent, cx| {
445                    tree_view.handle_click(event.position.y, cx);
446                }),
447            )
448            .text_bg(editor_colors.background);
449
450            rendered = rendered.child(list);
451        }
452
453        rendered
454    }
455}
456
457impl EventEmitter<()> for SyntaxTreeView {}
458
459impl FocusableView for SyntaxTreeView {
460    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
461        self.focus_handle.clone()
462    }
463}
464
465impl Item for SyntaxTreeView {
466    type Event = ();
467
468    fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
469
470    fn tab_content(&self, _: Option<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
471        Label::new("Syntax Tree").into_any_element()
472    }
473
474    fn clone_on_split(
475        &self,
476        _: workspace::WorkspaceId,
477        cx: &mut ViewContext<Self>,
478    ) -> Option<View<Self>>
479    where
480        Self: Sized,
481    {
482        Some(cx.build_view(|cx| {
483            let mut clone = Self::new(self.workspace_handle.clone(), None, cx);
484            if let Some(editor) = &self.editor {
485                clone.set_editor(editor.editor.clone(), cx)
486            }
487            clone
488        }))
489    }
490}
491
492impl SyntaxTreeToolbarItemView {
493    pub fn new() -> Self {
494        Self {
495            menu_open: false,
496            tree_view: None,
497            subscription: None,
498        }
499    }
500
501    fn render_menu(&mut self, cx: &mut ViewContext<'_, Self>) -> Option<Div> {
502        let tree_view = self.tree_view.as_ref()?;
503        let tree_view = tree_view.read(cx);
504
505        let editor_state = tree_view.editor.as_ref()?;
506        let buffer_state = editor_state.active_buffer.as_ref()?;
507        let active_layer = buffer_state.active_layer.clone()?;
508        let active_buffer = buffer_state.buffer.read(cx).snapshot();
509
510        Some(
511            v_stack()
512                .size_full()
513                .child(Self::render_header(&active_layer, cx))
514                .children(self.menu_open.then(|| {
515                    overlay().child(
516                        v_stack()
517                            .size_full()
518                            .children(active_buffer.syntax_layers().enumerate().map(
519                                |(ix, layer)| Self::render_menu_item(&active_layer, layer, ix, cx),
520                            ))
521                            .on_mouse_down_out(cx.listener(|this, e: &MouseDownEvent, cx| {
522                                if e.button == MouseButton::Left {
523                                    this.menu_open = false;
524                                    cx.notify()
525                                }
526                            })),
527                    )
528                }))
529                .z_index(99),
530        )
531    }
532
533    fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
534        self.menu_open = !self.menu_open;
535        cx.notify();
536    }
537
538    fn select_layer(&mut self, layer_ix: usize, cx: &mut ViewContext<Self>) -> Option<()> {
539        let tree_view = self.tree_view.as_ref()?;
540        tree_view.update(cx, |view, cx| {
541            let editor_state = view.editor.as_mut()?;
542            let buffer_state = editor_state.active_buffer.as_mut()?;
543            let snapshot = buffer_state.buffer.read(cx).snapshot();
544            let layer = snapshot.syntax_layers().nth(layer_ix)?;
545            buffer_state.active_layer = Some(layer.to_owned());
546            view.selected_descendant_ix = None;
547            self.menu_open = false;
548            cx.notify();
549            Some(())
550        })
551    }
552
553    fn render_header(active_layer: &OwnedSyntaxLayerInfo, cx: &mut ViewContext<Self>) -> Div {
554        h_stack()
555            .size_full()
556            .child(Label::new(active_layer.language.name()))
557            .child(Label::new(format_node_range(active_layer.node())))
558            .on_mouse_down(
559                MouseButton::Left,
560                cx.listener(move |view, _, cx| {
561                    view.toggle_menu(cx);
562                }),
563            )
564            .cursor(CursorStyle::PointingHand)
565            .border_1()
566            .border_color(red())
567    }
568
569    fn render_menu_item(
570        active_layer: &OwnedSyntaxLayerInfo,
571        layer: SyntaxLayerInfo,
572        layer_ix: usize,
573        cx: &mut ViewContext<Self>,
574    ) -> Div {
575        // todo!() styling
576        let _is_selected = layer.node() == active_layer.node();
577        h_stack()
578            .size_full()
579            .child(Label::new(layer.language.name().to_string()))
580            .child(Label::new(format_node_range(layer.node())))
581            .cursor(CursorStyle::PointingHand)
582            .on_mouse_down(
583                MouseButton::Left,
584                cx.listener(move |view, _, cx| {
585                    view.select_layer(layer_ix, cx);
586                }),
587            )
588            .border_1()
589            .border_color(red())
590            .bg(red())
591    }
592}
593
594fn format_node_range(node: Node) -> String {
595    let start = node.start_position();
596    let end = node.end_position();
597    format!(
598        "[{}:{} - {}:{}]",
599        start.row + 1,
600        start.column + 1,
601        end.row + 1,
602        end.column + 1,
603    )
604}
605
606impl Render for SyntaxTreeToolbarItemView {
607    type Element = Div;
608
609    // todo!()
610    // fn ui_name() -> &'static str {
611    //     "SyntaxTreeToolbarItemView"
612    // }
613
614    fn render(&mut self, cx: &mut ViewContext<'_, Self>) -> Div {
615        self.render_menu(cx).unwrap_or_else(|| div())
616    }
617}
618
619impl EventEmitter<ToolbarItemEvent> for SyntaxTreeToolbarItemView {}
620
621impl ToolbarItemView for SyntaxTreeToolbarItemView {
622    fn set_active_pane_item(
623        &mut self,
624        active_pane_item: Option<&dyn ItemHandle>,
625        cx: &mut ViewContext<Self>,
626    ) -> ToolbarItemLocation {
627        self.menu_open = false;
628        if let Some(item) = active_pane_item {
629            if let Some(view) = item.downcast::<SyntaxTreeView>() {
630                self.tree_view = Some(view.clone());
631                self.subscription = Some(cx.observe(&view, |_, _, cx| cx.notify()));
632                return ToolbarItemLocation::PrimaryLeft;
633            }
634        }
635        self.tree_view = None;
636        self.subscription = None;
637        ToolbarItemLocation::Hidden
638    }
639}