syntax_tree_view.rs

  1use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};
  2use gpui::{
  3    actions,
  4    elements::{Empty, Label, MouseEventHandler, UniformList, UniformListState},
  5    fonts::TextStyle,
  6    platform::MouseButton,
  7    AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle,
  8};
  9use language::{Buffer, OwnedSyntaxLayerInfo};
 10use std::ops::Range;
 11use theme::ThemeSettings;
 12use workspace::{
 13    item::{Item, ItemHandle},
 14    Workspace,
 15};
 16
 17actions!(log, [OpenSyntaxTreeView]);
 18
 19pub fn init(cx: &mut AppContext) {
 20    cx.add_action(
 21        move |workspace: &mut Workspace, _: &OpenSyntaxTreeView, cx: _| {
 22            let syntax_tree_view = cx.add_view(|cx| SyntaxTreeView::new(workspace, cx));
 23            workspace.add_item(Box::new(syntax_tree_view), cx);
 24        },
 25    );
 26}
 27
 28pub struct SyntaxTreeView {
 29    editor: Option<(ViewHandle<Editor>, gpui::Subscription)>,
 30    buffer: Option<(ModelHandle<Buffer>, usize, ExcerptId)>,
 31    layer: Option<OwnedSyntaxLayerInfo>,
 32    hover_y: Option<f32>,
 33    line_height: Option<f32>,
 34    list_state: UniformListState,
 35    active_descendant_ix: Option<usize>,
 36    highlighted_active_descendant: bool,
 37}
 38
 39impl SyntaxTreeView {
 40    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
 41        let mut this = Self {
 42            list_state: UniformListState::default(),
 43            editor: None,
 44            buffer: None,
 45            layer: None,
 46            hover_y: None,
 47            line_height: None,
 48            active_descendant_ix: None,
 49            highlighted_active_descendant: false,
 50        };
 51
 52        this.workspace_updated(workspace.active_item(cx), cx);
 53        cx.observe(
 54            &workspace.weak_handle().upgrade(cx).unwrap(),
 55            |this, workspace, cx| {
 56                this.workspace_updated(workspace.read(cx).active_item(cx), cx);
 57            },
 58        )
 59        .detach();
 60
 61        this
 62    }
 63
 64    fn workspace_updated(
 65        &mut self,
 66        active_item: Option<Box<dyn ItemHandle>>,
 67        cx: &mut ViewContext<Self>,
 68    ) {
 69        if let Some(item) = active_item {
 70            if item.id() != cx.view_id() {
 71                if let Some(editor) = item.act_as::<Editor>(cx) {
 72                    self.set_editor(editor, cx);
 73                }
 74            }
 75        }
 76    }
 77
 78    fn set_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
 79        if let Some((current_editor, _)) = &self.editor {
 80            if current_editor == &editor {
 81                return;
 82            }
 83            editor.update(cx, |editor, cx| {
 84                editor.clear_background_highlights::<Self>(cx);
 85            });
 86        }
 87
 88        let subscription = cx.subscribe(&editor, |this, editor, event, cx| {
 89            let selection_changed = match event {
 90                editor::Event::Reparsed => false,
 91                editor::Event::SelectionsChanged { .. } => true,
 92                _ => return,
 93            };
 94            this.editor_updated(&editor, selection_changed, cx);
 95        });
 96
 97        self.editor_updated(&editor, true, cx);
 98        self.editor = Some((editor, subscription));
 99    }
100
101    fn editor_updated(
102        &mut self,
103        editor: &ViewHandle<Editor>,
104        selection_changed: bool,
105        cx: &mut ViewContext<Self>,
106    ) {
107        let editor = editor.read(cx);
108        if selection_changed {
109            let cursor = editor.selections.last::<usize>(cx).end;
110            self.buffer = editor.buffer().read(cx).point_to_buffer_offset(cursor, cx);
111            self.layer = self.buffer.as_ref().and_then(|(buffer, offset, _)| {
112                buffer
113                    .read(cx)
114                    .snapshot()
115                    .syntax_layer_at(*offset)
116                    .map(|l| l.to_owned())
117            });
118        }
119        cx.notify();
120    }
121
122    fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) {
123        if let Some((y, line_height)) = self.hover_y.zip(self.line_height) {
124            let ix = ((self.list_state.scroll_top() + y) / line_height) as usize;
125            if self.active_descendant_ix != Some(ix) {
126                self.active_descendant_ix = Some(ix);
127                self.highlighted_active_descendant = false;
128                cx.notify();
129            }
130        }
131    }
132
133    fn handle_click(&mut self, y: f32, cx: &mut ViewContext<SyntaxTreeView>) {
134        if let Some(line_height) = self.line_height {
135            let ix = ((self.list_state.scroll_top() + y) / line_height) as usize;
136            if let Some(layer) = &self.layer {
137                let mut cursor = layer.node().walk();
138                cursor.goto_descendant(ix);
139                let node = cursor.node();
140                self.update_editor_with_node_range(node, cx, |editor, range, cx| {
141                    editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
142                        selections.select_ranges(vec![range]);
143                    });
144                });
145            }
146        }
147    }
148
149    fn update_editor_with_node_range(
150        &self,
151        node: tree_sitter::Node,
152        cx: &mut ViewContext<Self>,
153        mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>),
154    ) {
155        let range = node.byte_range();
156        if let Some((editor, _)) = &self.editor {
157            if let Some((buffer, _, excerpt_id)) = &self.buffer {
158                let buffer = &buffer.read(cx);
159                let multibuffer = editor.read(cx).buffer();
160                let multibuffer = multibuffer.read(cx).snapshot(cx);
161                let start =
162                    multibuffer.anchor_in_excerpt(*excerpt_id, buffer.anchor_before(range.start));
163                let end =
164                    multibuffer.anchor_in_excerpt(*excerpt_id, buffer.anchor_after(range.end));
165                editor.update(cx, |editor, cx| {
166                    f(editor, start..end, cx);
167                });
168            }
169        }
170    }
171
172    fn node_is_active(&mut self, node: tree_sitter::Node, cx: &mut ViewContext<Self>) {
173        if self.highlighted_active_descendant {
174            return;
175        }
176        self.highlighted_active_descendant = true;
177        self.update_editor_with_node_range(node, cx, |editor, range, cx| {
178            editor.clear_background_highlights::<Self>(cx);
179            editor.highlight_background::<Self>(
180                vec![range],
181                |theme| theme.editor.document_highlight_write_background,
182                cx,
183            );
184        });
185    }
186}
187
188impl Entity for SyntaxTreeView {
189    type Event = ();
190}
191
192impl View for SyntaxTreeView {
193    fn ui_name() -> &'static str {
194        "SyntaxTreeView"
195    }
196
197    fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
198        let settings = settings::get::<ThemeSettings>(cx);
199        let font_family_id = settings.buffer_font_family;
200        let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
201        let font_properties = Default::default();
202        let font_id = cx
203            .font_cache()
204            .select_font(font_family_id, &font_properties)
205            .unwrap();
206        let font_size = settings.buffer_font_size(cx);
207
208        let editor_theme = settings.theme.editor.clone();
209        let style = TextStyle {
210            color: editor_theme.text_color,
211            font_family_name,
212            font_family_id,
213            font_id,
214            font_size,
215            font_properties: Default::default(),
216            underline: Default::default(),
217        };
218        self.line_height = Some(cx.font_cache().line_height(font_size));
219
220        self.hover_state_changed(cx);
221
222        if let Some(layer) = &self.layer {
223            let layer = layer.clone();
224            return MouseEventHandler::<Self, Self>::new(0, cx, move |_, cx| {
225                UniformList::new(
226                    self.list_state.clone(),
227                    layer.node().descendant_count(),
228                    cx,
229                    move |this, range, items, cx| {
230                        let mut cursor = layer.node().walk();
231                        let mut descendant_ix = range.start as usize;
232                        cursor.goto_descendant(descendant_ix);
233                        let mut depth = cursor.depth();
234                        let mut visited_children = false;
235                        while descendant_ix < range.end {
236                            if visited_children {
237                                if cursor.goto_next_sibling() {
238                                    visited_children = false;
239                                } else if cursor.goto_parent() {
240                                    depth -= 1;
241                                } else {
242                                    break;
243                                }
244                            } else {
245                                let node = cursor.node();
246                                let is_hovered = Some(descendant_ix) == this.active_descendant_ix;
247                                if is_hovered {
248                                    this.node_is_active(node, cx);
249                                }
250                                items.push(
251                                    Label::new(node.kind(), style.clone())
252                                        .contained()
253                                        .with_background_color(if is_hovered {
254                                            editor_theme.active_line_background
255                                        } else {
256                                            Default::default()
257                                        })
258                                        .with_padding_left(depth as f32 * 10.0)
259                                        .into_any(),
260                                );
261                                descendant_ix += 1;
262                                if cursor.goto_first_child() {
263                                    depth += 1;
264                                } else {
265                                    visited_children = true;
266                                }
267                            }
268                        }
269                    },
270                )
271            })
272            .on_move(move |event, this, cx| {
273                let y = event.position.y() - event.region.origin_y();
274                this.hover_y = Some(y);
275                this.hover_state_changed(cx);
276            })
277            .on_click(MouseButton::Left, move |event, this, cx| {
278                let y = event.position.y() - event.region.origin_y();
279                this.handle_click(y, cx);
280            })
281            .into_any();
282        }
283
284        Empty::new().into_any()
285    }
286}
287
288impl Item for SyntaxTreeView {
289    fn tab_content<V: View>(
290        &self,
291        _: Option<usize>,
292        style: &theme::Tab,
293        _: &AppContext,
294    ) -> gpui::AnyElement<V> {
295        Label::new("Syntax Tree", style.label.clone()).into_any()
296    }
297}