mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-04 10:12:47 +00:00
Create a new crate
This commit is contained in:
parent
229b5aa8f7
commit
64925231b0
7 changed files with 1881 additions and 0 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -4990,6 +4990,29 @@ dependencies = [
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "language_tools2"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"client2",
|
||||||
|
"collections",
|
||||||
|
"editor2",
|
||||||
|
"env_logger 0.9.3",
|
||||||
|
"futures 0.3.28",
|
||||||
|
"gpui2",
|
||||||
|
"language2",
|
||||||
|
"lsp2",
|
||||||
|
"project2",
|
||||||
|
"serde",
|
||||||
|
"settings2",
|
||||||
|
"theme2",
|
||||||
|
"tree-sitter",
|
||||||
|
"unindent",
|
||||||
|
"util",
|
||||||
|
"workspace2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|
|
@ -64,6 +64,7 @@ members = [
|
||||||
"crates/language_selector",
|
"crates/language_selector",
|
||||||
"crates/language_selector2",
|
"crates/language_selector2",
|
||||||
"crates/language_tools",
|
"crates/language_tools",
|
||||||
|
"crates/language_tools2",
|
||||||
"crates/live_kit_client",
|
"crates/live_kit_client",
|
||||||
"crates/live_kit_server",
|
"crates/live_kit_server",
|
||||||
"crates/lsp",
|
"crates/lsp",
|
||||||
|
|
33
crates/language_tools2/Cargo.toml
Normal file
33
crates/language_tools2/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
[package]
|
||||||
|
name = "language_tools2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/language_tools.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
collections = { path = "../collections" }
|
||||||
|
editor = { package = "editor2", path = "../editor2" }
|
||||||
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
|
language = { package = "language2", path = "../language2" }
|
||||||
|
project = { package = "project2", path = "../project2" }
|
||||||
|
workspace = { package = "workspace2", path = "../workspace2" }
|
||||||
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
lsp = { package = "lsp2", path = "../lsp2" }
|
||||||
|
futures.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
tree-sitter.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
||||||
|
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
|
||||||
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
env_logger.workspace = true
|
||||||
|
unindent.workspace = true
|
15
crates/language_tools2/src/language_tools.rs
Normal file
15
crates/language_tools2/src/language_tools.rs
Normal 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::{SyntaxTreeToolbarItemView, SyntaxTreeView};
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
lsp_log::init(cx);
|
||||||
|
syntax_tree_view::init(cx);
|
||||||
|
}
|
1023
crates/language_tools2/src/lsp_log.rs
Normal file
1023
crates/language_tools2/src/lsp_log.rs
Normal file
File diff suppressed because it is too large
Load diff
109
crates/language_tools2/src/lsp_log_tests.rs
Normal file
109
crates/language_tools2/src/lsp_log_tests.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// todo!("TODO kb")
|
||||||
|
// 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, LanguageServerName};
|
||||||
|
// use project::{FakeFs, Project};
|
||||||
|
// use settings::SettingsStore;
|
||||||
|
|
||||||
|
// #[gpui::test]
|
||||||
|
// async fn test_lsp_logs(cx: &mut TestAppContext) {
|
||||||
|
// if std::env::var("RUST_LOG").is_ok() {
|
||||||
|
// env_logger::init();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// init_test(cx);
|
||||||
|
|
||||||
|
// let mut rust_language = Language::new(
|
||||||
|
// LanguageConfig {
|
||||||
|
// name: "Rust".into(),
|
||||||
|
// path_suffixes: vec!["rs".to_string()],
|
||||||
|
// ..Default::default()
|
||||||
|
// },
|
||||||
|
// Some(tree_sitter_rust::language()),
|
||||||
|
// );
|
||||||
|
// let mut fake_rust_servers = rust_language
|
||||||
|
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||||
|
// name: "the-rust-language-server",
|
||||||
|
// ..Default::default()
|
||||||
|
// }))
|
||||||
|
// .await;
|
||||||
|
|
||||||
|
// let fs = FakeFs::new(cx.background());
|
||||||
|
// fs.insert_tree(
|
||||||
|
// "/the-root",
|
||||||
|
// json!({
|
||||||
|
// "test.rs": "",
|
||||||
|
// "package.json": "",
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||||
|
// project.update(cx, |project, _| {
|
||||||
|
// project.languages().add(Arc::new(rust_language));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// let log_store = cx.add_model(|cx| LogStore::new(cx));
|
||||||
|
// log_store.update(cx, |store, cx| store.add_project(&project, cx));
|
||||||
|
|
||||||
|
// let _rust_buffer = project
|
||||||
|
// .update(cx, |project, cx| {
|
||||||
|
// project.open_local_buffer("/the-root/test.rs", cx)
|
||||||
|
// })
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// let mut language_server = fake_rust_servers.next().await.unwrap();
|
||||||
|
// language_server
|
||||||
|
// .receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
// .await;
|
||||||
|
|
||||||
|
// let log_view = cx
|
||||||
|
// .add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
|
||||||
|
// .root(cx);
|
||||||
|
|
||||||
|
// language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
|
||||||
|
// message: "hello from the server".into(),
|
||||||
|
// typ: lsp::MessageType::INFO,
|
||||||
|
// });
|
||||||
|
// cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// log_view.read_with(cx, |view, cx| {
|
||||||
|
// assert_eq!(
|
||||||
|
// view.menu_items(cx).unwrap(),
|
||||||
|
// &[LogMenuItem {
|
||||||
|
// server_id: language_server.server.server_id(),
|
||||||
|
// server_name: LanguageServerName("the-rust-language-server".into()),
|
||||||
|
// worktree_root_name: project
|
||||||
|
// .read(cx)
|
||||||
|
// .worktrees(cx)
|
||||||
|
// .next()
|
||||||
|
// .unwrap()
|
||||||
|
// .read(cx)
|
||||||
|
// .root_name()
|
||||||
|
// .to_string(),
|
||||||
|
// rpc_trace_enabled: false,
|
||||||
|
// rpc_trace_selected: false,
|
||||||
|
// logs_selected: true,
|
||||||
|
// }]
|
||||||
|
// );
|
||||||
|
// assert_eq!(view.editor.read(cx).text(cx), "hello from the server\n");
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn init_test(cx: &mut gpui::TestAppContext) {
|
||||||
|
// cx.foreground().forbid_parking();
|
||||||
|
|
||||||
|
// cx.update(|cx| {
|
||||||
|
// cx.set_global(SettingsStore::test(cx));
|
||||||
|
// theme::init((), cx);
|
||||||
|
// language::init(cx);
|
||||||
|
// client::init_settings(cx);
|
||||||
|
// Project::init_settings(cx);
|
||||||
|
// editor::init_settings(cx);
|
||||||
|
// });
|
||||||
|
// }
|
677
crates/language_tools2/src/syntax_tree_view.rs
Normal file
677
crates/language_tools2/src/syntax_tree_view.rs
Normal file
|
@ -0,0 +1,677 @@
|
||||||
|
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,
|
||||||
|
AppContext, CursorStyle, Element, Entity, Model, MouseButton, View, View, ViewContext,
|
||||||
|
WeakView,
|
||||||
|
};
|
||||||
|
use language::{Buffer, OwnedSyntaxLayerInfo, SyntaxLayerInfo};
|
||||||
|
use std::{mem, ops::Range, sync::Arc};
|
||||||
|
use theme::{Theme, ThemeSettings};
|
||||||
|
use tree_sitter::{Node, TreeCursor};
|
||||||
|
use workspace::{
|
||||||
|
item::{Item, ItemHandle},
|
||||||
|
ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
|
actions!(debug, [OpenSyntaxTreeView]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
cx.add_action(
|
||||||
|
move |workspace: &mut Workspace, _: &OpenSyntaxTreeView, cx: _| {
|
||||||
|
let active_item = workspace.active_item(cx);
|
||||||
|
let workspace_handle = workspace.weak_handle();
|
||||||
|
let syntax_tree_view =
|
||||||
|
cx.build_view(|cx| SyntaxTreeView::new(workspace_handle, active_item, cx));
|
||||||
|
workspace.add_item(Box::new(syntax_tree_view), cx);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SyntaxTreeView {
|
||||||
|
workspace_handle: WeakView<Workspace>,
|
||||||
|
editor: Option<EditorState>,
|
||||||
|
mouse_y: Option<f32>,
|
||||||
|
line_height: Option<f32>,
|
||||||
|
list_state: UniformListState,
|
||||||
|
selected_descendant_ix: Option<usize>,
|
||||||
|
hovered_descendant_ix: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SyntaxTreeToolbarItemView {
|
||||||
|
tree_view: Option<View<SyntaxTreeView>>,
|
||||||
|
subscription: Option<gpui::Subscription>,
|
||||||
|
menu_open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EditorState {
|
||||||
|
editor: ViewHandle<Editor>,
|
||||||
|
active_buffer: Option<BufferState>,
|
||||||
|
_subscription: gpui::Subscription,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct BufferState {
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
excerpt_id: ExcerptId,
|
||||||
|
active_layer: Option<OwnedSyntaxLayerInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntaxTreeView {
|
||||||
|
pub fn new(
|
||||||
|
workspace_handle: WeakView<Workspace>,
|
||||||
|
active_item: Option<Box<dyn ItemHandle>>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let mut this = Self {
|
||||||
|
workspace_handle: workspace_handle.clone(),
|
||||||
|
list_state: UniformListState::default(),
|
||||||
|
editor: None,
|
||||||
|
mouse_y: None,
|
||||||
|
line_height: None,
|
||||||
|
hovered_descendant_ix: None,
|
||||||
|
selected_descendant_ix: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.workspace_updated(active_item, cx);
|
||||||
|
cx.observe(
|
||||||
|
&workspace_handle.upgrade(cx).unwrap(),
|
||||||
|
|this, workspace, cx| {
|
||||||
|
this.workspace_updated(workspace.read(cx).active_item(cx), cx);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_updated(
|
||||||
|
&mut self,
|
||||||
|
active_item: Option<Box<dyn ItemHandle>>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(item) = active_item {
|
||||||
|
if item.id() != cx.view_id() {
|
||||||
|
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||||
|
self.set_editor(editor, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(state) = &self.editor {
|
||||||
|
if state.editor == editor {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.clear_background_highlights::<Self>(cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let subscription = cx.subscribe(&editor, |this, _, event, cx| {
|
||||||
|
let did_reparse = match event {
|
||||||
|
editor::Event::Reparsed => true,
|
||||||
|
editor::Event::SelectionsChanged { .. } => false,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
this.editor_updated(did_reparse, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.editor = Some(EditorState {
|
||||||
|
editor,
|
||||||
|
_subscription: subscription,
|
||||||
|
active_buffer: None,
|
||||||
|
});
|
||||||
|
self.editor_updated(true, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||||
|
// Find which excerpt the cursor is in, and the position within that excerpted buffer.
|
||||||
|
let editor_state = self.editor.as_mut()?;
|
||||||
|
let editor = &editor_state.editor.read(cx);
|
||||||
|
let selection_range = editor.selections.last::<usize>(cx).range();
|
||||||
|
let multibuffer = editor.buffer().read(cx);
|
||||||
|
let (buffer, range, excerpt_id) = multibuffer
|
||||||
|
.range_to_buffer_ranges(selection_range, cx)
|
||||||
|
.pop()?;
|
||||||
|
|
||||||
|
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
|
||||||
|
// from that buffer.
|
||||||
|
let buffer_state = editor_state
|
||||||
|
.active_buffer
|
||||||
|
.get_or_insert_with(|| BufferState {
|
||||||
|
buffer: buffer.clone(),
|
||||||
|
excerpt_id,
|
||||||
|
active_layer: None,
|
||||||
|
});
|
||||||
|
let mut prev_layer = None;
|
||||||
|
if did_reparse {
|
||||||
|
prev_layer = buffer_state.active_layer.take();
|
||||||
|
}
|
||||||
|
if buffer_state.buffer != buffer || buffer_state.excerpt_id != buffer_state.excerpt_id {
|
||||||
|
buffer_state.buffer = buffer.clone();
|
||||||
|
buffer_state.excerpt_id = excerpt_id;
|
||||||
|
buffer_state.active_layer = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = match &mut buffer_state.active_layer {
|
||||||
|
Some(layer) => layer,
|
||||||
|
None => {
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let layer = if let Some(prev_layer) = prev_layer {
|
||||||
|
let prev_range = prev_layer.node().byte_range();
|
||||||
|
snapshot
|
||||||
|
.syntax_layers()
|
||||||
|
.filter(|layer| layer.language == &prev_layer.language)
|
||||||
|
.min_by_key(|layer| {
|
||||||
|
let range = layer.node().byte_range();
|
||||||
|
((range.start as i64) - (prev_range.start as i64)).abs()
|
||||||
|
+ ((range.end as i64) - (prev_range.end as i64)).abs()
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
snapshot.syntax_layers().next()?
|
||||||
|
};
|
||||||
|
buffer_state.active_layer.insert(layer.to_owned())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Within the active layer, find the syntax node under the cursor,
|
||||||
|
// and scroll to it.
|
||||||
|
let mut cursor = layer.node().walk();
|
||||||
|
while cursor.goto_first_child_for_byte(range.start).is_some() {
|
||||||
|
if !range.is_empty() && cursor.node().end_byte() == range.start {
|
||||||
|
cursor.goto_next_sibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ascend to the smallest ancestor that contains the range.
|
||||||
|
loop {
|
||||||
|
let node_range = cursor.node().byte_range();
|
||||||
|
if node_range.start <= range.start && node_range.end >= range.end {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !cursor.goto_parent() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let descendant_ix = cursor.descendant_index();
|
||||||
|
self.selected_descendant_ix = Some(descendant_ix);
|
||||||
|
self.list_state.scroll_to(ScrollTarget::Show(descendant_ix));
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_click(&mut self, y: f32, cx: &mut ViewContext<SyntaxTreeView>) -> Option<()> {
|
||||||
|
let line_height = self.line_height?;
|
||||||
|
let ix = ((self.list_state.scroll_top() + y) / line_height) as usize;
|
||||||
|
|
||||||
|
self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| {
|
||||||
|
// Put the cursor at the beginning of the node.
|
||||||
|
mem::swap(&mut range.start, &mut range.end);
|
||||||
|
|
||||||
|
editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
|
||||||
|
selections.select_ranges(vec![range]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover_state_changed(&mut self, cx: &mut ViewContext<SyntaxTreeView>) {
|
||||||
|
if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) {
|
||||||
|
let ix = ((self.list_state.scroll_top() + y) / line_height) as usize;
|
||||||
|
if self.hovered_descendant_ix != Some(ix) {
|
||||||
|
self.hovered_descendant_ix = Some(ix);
|
||||||
|
self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| {
|
||||||
|
editor.clear_background_highlights::<Self>(cx);
|
||||||
|
editor.highlight_background::<Self>(
|
||||||
|
vec![range],
|
||||||
|
|theme| theme.editor.document_highlight_write_background,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_editor_with_range_for_descendant_ix(
|
||||||
|
&self,
|
||||||
|
descendant_ix: usize,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
mut f: impl FnMut(&mut Editor, Range<Anchor>, &mut ViewContext<Editor>),
|
||||||
|
) -> Option<()> {
|
||||||
|
let editor_state = self.editor.as_ref()?;
|
||||||
|
let buffer_state = editor_state.active_buffer.as_ref()?;
|
||||||
|
let layer = buffer_state.active_layer.as_ref()?;
|
||||||
|
|
||||||
|
// Find the node.
|
||||||
|
let mut cursor = layer.node().walk();
|
||||||
|
cursor.goto_descendant(descendant_ix);
|
||||||
|
let node = cursor.node();
|
||||||
|
let range = node.byte_range();
|
||||||
|
|
||||||
|
// Build a text anchor range.
|
||||||
|
let buffer = buffer_state.buffer.read(cx);
|
||||||
|
let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
|
||||||
|
|
||||||
|
// Build a multibuffer anchor range.
|
||||||
|
let multibuffer = editor_state.editor.read(cx).buffer();
|
||||||
|
let multibuffer = multibuffer.read(cx).snapshot(cx);
|
||||||
|
let excerpt_id = buffer_state.excerpt_id;
|
||||||
|
let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start)
|
||||||
|
..multibuffer.anchor_in_excerpt(excerpt_id, range.end);
|
||||||
|
|
||||||
|
// Update the editor with the anchor range.
|
||||||
|
editor_state.editor.update(cx, |editor, cx| {
|
||||||
|
f(editor, range, cx);
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_node(
|
||||||
|
cursor: &TreeCursor,
|
||||||
|
depth: u32,
|
||||||
|
selected: bool,
|
||||||
|
hovered: bool,
|
||||||
|
list_hovered: bool,
|
||||||
|
style: &TextStyle,
|
||||||
|
editor_theme: &theme::Editor,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> gpui::AnyElement<SyntaxTreeView> {
|
||||||
|
let node = cursor.node();
|
||||||
|
let mut range_style = style.clone();
|
||||||
|
let em_width = style.em_width(cx.font_cache());
|
||||||
|
let gutter_padding = (em_width * editor_theme.gutter_padding_factor).round();
|
||||||
|
|
||||||
|
range_style.color = editor_theme.line_number;
|
||||||
|
|
||||||
|
let mut anonymous_node_style = style.clone();
|
||||||
|
let string_color = editor_theme
|
||||||
|
.syntax
|
||||||
|
.highlights
|
||||||
|
.iter()
|
||||||
|
.find_map(|(name, style)| (name == "string").then(|| style.color)?);
|
||||||
|
let property_color = editor_theme
|
||||||
|
.syntax
|
||||||
|
.highlights
|
||||||
|
.iter()
|
||||||
|
.find_map(|(name, style)| (name == "property").then(|| style.color)?);
|
||||||
|
if let Some(color) = string_color {
|
||||||
|
anonymous_node_style.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut row = Flex::row();
|
||||||
|
if let Some(field_name) = cursor.field_name() {
|
||||||
|
let mut field_style = style.clone();
|
||||||
|
if let Some(color) = property_color {
|
||||||
|
field_style.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.add_children([
|
||||||
|
Label::new(field_name, field_style),
|
||||||
|
Label::new(": ", style.clone()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return row
|
||||||
|
.with_child(
|
||||||
|
if node.is_named() {
|
||||||
|
Label::new(node.kind(), style.clone())
|
||||||
|
} else {
|
||||||
|
Label::new(format!("\"{}\"", node.kind()), anonymous_node_style)
|
||||||
|
}
|
||||||
|
.contained()
|
||||||
|
.with_margin_right(em_width),
|
||||||
|
)
|
||||||
|
.with_child(Label::new(format_node_range(node), range_style))
|
||||||
|
.contained()
|
||||||
|
.with_background_color(if selected {
|
||||||
|
editor_theme.selection.selection
|
||||||
|
} else if hovered && list_hovered {
|
||||||
|
editor_theme.active_line_background
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
})
|
||||||
|
.with_padding_left(gutter_padding + depth as f32 * 18.0)
|
||||||
|
.into_any();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for SyntaxTreeView {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for SyntaxTreeView {
|
||||||
|
fn ui_name() -> &'static str {
|
||||||
|
"SyntaxTreeView"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
|
||||||
|
let settings = settings::get::<ThemeSettings>(cx);
|
||||||
|
let font_family_id = settings.buffer_font_family;
|
||||||
|
let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
|
||||||
|
let font_properties = Default::default();
|
||||||
|
let font_id = cx
|
||||||
|
.font_cache()
|
||||||
|
.select_font(font_family_id, &font_properties)
|
||||||
|
.unwrap();
|
||||||
|
let font_size = settings.buffer_font_size(cx);
|
||||||
|
|
||||||
|
let editor_theme = settings.theme.editor.clone();
|
||||||
|
let style = TextStyle {
|
||||||
|
color: editor_theme.text_color,
|
||||||
|
font_family_name,
|
||||||
|
font_family_id,
|
||||||
|
font_id,
|
||||||
|
font_size,
|
||||||
|
font_properties: Default::default(),
|
||||||
|
underline: Default::default(),
|
||||||
|
soft_wrap: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_height = cx.font_cache().line_height(font_size);
|
||||||
|
if Some(line_height) != self.line_height {
|
||||||
|
self.line_height = Some(line_height);
|
||||||
|
self.hover_state_changed(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(layer) = self
|
||||||
|
.editor
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|editor| editor.active_buffer.as_ref())
|
||||||
|
.and_then(|buffer| buffer.active_layer.as_ref())
|
||||||
|
{
|
||||||
|
let layer = layer.clone();
|
||||||
|
let theme = editor_theme.clone();
|
||||||
|
return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| {
|
||||||
|
let list_hovered = state.hovered();
|
||||||
|
UniformList::new(
|
||||||
|
self.list_state.clone(),
|
||||||
|
layer.node().descendant_count(),
|
||||||
|
cx,
|
||||||
|
move |this, range, items, cx| {
|
||||||
|
let mut cursor = layer.node().walk();
|
||||||
|
let mut descendant_ix = range.start as usize;
|
||||||
|
cursor.goto_descendant(descendant_ix);
|
||||||
|
let mut depth = cursor.depth();
|
||||||
|
let mut visited_children = false;
|
||||||
|
while descendant_ix < range.end {
|
||||||
|
if visited_children {
|
||||||
|
if cursor.goto_next_sibling() {
|
||||||
|
visited_children = false;
|
||||||
|
} else if cursor.goto_parent() {
|
||||||
|
depth -= 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items.push(Self::render_node(
|
||||||
|
&cursor,
|
||||||
|
depth,
|
||||||
|
Some(descendant_ix) == this.selected_descendant_ix,
|
||||||
|
Some(descendant_ix) == this.hovered_descendant_ix,
|
||||||
|
list_hovered,
|
||||||
|
&style,
|
||||||
|
&theme,
|
||||||
|
cx,
|
||||||
|
));
|
||||||
|
descendant_ix += 1;
|
||||||
|
if cursor.goto_first_child() {
|
||||||
|
depth += 1;
|
||||||
|
} else {
|
||||||
|
visited_children = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_move(move |event, this, cx| {
|
||||||
|
let y = event.position.y() - event.region.origin_y();
|
||||||
|
this.mouse_y = Some(y);
|
||||||
|
this.hover_state_changed(cx);
|
||||||
|
})
|
||||||
|
.on_click(MouseButton::Left, move |event, this, cx| {
|
||||||
|
let y = event.position.y() - event.region.origin_y();
|
||||||
|
this.handle_click(y, cx);
|
||||||
|
})
|
||||||
|
.contained()
|
||||||
|
.with_background_color(editor_theme.background)
|
||||||
|
.into_any();
|
||||||
|
}
|
||||||
|
|
||||||
|
Empty::new().into_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item for SyntaxTreeView {
|
||||||
|
fn tab_content<V: 'static>(
|
||||||
|
&self,
|
||||||
|
_: Option<usize>,
|
||||||
|
style: &theme::Tab,
|
||||||
|
_: &AppContext,
|
||||||
|
) -> gpui::AnyElement<V> {
|
||||||
|
Label::new("Syntax Tree", style.label.clone()).into_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_on_split(
|
||||||
|
&self,
|
||||||
|
_workspace_id: workspace::WorkspaceId,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let mut clone = Self::new(self.workspace_handle.clone(), None, cx);
|
||||||
|
if let Some(editor) = &self.editor {
|
||||||
|
clone.set_editor(editor.editor.clone(), cx)
|
||||||
|
}
|
||||||
|
Some(clone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntaxTreeToolbarItemView {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
menu_open: false,
|
||||||
|
tree_view: None,
|
||||||
|
subscription: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_menu(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut ViewContext<'_, '_, Self>,
|
||||||
|
) -> Option<gpui::AnyElement<Self>> {
|
||||||
|
let theme = theme::current(cx).clone();
|
||||||
|
let tree_view = self.tree_view.as_ref()?;
|
||||||
|
let tree_view = tree_view.read(cx);
|
||||||
|
|
||||||
|
let editor_state = tree_view.editor.as_ref()?;
|
||||||
|
let buffer_state = editor_state.active_buffer.as_ref()?;
|
||||||
|
let active_layer = buffer_state.active_layer.clone()?;
|
||||||
|
let active_buffer = buffer_state.buffer.read(cx).snapshot();
|
||||||
|
|
||||||
|
enum Menu {}
|
||||||
|
|
||||||
|
Some(
|
||||||
|
Stack::new()
|
||||||
|
.with_child(Self::render_header(&theme, &active_layer, cx))
|
||||||
|
.with_children(self.menu_open.then(|| {
|
||||||
|
Overlay::new(
|
||||||
|
MouseEventHandler::new::<Menu, _>(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>) {
|
||||||
|
self.menu_open = !self.menu_open;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_layer(&mut self, layer_ix: usize, cx: &mut ViewContext<Self>) -> 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<Theme>,
|
||||||
|
active_layer: &OwnedSyntaxLayerInfo,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> impl Element<Self> {
|
||||||
|
enum ToggleMenu {}
|
||||||
|
MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
|
||||||
|
let style = theme.toolbar_dropdown_menu.header.style_for(state);
|
||||||
|
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<Theme>,
|
||||||
|
active_layer: &OwnedSyntaxLayerInfo,
|
||||||
|
layer: SyntaxLayerInfo,
|
||||||
|
layer_ix: usize,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> impl Element<Self> {
|
||||||
|
enum ActivateLayer {}
|
||||||
|
MouseEventHandler::new::<ActivateLayer, _>(layer_ix, cx, move |state, _| {
|
||||||
|
let is_selected = layer.node() == active_layer.node();
|
||||||
|
let style = theme
|
||||||
|
.toolbar_dropdown_menu
|
||||||
|
.item
|
||||||
|
.in_state(is_selected)
|
||||||
|
.style_for(state);
|
||||||
|
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> {
|
||||||
|
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<Self>,
|
||||||
|
) -> workspace::ToolbarItemLocation {
|
||||||
|
self.menu_open = false;
|
||||||
|
if let Some(item) = active_pane_item {
|
||||||
|
if let Some(view) = item.downcast::<SyntaxTreeView>() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue