diff --git a/Cargo.lock b/Cargo.lock index 8ef48849ac..711c97df40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3515,6 +3515,29 @@ dependencies = [ "workspace", ] +[[package]] +name = "language_tools" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "collections", + "editor", + "env_logger 0.9.3", + "futures 0.3.28", + "gpui", + "language", + "lsp", + "project", + "serde", + "settings", + "theme", + "tree-sitter", + "unindent", + "util", + "workspace", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -3759,28 +3782,6 @@ dependencies = [ "url", ] -[[package]] -name = "lsp_log" -version = "0.1.0" -dependencies = [ - "anyhow", - "client", - "collections", - "editor", - "env_logger 0.9.3", - "futures 0.3.28", - "gpui", - "language", - "lsp", - "project", - "serde", - "settings", - "theme", - "unindent", - "util", - "workspace", -] - [[package]] name = "mach" version = "0.3.2" @@ -7358,8 +7359,8 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.20.9" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14#c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" +version = "0.20.10" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=a2119cb6914d62e626fcc40684ef900d7fa90d86#a2119cb6914d62e626fcc40684ef900d7fa90d86" dependencies = [ "cc", "regex", @@ -8829,11 +8830,11 @@ dependencies = [ "journal", "language", "language_selector", + "language_tools", "lazy_static", "libc", "log", "lsp", - "lsp_log", "node_runtime", "num_cpus", "outline", diff --git a/Cargo.toml b/Cargo.toml index 72a93177a9..e8003684c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,10 @@ members = [ "crates/journal", "crates/language", "crates/language_selector", + "crates/language_tools", "crates/live_kit_client", "crates/live_kit_server", "crates/lsp", - "crates/lsp_log", "crates/media", "crates/menu", "crates/node_runtime", @@ -98,10 +98,11 @@ tempdir = { version = "0.3.7" } thiserror = { version = "1.0.29" } time = { version = "0.3", features = ["serde", "serde-well-known"] } toml = { version = "0.5" } +tree-sitter = "0.20" unindent = { version = "0.1.7" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "a2119cb6914d62e626fcc40684ef900d7fa90d86" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 325883b7c0..dcc2220227 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -83,7 +83,7 @@ ctor.workspace = true env_logger.workspace = true rand.workspace = true unindent.workspace = true -tree-sitter = "0.20" +tree-sitter.workspace = true tree-sitter-rust = "0.20" tree-sitter-html = "0.19" tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 0d7fb6a450..4c7736e270 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1118,7 +1118,7 @@ impl MultiBuffer { &self, point: T, cx: &AppContext, - ) -> Option<(ModelHandle, usize)> { + ) -> Option<(ModelHandle, usize, ExcerptId)> { let snapshot = self.read(cx); let offset = point.to_offset(&snapshot); let mut cursor = snapshot.excerpts.cursor::(); @@ -1132,7 +1132,7 @@ impl MultiBuffer { let buffer_point = excerpt_start + offset - *cursor.start(); let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); - (buffer, buffer_point) + (buffer, buffer_point, excerpt.id) }) } @@ -1387,7 +1387,7 @@ impl MultiBuffer { cx: &'a AppContext, ) -> Option> { self.point_to_buffer_offset(point, cx) - .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) + .and_then(|(buffer, offset, _)| buffer.read(cx).language_at(offset)) } pub fn settings_at<'a, T: ToOffset>( @@ -1397,7 +1397,7 @@ impl MultiBuffer { ) -> &'a LanguageSettings { let mut language = None; let mut file = None; - if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) { + if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) { let buffer = buffer.read(cx); language = buffer.language_at(offset); file = buffer.file(); diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 9722b618f3..04b48c93ff 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -55,7 +55,7 @@ serde_json.workspace = true similar = "1.3" smallvec.workspace = true smol.workspace = true -tree-sitter = "0.20" +tree-sitter.workspace = true tree-sitter-rust = { version = "*", optional = true } tree-sitter-typescript = { version = "*", optional = true } unicase = "2.6" diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index e09ee48da6..d9efb0fbc6 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -8,7 +8,8 @@ use crate::{ language_settings::{language_settings, LanguageSettings}, outline::OutlineItem, syntax_map::{ - SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, + SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, + ToTreeSitterPoint, }, CodeLabel, LanguageScope, Outline, }; @@ -2116,12 +2117,16 @@ impl BufferSnapshot { } } - pub fn language_at(&self, position: D) -> Option<&Arc> { + pub fn syntax_layer_at(&self, position: D) -> Option { let offset = position.to_offset(self); self.syntax .layers_for_range(offset..offset, &self.text) - .filter(|l| l.node.end_byte() > offset) + .filter(|l| l.node().end_byte() > offset) .last() + } + + pub fn language_at(&self, position: D) -> Option<&Arc> { + self.syntax_layer_at(position) .map(|info| info.language) .or(self.language.as_ref()) } @@ -2140,7 +2145,7 @@ impl BufferSnapshot { if let Some(layer_info) = self .syntax .layers_for_range(offset..offset, &self.text) - .filter(|l| l.node.end_byte() > offset) + .filter(|l| l.node().end_byte() > offset) .last() { Some(LanguageScope { @@ -2188,7 +2193,7 @@ impl BufferSnapshot { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut result: Option> = None; 'outer: for layer in self.syntax.layers_for_range(range.clone(), &self.text) { - let mut cursor = layer.node.walk(); + let mut cursor = layer.node().walk(); // Descend to the first leaf that touches the start of the range, // and if the range is non-empty, extends beyond the start. diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 9f44de40ac..38cefbcef9 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2242,7 +2242,7 @@ fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> Str buffer.read_with(cx, |buffer, _| { let snapshot = buffer.snapshot(); let layers = snapshot.syntax.layers(buffer.as_text_snapshot()); - layers[0].node.to_sexp() + layers[0].node().to_sexp() }) } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 0ff1d973d3..e91d5770cf 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -57,6 +57,7 @@ pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; +pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo}; pub use tree_sitter::{Parser, Tree}; pub fn init(cx: &mut AppContext) { diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 16313db498..ebfa38c148 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -125,8 +125,17 @@ impl SyntaxLayerContent { #[derive(Debug)] pub struct SyntaxLayerInfo<'a> { pub depth: usize, - pub node: Node<'a>, pub language: &'a Arc, + tree: &'a Tree, + offset: (usize, tree_sitter::Point), +} + +#[derive(Clone)] +pub struct OwnedSyntaxLayerInfo { + pub depth: usize, + pub language: Arc, + tree: tree_sitter::Tree, + offset: (usize, tree_sitter::Point), } #[derive(Debug, Clone)] @@ -664,8 +673,9 @@ impl SyntaxSnapshot { text, [SyntaxLayerInfo { language, + tree, depth: 0, - node: tree.root_node(), + offset: (0, tree_sitter::Point::new(0, 0)), }] .into_iter(), query, @@ -728,9 +738,10 @@ impl SyntaxSnapshot { while let Some(layer) = cursor.item() { if let SyntaxLayerContent::Parsed { tree, language } = &layer.content { let info = SyntaxLayerInfo { + tree, language, depth: layer.depth, - node: tree.root_node_with_offset( + offset: ( layer.range.start.to_offset(buffer), layer.range.start.to_point(buffer).to_ts_point(), ), @@ -766,13 +777,8 @@ impl<'a> SyntaxMapCaptures<'a> { grammars: Vec::new(), active_layer_count: 0, }; - for SyntaxLayerInfo { - language, - depth, - node, - } in layers - { - let grammar = match &language.grammar { + for layer in layers { + let grammar = match &layer.language.grammar { Some(grammar) => grammar, None => continue, }; @@ -789,7 +795,7 @@ impl<'a> SyntaxMapCaptures<'a> { }; cursor.set_byte_range(range.clone()); - let captures = cursor.captures(query, node, TextProvider(text)); + let captures = cursor.captures(query, layer.node(), TextProvider(text)); let grammar_index = result .grammars .iter() @@ -799,7 +805,7 @@ impl<'a> SyntaxMapCaptures<'a> { result.grammars.len() - 1 }); let mut layer = SyntaxMapCapturesLayer { - depth, + depth: layer.depth, grammar_index, next_capture: None, captures, @@ -889,13 +895,8 @@ impl<'a> SyntaxMapMatches<'a> { query: fn(&Grammar) -> Option<&Query>, ) -> Self { let mut result = Self::default(); - for SyntaxLayerInfo { - language, - depth, - node, - } in layers - { - let grammar = match &language.grammar { + for layer in layers { + let grammar = match &layer.language.grammar { Some(grammar) => grammar, None => continue, }; @@ -912,7 +913,7 @@ impl<'a> SyntaxMapMatches<'a> { }; cursor.set_byte_range(range.clone()); - let matches = cursor.matches(query, node, TextProvider(text)); + let matches = cursor.matches(query, layer.node(), TextProvider(text)); let grammar_index = result .grammars .iter() @@ -922,7 +923,7 @@ impl<'a> SyntaxMapMatches<'a> { result.grammars.len() - 1 }); let mut layer = SyntaxMapMatchesLayer { - depth, + depth: layer.depth, grammar_index, matches, next_pattern_index: 0, @@ -1290,7 +1291,28 @@ fn splice_included_ranges( ranges } +impl OwnedSyntaxLayerInfo { + pub fn node(&self) -> Node { + self.tree + .root_node_with_offset(self.offset.0, self.offset.1) + } +} + impl<'a> SyntaxLayerInfo<'a> { + pub fn to_owned(&self) -> OwnedSyntaxLayerInfo { + OwnedSyntaxLayerInfo { + tree: self.tree.clone(), + offset: self.offset, + depth: self.depth, + language: self.language.clone(), + } + } + + pub fn node(&self) -> Node<'a> { + self.tree + .root_node_with_offset(self.offset.0, self.offset.1) + } + pub(crate) fn override_id(&self, offset: usize, text: &text::BufferSnapshot) -> Option { let text = TextProvider(text.as_rope()); let config = self.language.grammar.as_ref()?.override_config.as_ref()?; @@ -1299,7 +1321,7 @@ impl<'a> SyntaxLayerInfo<'a> { query_cursor.set_byte_range(offset..offset); let mut smallest_match: Option<(u32, Range)> = None; - for mat in query_cursor.matches(&config.query, self.node, text) { + for mat in query_cursor.matches(&config.query, self.node(), text) { for capture in mat.captures { if !config.values.contains_key(&capture.index) { continue; @@ -2328,8 +2350,11 @@ mod tests { let reference_layers = reference_syntax_map.layers(&buffer); for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) { - assert_eq!(edited_layer.node.to_sexp(), reference_layer.node.to_sexp()); - assert_eq!(edited_layer.node.range(), reference_layer.node.range()); + assert_eq!( + edited_layer.node().to_sexp(), + reference_layer.node().to_sexp() + ); + assert_eq!(edited_layer.node().range(), reference_layer.node().range()); } } @@ -2411,8 +2436,11 @@ mod tests { let reference_layers = reference_syntax_map.layers(&buffer); for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) { - assert_eq!(edited_layer.node.to_sexp(), reference_layer.node.to_sexp()); - assert_eq!(edited_layer.node.range(), reference_layer.node.range()); + assert_eq!( + edited_layer.node().to_sexp(), + reference_layer.node().to_sexp() + ); + assert_eq!(edited_layer.node().range(), reference_layer.node().range()); } } @@ -2563,13 +2591,13 @@ mod tests { mutated_layers.into_iter().zip(reference_layers.into_iter()) { assert_eq!( - edited_layer.node.to_sexp(), - reference_layer.node.to_sexp(), + edited_layer.node().to_sexp(), + reference_layer.node().to_sexp(), "different layer at step {i}" ); assert_eq!( - edited_layer.node.range(), - reference_layer.node.range(), + edited_layer.node().range(), + reference_layer.node().range(), "different layer at step {i}" ); } @@ -2709,10 +2737,8 @@ mod tests { expected_layers.len(), "wrong number of layers" ); - for (i, (SyntaxLayerInfo { node, .. }, expected_s_exp)) in - layers.iter().zip(expected_layers.iter()).enumerate() - { - let actual_s_exp = node.to_sexp(); + for (i, (layer, expected_s_exp)) in layers.iter().zip(expected_layers.iter()).enumerate() { + let actual_s_exp = layer.node().to_sexp(); assert!( string_contains_sequence( &actual_s_exp, diff --git a/crates/lsp_log/Cargo.toml b/crates/language_tools/Cargo.toml similarity index 90% rename from crates/lsp_log/Cargo.toml rename to crates/language_tools/Cargo.toml index 46f6006a23..e67a4b36df 100644 --- a/crates/lsp_log/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "lsp_log" +name = "language_tools" version = "0.1.0" edition = "2021" publish = false [lib] -path = "src/lsp_log.rs" +path = "src/language_tools.rs" doctest = false [dependencies] @@ -22,6 +22,7 @@ lsp = { path = "../lsp" } futures.workspace = true serde.workspace = true anyhow.workspace = true +tree-sitter.workspace = true [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/language_tools/src/language_tools.rs b/crates/language_tools/src/language_tools.rs new file mode 100644 index 0000000000..b61c8d3c80 --- /dev/null +++ b/crates/language_tools/src/language_tools.rs @@ -0,0 +1,15 @@ +mod lsp_log; +mod syntax_tree_view; + +#[cfg(test)] +mod lsp_log_tests; + +use gpui::AppContext; + +pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView}; +pub use syntax_tree_view::SyntaxTreeView; + +pub fn init(cx: &mut AppContext) { + lsp_log::init(cx); + syntax_tree_view::init(cx); +} diff --git a/crates/lsp_log/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs similarity index 98% rename from crates/lsp_log/src/lsp_log.rs rename to crates/language_tools/src/lsp_log.rs index 5808b4da2e..cec690d4a6 100644 --- a/crates/lsp_log/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod lsp_log_tests; - use collections::HashMap; use editor::Editor; use futures::{channel::mpsc, StreamExt}; @@ -27,7 +24,7 @@ use workspace::{ const SEND_LINE: &str = "// Send:\n"; const RECEIVE_LINE: &str = "// Receive:\n"; -struct LogStore { +pub struct LogStore { projects: HashMap, ProjectState>, io_tx: mpsc::UnboundedSender<(WeakModelHandle, LanguageServerId, bool, String)>, } @@ -49,10 +46,10 @@ struct LanguageServerRpcState { } pub struct LspLogView { + pub(crate) editor: ViewHandle, log_store: ModelHandle, current_server_id: Option, is_showing_rpc_trace: bool, - editor: ViewHandle, project: ModelHandle, } @@ -68,13 +65,13 @@ enum MessageKind { } #[derive(Clone, Debug, PartialEq)] -struct LogMenuItem { - server_id: LanguageServerId, - server_name: LanguageServerName, - worktree: ModelHandle, - rpc_trace_enabled: bool, - rpc_trace_selected: bool, - logs_selected: bool, +pub(crate) struct LogMenuItem { + pub server_id: LanguageServerId, + pub server_name: LanguageServerName, + pub worktree: ModelHandle, + pub rpc_trace_enabled: bool, + pub rpc_trace_selected: bool, + pub logs_selected: bool, } actions!(log, [OpenLanguageServerLogs]); @@ -114,7 +111,7 @@ pub fn init(cx: &mut AppContext) { } impl LogStore { - fn new(cx: &mut ModelContext) -> Self { + pub fn new(cx: &mut ModelContext) -> Self { let (io_tx, mut io_rx) = mpsc::unbounded(); let this = Self { projects: HashMap::default(), @@ -320,7 +317,7 @@ impl LogStore { } impl LspLogView { - fn new( + pub fn new( project: ModelHandle, log_store: ModelHandle, cx: &mut ViewContext, @@ -360,7 +357,7 @@ impl LspLogView { editor } - fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option> { + pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option> { let log_store = self.log_store.read(cx); let state = log_store.projects.get(&self.project.downgrade())?; let mut rows = self diff --git a/crates/lsp_log/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs similarity index 95% rename from crates/lsp_log/src/lsp_log_tests.rs rename to crates/language_tools/src/lsp_log_tests.rs index 572758ad63..d4a16b5758 100644 --- a/crates/lsp_log/src/lsp_log_tests.rs +++ b/crates/language_tools/src/lsp_log_tests.rs @@ -1,7 +1,12 @@ +use std::sync::Arc; + +use crate::lsp_log::LogMenuItem; + use super::*; +use futures::StreamExt; use gpui::{serde_json::json, TestAppContext}; -use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig}; -use project::FakeFs; +use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageServerName}; +use project::{FakeFs, Project}; use settings::SettingsStore; #[gpui::test] diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs new file mode 100644 index 0000000000..96a25cc712 --- /dev/null +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -0,0 +1,297 @@ +use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId}; +use gpui::{ + actions, + elements::{Empty, Label, MouseEventHandler, UniformList, UniformListState}, + fonts::TextStyle, + platform::MouseButton, + AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle, +}; +use language::{Buffer, OwnedSyntaxLayerInfo}; +use std::ops::Range; +use theme::ThemeSettings; +use workspace::{ + item::{Item, ItemHandle}, + Workspace, +}; + +actions!(log, [OpenSyntaxTreeView]); + +pub fn init(cx: &mut AppContext) { + cx.add_action( + move |workspace: &mut Workspace, _: &OpenSyntaxTreeView, cx: _| { + let syntax_tree_view = cx.add_view(|cx| SyntaxTreeView::new(workspace, cx)); + workspace.add_item(Box::new(syntax_tree_view), cx); + }, + ); +} + +pub struct SyntaxTreeView { + editor: Option<(ViewHandle, gpui::Subscription)>, + buffer: Option<(ModelHandle, usize, ExcerptId)>, + layer: Option, + hover_y: Option, + line_height: Option, + list_state: UniformListState, + active_descendant_ix: Option, + highlighted_active_descendant: bool, +} + +impl SyntaxTreeView { + pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let mut this = Self { + list_state: UniformListState::default(), + editor: None, + buffer: None, + layer: None, + hover_y: None, + line_height: None, + active_descendant_ix: None, + highlighted_active_descendant: false, + }; + + this.workspace_updated(workspace.active_item(cx), cx); + cx.observe( + &workspace.weak_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>, + cx: &mut ViewContext, + ) { + if let Some(item) = active_item { + if item.id() != cx.view_id() { + if let Some(editor) = item.act_as::(cx) { + self.set_editor(editor, cx); + } + } + } + } + + fn set_editor(&mut self, editor: ViewHandle, cx: &mut ViewContext) { + if let Some((current_editor, _)) = &self.editor { + if current_editor == &editor { + return; + } + editor.update(cx, |editor, cx| { + editor.clear_background_highlights::(cx); + }); + } + + let subscription = cx.subscribe(&editor, |this, editor, event, cx| { + let selection_changed = match event { + editor::Event::Reparsed => false, + editor::Event::SelectionsChanged { .. } => true, + _ => return, + }; + this.editor_updated(&editor, selection_changed, cx); + }); + + self.editor_updated(&editor, true, cx); + self.editor = Some((editor, subscription)); + } + + fn editor_updated( + &mut self, + editor: &ViewHandle, + selection_changed: bool, + cx: &mut ViewContext, + ) { + let editor = editor.read(cx); + if selection_changed { + let cursor = editor.selections.last::(cx).end; + self.buffer = editor.buffer().read(cx).point_to_buffer_offset(cursor, cx); + self.layer = self.buffer.as_ref().and_then(|(buffer, offset, _)| { + buffer + .read(cx) + .snapshot() + .syntax_layer_at(*offset) + .map(|l| l.to_owned()) + }); + } + cx.notify(); + } + + fn hover_state_changed(&mut self, cx: &mut ViewContext) { + if let Some((y, line_height)) = self.hover_y.zip(self.line_height) { + let ix = ((self.list_state.scroll_top() + y) / line_height) as usize; + if self.active_descendant_ix != Some(ix) { + self.active_descendant_ix = Some(ix); + self.highlighted_active_descendant = false; + cx.notify(); + } + } + } + + fn handle_click(&mut self, y: f32, cx: &mut ViewContext) { + if let Some(line_height) = self.line_height { + let ix = ((self.list_state.scroll_top() + y) / line_height) as usize; + if let Some(layer) = &self.layer { + let mut cursor = layer.node().walk(); + cursor.goto_descendant(ix); + let node = cursor.node(); + self.update_editor_with_node_range(node, cx, |editor, range, cx| { + editor.change_selections(Some(Autoscroll::newest()), cx, |selections| { + selections.select_ranges(vec![range]); + }); + }); + } + } + } + + fn update_editor_with_node_range( + &self, + node: tree_sitter::Node, + cx: &mut ViewContext, + mut f: impl FnMut(&mut Editor, Range, &mut ViewContext), + ) { + let range = node.byte_range(); + if let Some((editor, _)) = &self.editor { + if let Some((buffer, _, excerpt_id)) = &self.buffer { + let buffer = &buffer.read(cx); + let multibuffer = editor.read(cx).buffer(); + let multibuffer = multibuffer.read(cx).snapshot(cx); + let start = + multibuffer.anchor_in_excerpt(*excerpt_id, buffer.anchor_before(range.start)); + let end = + multibuffer.anchor_in_excerpt(*excerpt_id, buffer.anchor_after(range.end)); + editor.update(cx, |editor, cx| { + f(editor, start..end, cx); + }); + } + } + } + + fn node_is_active(&mut self, node: tree_sitter::Node, cx: &mut ViewContext) { + if self.highlighted_active_descendant { + return; + } + self.highlighted_active_descendant = true; + self.update_editor_with_node_range(node, cx, |editor, range, cx| { + editor.clear_background_highlights::(cx); + editor.highlight_background::( + vec![range], + |theme| theme.editor.document_highlight_write_background, + cx, + ); + }); + } +} + +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 { + let settings = settings::get::(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(), + }; + self.line_height = Some(cx.font_cache().line_height(font_size)); + + self.hover_state_changed(cx); + + if let Some(layer) = &self.layer { + let layer = layer.clone(); + return MouseEventHandler::::new(0, cx, move |_, cx| { + 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 { + let node = cursor.node(); + let is_hovered = Some(descendant_ix) == this.active_descendant_ix; + if is_hovered { + this.node_is_active(node, cx); + } + items.push( + Label::new(node.kind(), style.clone()) + .contained() + .with_background_color(if is_hovered { + editor_theme.active_line_background + } else { + Default::default() + }) + .with_padding_left(depth as f32 * 10.0) + .into_any(), + ); + 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.hover_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); + }) + .into_any(); + } + + Empty::new().into_any() + } +} + +impl Item for SyntaxTreeView { + fn tab_content( + &self, + _: Option, + style: &theme::Tab, + _: &AppContext, + ) -> gpui::AnyElement { + Label::new("Syntax Tree", style.label.clone()).into_any() + } +} diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index f0396266fc..dab4b91992 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -31,7 +31,7 @@ serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true toml.workspace = true -tree-sitter = "*" +tree-sitter.workspace = true tree-sitter-json = "*" [dev-dependencies] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 8fba2a302c..2a7274990b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -45,7 +45,7 @@ journal = { path = "../journal" } language = { path = "../language" } language_selector = { path = "../language_selector" } lsp = { path = "../lsp" } -lsp_log = { path = "../lsp_log" } +language_tools = { path = "../language_tools" } node_runtime = { path = "../node_runtime" } ai = { path = "../ai" } outline = { path = "../outline" } @@ -102,7 +102,7 @@ tempdir.workspace = true thiserror.workspace = true tiny_http = "0.8" toml.workspace = true -tree-sitter = "0.20" +tree-sitter.workspace = true tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 2393d0df3b..73a3346a9a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -191,7 +191,7 @@ fn main() { language_selector::init(cx); theme_selector::init(cx); activity_indicator::init(cx); - lsp_log::init(cx); + language_tools::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); feedback::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d15bace554..cef6cc7d3c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -312,7 +312,7 @@ pub fn initialize_workspace( let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new()); toolbar.add_item(feedback_info_text, cx); let lsp_log_item = - cx.add_view(|_| lsp_log::LspLogToolbarItemView::new()); + cx.add_view(|_| language_tools::LspLogToolbarItemView::new()); toolbar.add_item(lsp_log_item, cx); }) });