diff --git a/Cargo.lock b/Cargo.lock index 8ef48849acb94fe864b9af31d2999fc4c9e05271..df97ab9c51338f444418c0858d6f65717535bb51 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=49226023693107fba9a1191136a4f47f38cdca73#49226023693107fba9a1191136a4f47f38cdca73" dependencies = [ "cc", "regex", @@ -7559,7 +7560,7 @@ dependencies = [ [[package]] name = "tree-sitter-yaml" version = "0.0.1" -source = "git+https://github.com/zed-industries/tree-sitter-yaml?rev=5694b7f290cd9ef998829a0a6d8391a666370886#5694b7f290cd9ef998829a0a6d8391a666370886" +source = "git+https://github.com/zed-industries/tree-sitter-yaml?rev=f545a41f57502e1b5ddf2a6668896c1b0620f930#f545a41f57502e1b5ddf2a6668896c1b0620f930" dependencies = [ "cc", "tree-sitter", @@ -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 72a93177a9677e52f7cb0399dbc936fd584f6061..fca735596489a222208e2f65874efc4fcd04ffd5 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 = "49226023693107fba9a1191136a4f47f38cdca73" } 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 325883b7c03fcbc8ed4d2c33df7f4ff00d4e3ef2..dcc22202273a5d087a5892f279e1d153b3d9770e 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/editor.rs b/crates/editor/src/editor.rs index cecefc7061eeb22791497db76af0ccad7af6cded..9562d18df495a50451768a0a156086f6184c4764 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7102,7 +7102,7 @@ impl Editor { let mut new_selections_by_buffer = HashMap::default(); for selection in editor.selections.all::(cx) { - for (buffer, mut range) in + for (buffer, mut range, _) in buffer.range_to_buffer_ranges(selection.start..selection.end, cx) { if selection.reversed { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 0d7fb6a450d9ee784c1c601450d2170939a7e073..955902da1263f875c6e3c3779b6cf39d84ccc191 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) }) } @@ -1140,7 +1140,7 @@ impl MultiBuffer { &self, range: Range, cx: &AppContext, - ) -> Vec<(ModelHandle, Range)> { + ) -> Vec<(ModelHandle, Range, ExcerptId)> { let snapshot = self.read(cx); let start = range.start.to_offset(&snapshot); let end = range.end.to_offset(&snapshot); @@ -1165,7 +1165,7 @@ impl MultiBuffer { let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start()); let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start()); let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); - result.push((buffer, start..end)); + result.push((buffer, start..end, excerpt.id)); cursor.next(&()); } @@ -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(); @@ -5196,7 +5196,7 @@ mod tests { .range_to_buffer_ranges(start_ix..end_ix, cx); let excerpted_buffers_text = excerpted_buffer_ranges .iter() - .map(|(buffer, buffer_range)| { + .map(|(buffer, buffer_range, _)| { buffer .read(cx) .text_for_range(buffer_range.clone()) diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 9722b618f3a135eb365699769150b06b16086624..04b48c93ff7b305b77c523b9c041c393116ca1b2 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 e09ee48da630989774abdbf0bdffd386afa44a1b..5041ab759d1fe0aa892feec2b472086f62a01242 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,20 @@ impl BufferSnapshot { } } - pub fn language_at(&self, position: D) -> Option<&Arc> { + pub fn syntax_layers(&self) -> impl Iterator + '_ { + self.syntax.layers_for_range(0..self.len(), &self.text) + } + + 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 +2149,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 +2197,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 9f44de40ac1f4010f7e335277c38e594650f0140..38cefbcef9393ef577f67829febca7bbf03a6358 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 0ff1d973d3ff47d7e67a82a7639ddcac810702eb..e91d5770cfba80485f3cbe6272257a81c48c4fc3 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 16313db49881070f6c0baac9b71b317761079830..ebfa38c148dfb7c28e51821ad5339ae627e52c6d 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 46f6006a23efde3569311da8f936c65e40ea9a93..e67a4b36df761fd9ac23b95d83b3fa43b78c6da1 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 0000000000000000000000000000000000000000..0a1f31f03fe82eece9911a9ecc474cd714a364c4 --- /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::{SyntaxTreeToolbarItemView, 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 94% rename from crates/lsp_log/src/lsp_log.rs rename to crates/language_tools/src/lsp_log.rs index 5808b4da2ed0e0b0b294f8840477064477c8f118..b9036fa6d2a0cc9468bc816093d1526a652161fd 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 @@ -544,12 +541,7 @@ impl View for LspLogToolbarItemView { let theme = theme::current(cx).clone(); let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() }; let log_view = log_view.read(cx); - - let menu_rows = self - .log_view - .as_ref() - .and_then(|view| view.read(cx).menu_items(cx)) - .unwrap_or_default(); + let menu_rows = log_view.menu_items(cx).unwrap_or_default(); let current_server_id = log_view.current_server_id; let current_server = current_server_id.and_then(|current_server_id| { @@ -586,7 +578,7 @@ impl View for LspLogToolbarItemView { ) })) .contained() - .with_style(theme.lsp_log_menu.container) + .with_style(theme.toolbar_dropdown_menu.container) .constrained() .with_width(400.) .with_height(400.) @@ -596,6 +588,7 @@ impl View for LspLogToolbarItemView { cx.notify() }), ) + .with_hoverable(true) .with_fit_mode(OverlayFitMode::SwitchAnchor) .with_anchor_corner(AnchorCorner::TopLeft) .with_z_index(999) @@ -688,7 +681,7 @@ impl LspLogToolbarItemView { ) }) .unwrap_or_else(|| "No server selected".into()); - let style = theme.lsp_log_menu.header.style_for(state, false); + let style = theme.toolbar_dropdown_menu.header.style_for(state, false); Label::new(label, style.text.clone()) .contained() .with_style(style.container) @@ -714,7 +707,7 @@ impl LspLogToolbarItemView { Flex::column() .with_child({ - let style = &theme.lsp_log_menu.server; + let style = &theme.toolbar_dropdown_menu.section_header; Label::new( format!("{} ({})", name.0, worktree.read(cx).root_name()), style.text.clone(), @@ -722,16 +715,19 @@ impl LspLogToolbarItemView { .contained() .with_style(style.container) .constrained() - .with_height(theme.lsp_log_menu.row_height) + .with_height(theme.toolbar_dropdown_menu.row_height) }) .with_child( MouseEventHandler::::new(id.0, cx, move |state, _| { - let style = theme.lsp_log_menu.item.style_for(state, logs_selected); + let style = theme + .toolbar_dropdown_menu + .item + .style_for(state, logs_selected); Label::new(SERVER_LOGS, style.text.clone()) .contained() .with_style(style.container) .constrained() - .with_height(theme.lsp_log_menu.row_height) + .with_height(theme.toolbar_dropdown_menu.row_height) }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, view, cx| { @@ -740,12 +736,15 @@ impl LspLogToolbarItemView { ) .with_child( MouseEventHandler::::new(id.0, cx, move |state, cx| { - let style = theme.lsp_log_menu.item.style_for(state, rpc_trace_selected); + let style = theme + .toolbar_dropdown_menu + .item + .style_for(state, rpc_trace_selected); Flex::row() .with_child( Label::new(RPC_MESSAGES, style.text.clone()) .constrained() - .with_height(theme.lsp_log_menu.row_height), + .with_height(theme.toolbar_dropdown_menu.row_height), ) .with_child( ui::checkbox_with_label::( @@ -764,7 +763,7 @@ impl LspLogToolbarItemView { .contained() .with_style(style.container) .constrained() - .with_height(theme.lsp_log_menu.row_height) + .with_height(theme.toolbar_dropdown_menu.row_height) }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, view, cx| { 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 572758ad63bbd0ff7c8a964fab4f1878baab205f..d4a16b5758a2a905c0e621b368a9653114828ec4 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 0000000000000000000000000000000000000000..6a939a1543c026ac1902405c8a11295c380f836f --- /dev/null +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -0,0 +1,675 @@ +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!(log, [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, + editor: Option, + mouse_y: Option, + line_height: Option, + list_state: UniformListState, + selected_descendant_ix: Option, + hovered_descendant_ix: Option, +} + +pub struct SyntaxTreeToolbarItemView { + tree_view: Option>, + subscription: Option, + menu_open: bool, +} + +struct EditorState { + editor: ViewHandle, + active_buffer: Option, + _subscription: gpui::Subscription, +} + +#[derive(Clone)] +struct BufferState { + buffer: ModelHandle, + excerpt_id: ExcerptId, + active_layer: Option, +} + +impl SyntaxTreeView { + pub fn new( + workspace_handle: WeakViewHandle, + active_item: Option>, + cx: &mut ViewContext, + ) -> 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>, + 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(state) = &self.editor { + if state.editor == editor { + return; + } + editor.update(cx, |editor, cx| { + editor.clear_background_highlights::(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) -> 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::(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) -> 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) { + 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::(cx); + editor.highlight_background::( + 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, + mut f: impl FnMut(&mut Editor, Range, &mut ViewContext), + ) -> 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 { + 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 { + 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(), + }; + + 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(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( + &self, + _: Option, + style: &theme::Tab, + _: &AppContext, + ) -> gpui::AnyElement { + Label::new("Syntax Tree", style.label.clone()).into_any() + } + + fn clone_on_split( + &self, + _workspace_id: workspace::WorkspaceId, + cx: &mut ViewContext, + ) -> Option + 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> { + 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(); + + enum Menu {} + + Some( + Stack::new() + .with_child(Self::render_header(&theme, &active_layer, cx)) + .with_children(self.menu_open.then(|| { + Overlay::new( + MouseEventHandler::::new(0, cx, move |_, cx| { + Flex::column() + .with_children(active_buffer.syntax_layers().enumerate().map( + |(ix, layer)| { + Self::render_menu_item(&theme, &active_layer, layer, ix, cx) + }, + )) + .contained() + .with_style(theme.toolbar_dropdown_menu.container) + .constrained() + .with_width(400.) + .with_height(400.) + }) + .on_down_out(MouseButton::Left, |_, this, cx| { + this.menu_open = false; + cx.notify() + }), + ) + .with_hoverable(true) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::TopLeft) + .with_z_index(999) + .aligned() + .bottom() + .left() + })) + .aligned() + .left() + .clipped() + .into_any(), + ) + } + + fn toggle_menu(&mut self, cx: &mut ViewContext) { + self.menu_open = !self.menu_open; + cx.notify(); + } + + fn select_layer(&mut self, layer_ix: usize, cx: &mut ViewContext) -> Option<()> { + let tree_view = self.tree_view.as_ref()?; + tree_view.update(cx, |view, cx| { + let editor_state = view.editor.as_mut()?; + let buffer_state = editor_state.active_buffer.as_mut()?; + let snapshot = buffer_state.buffer.read(cx).snapshot(); + let layer = snapshot.syntax_layers().nth(layer_ix)?; + buffer_state.active_layer = Some(layer.to_owned()); + view.selected_descendant_ix = None; + self.menu_open = false; + cx.notify(); + Some(()) + }) + } + + fn render_header( + theme: &Arc, + active_layer: &OwnedSyntaxLayerInfo, + cx: &mut ViewContext, + ) -> impl Element { + enum ToggleMenu {} + MouseEventHandler::::new(0, cx, move |state, _| { + let style = theme.toolbar_dropdown_menu.header.style_for(state, false); + Flex::row() + .with_child( + Label::new(active_layer.language.name().to_string(), style.text.clone()) + .contained() + .with_margin_right(style.secondary_text_spacing), + ) + .with_child(Label::new( + format_node_range(active_layer.node()), + style + .secondary_text + .clone() + .unwrap_or_else(|| style.text.clone()), + )) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, view, cx| { + view.toggle_menu(cx); + }) + } + + fn render_menu_item( + theme: &Arc, + active_layer: &OwnedSyntaxLayerInfo, + layer: SyntaxLayerInfo, + layer_ix: usize, + cx: &mut ViewContext, + ) -> impl Element { + enum ActivateLayer {} + MouseEventHandler::::new(layer_ix, cx, move |state, _| { + let is_selected = layer.node() == active_layer.node(); + let style = theme + .toolbar_dropdown_menu + .item + .style_for(state, is_selected); + Flex::row() + .with_child( + Label::new(layer.language.name().to_string(), style.text.clone()) + .contained() + .with_margin_right(style.secondary_text_spacing), + ) + .with_child(Label::new( + format_node_range(layer.node()), + style + .secondary_text + .clone() + .unwrap_or_else(|| style.text.clone()), + )) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, view, cx| { + view.select_layer(layer_ix, cx); + }) + } +} + +fn format_node_range(node: Node) -> String { + let start = node.start_position(); + let end = node.end_position(); + format!( + "[{}:{} - {}:{}]", + start.row + 1, + start.column + 1, + end.row + 1, + end.column + 1, + ) +} + +impl Entity for SyntaxTreeToolbarItemView { + type Event = (); +} + +impl View for SyntaxTreeToolbarItemView { + fn ui_name() -> &'static str { + "SyntaxTreeToolbarItemView" + } + + fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> gpui::AnyElement { + self.render_menu(cx) + .unwrap_or_else(|| Empty::new().into_any()) + } +} + +impl ToolbarItemView for SyntaxTreeToolbarItemView { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) -> workspace::ToolbarItemLocation { + self.menu_open = false; + if let Some(item) = active_pane_item { + if let Some(view) = item.downcast::() { + self.tree_view = Some(view.clone()); + self.subscription = Some(cx.observe(&view, |_, _, cx| cx.notify())); + return ToolbarItemLocation::PrimaryLeft { + flex: Some((1., false)), + }; + } + } + self.tree_view = None; + self.subscription = None; + ToolbarItemLocation::Hidden + } +} diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index f0396266fc78ff3ce515ce63350f98aa5bc9d2d6..dab4b919923678bf73ec588e5affb7cf48d8bda4 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/theme/src/theme.rs b/crates/theme/src/theme.rs index 9bd17910d2a166bbe56b2a6cfffcd404c686e1e9..c7563ec87a3dfd20f3f2682a2a6e9848cecf5279 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -44,7 +44,7 @@ pub struct Theme { pub context_menu: ContextMenu, pub contacts_popover: ContactsPopover, pub contact_list: ContactList, - pub lsp_log_menu: LspLogMenu, + pub toolbar_dropdown_menu: DropdownMenu, pub copilot: Copilot, pub contact_finder: ContactFinder, pub project_panel: ProjectPanel, @@ -246,15 +246,26 @@ pub struct ContactFinder { } #[derive(Deserialize, Default)] -pub struct LspLogMenu { +pub struct DropdownMenu { #[serde(flatten)] pub container: ContainerStyle, - pub header: Interactive, - pub server: ContainedText, - pub item: Interactive, + pub header: Interactive, + pub section_header: ContainedText, + pub item: Interactive, pub row_height: f32, } +#[derive(Deserialize, Default)] +pub struct DropdownMenuItem { + #[serde(flatten)] + pub container: ContainerStyle, + #[serde(flatten)] + pub text: TextStyle, + pub secondary_text: Option, + #[serde(default)] + pub secondary_text_spacing: f32, +} + #[derive(Clone, Deserialize, Default)] pub struct TabBar { #[serde(flatten)] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 8fba2a302cdfcdd71967e0c0be26f4323af6a8a3..2a7274990b76650d188b61c430a3ba201ff6ab7f 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 2393d0df3b1ce38bd41c56786ad8d232d5734b6b..73a3346a9adc75b61af3ab4e5f50b4484129bb1b 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 d15bace5545d4087ae708adf7b0504838cfe6e54..b0b88b37ace7da3d161d87fb1b4367dfae9e8a76 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -312,8 +312,11 @@ 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); + let syntax_tree_item = cx + .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new()); + toolbar.add_item(syntax_tree_item, cx); }) }); } diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index 886553d418d08095c80d5ebfceef9502e7ab9bd0..6244cbae102b2a0d44d0494438182b07c1144f4a 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -17,7 +17,7 @@ import projectSharedNotification from "./projectSharedNotification" import tooltip from "./tooltip" import terminal from "./terminal" import contactList from "./contactList" -import lspLogMenu from "./lspLogMenu" +import toolbarDropdownMenu from "./toolbarDropdownMenu" import incomingCallNotification from "./incomingCallNotification" import { ColorScheme } from "../theme/colorScheme" import feedback from "./feedback" @@ -46,7 +46,7 @@ export default function app(colorScheme: ColorScheme): Object { contactsPopover: contactsPopover(colorScheme), contactFinder: contactFinder(colorScheme), contactList: contactList(colorScheme), - lspLogMenu: lspLogMenu(colorScheme), + toolbarDropdownMenu: toolbarDropdownMenu(colorScheme), search: search(colorScheme), sharedScreen: sharedScreen(colorScheme), updateNotification: updateNotification(colorScheme), diff --git a/styles/src/styleTree/lspLogMenu.ts b/styles/src/styleTree/toolbarDropdownMenu.ts similarity index 81% rename from styles/src/styleTree/lspLogMenu.ts rename to styles/src/styleTree/toolbarDropdownMenu.ts index 94dd4831b259facbe01ff2bf5136202b8c30faad..92616eb0223781deae3162bfd7d429a3f896e6eb 100644 --- a/styles/src/styleTree/lspLogMenu.ts +++ b/styles/src/styleTree/toolbarDropdownMenu.ts @@ -1,7 +1,7 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, text } from "./components" -export default function contactsPanel(colorScheme: ColorScheme) { +export default function dropdownMenu(colorScheme: ColorScheme) { let layer = colorScheme.middle return { @@ -11,6 +11,8 @@ export default function contactsPanel(colorScheme: ColorScheme) { shadow: colorScheme.popoverShadow, header: { ...text(layer, "sans", { size: "sm" }), + secondaryText: text(layer, "sans", { size: "sm", color: "#aaaaaa" }), + secondaryTextSpacing: 10, padding: { left: 8, right: 8, top: 2, bottom: 2 }, cornerRadius: 6, background: background(layer, "on"), @@ -20,12 +22,14 @@ export default function contactsPanel(colorScheme: ColorScheme) { ...text(layer, "sans", "hovered", { size: "sm" }), } }, - server: { + sectionHeader: { ...text(layer, "sans", { size: "sm" }), padding: { left: 8, right: 8, top: 8, bottom: 8 }, }, item: { ...text(layer, "sans", { size: "sm" }), + secondaryTextSpacing: 10, + secondaryText: text(layer, "sans", { size: "sm" }), padding: { left: 18, right: 18, top: 2, bottom: 2 }, hover: { background: background(layer, "hovered"),