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}