use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};use gpui::{ actions, elements::{ AnchorCorner, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode, ParentElement, ScrollTarget, Stack, UniformList, UniformListState, }, fonts::TextStyle, platform::{CursorStyle, MouseButton}, AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle,};use language::{Buffer, OwnedSyntaxLayerInfo, SyntaxLayerInfo};use std::{mem, ops::Range, sync::Arc};use theme::{Theme, ThemeSettings};use tree_sitter::{Node, TreeCursor};use workspace::{ item::{Item, ItemHandle}, ToolbarItemLocation, ToolbarItemView, Workspace,}; actions!(debug, [OpenSyntaxTreeView]); pub fn init(cx: &mut AppContext) { cx.add_action( move |workspace: &mut Workspace, _: &OpenSyntaxTreeView, cx: _| { let active_item = workspace.active_item(cx); let workspace_handle = workspace.weak_handle(); let syntax_tree_view = cx.add_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx)); workspace.add_item(Box::new(syntax_tree_view), cx); }, );} pub struct SyntaxTreeView { workspace_handle: WeakViewHandle<Workspace>, editor: Option<EditorState>, mouse_y: Option<f32>, line_height: Option<f32>, list_state: UniformListState, selected_descendant_ix: Option<usize>, hovered_descendant_ix: Option<usize>,} pub struct SyntaxTreeToolbarItemView { tree_view: Option<ViewHandle<SyntaxTreeView>>, subscription: Option<gpui::Subscription>, menu_open: bool,} struct EditorState { editor: ViewHandle<Editor>, active_buffer: Option<BufferState>, _subscription: gpui::Subscription,} #[derive(Clone)]struct BufferState { buffer: ModelHandle<Buffer>, excerpt_id: ExcerptId, active_layer: Option<OwnedSyntaxLayerInfo>,} impl SyntaxTreeView { pub fn new( workspace_handle: WeakViewHandle<Workspace>, active_item: Option<Box<dyn ItemHandle>>, cx: &mut ViewContext<Self>, ) -> Self { let mut this = Self { workspace_handle: workspace_handle.clone(), list_state: UniformListState::default(), editor: None, mouse_y: None, line_height: None, hovered_descendant_ix: None, selected_descendant_ix: None, }; this.workspace_updated(active_item, cx); cx.observe( &workspace_handle.upgrade(cx).unwrap(), |this, workspace, cx| { this.workspace_updated(workspace.read(cx).active_item(cx), cx); }, ) .detach(); this } fn workspace_updated( &mut self, active_item: Option<Box<dyn ItemHandle>>, cx: &mut ViewContext<Self>, ) { if let Some(item) = active_item { if item.id() != cx.view_id() { if let Some(editor) = item.act_as::<Editor>(cx) { self.set_editor(editor, cx); } } } } fn set_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) { if let Some(state) = &self.editor { if state.editor == editor { return; } editor.update(cx, |editor, cx| { editor.clear_background_highlights::<Self>(cx) }); } let subscription = cx.subscribe(&editor, |this, _, event, cx| { let did_reparse = match event { editor::Event::Reparsed => true, editor::Event::SelectionsChanged { .. } => false, _ => return, }; this.editor_updated(did_reparse, cx); }); self.editor = Some(EditorState { editor, _subscription: subscription, active_buffer: None, }); self.editor_updated(true, cx); } fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> { // Find which excerpt the cursor is in, and the position within that excerpted buffer. let editor_state = self.editor.as_mut()?; let editor = &editor_state.editor.read(cx); let selection_range = editor.selections.last::<usize>(cx).range(); let multibuffer = editor.buffer().read(cx); let (buffer, range, excerpt_id) = multibuffer .range_to_buffer_ranges(selection_range, cx) .pop()?; // If the cursor has moved into a different excerpt, retrieve a new syntax layer // from that buffer. let buffer_state = editor_state .active_buffer .get_or_insert_with(|| BufferState { buffer: buffer.clone(), excerpt_id, active_layer: None, }); let mut prev_layer = None; if did_reparse { prev_layer = buffer_state.active_layer.take(); } if buffer_state.buffer != buffer || buffer_state.excerpt_id != buffer_state.excerpt_id { buffer_state.buffer = buffer.clone(); buffer_state.excerpt_id = excerpt_id; buffer_state.active_layer = None; } let layer = match &mut buffer_state.active_layer { Some(layer) => layer, None => { let snapshot = buffer.read(cx).snapshot(); let layer = if let Some(prev_layer) = prev_layer { let prev_range = prev_layer.node().byte_range(); snapshot .syntax_layers() .filter(|layer| layer.language == &prev_layer.language) .min_by_key(|layer| { let range = layer.node().byte_range(); ((range.start as i64) - (prev_range.start as i64)).abs() + ((range.end as i64) - (prev_range.end as i64)).abs() })? } else { snapshot.syntax_layers().next()? }; buffer_state.active_layer.insert(layer.to_owned()) } }; // Within the active layer, find the syntax node under the cursor, // and scroll to it. let mut cursor = layer.node().walk(); while cursor.goto_first_child_for_byte(range.start).is_some() { if !range.is_empty() && cursor.node().end_byte() == range.start { cursor.goto_next_sibling(); } } // Ascend to the smallest ancestor that contains the range. loop { let node_range = cursor.node().byte_range(); if node_range.start <= range.start && node_range.end >= range.end { break; } if !cursor.goto_parent() { break; } } let descendant_ix = cursor.descendant_index(); self.selected_descendant_ix = Some(descendant_ix); self.list_state.scroll_to(ScrollTarget::Show(descendant_ix)); cx.notify(); Some(()) } fn handle_click(&mut self, y: f32, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> { let line_height = self.line_height?; let ix = ((self.list_state.scroll_top() + y) / line_height) as usize; self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| { // Put the cursor at the beginning of the node. mem::swap(&mut range.start, &mut range.end); editor.change_selections(Some(Autoscroll::newest()), cx, |selections| { selections.select_ranges(vec![range]); }); }); Some(()) } fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) { if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) { let ix = ((self.list_state.scroll_top() + y) / line_height) as usize; if self.hovered_descendant_ix != Some(ix) { self.hovered_descendant_ix = Some(ix); self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| { editor.clear_background_highlights::<Self>(cx); editor.highlight_background::<Self>( vec![range], |theme| theme.editor.document_highlight_write_background, cx, ); }); cx.notify(); } } } fn update_editor_with_range_for_descendant_ix( &self, descendant_ix: usize, cx: &mut ViewContext<Self>, mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>), ) -> Option<()> { let editor_state = self.editor.as_ref()?; let buffer_state = editor_state.active_buffer.as_ref()?; let layer = buffer_state.active_layer.as_ref()?; // Find the node. let mut cursor = layer.node().walk(); cursor.goto_descendant(descendant_ix); let node = cursor.node(); let range = node.byte_range(); // Build a text anchor range. let buffer = buffer_state.buffer.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end); // Build a multibuffer anchor range. let multibuffer = editor_state.editor.read(cx).buffer(); let multibuffer = multibuffer.read(cx).snapshot(cx); let excerpt_id = buffer_state.excerpt_id; let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start) ..multibuffer.anchor_in_excerpt(excerpt_id, range.end); // Update the editor with the anchor range. editor_state.editor.update(cx, |editor, cx| { f(editor, range, cx); }); Some(()) } fn render_node( cursor: &TreeCursor, depth: u32, selected: bool, hovered: bool, list_hovered: bool, style: &TextStyle, editor_theme: &theme::Editor, cx: &AppContext, ) -> gpui::AnyElement<SyntaxTreeView> { let node = cursor.node(); let mut range_style = style.clone(); let em_width = style.em_width(cx.font_cache()); let gutter_padding = (em_width * editor_theme.gutter_padding_factor).round(); range_style.color = editor_theme.line_number; let mut anonymous_node_style = style.clone(); let string_color = editor_theme .syntax .highlights .iter() .find_map(|(name, style)| (name == "string").then(|| style.color)?); let property_color = editor_theme .syntax .highlights .iter() .find_map(|(name, style)| (name == "property").then(|| style.color)?); if let Some(color) = string_color { anonymous_node_style.color = color; } let mut row = Flex::row(); if let Some(field_name) = cursor.field_name() { let mut field_style = style.clone(); if let Some(color) = property_color { field_style.color = color; } row.add_children([ Label::new(field_name, field_style), Label::new(": ", style.clone()), ]); } return row .with_child( if node.is_named() { Label::new(node.kind(), style.clone()) } else { Label::new(format!("\"{}\"", node.kind()), anonymous_node_style) } .contained() .with_margin_right(em_width), ) .with_child(Label::new(format_node_range(node), range_style)) .contained() .with_background_color(if selected { editor_theme.selection.selection } else if hovered && list_hovered { editor_theme.active_line_background } else { Default::default() }) .with_padding_left(gutter_padding + depth as f32 * 18.0) .into_any(); }} impl Entity for SyntaxTreeView { type Event = ();} impl View for SyntaxTreeView { fn ui_name() -> &'static str { "SyntaxTreeView" } fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> { let settings = settings::get::<ThemeSettings>(cx); let font_family_id = settings.buffer_font_family; let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); let font_properties = Default::default(); let font_id = cx .font_cache() .select_font(font_family_id, &font_properties) .unwrap(); let font_size = settings.buffer_font_size(cx); let editor_theme = settings.theme.editor.clone(); let style = TextStyle { color: editor_theme.text_color, font_family_name, font_family_id, font_id, font_size, font_properties: Default::default(), underline: Default::default(), soft_wrap: false, }; let line_height = cx.font_cache().line_height(font_size); if Some(line_height) != self.line_height { self.line_height = Some(line_height); self.hover_state_changed(cx); } if let Some(layer) = self .editor .as_ref() .and_then(|editor| editor.active_buffer.as_ref()) .and_then(|buffer| buffer.active_layer.as_ref()) { let layer = layer.clone(); let theme = editor_theme.clone(); return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| { let list_hovered = state.hovered(); UniformList::new( self.list_state.clone(), layer.node().descendant_count(), cx, move |this, range, items, cx| { let mut cursor = layer.node().walk(); let mut descendant_ix = range.start as usize; cursor.goto_descendant(descendant_ix); let mut depth = cursor.depth(); let mut visited_children = false; while descendant_ix < range.end { if visited_children { if cursor.goto_next_sibling() { visited_children = false; } else if cursor.goto_parent() { depth -= 1; } else { break; } } else { items.push(Self::render_node( &cursor, depth, Some(descendant_ix) == this.selected_descendant_ix, Some(descendant_ix) == this.hovered_descendant_ix, list_hovered, &style, &theme, cx, )); descendant_ix += 1; if cursor.goto_first_child() { depth += 1; } else { visited_children = true; } } } }, ) }) .on_move(move |event, this, cx| { let y = event.position.y() - event.region.origin_y(); this.mouse_y = Some(y); this.hover_state_changed(cx); }) .on_click(MouseButton::Left, move |event, this, cx| { let y = event.position.y() - event.region.origin_y(); this.handle_click(y, cx); }) .contained() .with_background_color(editor_theme.background) .into_any(); } Empty::new().into_any() }} impl Item for SyntaxTreeView { fn tab_content<V: 'static>( &self, _: Option<usize>, style: &theme::Tab, _: &AppContext, ) -> gpui::AnyElement<V> { Label::new("Syntax Tree", style.label.clone()).into_any() } fn clone_on_split( &self, _workspace_id: workspace::WorkspaceId, cx: &mut ViewContext<Self>, ) -> Option<Self> where Self: Sized, { let mut clone = Self::new(self.workspace_handle.clone(), None, cx); if let Some(editor) = &self.editor { clone.set_editor(editor.editor.clone(), cx) } Some(clone) }} impl SyntaxTreeToolbarItemView { pub fn new() -> Self { Self { menu_open: false, tree_view: None, subscription: None, } } fn render_menu( &mut self, cx: &mut ViewContext<'_, '_, Self>, ) -> Option<gpui::AnyElement<Self>> { let theme = theme::current(cx).clone(); let tree_view = self.tree_view.as_ref()?; let tree_view = tree_view.read(cx); let editor_state = tree_view.editor.as_ref()?; let buffer_state = editor_state.active_buffer.as_ref()?; let active_layer = buffer_state.active_layer.clone()?; let active_buffer = buffer_state.buffer.read(cx).snapshot();Lines 1–500 of 677. Load more
use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId}; use gpui::{ actions, elements::{ AnchorCorner, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode, ParentElement, ScrollTarget, Stack, UniformList, UniformListState, }, fonts::TextStyle, platform::{CursorStyle, MouseButton}, AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{Buffer, OwnedSyntaxLayerInfo, SyntaxLayerInfo}; use std::{mem, ops::Range, sync::Arc}; use theme::{Theme, ThemeSettings}; use tree_sitter::{Node, TreeCursor}; use workspace::{ item::{Item, ItemHandle}, ToolbarItemLocation, ToolbarItemView, Workspace, }; actions!(debug, [OpenSyntaxTreeView]); pub fn init(cx: &mut AppContext) { cx.add_action( move |workspace: &mut Workspace, _: &OpenSyntaxTreeView, cx: _| { let active_item = workspace.active_item(cx); let workspace_handle = workspace.weak_handle(); let syntax_tree_view = cx.add_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx)); workspace.add_item(Box::new(syntax_tree_view), cx); }, ); } pub struct SyntaxTreeView { workspace_handle: WeakViewHandle<Workspace>, editor: Option<EditorState>, mouse_y: Option<f32>, line_height: Option<f32>, list_state: UniformListState, selected_descendant_ix: Option<usize>, hovered_descendant_ix: Option<usize>, } pub struct SyntaxTreeToolbarItemView { tree_view: Option<ViewHandle<SyntaxTreeView>>, subscription: Option<gpui::Subscription>, menu_open: bool, } struct EditorState { editor: ViewHandle<Editor>, active_buffer: Option<BufferState>, _subscription: gpui::Subscription, } #[derive(Clone)] struct BufferState { buffer: ModelHandle<Buffer>, excerpt_id: ExcerptId, active_layer: Option<OwnedSyntaxLayerInfo>, } impl SyntaxTreeView { pub fn new( workspace_handle: WeakViewHandle<Workspace>, active_item: Option<Box<dyn ItemHandle>>, cx: &mut ViewContext<Self>, ) -> Self { let mut this = Self { workspace_handle: workspace_handle.clone(), list_state: UniformListState::default(), editor: None, mouse_y: None, line_height: None, hovered_descendant_ix: None, selected_descendant_ix: None, }; this.workspace_updated(active_item, cx); cx.observe( &workspace_handle.upgrade(cx).unwrap(), |this, workspace, cx| { this.workspace_updated(workspace.read(cx).active_item(cx), cx); }, ) .detach(); this } fn workspace_updated( &mut self, active_item: Option<Box<dyn ItemHandle>>, cx: &mut ViewContext<Self>, ) { if let Some(item) = active_item { if item.id() != cx.view_id() { if let Some(editor) = item.act_as::<Editor>(cx) { self.set_editor(editor, cx); } } } } fn set_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) { if let Some(state) = &self.editor { if state.editor == editor { return; } editor.update(cx, |editor, cx| { editor.clear_background_highlights::<Self>(cx) }); } let subscription = cx.subscribe(&editor, |this, _, event, cx| { let did_reparse = match event { editor::Event::Reparsed => true, editor::Event::SelectionsChanged { .. } => false, _ => return, }; this.editor_updated(did_reparse, cx); }); self.editor = Some(EditorState { editor, _subscription: subscription, active_buffer: None, }); self.editor_updated(true, cx); } fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> { // Find which excerpt the cursor is in, and the position within that excerpted buffer. let editor_state = self.editor.as_mut()?; let editor = &editor_state.editor.read(cx); let selection_range = editor.selections.last::<usize>(cx).range(); let multibuffer = editor.buffer().read(cx); let (buffer, range, excerpt_id) = multibuffer .range_to_buffer_ranges(selection_range, cx) .pop()?; // If the cursor has moved into a different excerpt, retrieve a new syntax layer // from that buffer. let buffer_state = editor_state .active_buffer .get_or_insert_with(|| BufferState { buffer: buffer.clone(), excerpt_id, active_layer: None, }); let mut prev_layer = None; if did_reparse { prev_layer = buffer_state.active_layer.take(); } if buffer_state.buffer != buffer || buffer_state.excerpt_id != buffer_state.excerpt_id { buffer_state.buffer = buffer.clone(); buffer_state.excerpt_id = excerpt_id; buffer_state.active_layer = None; } let layer = match &mut buffer_state.active_layer { Some(layer) => layer, None => { let snapshot = buffer.read(cx).snapshot(); let layer = if let Some(prev_layer) = prev_layer { let prev_range = prev_layer.node().byte_range(); snapshot .syntax_layers() .filter(|layer| layer.language == &prev_layer.language) .min_by_key(|layer| { let range = layer.node().byte_range(); ((range.start as i64) - (prev_range.start as i64)).abs() + ((range.end as i64) - (prev_range.end as i64)).abs() })? } else { snapshot.syntax_layers().next()? }; buffer_state.active_layer.insert(layer.to_owned()) } }; // Within the active layer, find the syntax node under the cursor, // and scroll to it. let mut cursor = layer.node().walk(); while cursor.goto_first_child_for_byte(range.start).is_some() { if !range.is_empty() && cursor.node().end_byte() == range.start { cursor.goto_next_sibling(); } } // Ascend to the smallest ancestor that contains the range. loop { let node_range = cursor.node().byte_range(); if node_range.start <= range.start && node_range.end >= range.end { break; } if !cursor.goto_parent() { break; } } let descendant_ix = cursor.descendant_index(); self.selected_descendant_ix = Some(descendant_ix); self.list_state.scroll_to(ScrollTarget::Show(descendant_ix)); cx.notify(); Some(()) } fn handle_click(&mut self, y: f32, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> { let line_height = self.line_height?; let ix = ((self.list_state.scroll_top() + y) / line_height) as usize; self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| { // Put the cursor at the beginning of the node. mem::swap(&mut range.start, &mut range.end); editor.change_selections(Some(Autoscroll::newest()), cx, |selections| { selections.select_ranges(vec![range]); }); }); Some(()) } fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) { if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) { let ix = ((self.list_state.scroll_top() + y) / line_height) as usize; if self.hovered_descendant_ix != Some(ix) { self.hovered_descendant_ix = Some(ix); self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| { editor.clear_background_highlights::<Self>(cx); editor.highlight_background::<Self>( vec![range], |theme| theme.editor.document_highlight_write_background, cx, ); }); cx.notify(); } } } fn update_editor_with_range_for_descendant_ix( &self, descendant_ix: usize, cx: &mut ViewContext<Self>, mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>), ) -> Option<()> { let editor_state = self.editor.as_ref()?; let buffer_state = editor_state.active_buffer.as_ref()?; let layer = buffer_state.active_layer.as_ref()?; // Find the node. let mut cursor = layer.node().walk(); cursor.goto_descendant(descendant_ix); let node = cursor.node(); let range = node.byte_range(); // Build a text anchor range. let buffer = buffer_state.buffer.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end); // Build a multibuffer anchor range. let multibuffer = editor_state.editor.read(cx).buffer(); let multibuffer = multibuffer.read(cx).snapshot(cx); let excerpt_id = buffer_state.excerpt_id; let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start) ..multibuffer.anchor_in_excerpt(excerpt_id, range.end); // Update the editor with the anchor range. editor_state.editor.update(cx, |editor, cx| { f(editor, range, cx); }); Some(()) } fn render_node( cursor: &TreeCursor, depth: u32, selected: bool, hovered: bool, list_hovered: bool, style: &TextStyle, editor_theme: &theme::Editor, cx: &AppContext, ) -> gpui::AnyElement<SyntaxTreeView> { let node = cursor.node(); let mut range_style = style.clone(); let em_width = style.em_width(cx.font_cache()); let gutter_padding = (em_width * editor_theme.gutter_padding_factor).round(); range_style.color = editor_theme.line_number; let mut anonymous_node_style = style.clone(); let string_color = editor_theme .syntax .highlights .iter() .find_map(|(name, style)| (name == "string").then(|| style.color)?); let property_color = editor_theme .syntax .highlights .iter() .find_map(|(name, style)| (name == "property").then(|| style.color)?); if let Some(color) = string_color { anonymous_node_style.color = color; } let mut row = Flex::row(); if let Some(field_name) = cursor.field_name() { let mut field_style = style.clone(); if let Some(color) = property_color { field_style.color = color; } row.add_children([ Label::new(field_name, field_style), Label::new(": ", style.clone()), ]); } return row .with_child( if node.is_named() { Label::new(node.kind(), style.clone()) } else { Label::new(format!("\"{}\"", node.kind()), anonymous_node_style) } .contained() .with_margin_right(em_width), ) .with_child(Label::new(format_node_range(node), range_style)) .contained() .with_background_color(if selected { editor_theme.selection.selection } else if hovered && list_hovered { editor_theme.active_line_background } else { Default::default() }) .with_padding_left(gutter_padding + depth as f32 * 18.0) .into_any(); } } impl Entity for SyntaxTreeView { type Event = (); } impl View for SyntaxTreeView { fn ui_name() -> &'static str { "SyntaxTreeView" } fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> { let settings = settings::get::<ThemeSettings>(cx); let font_family_id = settings.buffer_font_family; let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); let font_properties = Default::default(); let font_id = cx .font_cache() .select_font(font_family_id, &font_properties) .unwrap(); let font_size = settings.buffer_font_size(cx); let editor_theme = settings.theme.editor.clone(); let style = TextStyle { color: editor_theme.text_color, font_family_name, font_family_id, font_id, font_size, font_properties: Default::default(), underline: Default::default(), soft_wrap: false, }; let line_height = cx.font_cache().line_height(font_size); if Some(line_height) != self.line_height { self.line_height = Some(line_height); self.hover_state_changed(cx); } if let Some(layer) = self .editor .as_ref() .and_then(|editor| editor.active_buffer.as_ref()) .and_then(|buffer| buffer.active_layer.as_ref()) { let layer = layer.clone(); let theme = editor_theme.clone(); return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| { let list_hovered = state.hovered(); UniformList::new( self.list_state.clone(), layer.node().descendant_count(), cx, move |this, range, items, cx| { let mut cursor = layer.node().walk(); let mut descendant_ix = range.start as usize; cursor.goto_descendant(descendant_ix); let mut depth = cursor.depth(); let mut visited_children = false; while descendant_ix < range.end { if visited_children { if cursor.goto_next_sibling() { visited_children = false; } else if cursor.goto_parent() { depth -= 1; } else { break; } } else { items.push(Self::render_node( &cursor, depth, Some(descendant_ix) == this.selected_descendant_ix, Some(descendant_ix) == this.hovered_descendant_ix, list_hovered, &style, &theme, cx, )); descendant_ix += 1; if cursor.goto_first_child() { depth += 1; } else { visited_children = true; } } } }, ) }) .on_move(move |event, this, cx| { let y = event.position.y() - event.region.origin_y(); this.mouse_y = Some(y); this.hover_state_changed(cx); }) .on_click(MouseButton::Left, move |event, this, cx| { let y = event.position.y() - event.region.origin_y(); this.handle_click(y, cx); }) .contained() .with_background_color(editor_theme.background) .into_any(); } Empty::new().into_any() } } impl Item for SyntaxTreeView { fn tab_content<V: 'static>( &self, _: Option<usize>, style: &theme::Tab, _: &AppContext, ) -> gpui::AnyElement<V> { Label::new("Syntax Tree", style.label.clone()).into_any() } fn clone_on_split( &self, _workspace_id: workspace::WorkspaceId, cx: &mut ViewContext<Self>, ) -> Option<Self> where Self: Sized, { let mut clone = Self::new(self.workspace_handle.clone(), None, cx); if let Some(editor) = &self.editor { clone.set_editor(editor.editor.clone(), cx) } Some(clone) } } impl SyntaxTreeToolbarItemView { pub fn new() -> Self { Self { menu_open: false, tree_view: None, subscription: None, } } fn render_menu( &mut self, cx: &mut ViewContext<'_, '_, Self>, ) -> Option<gpui::AnyElement<Self>> { let theme = theme::current(cx).clone(); let tree_view = self.tree_view.as_ref()?; let tree_view = tree_view.read(cx); let editor_state = tree_view.editor.as_ref()?; let buffer_state = editor_state.active_buffer.as_ref()?; let active_layer = buffer_state.active_layer.clone()?; let active_buffer = buffer_state.buffer.read(cx).snapshot();