Cargo.lock 🔗
@@ -3763,8 +3763,10 @@ name = "lsp_log"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"collections",
"editor",
+ "env_logger 0.9.3",
"futures 0.3.28",
"gpui",
"language",
Max Brunsfeld created
Cargo.lock | 2
crates/lsp_log/Cargo.toml | 2
crates/lsp_log/src/lsp_log.rs | 94 +++++++++++++++++------------
crates/lsp_log/src/lsp_log_tests.rs | 97 +++++++++++++++++++++++++++++++
crates/theme/src/theme.rs | 10 +++
styles/src/styleTree/app.ts | 2
styles/src/styleTree/lspLogMenu.ts | 41 +++++++++++++
7 files changed, 210 insertions(+), 38 deletions(-)
@@ -3763,8 +3763,10 @@ name = "lsp_log"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"collections",
"editor",
+ "env_logger 0.9.3",
"futures 0.3.28",
"gpui",
"language",
@@ -24,7 +24,9 @@ serde.workspace = true
anyhow.workspace = true
[dev-dependencies]
+client = { path = "../client", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
+env_logger.workspace = true
unindent.workspace = true
@@ -1,3 +1,6 @@
+#[cfg(test)]
+mod lsp_log_tests;
+
use collections::HashMap;
use editor::Editor;
use futures::{channel::mpsc, StreamExt};
@@ -521,7 +524,7 @@ impl View for LspLogToolbarItemView {
)
}))
.contained()
- .with_style(theme.context_menu.container)
+ .with_style(theme.lsp_log_menu.container)
.constrained()
.with_width(400.)
.with_height(400.)
@@ -548,6 +551,9 @@ impl View for LspLogToolbarItemView {
}
}
+const RPC_MESSAGES: &str = "RPC Messages";
+const SERVER_LOGS: &str = "Server Logs";
+
impl LspLogToolbarItemView {
pub fn new() -> Self {
Self {
@@ -605,18 +611,25 @@ impl LspLogToolbarItemView {
let label: Cow<str> = current_server
.and_then(|row| {
let worktree = row.worktree.read(cx);
- Some(format!("{} - ({})", row.server_name.0, worktree.root_name()).into())
+ Some(
+ format!(
+ "{} ({}) - {}",
+ row.server_name.0,
+ worktree.root_name(),
+ if row.rpc_trace_selected {
+ RPC_MESSAGES
+ } else {
+ SERVER_LOGS
+ },
+ )
+ .into(),
+ )
})
.unwrap_or_else(|| "No server selected".into());
- Label::new(
- label,
- theme
- .context_menu
- .item
- .style_for(state, false)
- .label
- .clone(),
- )
+ let style = theme.lsp_log_menu.header.style_for(state, false);
+ Label::new(label, style.text.clone())
+ .contained()
+ .with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, view, cx| {
@@ -628,7 +641,7 @@ impl LspLogToolbarItemView {
id: LanguageServerId,
name: LanguageServerName,
worktree: ModelHandle<Worktree>,
- logging_enabled: bool,
+ rpc_trace_enabled: bool,
logs_selected: bool,
rpc_trace_selected: bool,
theme: &Arc<Theme>,
@@ -637,23 +650,25 @@ impl LspLogToolbarItemView {
enum ActivateLog {}
enum ActivateRpcTrace {}
- let header = format!("{} - ({})", name.0, worktree.read(cx).root_name());
-
- let item_style = &theme.context_menu.item.default;
Flex::column()
- .with_child(
- Label::new(header, item_style.label.clone())
- .aligned()
- .left(),
- )
+ .with_child({
+ let style = &theme.lsp_log_menu.server;
+ Label::new(
+ format!("{} ({})", name.0, worktree.read(cx).root_name()),
+ style.text.clone(),
+ )
+ .contained()
+ .with_style(style.container)
+ .aligned()
+ .left()
+ })
.with_child(
MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, _| {
- let item_style = &theme.context_menu.item.style_for(state, logs_selected);
- Label::new("logs", item_style.label.clone())
- .aligned()
- .left()
+ let style = theme.lsp_log_menu.item.style_for(state, logs_selected);
+ Flex::row()
+ .with_child(Label::new(SERVER_LOGS, style.text.clone()).aligned().left())
.contained()
- .with_style(item_style.container)
+ .with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, view, cx| {
@@ -662,26 +677,29 @@ impl LspLogToolbarItemView {
)
.with_child(
MouseEventHandler::<ActivateRpcTrace, _>::new(id.0, cx, move |state, cx| {
- let item_style = &theme.context_menu.item.style_for(state, rpc_trace_selected);
+ let style = theme.lsp_log_menu.item.style_for(state, rpc_trace_selected);
Flex::row()
- .with_child(ui::checkbox_with_label::<Self, _, Self, _>(
- Empty::new(),
- &theme.welcome.checkbox,
- logging_enabled,
- id.0,
- cx,
- move |this, enabled, cx| {
- this.toggle_logging_for_server(id, enabled, cx);
- },
- ))
.with_child(
- Label::new("rpc trace", item_style.label.clone())
+ Label::new(RPC_MESSAGES, style.text.clone())
.aligned()
.left(),
)
+ .with_child(
+ ui::checkbox_with_label::<Self, _, Self, _>(
+ Empty::new(),
+ &theme.welcome.checkbox,
+ rpc_trace_enabled,
+ id.0,
+ cx,
+ move |this, enabled, cx| {
+ this.toggle_logging_for_server(id, enabled, cx);
+ },
+ )
+ .flex_float(),
+ )
.align_children_center()
.contained()
- .with_style(item_style.container)
+ .with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, view, cx| {
@@ -0,0 +1,97 @@
+use super::*;
+use gpui::{serde_json::json, TestAppContext};
+use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig};
+use project::FakeFs;
+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));
+
+ 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: project.read(cx).worktrees(cx).next().unwrap(),
+ rpc_trace_enabled: false,
+ rpc_trace_selected: false,
+ logs_selected: true,
+ }]
+ );
+ assert_eq!(
+ view.editor.as_ref().unwrap().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);
+ });
+}
@@ -44,6 +44,7 @@ pub struct Theme {
pub context_menu: ContextMenu,
pub contacts_popover: ContactsPopover,
pub contact_list: ContactList,
+ pub lsp_log_menu: LspLogMenu,
pub copilot: Copilot,
pub contact_finder: ContactFinder,
pub project_panel: ProjectPanel,
@@ -244,6 +245,15 @@ pub struct ContactFinder {
pub disabled_contact_button: IconButton,
}
+#[derive(Deserialize, Default)]
+pub struct LspLogMenu {
+ #[serde(flatten)]
+ pub container: ContainerStyle,
+ pub header: Interactive<ContainedText>,
+ pub server: ContainedText,
+ pub item: Interactive<ContainedText>,
+}
+
#[derive(Clone, Deserialize, Default)]
pub struct TabBar {
#[serde(flatten)]
@@ -17,6 +17,7 @@ import projectSharedNotification from "./projectSharedNotification"
import tooltip from "./tooltip"
import terminal from "./terminal"
import contactList from "./contactList"
+import lspLogMenu from "./lspLogMenu"
import incomingCallNotification from "./incomingCallNotification"
import { ColorScheme } from "../themes/common/colorScheme"
import feedback from "./feedback"
@@ -45,6 +46,7 @@ export default function app(colorScheme: ColorScheme): Object {
contactsPopover: contactsPopover(colorScheme),
contactFinder: contactFinder(colorScheme),
contactList: contactList(colorScheme),
+ lspLogMenu: lspLogMenu(colorScheme),
search: search(colorScheme),
sharedScreen: sharedScreen(colorScheme),
updateNotification: updateNotification(colorScheme),
@@ -0,0 +1,41 @@
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
+
+export default function contactsPanel(colorScheme: ColorScheme) {
+ let layer = colorScheme.middle
+
+ return {
+ background: background(layer),
+ border: border(layer),
+ shadow: colorScheme.popoverShadow,
+ header: {
+ ...text(layer, "sans", { size: "sm" }),
+ padding: { left: 8, right: 8, top: 2, bottom: 2 },
+ cornerRadius: 6,
+ background: background(layer, "on"),
+ border: border(layer, "on", { overlay: true }),
+ hover: {
+ background: background(layer, "hovered"),
+ ...text(layer, "sans", "hovered", { size: "sm" }),
+ }
+ },
+ server: {
+ ...text(layer, "sans", { size: "sm" }),
+ padding: { left: 8, right: 8, top: 8, bottom: 8 },
+ },
+ item: {
+ ...text(layer, "sans", { size: "sm" }),
+ padding: { left: 18, right: 18, top: 2, bottom: 2 },
+ hover: {
+ background: background(layer, "hovered"),
+ ...text(layer, "sans", "hovered", { size: "sm" }),
+ },
+ active: {
+ background: background(layer, "active"),
+ },
+ activeHover: {
+ background: background(layer, "active"),
+ },
+ },
+ }
+}