Start work on a syntax tree view

This commit is contained in:
Max Brunsfeld 2023-06-09 14:55:46 -07:00
parent 3d02f7ce5f
commit 086cfe57c5
18 changed files with 446 additions and 97 deletions

51
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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" }

View File

@ -1118,7 +1118,7 @@ impl MultiBuffer {
&self,
point: T,
cx: &AppContext,
) -> Option<(ModelHandle<Buffer>, usize)> {
) -> Option<(ModelHandle<Buffer>, usize, ExcerptId)> {
let snapshot = self.read(cx);
let offset = point.to_offset(&snapshot);
let mut cursor = snapshot.excerpts.cursor::<usize>();
@ -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<Arc<Language>> {
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();

View File

@ -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"

View File

@ -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<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
pub fn syntax_layer_at<D: ToOffset>(&self, position: D) -> Option<SyntaxLayerInfo> {
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<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
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<Range<usize>> = 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.

View File

@ -2242,7 +2242,7 @@ fn get_tree_sexp(buffer: &ModelHandle<Buffer>, 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()
})
}

View File

@ -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) {

View File

@ -125,8 +125,17 @@ impl SyntaxLayerContent {
#[derive(Debug)]
pub struct SyntaxLayerInfo<'a> {
pub depth: usize,
pub node: Node<'a>,
pub language: &'a Arc<Language>,
tree: &'a Tree,
offset: (usize, tree_sitter::Point),
}
#[derive(Clone)]
pub struct OwnedSyntaxLayerInfo {
pub depth: usize,
pub language: Arc<Language>,
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<u32> {
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<usize>)> = 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,

View File

@ -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"] }

View File

@ -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);
}

View File

@ -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<WeakModelHandle<Project>, ProjectState>,
io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, bool, String)>,
}
@ -49,10 +46,10 @@ struct LanguageServerRpcState {
}
pub struct LspLogView {
pub(crate) editor: ViewHandle<Editor>,
log_store: ModelHandle<LogStore>,
current_server_id: Option<LanguageServerId>,
is_showing_rpc_trace: bool,
editor: ViewHandle<Editor>,
project: ModelHandle<Project>,
}
@ -68,13 +65,13 @@ enum MessageKind {
}
#[derive(Clone, Debug, PartialEq)]
struct LogMenuItem {
server_id: LanguageServerId,
server_name: LanguageServerName,
worktree: ModelHandle<Worktree>,
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<Worktree>,
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>) -> Self {
pub fn new(cx: &mut ModelContext<Self>) -> 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<Project>,
log_store: ModelHandle<LogStore>,
cx: &mut ViewContext<Self>,
@ -360,7 +357,7 @@ impl LspLogView {
editor
}
fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
let log_store = self.log_store.read(cx);
let state = log_store.projects.get(&self.project.downgrade())?;
let mut rows = self

View File

@ -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]

View File

@ -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<Editor>, gpui::Subscription)>,
buffer: Option<(ModelHandle<Buffer>, usize, ExcerptId)>,
layer: Option<OwnedSyntaxLayerInfo>,
hover_y: Option<f32>,
line_height: Option<f32>,
list_state: UniformListState,
active_descendant_ix: Option<usize>,
highlighted_active_descendant: bool,
}
impl SyntaxTreeView {
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> 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<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((current_editor, _)) = &self.editor {
if current_editor == &editor {
return;
}
editor.update(cx, |editor, cx| {
editor.clear_background_highlights::<Self>(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<Editor>,
selection_changed: bool,
cx: &mut ViewContext<Self>,
) {
let editor = editor.read(cx);
if selection_changed {
let cursor = editor.selections.last::<usize>(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<SyntaxTreeView>) {
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<SyntaxTreeView>) {
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<Self>,
mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>),
) {
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<Self>) {
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::<Self>(cx);
editor.highlight_background::<Self>(
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<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(),
};
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::<Self, Self>::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<V: View>(
&self,
_: Option<usize>,
style: &theme::Tab,
_: &AppContext,
) -> gpui::AnyElement<V> {
Label::new("Syntax Tree", style.label.clone()).into_any()
}
}

View File

@ -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]

View File

@ -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" }

View File

@ -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);

View File

@ -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);
})
});