Merge remote-tracking branch 'origin/main' into fewer-context-traits

Antonio Scandurra created

Change summary

Cargo.lock                                    |  26 
Cargo.toml                                    |  20 
crates/activity_indicator/Cargo.toml          |   4 
crates/assets/Cargo.toml                      |   3 
crates/auto_update/Cargo.toml                 |  16 
crates/call/Cargo.toml                        |   8 
crates/cli/Cargo.toml                         |   6 
crates/client/Cargo.toml                      |  27 
crates/clock/Cargo.toml                       |   2 
crates/collab/Cargo.toml                      |  32 
crates/collab/src/tests/integration_tests.rs  | 140 +++++
crates/collab_ui/Cargo.toml                   |  13 
crates/command_palette/Cargo.toml             |   6 
crates/command_palette/src/command_palette.rs |  16 
crates/context_menu/Cargo.toml                |   2 
crates/copilot/Cargo.toml                     |  12 
crates/copilot_button/Cargo.toml              |   6 
crates/copilot_button/src/copilot_button.rs   |   2 
crates/db/Cargo.toml                          |  20 
crates/diagnostics/Cargo.toml                 |  14 
crates/editor/Cargo.toml                      |  33 
crates/editor/src/editor.rs                   |  21 
crates/feedback/Cargo.toml                    |  14 
crates/file_finder/Cargo.toml                 |   8 
crates/fs/Cargo.toml                          |  22 
crates/fsevent/Cargo.toml                     |   4 
crates/git/Cargo.toml                         |  16 
crates/go_to_line/Cargo.toml                  |   2 
crates/gpui/Cargo.toml                        |  34 
crates/gpui/src/app/window.rs                 |   5 
crates/install_cli/Cargo.toml                 |   6 
crates/journal/Cargo.toml                     |   4 
crates/language/Cargo.toml                    |  36 
crates/language_selector/Cargo.toml           |   2 
crates/live_kit_client/Cargo.toml             |  34 
crates/live_kit_server/Cargo.toml             |  12 
crates/lsp/Cargo.toml                         |  26 
crates/lsp/src/lsp.rs                         | 122 +++-
crates/lsp_log/Cargo.toml                     |  29 +
crates/lsp_log/src/lsp_log.rs                 | 523 +++++++++++++++++++++
crates/media/Cargo.toml                       |   2 
crates/node_runtime/Cargo.toml                |  14 
crates/outline/Cargo.toml                     |   6 
crates/picker/Cargo.toml                      |   8 
crates/plugin/Cargo.toml                      |   4 
crates/plugin_macros/Cargo.toml               |   4 
crates/plugin_runtime/Cargo.toml              |  10 
crates/project/Cargo.toml                     |  36 
crates/project/src/project.rs                 | 294 +++++++----
crates/project_panel/Cargo.toml               |   6 
crates/project_symbols/Cargo.toml             |  10 
crates/recent_projects/Cargo.toml             |   9 
crates/rope/Cargo.toml                        |   6 
crates/rpc/Cargo.toml                         |  20 
crates/rpc/proto/zed.proto                    |   6 
crates/search/Cargo.toml                      |  20 
crates/search/src/project_search.rs           |  14 
crates/settings/Cargo.toml                    |  16 
crates/snippet/Cargo.toml                     |   4 
crates/sqlez/Cargo.toml                       |  14 
crates/sqlez_macros/Cargo.toml                |   4 
crates/staff_mode/Cargo.toml                  |   2 
crates/sum_tree/Cargo.toml                    |   8 
crates/terminal/Cargo.toml                    |  20 
crates/terminal/src/terminal.rs               | 122 ++--
crates/terminal_view/Cargo.toml               |  20 
crates/terminal_view/src/terminal_element.rs  |  10 
crates/text/Cargo.toml                        |  24 
crates/theme/Cargo.toml                       |  10 
crates/theme/src/ui.rs                        |  34 
crates/theme_selector/Cargo.toml              |   8 
crates/theme_testbench/Cargo.toml             |   2 
crates/util/Cargo.toml                        |  20 
crates/vim/Cargo.toml                         |  14 
crates/welcome/Cargo.toml                     |   6 
crates/welcome/src/welcome.rs                 |  10 
crates/workspace/Cargo.toml                   |  26 
crates/workspace/src/workspace.rs             |  10 
crates/zed/Cargo.toml                         |  43 
crates/zed/src/languages/rust.rs              |   3 
crates/zed/src/zed.rs                         |   7 
81 files changed, 1,579 insertions(+), 655 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -148,9 +148,6 @@ name = "anyhow"
 version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
-dependencies = [
- "backtrace",
-]
 
 [[package]]
 name = "arrayref"
@@ -3612,6 +3609,26 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "lsp_log"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "editor",
+ "futures 0.3.25",
+ "gpui",
+ "language",
+ "lsp",
+ "project",
+ "serde",
+ "settings",
+ "theme",
+ "unindent",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "mach"
 version = "0.3.2"
@@ -7239,7 +7256,7 @@ dependencies = [
 [[package]]
 name = "tree-sitter-json"
 version = "0.20.0"
-source = "git+https://github.com/tree-sitter/tree-sitter-json?rev=137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8#137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8"
+source = "git+https://github.com/tree-sitter/tree-sitter-json?rev=40a81c01a40ac48744e0c8ccabbaba1920441199#40a81c01a40ac48744e0c8ccabbaba1920441199"
 dependencies = [
  "cc",
  "tree-sitter",
@@ -8571,6 +8588,7 @@ dependencies = [
  "libc",
  "log",
  "lsp",
+ "lsp_log",
  "node_runtime",
  "num_cpus",
  "outline",

Cargo.toml 🔗

@@ -35,6 +35,7 @@ members = [
     "crates/live_kit_client",
     "crates/live_kit_server",
     "crates/lsp",
+    "crates/lsp_log",
     "crates/media",
     "crates/menu",
     "crates/node_runtime",
@@ -71,12 +72,27 @@ default-members = ["crates/zed"]
 resolver = "2"
 
 [workspace.dependencies]
+anyhow = { version = "1.0.57" }
+async-trait = { version = "0.1" }
+ctor = { version = "0.1" }
+env_logger = { version = "0.9" }
+futures = { version = "0.3" }
+lazy_static = { version = "1.4.0" }
+log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+ordered-float = { version = "2.1.1" }
+parking_lot = { version = "0.11.1" }
+postage = { version = "0.5", features = ["futures-traits"] }
+rand = { version = "0.8.5" }
+regex = { version = "1.5" }
 serde = { version = "1.0", features = ["derive", "rc"] }
 serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
 serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
-rand = { version = "0.8" }
-postage = { version = "0.5", features = ["futures-traits"] }
 smallvec = { version = "1.6", features = ["union"] }
+smol = { version = "1.2" }
+tempdir = { version = "0.3.7" }
+thiserror = { version = "1.0.29" }
+time = { version = "0.3", features = ["serde", "serde-well-known"] }
+unindent = { version = "0.1.7" }
 
 [patch.crates-io]
 tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }

crates/activity_indicator/Cargo.toml 🔗

@@ -17,5 +17,5 @@ project = { path = "../project" }
 settings = { path = "../settings" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-futures = "0.3"
-smallvec = { workspace = true }
+futures.workspace = true
+smallvec.workspace = true

crates/assets/Cargo.toml 🔗

@@ -10,6 +10,5 @@ doctest = false
 
 [dependencies]
 gpui = { path = "../gpui" }
-anyhow = "1.0.38"
+anyhow.workspace = true
 rust-embed = { version = "6.3", features = ["include-exclude"] }
-

crates/auto_update/Cargo.toml 🔗

@@ -18,12 +18,12 @@ settings = { path = "../settings" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
 util = { path = "../util" }
-anyhow = "1.0.38"
+anyhow.workspace = true
 isahc = "1.7"
-lazy_static = "1.4"
-log = "0.4"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
-smol = "1.2.5"
-tempdir = "0.3.7"
+lazy_static.workspace = true
+log.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smol.workspace = true
+tempdir.workspace = true

crates/call/Cargo.toml 🔗

@@ -22,7 +22,7 @@ test-support = [
 client = { path = "../client" }
 collections = { path = "../collections" }
 gpui = { path = "../gpui" }
-log = "0.4"
+log.workspace = true
 live_kit_client = { path = "../live_kit_client" }
 fs = { path = "../fs" }
 language = { path = "../language" }
@@ -31,10 +31,10 @@ project = { path = "../project" }
 settings = { path = "../settings" }
 util = { path = "../util" }
 
-anyhow = "1.0.38"
+anyhow.workspace = true
 async-broadcast = "0.4"
-futures = "0.3"
-postage = { workspace = true }
+futures.workspace = true
+postage.workspace = true
 
 [dev-dependencies]
 client = { path = "../client", features = ["test-support"] }

crates/cli/Cargo.toml 🔗

@@ -13,12 +13,12 @@ name = "cli"
 path = "src/main.rs"
 
 [dependencies]
-anyhow = "1.0"
+anyhow.workspace = true
 clap = { version = "3.1", features = ["derive"] }
 dirs = "3.0"
 ipc-channel = "0.16"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.9"

crates/client/Cargo.toml 🔗

@@ -17,27 +17,28 @@ db = { path = "../db" }
 gpui = { path = "../gpui" }
 util = { path = "../util" }
 rpc = { path = "../rpc" }
+settings = { path = "../settings" }
 staff_mode = { path = "../staff_mode" }
 sum_tree = { path = "../sum_tree" }
-anyhow = "1.0.38"
+
+anyhow.workspace = true
 async-recursion = "0.3"
 async-tungstenite = { version = "0.16", features = ["async-tls"] }
-futures = "0.3"
+futures.workspace = true
 image = "0.23"
-lazy_static = "1.4.0"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-postage = { workspace = true }
-rand = "0.8.3"
-smol = "1.2.5"
-thiserror = "1.0.29"
-time = { version = "0.3", features = ["serde", "serde-well-known"] }
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rand.workspace = true
+smol.workspace = true
+thiserror.workspace = true
+time.workspace = true
 tiny_http = "0.8"
 uuid = { version = "1.1.2", features = ["v4"] }
 url = "2.2"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-settings = { path = "../settings" }
+serde.workspace = true
+serde_derive.workspace = true
 tempfile = "3"
 
 [dev-dependencies]

crates/clock/Cargo.toml 🔗

@@ -9,4 +9,4 @@ path = "src/clock.rs"
 doctest = false
 
 [dependencies]
-smallvec = { workspace = true }
+smallvec.workspace = true

crates/collab/Cargo.toml 🔗

@@ -19,7 +19,7 @@ live_kit_server = { path = "../live_kit_server" }
 rpc = { path = "../rpc" }
 util = { path = "../util" }
 
-anyhow = "1.0.40"
+anyhow.workspace = true
 async-tungstenite = "0.16"
 axum = { version = "0.5", features = ["json", "headers", "ws"] }
 axum-extra = { version = "0.3", features = ["erased-json"] }
@@ -27,26 +27,26 @@ base64 = "0.13"
 clap = { version = "3.1", features = ["derive"], optional = true }
 dashmap = "5.4"
 envy = "0.4.2"
-futures = "0.3"
+futures.workspace = true
 hyper = "0.14"
-lazy_static = "1.4"
+lazy_static.workspace = true
 lipsum = { version = "0.8", optional = true }
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 nanoid = "0.4"
-parking_lot = "0.11.1"
+parking_lot.workspace = true
 prometheus = "0.13"
-rand = "0.8"
+rand.workspace = true
 reqwest = { version = "0.11", features = ["json"], optional = true }
 scrypt = "0.7"
 # Remove fork dependency when a version with https://github.com/SeaQL/sea-orm/pull/1283 is released.
 sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls"] }
 sea-query = "0.27"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 sha-1 = "0.9"
 sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
-time = { version = "0.3", features = ["serde", "serde-well-known"] }
+time.workspace = true
 tokio = { version = "1", features = ["full"] }
 tokio-tungstenite = "0.17"
 tonic = "0.6"
@@ -55,7 +55,6 @@ toml = "0.5.8"
 tracing = "0.1.34"
 tracing-log = "0.1.3"
 tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
-indoc = "1.0.4"
 
 [dev-dependencies]
 collections = { path = "../collections", features = ["test-support"] }
@@ -75,14 +74,15 @@ settings = { path = "../settings", features = ["test-support"] }
 theme = { path = "../theme" }
 workspace = { path = "../workspace", features = ["test-support"] }
 
-ctor = "0.1"
-env_logger = "0.9"
+ctor.workspace = true
+env_logger.workspace = true
+indoc = "1.0.4"
 util = { path = "../util" }
-lazy_static = "1.4"
+lazy_static.workspace = true
 sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-sqlite"] }
-serde_json = { workspace = true }
+serde_json.workspace = true
 sqlx = { version = "0.6", features = ["sqlite"] }
-unindent = "0.1"
+unindent.workspace = true
 
 [features]
 seed-support = ["clap", "lipsum", "reqwest"]

crates/collab/src/tests/integration_tests.rs 🔗

@@ -32,7 +32,10 @@ use std::{
     env, future, mem,
     path::{Path, PathBuf},
     rc::Rc,
-    sync::Arc,
+    sync::{
+        atomic::{AtomicBool, Ordering::SeqCst},
+        Arc,
+    },
 };
 use unindent::Unindent as _;
 use workspace::{
@@ -3636,6 +3639,141 @@ async fn test_collaborating_with_diagnostics(
     });
 }
 
+#[gpui::test(iterations = 10)]
+async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
+    deterministic: Arc<Deterministic>,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    deterministic.forbid_parking();
+    let mut server = TestServer::start(&deterministic).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+
+    // Set up a fake language server.
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
+            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry.add(Arc::new(language));
+
+    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
+    client_a
+        .fs
+        .insert_tree(
+            "/test",
+            json!({
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = 2;",
+                "three.rs": "const THREE: usize = 3;",
+                "four.rs": "const FOUR: usize = 3;",
+                "five.rs": "const FIVE: usize = 3;",
+            }),
+        )
+        .await;
+
+    let (project_a, worktree_id) = client_a.build_local_project("/test", cx_a).await;
+
+    // Share a project as client A
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+
+    // Join the project as client B and open all three files.
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
+        project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
+    }))
+    .await
+    .unwrap();
+
+    // Simulate a language server reporting errors for a file.
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    fake_language_server
+        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
+            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
+        })
+        .await
+        .unwrap();
+    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
+        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
+            lsp::WorkDoneProgressBegin {
+                title: "Progress Began".into(),
+                ..Default::default()
+            },
+        )),
+    });
+    for file_name in file_names {
+        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
+            lsp::PublishDiagnosticsParams {
+                uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
+                version: None,
+                diagnostics: vec![lsp::Diagnostic {
+                    severity: Some(lsp::DiagnosticSeverity::WARNING),
+                    source: Some("the-disk-based-diagnostics-source".into()),
+                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+                    message: "message one".to_string(),
+                    ..Default::default()
+                }],
+            },
+        );
+    }
+    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
+        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
+            lsp::WorkDoneProgressEnd { message: None },
+        )),
+    });
+
+    // When the "disk base diagnostics finished" message is received, the buffers'
+    // diagnostics are expected to be present.
+    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
+    project_b.update(cx_b, {
+        let project_b = project_b.clone();
+        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
+        move |_, cx| {
+            cx.subscribe(&project_b, move |_, _, event, cx| {
+                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
+                    disk_based_diagnostics_finished.store(true, SeqCst);
+                    for buffer in &guest_buffers {
+                        assert_eq!(
+                            buffer
+                                .read(cx)
+                                .snapshot()
+                                .diagnostics_in_range::<_, usize>(0..5, false)
+                                .count(),
+                            1,
+                            "expected a diagnostic for buffer {:?}",
+                            buffer.read(cx).file().unwrap().path(),
+                        );
+                    }
+                }
+            })
+            .detach();
+        }
+    });
+
+    deterministic.run_until_parked();
+    assert!(disk_based_diagnostics_finished.load(SeqCst));
+}
+
 #[gpui::test(iterations = 10)]
 async fn test_collaborating_with_completion(
     deterministic: Arc<Deterministic>,

crates/collab_ui/Cargo.toml 🔗

@@ -39,12 +39,13 @@ settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-anyhow = "1.0"
-futures = "0.3"
-log = "0.4"
-postage = { workspace = true }
-serde = { workspace = true }
-serde_derive = { workspace = true }
+
+anyhow.workspace = true
+futures.workspace = true
+log.workspace = true
+postage.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
 
 [dev-dependencies]
 call = { path = "../call", features = ["test-support"] }

crates/command_palette/Cargo.toml 🔗

@@ -24,7 +24,7 @@ workspace = { path = "../workspace" }
 gpui = { path = "../gpui", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
-serde_json = { workspace = true }
+serde_json.workspace = true
 workspace = { path = "../workspace", features = ["test-support"] }
-ctor = "0.1"
-env_logger = "0.9"
+ctor.workspace = true
+env_logger.workspace = true

crates/command_palette/src/command_palette.rs 🔗

@@ -2,7 +2,7 @@ use collections::CommandPaletteFilter;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
-    ViewContext,
+    ViewContext, WindowContext,
 };
 use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
@@ -45,15 +45,19 @@ fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Wo
     let workspace = cx.handle();
     let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id());
 
-    cx.defer(move |workspace, cx| {
-        workspace.toggle_modal(cx, |_, cx| {
-            cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id, cx), cx))
-        });
+    cx.window_context().defer(move |cx| {
+        // Build the delegate before the workspace is put on the stack so we can find it when
+        // computing the actions. We should really not allow available_actions to be called
+        // if it's not reliable however.
+        let delegate = CommandPaletteDelegate::new(focused_view_id, cx);
+        workspace.update(cx, |workspace, cx| {
+            workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| Picker::new(delegate, cx)));
+        })
     });
 }
 
 impl CommandPaletteDelegate {
-    pub fn new(focused_view_id: usize, cx: &mut ViewContext<Picker<Self>>) -> Self {
+    pub fn new(focused_view_id: usize, cx: &mut WindowContext) -> Self {
         let actions = cx
             .available_actions(focused_view_id)
             .filter_map(|(name, action, bindings)| {

crates/context_menu/Cargo.toml 🔗

@@ -13,4 +13,4 @@ gpui = { path = "../gpui" }
 menu = { path = "../menu" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
-smallvec = { workspace = true }
+smallvec.workspace = true

crates/copilot/Cargo.toml 🔗

@@ -30,12 +30,12 @@ node_runtime = { path = "../node_runtime"}
 util = { path = "../util" }
 async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
 async-tar = "0.4.2"
-anyhow = "1.0"
-log = "0.4"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-smol = "1.2.5"
-futures = "0.3"
+anyhow.workspace = true
+log.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+smol.workspace = true
+futures.workspace = true
 
 [dev-dependencies]
 clock = { path = "../clock" }

crates/copilot_button/Cargo.toml 🔗

@@ -17,6 +17,6 @@ settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-anyhow = "1.0"
-smol = "1.2.5"
-futures = "0.3"
+anyhow.workspace = true
+smol.workspace = true
+futures.workspace = true

crates/copilot_button/src/copilot_button.rs 🔗

@@ -272,7 +272,7 @@ impl CopilotButton {
         let mut menu_options = Vec::with_capacity(2);
 
         menu_options.push(ContextMenuItem::item("Sign In", InitiateSignIn));
-        menu_options.push(ContextMenuItem::item("Hide Copilot", HideCopilot));
+        menu_options.push(ContextMenuItem::item("Disable Copilot", HideCopilot));
 
         self.popup_menu.update(cx, |menu, cx| {
             menu.show(

crates/db/Cargo.toml 🔗

@@ -17,17 +17,17 @@ gpui = { path = "../gpui" }
 sqlez = { path = "../sqlez" }
 sqlez_macros = { path = "../sqlez_macros" }
 util = { path = "../util" }
-anyhow = "1.0.57"
+anyhow.workspace = true
 indoc = "1.0.4"
-async-trait = "0.1"
-lazy_static = "1.4.0"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-smol = "1.2"
+async-trait.workspace = true
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+smol.workspace = true
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
-env_logger = "0.9.1"
-tempdir = { version = "0.3.7" }
+env_logger.workspace = true
+tempdir.workspace = true

crates/diagnostics/Cargo.toml 🔗

@@ -9,26 +9,28 @@ path = "src/diagnostics.rs"
 doctest = false
 
 [dependencies]
-anyhow = "1.0"
-smallvec = { workspace = true }
 collections = { path = "../collections" }
 editor = { path = "../editor" }
+gpui = { path = "../gpui" }
 language = { path = "../language" }
 lsp = { path = "../lsp" }
-gpui = { path = "../gpui" }
 project = { path = "../project" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-postage = { workspace = true }
+
+anyhow.workspace = true
+smallvec.workspace = true
+postage.workspace = true
 
 [dev-dependencies]
-unindent = "0.1"
 client = { path = "../client", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }
 lsp = { path = "../lsp", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
-serde_json = { workspace = true }
+
+serde_json.workspace = true
+unindent.workspace = true

crates/editor/Cargo.toml 🔗

@@ -46,20 +46,20 @@ sqlez = { path = "../sqlez" }
 workspace = { path = "../workspace" }
 
 aho-corasick = "0.7"
-anyhow = "1.0"
-futures = "0.3"
+anyhow.workspace = true
+futures.workspace = true
 indoc = "1.0.4"
 itertools = "0.10"
-lazy_static = "1.4"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-ordered-float = "2.1.1"
-parking_lot = "0.11"
-postage = { workspace = true }
-rand = { version = "0.8.3", optional = true }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-smallvec = { workspace = true }
-smol = "1.2"
+lazy_static.workspace = true
+log.workspace = true
+ordered-float.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rand = { workspace = true, optional = true }
+serde.workspace = true
+serde_derive.workspace = true
+smallvec.workspace = true
+smol.workspace = true
 tree-sitter-rust = { version = "*", optional = true }
 tree-sitter-html = { version = "*", optional = true }
 tree-sitter-javascript = { version = "*", optional = true }
@@ -75,10 +75,11 @@ util = { path = "../util", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
-ctor = "0.1"
-env_logger = "0.9"
-rand = "0.8"
-unindent = "0.1.7"
+
+ctor.workspace = true
+env_logger.workspace = true
+rand.workspace = true
+unindent.workspace = true
 tree-sitter = "0.20"
 tree-sitter-rust = "0.20"
 tree-sitter-html = "0.19"

crates/editor/src/editor.rs 🔗

@@ -511,6 +511,7 @@ pub struct Editor {
     workspace_id: Option<WorkspaceId>,
     keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
     input_enabled: bool,
+    read_only: bool,
     leader_replica_id: Option<u16>,
     remote_id: Option<ViewId>,
     hover_state: HoverState,
@@ -1283,6 +1284,7 @@ impl Editor {
             workspace_id: None,
             keymap_context_layers: Default::default(),
             input_enabled: true,
+            read_only: false,
             leader_replica_id: None,
             remote_id: None,
             hover_state: Default::default(),
@@ -1425,6 +1427,10 @@ impl Editor {
         self.input_enabled = input_enabled;
     }
 
+    pub fn set_read_only(&mut self, read_only: bool) {
+        self.read_only = read_only;
+    }
+
     fn selections_did_change(
         &mut self,
         local: bool,
@@ -1533,6 +1539,10 @@ impl Editor {
         S: ToOffset,
         T: Into<Arc<str>>,
     {
+        if self.read_only {
+            return;
+        }
+
         self.buffer
             .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
     }
@@ -1543,6 +1553,10 @@ impl Editor {
         S: ToOffset,
         T: Into<Arc<str>>,
     {
+        if self.read_only {
+            return;
+        }
+
         self.buffer.update(cx, |buffer, cx| {
             buffer.edit(edits, Some(AutoindentMode::EachLine), cx)
         });
@@ -1897,6 +1911,9 @@ impl Editor {
     pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
         let text: Arc<str> = text.into();
 
+        if self.read_only {
+            return;
+        }
         if !self.input_enabled {
             cx.emit(Event::InputIgnored { text });
             return;
@@ -2282,6 +2299,10 @@ impl Editor {
         autoindent_mode: Option<AutoindentMode>,
         cx: &mut ViewContext<Self>,
     ) {
+        if self.read_only {
+            return;
+        }
+
         let text: Arc<str> = text.into();
         self.transact(cx, |this, cx| {
             let old_selections = this.selections.all_adjusted(cx);

crates/feedback/Cargo.toml 🔗

@@ -11,21 +11,21 @@ path = "src/feedback.rs"
 test-support = []
 
 [dependencies]
-anyhow = "1.0.38"
+anyhow.workspace = true
 client = { path = "../client" }
 editor = { path = "../editor" }
 language = { path = "../language" }
-log = "0.4"
-futures = "0.3"
+log.workspace = true
+futures.workspace = true
 gpui = { path = "../gpui" }
 human_bytes = "0.4.1"
 isahc = "1.7"
-lazy_static = "1.4.0"
-postage = { workspace = true }
+lazy_static.workspace = true
+postage.workspace = true
 project = { path = "../project" }
 search = { path = "../search" }
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 settings = { path = "../settings" }
 sysinfo = "0.27.1"
 theme = { path = "../theme" }

crates/file_finder/Cargo.toml 🔗

@@ -19,11 +19,11 @@ settings = { path = "../settings" }
 util = { path = "../util" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
-postage = { workspace = true }
+postage.workspace = true
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
-serde_json = { workspace = true }
+serde_json.workspace = true
 workspace = { path = "../workspace", features = ["test-support"] }
-ctor = "0.1"
-env_logger = "0.9"
+ctor.workspace = true
+env_logger.workspace = true

crates/fs/Cargo.toml 🔗

@@ -13,20 +13,20 @@ gpui = { path = "../gpui" }
 lsp = { path = "../lsp" }
 rope = { path = "../rope" }
 util = { path = "../util" }
-anyhow = "1.0.57"
-async-trait = "0.1"
-futures = "0.3"
+anyhow.workspace = true
+async-trait.workspace = true
+futures.workspace = true
 tempfile = "3"
 fsevent = { path = "../fsevent" }
-lazy_static = "1.4.0"
-parking_lot = "0.11.1"
-smol = "1.2.5"
-regex = "1.5"
+lazy_static.workspace = true
+parking_lot.workspace = true
+smol.workspace = true
+regex.workspace = true
 git2 = { version = "0.15", default-features = false }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+log.workspace = true
 libc = "0.2"
 
 [features]

crates/fsevent/Cargo.toml 🔗

@@ -12,10 +12,10 @@ doctest = false
 [dependencies]
 bitflags = "1"
 fsevent-sys = "3.0.2"
-parking_lot = "0.11.1"
+parking_lot.workspace = true
 
 [dev-dependencies]
-tempdir = "0.3.7"
+tempdir.workspace = true
 
 [package.metadata.docs.rs]
 targets = ["x86_64-apple-darwin"]

crates/git/Cargo.toml 🔗

@@ -8,22 +8,22 @@ publish = false
 path = "src/git.rs"
 
 [dependencies]
-anyhow = "1.0.38"
+anyhow.workspace = true
 clock = { path = "../clock" }
-lazy_static = "1.4.0"
+lazy_static.workspace = true
 sum_tree = { path = "../sum_tree" }
 text = { path = "../text" }
 collections = { path = "../collections" }
 util = { path = "../util" }
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-smol = "1.2"
-parking_lot = "0.11.1"
-async-trait = "0.1"
-futures = "0.3"
+log.workspace = true
+smol.workspace = true
+parking_lot.workspace = true
+async-trait.workspace = true
+futures.workspace = true
 git2 = { version = "0.15", default-features = false }
 
 [dev-dependencies]
-unindent = "0.1.7"
+unindent.workspace = true
 
 [features]
 test-support = []

crates/go_to_line/Cargo.toml 🔗

@@ -15,4 +15,4 @@ menu = { path = "../menu" }
 settings = { path = "../settings" }
 text = { path = "../text" }
 workspace = { path = "../workspace" }
-postage = { workspace = true }
+postage.workspace = true

crates/gpui/Cargo.toml 🔗

@@ -21,32 +21,32 @@ sum_tree = { path = "../sum_tree" }
 sqlez = { path = "../sqlez" }
 async-task = "4.0.3"
 backtrace = { version = "0.3", optional = true }
-ctor = "0.1"
+ctor.workspace = true
 dhat = { version = "0.3", optional = true }
 env_logger = { version = "0.9", optional = true }
 etagere = "0.2"
-futures = "0.3"
+futures.workspace = true
 image = "0.23"
 itertools = "0.10"
-lazy_static = "1.4.0"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+lazy_static.workspace = true
+log.workspace = true
 num_cpus = "1.13"
-ordered-float = "2.1.1"
+ordered-float.workspace = true
 parking = "2.0.0"
-parking_lot = "0.11.1"
+parking_lot.workspace = true
 pathfinder_color = "0.5"
 pathfinder_geometry = "0.5"
-postage = { workspace = true }
-rand = "0.8.3"
+postage.workspace = true
+rand.workspace = true
 resvg = "0.14"
 schemars = "0.8"
 seahash = "4.1"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
-smallvec = { workspace = true }
-smol = "1.2"
-time = { version = "0.3", features = ["serde", "serde-well-known"] }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+time.workspace = true
 tiny-skia = "0.5"
 usvg = "0.14"
 uuid = { version = "1.1.2", features = ["v4"] }
@@ -60,13 +60,13 @@ cc = "1.0.67"
 backtrace = "0.3"
 collections = { path = "../collections", features = ["test-support"] }
 dhat = "0.3"
-env_logger = "0.9"
+env_logger.workspace = true
 png = "0.16"
 simplelog = "0.9"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 media = { path = "../media" }
-anyhow = "1"
+anyhow.workspace = true
 block = "0.1"
 cocoa = "0.24"
 core-foundation = { version = "0.9.3", features = ["with-uuid"] }
@@ -74,6 +74,6 @@ core-graphics = "0.22.3"
 core-text = "19.2"
 font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" }
 foreign-types = "0.3"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 metal = "0.21.0"
 objc = "0.2"

crates/gpui/src/app/window.rs 🔗

@@ -419,6 +419,11 @@ impl<'a> WindowContext<'a> {
                             .map(|action_type| (action_type, depth)),
                     );
                 }
+            } else {
+                log::error!(
+                    "view {} not found when computing available actions",
+                    view_id
+                );
             }
         }
 

crates/install_cli/Cargo.toml 🔗

@@ -11,8 +11,8 @@ path = "src/install_cli.rs"
 test-support = []
 
 [dependencies]
-smol = "1.2.5"
-anyhow = "1.0.38"
-log = "0.4"
+smol.workspace = true
+anyhow.workspace = true
+log.workspace = true
 gpui = { path = "../gpui" }
 util = { path = "../util" }

crates/journal/Cargo.toml 🔗

@@ -13,9 +13,9 @@ editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-anyhow = "1.0"
+anyhow.workspace = true
 chrono = "0.4"
 dirs = "4.0"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 settings = { path = "../settings" }
 shellexpand = "2.1.0"

crates/language/Cargo.toml 🔗

@@ -36,22 +36,22 @@ sum_tree = { path = "../sum_tree" }
 text = { path = "../text" }
 theme = { path = "../theme" }
 util = { path = "../util" }
-anyhow = "1.0.38"
+anyhow.workspace = true
 async-broadcast = "0.4"
-async-trait = "0.1"
-futures = "0.3"
-lazy_static = "1.4"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-postage = { workspace = true }
-rand = { version = "0.8.3", optional = true }
-regex = "1.5"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+async-trait.workspace = true
+futures.workspace = true
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rand = { workspace = true, optional = true }
+regex.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 similar = "1.3"
-smallvec = { workspace = true }
-smol = "1.2"
+smallvec.workspace = true
+smol.workspace = true
 tree-sitter = "0.20"
 tree-sitter-rust = { version = "*", optional = true }
 tree-sitter-typescript = { version = "*", optional = true }
@@ -65,10 +65,10 @@ lsp = { path = "../lsp", features = ["test-support"] }
 text = { path = "../text", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
-ctor = "0.1"
-env_logger = "0.9"
+ctor.workspace = true
+env_logger.workspace = true
 indoc = "1.0.4"
-rand = "0.8.3"
+rand.workspace = true
 tree-sitter-embedded-template = "*"
 tree-sitter-html = "*"
 tree-sitter-javascript = "*"
@@ -78,4 +78,4 @@ tree-sitter-rust = "*"
 tree-sitter-python = "*"
 tree-sitter-typescript = "*"
 tree-sitter-ruby = "*"
-unindent = "0.1.7"
+unindent.workspace = true

crates/language_selector/Cargo.toml 🔗

@@ -19,4 +19,4 @@ theme = { path = "../theme" }
 settings = { path = "../settings" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-anyhow = "1.0"
+anyhow.workspace = true

crates/live_kit_client/Cargo.toml 🔗

@@ -28,17 +28,17 @@ gpui = { path = "../gpui", optional = true }
 live_kit_server = { path = "../live_kit_server", optional = true }
 media = { path = "../media" }
 
-anyhow = "1.0.38"
+anyhow.workspace = true
 async-broadcast = "0.4"
 core-foundation = "0.9.3"
 core-graphics = "0.22.3"
-futures = "0.3"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-postage = { workspace = true }
+futures.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
 
-async-trait = { version = "0.1", optional = true }
-lazy_static = { version = "1.4", optional = true }
+async-trait = { workspace = true, optional = true }
+lazy_static = { workspace = true, optional = true }
 nanoid = { version ="0.4", optional = true}
 
 [dev-dependencies]
@@ -47,8 +47,8 @@ gpui = { path = "../gpui", features = ["test-support"] }
 live_kit_server = { path = "../live_kit_server" }
 media = { path = "../media" }
 
-anyhow = "1.0.38"
-async-trait = "0.1"
+anyhow.workspace = true
+async-trait.workspace = true
 block = "0.1"
 bytes = "1.2"
 byteorder = "1.4"
@@ -56,18 +56,18 @@ cocoa = "0.24"
 core-foundation = "0.9.3"
 core-graphics = "0.22.3"
 foreign-types = "0.3"
-futures = "0.3"
+futures.workspace = true
 hmac = "0.12"
 jwt = "0.16"
-lazy_static = "1.4"
+lazy_static.workspace = true
 objc = "0.2"
-parking_lot = "0.11.1"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+parking_lot.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
 sha2 = "0.10"
 simplelog = "0.9"
 
 [build-dependencies]
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true

crates/live_kit_server/Cargo.toml 🔗

@@ -10,17 +10,17 @@ path = "src/live_kit_server.rs"
 doctest = false
 
 [dependencies]
-anyhow = "1.0.38"
-async-trait = "0.1"
-futures = "0.3"
+anyhow.workspace = true
+async-trait.workspace = true
+futures.workspace = true
 hmac = "0.12"
-log = "0.4"
+log.workspace = true
 jwt = "0.16"
 prost = "0.8"
 prost-types = "0.8"
 reqwest = "0.11"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 sha2 = "0.10"
 
 [build-dependencies]

crates/lsp/Cargo.toml 🔗

@@ -15,22 +15,24 @@ test-support = ["async-pipe"]
 collections = { path = "../collections" }
 gpui = { path = "../gpui" }
 util = { path = "../util" }
-anyhow = "1.0"
+
+anyhow.workspace = true
 async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
-futures = "0.3"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+futures.workspace = true
+log.workspace = true
 lsp-types = "0.91"
-parking_lot = "0.11"
-postage = { workspace = true }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
-smol = "1.2"
+parking_lot.workspace = true
+postage.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smol.workspace = true
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
+
 async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
-ctor = "0.1"
-env_logger = "0.9"
-unindent = "0.1.7"
+ctor.workspace = true
+env_logger.workspace = true
+unindent.workspace = true

crates/lsp/src/lsp.rs 🔗

@@ -20,10 +20,10 @@ use std::{
     future::Future,
     io::Write,
     path::PathBuf,
-    str::FromStr,
+    str::{self, FromStr as _},
     sync::{
         atomic::{AtomicUsize, Ordering::SeqCst},
-        Arc,
+        Arc, Weak,
     },
 };
 use std::{path::Path, process::Stdio};
@@ -34,16 +34,18 @@ const CONTENT_LEN_HEADER: &str = "Content-Length: ";
 
 type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
 type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
+type IoHandler = Box<dyn Send + FnMut(bool, &str)>;
 
 pub struct LanguageServer {
     server_id: LanguageServerId,
     next_id: AtomicUsize,
-    outbound_tx: channel::Sender<Vec<u8>>,
+    outbound_tx: channel::Sender<String>,
     name: String,
     capabilities: ServerCapabilities,
     code_action_kinds: Option<Vec<CodeActionKind>>,
     notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
     response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+    io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
     executor: Arc<executor::Background>,
     #[allow(clippy::type_complexity)]
     io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
@@ -56,9 +58,15 @@ pub struct LanguageServer {
 #[repr(transparent)]
 pub struct LanguageServerId(pub usize);
 
-pub struct Subscription {
-    method: &'static str,
-    notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
+pub enum Subscription {
+    Notification {
+        method: &'static str,
+        notification_handlers: Option<Arc<Mutex<HashMap<&'static str, NotificationHandler>>>>,
+    },
+    Io {
+        id: usize,
+        io_handlers: Option<Weak<Mutex<HashMap<usize, IoHandler>>>>,
+    },
 }
 
 #[derive(Serialize, Deserialize)]
@@ -177,33 +185,40 @@ impl LanguageServer {
         Stdout: AsyncRead + Unpin + Send + 'static,
         F: FnMut(AnyNotification) + 'static + Send,
     {
-        let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
+        let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
+        let (output_done_tx, output_done_rx) = barrier::channel();
         let notification_handlers =
             Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
         let response_handlers =
             Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
+        let io_handlers = Arc::new(Mutex::new(HashMap::default()));
         let input_task = cx.spawn(|cx| {
-            let notification_handlers = notification_handlers.clone();
-            let response_handlers = response_handlers.clone();
             Self::handle_input(
                 stdout,
                 on_unhandled_notification,
-                notification_handlers,
-                response_handlers,
+                notification_handlers.clone(),
+                response_handlers.clone(),
+                io_handlers.clone(),
                 cx,
             )
             .log_err()
         });
-        let (output_done_tx, output_done_rx) = barrier::channel();
         let output_task = cx.background().spawn({
-            let response_handlers = response_handlers.clone();
-            Self::handle_output(stdin, outbound_rx, output_done_tx, response_handlers).log_err()
+            Self::handle_output(
+                stdin,
+                outbound_rx,
+                output_done_tx,
+                response_handlers.clone(),
+                io_handlers.clone(),
+            )
+            .log_err()
         });
 
         Self {
             server_id,
             notification_handlers,
             response_handlers,
+            io_handlers,
             name: Default::default(),
             capabilities: Default::default(),
             code_action_kinds,
@@ -226,6 +241,7 @@ impl LanguageServer {
         mut on_unhandled_notification: F,
         notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
         response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+        io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
         cx: AsyncAppContext,
     ) -> anyhow::Result<()>
     where
@@ -252,7 +268,13 @@ impl LanguageServer {
 
             buffer.resize(message_len, 0);
             stdout.read_exact(&mut buffer).await?;
-            log::trace!("incoming message:{}", String::from_utf8_lossy(&buffer));
+
+            if let Ok(message) = str::from_utf8(&buffer) {
+                log::trace!("incoming message:{}", message);
+                for handler in io_handlers.lock().values_mut() {
+                    handler(true, message);
+                }
+            }
 
             if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
                 if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
@@ -291,9 +313,10 @@ impl LanguageServer {
 
     async fn handle_output<Stdin>(
         stdin: Stdin,
-        outbound_rx: channel::Receiver<Vec<u8>>,
+        outbound_rx: channel::Receiver<String>,
         output_done_tx: barrier::Sender,
         response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
+        io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
     ) -> anyhow::Result<()>
     where
         Stdin: AsyncWrite + Unpin + Send + 'static,
@@ -307,13 +330,17 @@ impl LanguageServer {
         });
         let mut content_len_buffer = Vec::new();
         while let Ok(message) = outbound_rx.recv().await {
-            log::trace!("outgoing message:{}", String::from_utf8_lossy(&message));
+            log::trace!("outgoing message:{}", message);
+            for handler in io_handlers.lock().values_mut() {
+                handler(false, &message);
+            }
+
             content_len_buffer.clear();
             write!(content_len_buffer, "{}", message.len()).unwrap();
             stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
             stdin.write_all(&content_len_buffer).await?;
             stdin.write_all("\r\n\r\n".as_bytes()).await?;
-            stdin.write_all(&message).await?;
+            stdin.write_all(message.as_bytes()).await?;
             stdin.flush().await?;
         }
         drop(output_done_tx);
@@ -464,6 +491,19 @@ impl LanguageServer {
         self.on_custom_request(T::METHOD, f)
     }
 
+    #[must_use]
+    pub fn on_io<F>(&self, f: F) -> Subscription
+    where
+        F: 'static + Send + FnMut(bool, &str),
+    {
+        let id = self.next_id.fetch_add(1, SeqCst);
+        self.io_handlers.lock().insert(id, Box::new(f));
+        Subscription::Io {
+            id,
+            io_handlers: Some(Arc::downgrade(&self.io_handlers)),
+        }
+    }
+
     pub fn remove_request_handler<T: request::Request>(&self) {
         self.notification_handlers.lock().remove(T::METHOD);
     }
@@ -490,9 +530,9 @@ impl LanguageServer {
             prev_handler.is_none(),
             "registered multiple handlers for the same LSP method"
         );
-        Subscription {
+        Subscription::Notification {
             method,
-            notification_handlers: self.notification_handlers.clone(),
+            notification_handlers: Some(self.notification_handlers.clone()),
         }
     }
 
@@ -537,7 +577,7 @@ impl LanguageServer {
                                             },
                                         };
                                         if let Some(response) =
-                                            serde_json::to_vec(&response).log_err()
+                                            serde_json::to_string(&response).log_err()
                                         {
                                             outbound_tx.try_send(response).ok();
                                         }
@@ -560,7 +600,7 @@ impl LanguageServer {
                                     message: error.to_string(),
                                 }),
                             };
-                            if let Some(response) = serde_json::to_vec(&response).log_err() {
+                            if let Some(response) = serde_json::to_string(&response).log_err() {
                                 outbound_tx.try_send(response).ok();
                             }
                         }
@@ -572,9 +612,9 @@ impl LanguageServer {
             prev_handler.is_none(),
             "registered multiple handlers for the same LSP method"
         );
-        Subscription {
+        Subscription::Notification {
             method,
-            notification_handlers: self.notification_handlers.clone(),
+            notification_handlers: Some(self.notification_handlers.clone()),
         }
     }
 
@@ -612,14 +652,14 @@ impl LanguageServer {
     fn request_internal<T: request::Request>(
         next_id: &AtomicUsize,
         response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
-        outbound_tx: &channel::Sender<Vec<u8>>,
+        outbound_tx: &channel::Sender<String>,
         params: T::Params,
     ) -> impl 'static + Future<Output = Result<T::Result>>
     where
         T::Result: 'static + Send,
     {
         let id = next_id.fetch_add(1, SeqCst);
-        let message = serde_json::to_vec(&Request {
+        let message = serde_json::to_string(&Request {
             jsonrpc: JSON_RPC_VERSION,
             id,
             method: T::METHOD,
@@ -662,10 +702,10 @@ impl LanguageServer {
     }
 
     fn notify_internal<T: notification::Notification>(
-        outbound_tx: &channel::Sender<Vec<u8>>,
+        outbound_tx: &channel::Sender<String>,
         params: T::Params,
     ) -> Result<()> {
-        let message = serde_json::to_vec(&Notification {
+        let message = serde_json::to_string(&Notification {
             jsonrpc: JSON_RPC_VERSION,
             method: T::METHOD,
             params,
@@ -685,8 +725,14 @@ impl Drop for LanguageServer {
 }
 
 impl Subscription {
-    pub fn detach(mut self) {
-        self.method = "";
+    pub fn detach(&mut self) {
+        match self {
+            Subscription::Notification {
+                notification_handlers,
+                ..
+            } => *notification_handlers = None,
+            Subscription::Io { io_handlers, .. } => *io_handlers = None,
+        }
     }
 }
 
@@ -698,7 +744,21 @@ impl fmt::Display for LanguageServerId {
 
 impl Drop for Subscription {
     fn drop(&mut self) {
-        self.notification_handlers.lock().remove(self.method);
+        match self {
+            Subscription::Notification {
+                method,
+                notification_handlers,
+            } => {
+                if let Some(handlers) = notification_handlers {
+                    handlers.lock().remove(method);
+                }
+            }
+            Subscription::Io { id, io_handlers } => {
+                if let Some(io_handlers) = io_handlers.as_ref().and_then(|h| h.upgrade()) {
+                    io_handlers.lock().remove(id);
+                }
+            }
+        }
     }
 }
 

crates/lsp_log/Cargo.toml 🔗

@@ -0,0 +1,29 @@
+[package]
+name = "lsp_log"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/lsp_log.rs"
+doctest = false
+
+[dependencies]
+collections = { path = "../collections" }
+editor = { path = "../editor" }
+settings = { path = "../settings" }
+theme = { path = "../theme" }
+language = { path = "../language" }
+project = { path = "../project" }
+workspace = { path = "../workspace" }
+gpui = { path = "../gpui" }
+util = { path = "../util" }
+lsp = { path = "../lsp" }
+futures.workspace = true
+serde.workspace = true
+anyhow.workspace = true
+
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }
+util = { path = "../util", features = ["test-support"] }
+unindent.workspace = true

crates/lsp_log/src/lsp_log.rs 🔗

@@ -0,0 +1,523 @@
+use collections::{hash_map, HashMap};
+use editor::Editor;
+use futures::{channel::mpsc, StreamExt};
+use gpui::{
+    actions,
+    elements::{
+        AnchorCorner, ChildView, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
+        ParentElement, Stack,
+    },
+    platform::{CursorStyle, MouseButton},
+    AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, View, ViewContext,
+    ViewHandle, WeakModelHandle,
+};
+use language::{Buffer, LanguageServerId, LanguageServerName};
+use project::{Project, WorktreeId};
+use settings::Settings;
+use std::{borrow::Cow, sync::Arc};
+use theme::{ui, Theme};
+use workspace::{
+    item::{Item, ItemHandle},
+    ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+const SEND_LINE: &str = "// Send:\n";
+const RECEIVE_LINE: &str = "// Receive:\n";
+
+struct LogStore {
+    projects: HashMap<WeakModelHandle<Project>, LogStoreProject>,
+    io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, bool, String)>,
+}
+
+struct LogStoreProject {
+    servers: HashMap<LanguageServerId, LogStoreLanguageServer>,
+    _subscription: gpui::Subscription,
+}
+
+struct LogStoreLanguageServer {
+    buffer: ModelHandle<Buffer>,
+    last_message_kind: Option<MessageKind>,
+    _subscription: lsp::Subscription,
+}
+
+pub struct LspLogView {
+    log_store: ModelHandle<LogStore>,
+    current_server_id: Option<LanguageServerId>,
+    editor: Option<ViewHandle<Editor>>,
+    project: ModelHandle<Project>,
+}
+
+pub struct LspLogToolbarItemView {
+    log_view: Option<ViewHandle<LspLogView>>,
+    menu_open: bool,
+    project: ModelHandle<Project>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+enum MessageKind {
+    Send,
+    Receive,
+}
+
+actions!(log, [OpenLanguageServerLogs]);
+
+pub fn init(cx: &mut AppContext) {
+    let log_set = cx.add_model(|cx| LogStore::new(cx));
+
+    cx.add_action(
+        move |workspace: &mut Workspace, _: &OpenLanguageServerLogs, cx: _| {
+            let project = workspace.project().read(cx);
+            if project.is_local() {
+                workspace.add_item(
+                    Box::new(cx.add_view(|cx| {
+                        LspLogView::new(workspace.project().clone(), log_set.clone(), cx)
+                    })),
+                    cx,
+                );
+            }
+        },
+    );
+}
+
+impl LogStore {
+    fn new(cx: &mut ModelContext<Self>) -> Self {
+        let (io_tx, mut io_rx) = mpsc::unbounded();
+        let this = Self {
+            projects: HashMap::default(),
+            io_tx,
+        };
+        cx.spawn_weak(|this, mut cx| async move {
+            while let Some((project, server_id, is_output, mut message)) = io_rx.next().await {
+                if let Some(this) = this.upgrade(&cx) {
+                    this.update(&mut cx, |this, cx| {
+                        message.push('\n');
+                        this.on_io(project, server_id, is_output, &message, cx);
+                    });
+                }
+            }
+            anyhow::Ok(())
+        })
+        .detach();
+        this
+    }
+
+    pub fn has_enabled_logs_for_language_server(
+        &self,
+        project: &ModelHandle<Project>,
+        server_id: LanguageServerId,
+    ) -> bool {
+        self.projects
+            .get(&project.downgrade())
+            .map_or(false, |store| store.servers.contains_key(&server_id))
+    }
+
+    pub fn enable_logs_for_language_server(
+        &mut self,
+        project: &ModelHandle<Project>,
+        server_id: LanguageServerId,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<ModelHandle<Buffer>> {
+        let server = project.read(cx).language_server_for_id(server_id)?;
+        let weak_project = project.downgrade();
+        let project_logs = match self.projects.entry(weak_project) {
+            hash_map::Entry::Occupied(entry) => entry.into_mut(),
+            hash_map::Entry::Vacant(entry) => entry.insert(LogStoreProject {
+                servers: HashMap::default(),
+                _subscription: cx.observe_release(&project, move |this, _, _| {
+                    this.projects.remove(&weak_project);
+                }),
+            }),
+        };
+        let server_log_state = project_logs.servers.entry(server_id).or_insert_with(|| {
+            let io_tx = self.io_tx.clone();
+            let language = project.read(cx).languages().language_for_name("JSON");
+            let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
+            cx.spawn_weak({
+                let buffer = buffer.clone();
+                |_, mut cx| async move {
+                    let language = language.await.ok();
+                    buffer.update(&mut cx, |buffer, cx| {
+                        buffer.set_language(language, cx);
+                    });
+                }
+            })
+            .detach();
+
+            let project = project.downgrade();
+            LogStoreLanguageServer {
+                buffer,
+                last_message_kind: None,
+                _subscription: server.on_io(move |is_received, json| {
+                    io_tx
+                        .unbounded_send((project, server_id, is_received, json.to_string()))
+                        .ok();
+                }),
+            }
+        });
+        Some(server_log_state.buffer.clone())
+    }
+
+    pub fn disable_logs_for_language_server(
+        &mut self,
+        project: &ModelHandle<Project>,
+        server_id: LanguageServerId,
+        _: &mut ModelContext<Self>,
+    ) {
+        let project = project.downgrade();
+        if let Some(store) = self.projects.get_mut(&project) {
+            store.servers.remove(&server_id);
+            if store.servers.is_empty() {
+                self.projects.remove(&project);
+            }
+        }
+    }
+
+    fn on_io(
+        &mut self,
+        project: WeakModelHandle<Project>,
+        language_server_id: LanguageServerId,
+        is_received: bool,
+        message: &str,
+        cx: &mut AppContext,
+    ) -> Option<()> {
+        let state = self
+            .projects
+            .get_mut(&project)?
+            .servers
+            .get_mut(&language_server_id)?;
+        state.buffer.update(cx, |buffer, cx| {
+            let kind = if is_received {
+                MessageKind::Receive
+            } else {
+                MessageKind::Send
+            };
+            if state.last_message_kind != Some(kind) {
+                let len = buffer.len();
+                let line = match kind {
+                    MessageKind::Send => SEND_LINE,
+                    MessageKind::Receive => RECEIVE_LINE,
+                };
+                buffer.edit([(len..len, line)], None, cx);
+                state.last_message_kind = Some(kind);
+            }
+            let len = buffer.len();
+            buffer.edit([(len..len, message)], None, cx);
+        });
+        Some(())
+    }
+}
+
+impl LspLogView {
+    fn new(
+        project: ModelHandle<Project>,
+        log_set: ModelHandle<LogStore>,
+        _: &mut ViewContext<Self>,
+    ) -> Self {
+        Self {
+            project,
+            log_store: log_set,
+            editor: None,
+            current_server_id: None,
+        }
+    }
+
+    fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
+        let buffer = self.log_store.update(cx, |log_set, cx| {
+            log_set.enable_logs_for_language_server(&self.project, server_id, cx)
+        });
+        if let Some(buffer) = buffer {
+            self.current_server_id = Some(server_id);
+            self.editor = Some(cx.add_view(|cx| {
+                let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx);
+                editor.set_read_only(true);
+                editor.move_to_end(&Default::default(), cx);
+                editor
+            }));
+            cx.notify();
+        }
+    }
+
+    fn toggle_logging_for_server(
+        &mut self,
+        server_id: LanguageServerId,
+        enabled: bool,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.log_store.update(cx, |log_store, cx| {
+            if enabled {
+                log_store.enable_logs_for_language_server(&self.project, server_id, cx);
+            } else {
+                log_store.disable_logs_for_language_server(&self.project, server_id, cx);
+            }
+        });
+    }
+}
+
+impl View for LspLogView {
+    fn ui_name() -> &'static str {
+        "LspLogView"
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        if let Some(editor) = &self.editor {
+            ChildView::new(&editor, cx).into_any()
+        } else {
+            Empty::new().into_any()
+        }
+    }
+}
+
+impl Item for LspLogView {
+    fn tab_content<V: View>(
+        &self,
+        _: Option<usize>,
+        style: &theme::Tab,
+        _: &AppContext,
+    ) -> AnyElement<V> {
+        Label::new("LSP Logs", style.label.clone()).into_any()
+    }
+}
+
+impl ToolbarItemView for LspLogToolbarItemView {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        _: &mut ViewContext<Self>,
+    ) -> workspace::ToolbarItemLocation {
+        self.menu_open = false;
+        if let Some(item) = active_pane_item {
+            if let Some(log_view) = item.downcast::<LspLogView>() {
+                self.log_view = Some(log_view.clone());
+                return ToolbarItemLocation::PrimaryLeft {
+                    flex: Some((1., false)),
+                };
+            }
+        }
+        self.log_view = None;
+        ToolbarItemLocation::Hidden
+    }
+}
+
+impl View for LspLogToolbarItemView {
+    fn ui_name() -> &'static str {
+        "LspLogView"
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        let theme = cx.global::<Settings>().theme.clone();
+        let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
+        let project = self.project.read(cx);
+        let log_view = log_view.read(cx);
+        let log_store = log_view.log_store.read(cx);
+
+        let mut language_servers = project
+            .language_servers()
+            .map(|(id, name, worktree)| {
+                (
+                    id,
+                    name,
+                    worktree,
+                    log_store.has_enabled_logs_for_language_server(&self.project, id),
+                )
+            })
+            .collect::<Vec<_>>();
+        language_servers.sort_by_key(|a| (a.0, a.2));
+        language_servers.dedup_by_key(|a| a.0);
+
+        let current_server_id = log_view.current_server_id;
+        let current_server = current_server_id.and_then(|current_server_id| {
+            if let Ok(ix) = language_servers.binary_search_by_key(&current_server_id, |e| e.0) {
+                Some(language_servers[ix].clone())
+            } else {
+                None
+            }
+        });
+
+        enum Menu {}
+
+        Stack::new()
+            .with_child(Self::render_language_server_menu_header(
+                current_server,
+                &self.project,
+                &theme,
+                cx,
+            ))
+            .with_children(if self.menu_open {
+                Some(
+                    Overlay::new(
+                        MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
+                            Flex::column()
+                                .with_children(language_servers.into_iter().filter_map(
+                                    |(id, name, worktree_id, logging_enabled)| {
+                                        Self::render_language_server_menu_item(
+                                            id,
+                                            name,
+                                            worktree_id,
+                                            logging_enabled,
+                                            Some(id) == current_server_id,
+                                            &self.project,
+                                            &theme,
+                                            cx,
+                                        )
+                                    },
+                                ))
+                                .contained()
+                                .with_style(theme.context_menu.container)
+                                .constrained()
+                                .with_width(400.)
+                                .with_height(400.)
+                        })
+                        .on_down_out(MouseButton::Left, |_, this, cx| {
+                            this.menu_open = false;
+                            cx.notify()
+                        }),
+                    )
+                    .with_fit_mode(OverlayFitMode::SwitchAnchor)
+                    .with_anchor_corner(AnchorCorner::TopLeft)
+                    .with_z_index(999)
+                    .aligned()
+                    .bottom()
+                    .left(),
+                )
+            } else {
+                None
+            })
+            .aligned()
+            .left()
+            .clipped()
+            .into_any()
+    }
+}
+
+impl LspLogToolbarItemView {
+    pub fn new(project: ModelHandle<Project>) -> Self {
+        Self {
+            menu_open: false,
+            log_view: None,
+            project,
+        }
+    }
+
+    fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
+        self.menu_open = !self.menu_open;
+        cx.notify();
+    }
+
+    fn toggle_logging_for_server(
+        &mut self,
+        id: LanguageServerId,
+        enabled: bool,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if let Some(log_view) = &self.log_view {
+            log_view.update(cx, |log_view, cx| {
+                log_view.toggle_logging_for_server(id, enabled, cx);
+                if !enabled && Some(id) == log_view.current_server_id {
+                    log_view.current_server_id = None;
+                    log_view.editor = None;
+                    cx.notify();
+                }
+            });
+        }
+        cx.notify();
+    }
+
+    fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
+        if let Some(log_view) = &self.log_view {
+            log_view.update(cx, |log_view, cx| {
+                log_view.show_logs_for_server(id, cx);
+            });
+            self.menu_open = false;
+        }
+        cx.notify();
+    }
+
+    fn render_language_server_menu_header(
+        current_server: Option<(LanguageServerId, LanguageServerName, WorktreeId, bool)>,
+        project: &ModelHandle<Project>,
+        theme: &Arc<Theme>,
+        cx: &mut ViewContext<Self>,
+    ) -> impl Element<Self> {
+        enum ToggleMenu {}
+        MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
+            let project = project.read(cx);
+            let label: Cow<str> = current_server
+                .and_then(|(_, server_name, worktree_id, _)| {
+                    let worktree = project.worktree_for_id(worktree_id, cx)?;
+                    let worktree = &worktree.read(cx);
+                    Some(format!("{} - ({})", server_name.0, worktree.root_name()).into())
+                })
+                .unwrap_or_else(|| "No server selected".into());
+            Label::new(
+                label,
+                theme
+                    .context_menu
+                    .item
+                    .style_for(state, false)
+                    .label
+                    .clone(),
+            )
+        })
+        .with_cursor_style(CursorStyle::PointingHand)
+        .on_click(MouseButton::Left, move |_, view, cx| {
+            view.toggle_menu(cx);
+        })
+    }
+
+    fn render_language_server_menu_item(
+        id: LanguageServerId,
+        name: LanguageServerName,
+        worktree_id: WorktreeId,
+        logging_enabled: bool,
+        is_selected: bool,
+        project: &ModelHandle<Project>,
+        theme: &Arc<Theme>,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<impl Element<Self>> {
+        enum ActivateLog {}
+        let project = project.read(cx);
+        let worktree = project.worktree_for_id(worktree_id, cx)?;
+        let worktree = &worktree.read(cx);
+        if !worktree.is_visible() {
+            return None;
+        }
+        let label = format!("{} - ({})", name.0, worktree.root_name());
+
+        Some(
+            MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, cx| {
+                let item_style = theme.context_menu.item.style_for(state, is_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(label, item_style.label.clone()).aligned().left())
+                    .align_children_center()
+                    .contained()
+                    .with_style(item_style.container)
+            })
+            .with_cursor_style(CursorStyle::PointingHand)
+            .on_click(MouseButton::Left, move |_, view, cx| {
+                view.show_logs_for_server(id, cx);
+            }),
+        )
+    }
+}
+
+impl Entity for LogStore {
+    type Event = ();
+}
+
+impl Entity for LspLogView {
+    type Event = ();
+}
+
+impl Entity for LspLogToolbarItemView {
+    type Event = ();
+}

crates/media/Cargo.toml 🔗

@@ -9,7 +9,7 @@ path = "src/media.rs"
 doctest = false
 
 [dependencies]
-anyhow = "1.0"
+anyhow.workspace = true
 block = "0.1"
 bytes = "1.2"
 core-foundation = "0.9.3"

crates/node_runtime/Cargo.toml 🔗

@@ -13,10 +13,10 @@ gpui = { path = "../gpui" }
 util = { path = "../util" }
 async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
 async-tar = "0.4.2"
-futures = "0.3"
-anyhow = "1.0.38"
-parking_lot = "0.11.1"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
-smol = "1.2.5"
+futures.workspace = true
+anyhow.workspace = true
+parking_lot.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smol.workspace = true

crates/outline/Cargo.toml 🔗

@@ -17,6 +17,6 @@ picker = { path = "../picker" }
 settings = { path = "../settings" }
 text = { path = "../text" }
 workspace = { path = "../workspace" }
-ordered-float = "2.1.1"
-postage = { workspace = true }
-smol = "1.2"
+ordered-float.workspace = true
+postage.workspace = true
+smol.workspace = true

crates/picker/Cargo.toml 🔗

@@ -17,11 +17,11 @@ util = { path = "../util" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
 
-parking_lot = "0.11.1"
+parking_lot.workspace = true
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
-serde_json = { workspace = true }
+serde_json.workspace = true
 workspace = { path = "../workspace", features = ["test-support"] }
-ctor = "0.1"
-env_logger = "0.9"
+ctor.workspace = true
+env_logger.workspace = true

crates/plugin/Cargo.toml 🔗

@@ -5,7 +5,7 @@ edition = "2021"
 publish = false
 
 [dependencies]
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 bincode = "1.3"
 plugin_macros = { path = "../plugin_macros" }

crates/plugin_macros/Cargo.toml 🔗

@@ -11,6 +11,6 @@ proc-macro = true
 syn = { version = "1.0", features = ["full",  "extra-traits"] }
 quote = "1.0"
 proc-macro2 = "1.0"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 bincode = "1.3"

crates/plugin_runtime/Cargo.toml 🔗

@@ -8,13 +8,13 @@ publish = false
 wasmtime = "0.38"
 wasmtime-wasi = "0.38"
 wasi-common = "0.38"
-anyhow = { version = "1.0", features = ["std"] }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+anyhow.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 bincode = "1.3"
 pollster = "0.2.5"
-smol = "1.2.5"
+smol.workspace = true
 
 [build-dependencies]
 wasmtime = { version = "0.38", features = ["all-arch"] }

crates/project/Cargo.toml 🔗

@@ -38,30 +38,30 @@ sum_tree = { path = "../sum_tree" }
 terminal = { path = "../terminal" }
 util = { path = "../util" }
 aho-corasick = "0.7"
-anyhow = "1.0.57"
-async-trait = "0.1"
+anyhow.workspace = true
+async-trait.workspace = true
 backtrace = "0.3"
-futures = "0.3"
+futures.workspace = true
 ignore = "0.4"
-lazy_static = "1.4.0"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-postage = { workspace = true }
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
 pulldown-cmark = { version = "0.9.1", default-features = false }
-rand = "0.8.3"
-regex = "1.5"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+rand.workspace = true
+regex.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 sha2 = "0.10"
 similar = "1.3"
-smol = "1.2.5"
-thiserror = "1.0.29"
+smol.workspace = true
+thiserror.workspace = true
 toml = "0.5"
 
 [dev-dependencies]
-ctor = "0.1"
-env_logger = "0.9"
+ctor.workspace = true
+env_logger.workspace = true
 pretty_assertions = "1.3.0"
 client = { path = "../client", features = ["test-support"] }
 collections = { path = "../collections", features = ["test-support"] }
@@ -73,5 +73,5 @@ lsp = { path = "../lsp", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 rpc = { path = "../rpc", features = ["test-support"] }
-tempdir = { version = "0.3.7" }
-unindent = "0.1.7"
+tempdir.workspace = true
+unindent.workspace = true

crates/project/src/project.rs 🔗

@@ -93,7 +93,7 @@ pub trait Item {
 pub struct Project {
     worktrees: Vec<WorktreeHandle>,
     active_entry: Option<ProjectEntryId>,
-    buffer_changes_tx: mpsc::UnboundedSender<BufferMessage>,
+    buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
     languages: Arc<LanguageRegistry>,
     language_servers: HashMap<LanguageServerId, LanguageServerState>,
     language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
@@ -137,11 +137,16 @@ struct LspBufferSnapshot {
     snapshot: TextBufferSnapshot,
 }
 
-enum BufferMessage {
+/// Message ordered with respect to buffer operations
+enum BufferOrderedMessage {
     Operation {
         buffer_id: u64,
         operation: proto::Operation,
     },
+    LanguageServerUpdate {
+        language_server_id: LanguageServerId,
+        message: proto::update_language_server::Variant,
+    },
     Resync,
 }
 
@@ -185,6 +190,8 @@ pub struct Collaborator {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Event {
+    LanguageServerAdded(LanguageServerId),
+    LanguageServerRemoved(LanguageServerId),
     ActiveEntryChanged(Option<ProjectEntryId>),
     WorktreeAdded,
     WorktreeRemoved(WorktreeId),
@@ -441,11 +448,11 @@ impl Project {
     ) -> ModelHandle<Self> {
         cx.add_model(|cx: &mut ModelContext<Self>| {
             let (tx, rx) = mpsc::unbounded();
-            cx.spawn_weak(|this, cx| Self::send_buffer_messages(this, rx, cx))
+            cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
             Self {
                 worktrees: Default::default(),
-                buffer_changes_tx: tx,
+                buffer_ordered_messages_tx: tx,
                 collaborators: Default::default(),
                 opened_buffers: Default::default(),
                 shared_buffers: Default::default(),
@@ -509,11 +516,11 @@ impl Project {
             }
 
             let (tx, rx) = mpsc::unbounded();
-            cx.spawn_weak(|this, cx| Self::send_buffer_messages(this, rx, cx))
+            cx.spawn_weak(|this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
             let mut this = Self {
                 worktrees: Vec::new(),
-                buffer_changes_tx: tx,
+                buffer_ordered_messages_tx: tx,
                 loading_buffers_by_path: Default::default(),
                 opened_buffer: watch::channel(),
                 shared_buffers: Default::default(),
@@ -1166,8 +1173,8 @@ impl Project {
                 )
             })
             .collect();
-        self.buffer_changes_tx
-            .unbounded_send(BufferMessage::Resync)
+        self.buffer_ordered_messages_tx
+            .unbounded_send(BufferOrderedMessage::Resync)
             .unwrap();
         cx.notify();
         Ok(())
@@ -1782,23 +1789,49 @@ impl Project {
         }
     }
 
-    async fn send_buffer_messages(
+    async fn send_buffer_ordered_messages(
         this: WeakModelHandle<Self>,
-        rx: UnboundedReceiver<BufferMessage>,
+        rx: UnboundedReceiver<BufferOrderedMessage>,
         mut cx: AsyncAppContext,
     ) -> Option<()> {
         const MAX_BATCH_SIZE: usize = 128;
 
-        let mut needs_resync_with_host = false;
         let mut operations_by_buffer_id = HashMap::default();
+        async fn flush_operations(
+            this: &ModelHandle<Project>,
+            operations_by_buffer_id: &mut HashMap<u64, Vec<proto::Operation>>,
+            needs_resync_with_host: &mut bool,
+            is_local: bool,
+            cx: &AsyncAppContext,
+        ) {
+            for (buffer_id, operations) in operations_by_buffer_id.drain() {
+                let request = this.read_with(cx, |this, _| {
+                    let project_id = this.remote_id()?;
+                    Some(this.client.request(proto::UpdateBuffer {
+                        buffer_id,
+                        project_id,
+                        operations,
+                    }))
+                });
+                if let Some(request) = request {
+                    if request.await.is_err() && !is_local {
+                        *needs_resync_with_host = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        let mut needs_resync_with_host = false;
         let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
+
         while let Some(changes) = changes.next().await {
             let this = this.upgrade(&mut cx)?;
             let is_local = this.read_with(&cx, |this, _| this.is_local());
 
             for change in changes {
                 match change {
-                    BufferMessage::Operation {
+                    BufferOrderedMessage::Operation {
                         buffer_id,
                         operation,
                     } => {
@@ -1811,7 +1844,8 @@ impl Project {
                             .or_insert(Vec::new())
                             .push(operation);
                     }
-                    BufferMessage::Resync => {
+
+                    BufferOrderedMessage::Resync => {
                         operations_by_buffer_id.clear();
                         if this
                             .update(&mut cx, |this, cx| this.synchronize_remote_buffers(cx))
@@ -1821,25 +1855,43 @@ impl Project {
                             needs_resync_with_host = false;
                         }
                     }
-                }
-            }
 
-            for (buffer_id, operations) in operations_by_buffer_id.drain() {
-                let request = this.read_with(&cx, |this, _| {
-                    let project_id = this.remote_id()?;
-                    Some(this.client.request(proto::UpdateBuffer {
-                        buffer_id,
-                        project_id,
-                        operations,
-                    }))
-                });
-                if let Some(request) = request {
-                    if request.await.is_err() && !is_local {
-                        needs_resync_with_host = true;
-                        break;
+                    BufferOrderedMessage::LanguageServerUpdate {
+                        language_server_id,
+                        message,
+                    } => {
+                        flush_operations(
+                            &this,
+                            &mut operations_by_buffer_id,
+                            &mut needs_resync_with_host,
+                            is_local,
+                            &cx,
+                        )
+                        .await;
+
+                        this.read_with(&cx, |this, _| {
+                            if let Some(project_id) = this.remote_id() {
+                                this.client
+                                    .send(proto::UpdateLanguageServer {
+                                        project_id,
+                                        language_server_id: language_server_id.0 as u64,
+                                        variant: Some(message),
+                                    })
+                                    .log_err();
+                            }
+                        });
                     }
                 }
             }
+
+            flush_operations(
+                &this,
+                &mut operations_by_buffer_id,
+                &mut needs_resync_with_host,
+                is_local,
+                &cx,
+            )
+            .await;
         }
 
         None
@@ -1853,8 +1905,8 @@ impl Project {
     ) -> Option<()> {
         match event {
             BufferEvent::Operation(operation) => {
-                self.buffer_changes_tx
-                    .unbounded_send(BufferMessage::Operation {
+                self.buffer_ordered_messages_tx
+                    .unbounded_send(BufferOrderedMessage::Operation {
                         buffer_id: buffer.read(cx).remote_id(),
                         operation: language::proto::serialize_operation(operation),
                     })
@@ -1869,7 +1921,7 @@ impl Project {
                 let next_snapshot = buffer.text_snapshot();
 
                 let language_servers: Vec<_> = self
-                    .language_servers_iter_for_buffer(buffer, cx)
+                    .language_servers_for_buffer(buffer, cx)
                     .map(|i| i.1.clone())
                     .collect();
 
@@ -1960,19 +2012,24 @@ impl Project {
                                 Duration::from_secs(1);
 
                             let task = cx.spawn_weak(|this, mut cx| async move {
-                            cx.background().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await;
-                            if let Some(this) = this.upgrade(&cx) {
-                                this.update(&mut cx, |this, cx | {
-                                    this.disk_based_diagnostics_finished(language_server_id, cx);
-                                    this.broadcast_language_server_update(
-                                        language_server_id,
-                                        proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
-                                            proto::LspDiskBasedDiagnosticsUpdated {},
-                                        ),
-                                    );
-                                });
-                            }
-                        });
+                                cx.background().timer(DISK_BASED_DIAGNOSTICS_DEBOUNCE).await;
+                                if let Some(this) = this.upgrade(&cx) {
+                                    this.update(&mut cx, |this, cx| {
+                                        this.disk_based_diagnostics_finished(
+                                            language_server_id,
+                                            cx,
+                                        );
+                                        this.buffer_ordered_messages_tx
+                                            .unbounded_send(
+                                                BufferOrderedMessage::LanguageServerUpdate {
+                                                    language_server_id,
+                                                    message:proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(Default::default())
+                                                },
+                                            )
+                                            .ok();
+                                    });
+                                }
+                            });
                             *simulate_disk_based_diagnostics_completion = Some(task);
                         }
                     }
@@ -2607,7 +2664,7 @@ impl Project {
     fn on_lsp_progress(
         &mut self,
         progress: lsp::ProgressParams,
-        server_id: LanguageServerId,
+        language_server_id: LanguageServerId,
         disk_based_diagnostics_progress_token: Option<String>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2620,7 +2677,7 @@ impl Project {
         };
         let lsp::ProgressParamsValue::WorkDone(progress) = progress.value;
         let language_server_status =
-            if let Some(status) = self.language_server_statuses.get_mut(&server_id) {
+            if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
                 status
             } else {
                 return;
@@ -2640,16 +2697,16 @@ impl Project {
             lsp::WorkDoneProgress::Begin(report) => {
                 if is_disk_based_diagnostics_progress {
                     language_server_status.has_pending_diagnostic_updates = true;
-                    self.disk_based_diagnostics_started(server_id, cx);
-                    self.broadcast_language_server_update(
-                        server_id,
-                        proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
-                            proto::LspDiskBasedDiagnosticsUpdating {},
-                        ),
-                    );
+                    self.disk_based_diagnostics_started(language_server_id, cx);
+                    self.buffer_ordered_messages_tx
+                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
+                            language_server_id,
+                            message: proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(Default::default())
+                        })
+                        .ok();
                 } else {
                     self.on_lsp_work_start(
-                        server_id,
+                        language_server_id,
                         token.clone(),
                         LanguageServerProgress {
                             message: report.message.clone(),
@@ -2658,20 +2715,24 @@ impl Project {
                         },
                         cx,
                     );
-                    self.broadcast_language_server_update(
-                        server_id,
-                        proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
-                            token,
-                            message: report.message,
-                            percentage: report.percentage.map(|p| p as u32),
-                        }),
-                    );
+                    self.buffer_ordered_messages_tx
+                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
+                            language_server_id,
+                            message: proto::update_language_server::Variant::WorkStart(
+                                proto::LspWorkStart {
+                                    token,
+                                    message: report.message,
+                                    percentage: report.percentage.map(|p| p as u32),
+                                },
+                            ),
+                        })
+                        .ok();
                 }
             }
             lsp::WorkDoneProgress::Report(report) => {
                 if !is_disk_based_diagnostics_progress {
                     self.on_lsp_work_progress(
-                        server_id,
+                        language_server_id,
                         token.clone(),
                         LanguageServerProgress {
                             message: report.message.clone(),
@@ -2680,16 +2741,18 @@ impl Project {
                         },
                         cx,
                     );
-                    self.broadcast_language_server_update(
-                        server_id,
-                        proto::update_language_server::Variant::WorkProgress(
-                            proto::LspWorkProgress {
-                                token,
-                                message: report.message,
-                                percentage: report.percentage.map(|p| p as u32),
-                            },
-                        ),
-                    );
+                    self.buffer_ordered_messages_tx
+                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
+                            language_server_id,
+                            message: proto::update_language_server::Variant::WorkProgress(
+                                proto::LspWorkProgress {
+                                    token,
+                                    message: report.message,
+                                    percentage: report.percentage.map(|p| p as u32),
+                                },
+                            ),
+                        })
+                        .ok();
                 }
             }
             lsp::WorkDoneProgress::End(_) => {
@@ -2697,21 +2760,26 @@ impl Project {
 
                 if is_disk_based_diagnostics_progress {
                     language_server_status.has_pending_diagnostic_updates = false;
-                    self.disk_based_diagnostics_finished(server_id, cx);
-                    self.broadcast_language_server_update(
-                        server_id,
-                        proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
-                            proto::LspDiskBasedDiagnosticsUpdated {},
-                        ),
-                    );
+                    self.disk_based_diagnostics_finished(language_server_id, cx);
+                    self.buffer_ordered_messages_tx
+                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
+                            language_server_id,
+                            message:
+                                proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
+                                    Default::default(),
+                                ),
+                        })
+                        .ok();
                 } else {
-                    self.on_lsp_work_end(server_id, token.clone(), cx);
-                    self.broadcast_language_server_update(
-                        server_id,
-                        proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
-                            token,
-                        }),
-                    );
+                    self.on_lsp_work_end(language_server_id, token.clone(), cx);
+                    self.buffer_ordered_messages_tx
+                        .unbounded_send(BufferOrderedMessage::LanguageServerUpdate {
+                            language_server_id,
+                            message: proto::update_language_server::Variant::WorkEnd(
+                                proto::LspWorkEnd { token },
+                            ),
+                        })
+                        .ok();
                 }
             }
         }
@@ -2820,22 +2888,6 @@ impl Project {
         })
     }
 
-    fn broadcast_language_server_update(
-        &self,
-        language_server_id: LanguageServerId,
-        event: proto::update_language_server::Variant,
-    ) {
-        if let Some(project_id) = self.remote_id() {
-            self.client
-                .send(proto::UpdateLanguageServer {
-                    project_id,
-                    language_server_id: language_server_id.0 as u64,
-                    variant: Some(event),
-                })
-                .log_err();
-        }
-    }
-
     pub fn language_server_statuses(
         &self,
     ) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
@@ -4864,8 +4916,8 @@ impl Project {
             if is_host {
                 this.opened_buffers
                     .retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
-                this.buffer_changes_tx
-                    .unbounded_send(BufferMessage::Resync)
+                this.buffer_ordered_messages_tx
+                    .unbounded_send(BufferOrderedMessage::Resync)
                     .unwrap();
             }
 
@@ -6279,7 +6331,25 @@ impl Project {
         }
     }
 
-    pub fn language_servers_iter_for_buffer(
+    pub fn language_servers(
+        &self,
+    ) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName, WorktreeId)> {
+        self.language_server_ids
+            .iter()
+            .map(|((worktree_id, server_name), server_id)| {
+                (*server_id, server_name.clone(), *worktree_id)
+            })
+    }
+
+    pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
+        if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? {
+            Some(server.clone())
+        } else {
+            None
+        }
+    }
+
+    pub fn language_servers_for_buffer(
         &self,
         buffer: &Buffer,
         cx: &AppContext,
@@ -6299,20 +6369,12 @@ impl Project {
             })
     }
 
-    fn language_servers_for_buffer(
-        &self,
-        buffer: &Buffer,
-        cx: &AppContext,
-    ) -> Vec<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_iter_for_buffer(buffer, cx).collect()
-    }
-
     fn primary_language_servers_for_buffer(
         &self,
         buffer: &Buffer,
         cx: &AppContext,
     ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_iter_for_buffer(buffer, cx).next()
+        self.language_servers_for_buffer(buffer, cx).next()
     }
 
     fn language_server_for_buffer(
@@ -6321,7 +6383,7 @@ impl Project {
         server_id: LanguageServerId,
         cx: &AppContext,
     ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
-        self.language_servers_iter_for_buffer(buffer, cx)
+        self.language_servers_for_buffer(buffer, cx)
             .find(|(_, s)| s.server_id() == server_id)
     }
 

crates/project_panel/Cargo.toml 🔗

@@ -19,12 +19,12 @@ settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-postage = { workspace = true }
-futures = "0.3"
+postage.workspace = true
+futures.workspace = true
 unicase = "2.6"
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
-serde_json = { workspace = true }
+serde_json.workspace = true

crates/project_symbols/Cargo.toml 🔗

@@ -18,13 +18,13 @@ text = { path = "../text" }
 settings = { path = "../settings" }
 workspace = { path = "../workspace" }
 util = { path = "../util" }
-anyhow = "1.0.38"
-ordered-float = "2.1.1"
-postage = { workspace = true }
-smol = "1.2"
+anyhow.workspace = true
+ordered-float.workspace = true
+postage.workspace = true
+smol.workspace = true
 
 [dev-dependencies]
-futures = "0.3"
+futures.workspace = true
 settings = { path = "../settings", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }

crates/recent_projects/Cargo.toml 🔗

@@ -17,8 +17,9 @@ language = { path = "../language" }
 picker = { path = "../picker" }
 settings = { path = "../settings" }
 text = { path = "../text" }
-workspace = { path = "../workspace" }
-ordered-float = "2.1.1"
-postage = { workspace = true }
-smol = "1.2"
 util = { path = "../util"}
+workspace = { path = "../workspace" }
+
+ordered-float.workspace = true
+postage.workspace = true
+smol.workspace = true

crates/rope/Cargo.toml 🔗

@@ -9,13 +9,13 @@ path = "src/rope.rs"
 
 [dependencies]
 bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
-smallvec = { workspace = true }
+smallvec.workspace = true
 sum_tree = { path = "../sum_tree" }
 arrayvec = "0.7.1"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 util = { path = "../util" }
 
 [dev-dependencies]
-rand = "0.8.3"
+rand.workspace = true
 util = { path = "../util", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"]  }

crates/rpc/Cargo.toml 🔗

@@ -17,17 +17,17 @@ clock = { path = "../clock" }
 collections = { path = "../collections" }
 gpui = { path = "../gpui", optional = true }
 util = { path = "../util" }
-anyhow = "1.0"
+anyhow.workspace = true
 async-lock = "2.4"
 async-tungstenite = "0.16"
 base64 = "0.13"
-futures = "0.3"
-parking_lot = "0.11.1"
+futures.workspace = true
+parking_lot.workspace = true
 prost = "0.8"
-rand = "0.8"
+rand.workspace = true
 rsa = "0.4"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 smol-timeout = "0.6"
 tracing = { version = "0.1.34", features = ["log"] }
 zstd = "0.11"
@@ -38,7 +38,7 @@ prost-build = "0.9"
 [dev-dependencies]
 collections = { path = "../collections", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
-smol = "1.2.5"
-tempdir = "0.3.7"
-ctor = "0.1"
-env_logger = "0.9"
+smol.workspace = true
+tempdir.workspace = true
+ctor.workspace = true
+env_logger.workspace = true

crates/rpc/proto/zed.proto 🔗

@@ -240,13 +240,13 @@ message ParticipantLocation {
         UnsharedProject unshared_project = 2;
         External external = 3;
     }
-    
+
     message SharedProject {
         uint64 id = 1;
     }
-    
+
     message UnsharedProject {}
-    
+
     message External {}
 }
 

crates/search/Cargo.toml 🔗

@@ -19,18 +19,18 @@ settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }
 workspace = { path = "../workspace" }
-anyhow = "1.0"
-futures = "0.3"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-postage = { workspace = true }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-smallvec = { workspace = true }
-smol = "1.2"
+anyhow.workspace = true
+futures.workspace = true
+log.workspace = true
+postage.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+smallvec.workspace = true
+smol.workspace = true
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
-serde_json = { workspace = true }
+serde_json.workspace = true
 workspace = { path = "../workspace", features = ["test-support"] }
-unindent = "0.1"
+unindent.workspace = true

crates/search/src/project_search.rs 🔗

@@ -85,6 +85,7 @@ pub struct ProjectSearchView {
     query_contains_error: bool,
     active_match_index: Option<usize>,
     search_id: usize,
+    query_editor_was_focused: bool,
 }
 
 pub struct ProjectSearchBar {
@@ -218,7 +219,11 @@ impl View for ProjectSearchView {
         });
 
         if cx.is_self_focused() {
-            self.focus_query_editor(cx);
+            if self.query_editor_was_focused {
+                cx.focus(&self.query_editor);
+            } else {
+                cx.focus(&self.results_editor);
+            }
         }
     }
 }
@@ -448,6 +453,7 @@ impl ProjectSearchView {
             regex,
             query_contains_error: false,
             active_match_index: None,
+            query_editor_was_focused: false,
         };
         this.model_changed(cx);
         this
@@ -549,10 +555,11 @@ impl ProjectSearchView {
         }
     }
 
-    fn focus_query_editor(&self, cx: &mut ViewContext<Self>) {
+    fn focus_query_editor(&mut self, cx: &mut ViewContext<Self>) {
         self.query_editor.update(cx, |query_editor, cx| {
             query_editor.select_all(&SelectAll, cx);
         });
+        self.query_editor_was_focused = true;
         cx.focus(&self.query_editor);
     }
 
@@ -561,11 +568,12 @@ impl ProjectSearchView {
             .update(cx, |query_editor, cx| query_editor.set_text(query, cx));
     }
 
-    fn focus_results_editor(&self, cx: &mut ViewContext<Self>) {
+    fn focus_results_editor(&mut self, cx: &mut ViewContext<Self>) {
         self.query_editor.update(cx, |query_editor, cx| {
             let cursor = query_editor.selections.newest_anchor().head();
             query_editor.change_selections(None, cx, |s| s.select_ranges([cursor.clone()..cursor]));
         });
+        self.query_editor_was_focused = false;
         cx.focus(&self.results_editor);
     }
 

crates/settings/Cargo.toml 🔗

@@ -17,24 +17,26 @@ collections = { path = "../collections" }
 gpui = { path = "../gpui" }
 sqlez = { path = "../sqlez" }
 fs = { path = "../fs" }
-anyhow = "1.0.38"
-futures = "0.3"
+anyhow.workspace = true
+futures.workspace = true
 theme = { path = "../theme" }
 staff_mode = { path = "../staff_mode" }
 util = { path = "../util" }
+
 json_comments = "0.2"
-postage = { workspace = true }
+postage.workspace = true
 schemars = "0.8"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 serde_path_to_error = "0.1.4"
 toml = "0.5"
 tree-sitter = "*"
 tree-sitter-json = "*"
 
 [dev-dependencies]
-unindent = "0.1"
 gpui = { path = "../gpui", features = ["test-support"] }
 fs = { path = "../fs", features = ["test-support"] }
+
 pretty_assertions = "1.3.0"
+unindent.workspace = true

crates/snippet/Cargo.toml 🔗

@@ -9,5 +9,5 @@ path = "src/snippet.rs"
 doctest = false
 
 [dependencies]
-anyhow = "1.0"
-smallvec = { workspace = true }
+anyhow.workspace = true
+smallvec.workspace = true

crates/sqlez/Cargo.toml 🔗

@@ -4,15 +4,13 @@ version = "0.1.0"
 edition = "2021"
 publish = false
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
-anyhow = { version = "1.0.38", features = ["backtrace"] }
+anyhow.workspace = true
 indoc = "1.0.7"
 libsqlite3-sys = { version = "0.24", features = ["bundled"] }
-smol = "1.2"
+smol.workspace = true
 thread_local = "1.1.4"
-lazy_static = "1.4"
-parking_lot = "0.11.1"
-futures = "0.3"
-uuid = { version = "1.1.2", features = ["v4"] }
+lazy_static.workspace = true
+parking_lot.workspace = true
+futures.workspace = true
+uuid = { version = "1.1.2", features = ["v4"] }

crates/sqlez_macros/Cargo.toml 🔗

@@ -13,6 +13,6 @@ doctest = false
 syn = "1.0"
 quote = "1.0"
 proc-macro2 = "1.0"
-lazy_static = "1.4"
+lazy_static.workspace = true
 sqlez = { path = "../sqlez" }
-sqlformat = "0.2"
+sqlformat = "0.2"

crates/staff_mode/Cargo.toml 🔗

@@ -9,4 +9,4 @@ path = "src/staff_mode.rs"
 
 [dependencies]
 gpui = { path = "../gpui" }
-anyhow = "1.0.38"
+anyhow.workspace = true

crates/sum_tree/Cargo.toml 🔗

@@ -10,9 +10,9 @@ doctest = false
 
 [dependencies]
 arrayvec = "0.7.1"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 
 [dev-dependencies]
-ctor = "0.1"
-env_logger = "0.9"
-rand = "0.8.3"
+ctor.workspace = true
+env_logger.workspace = true
+rand.workspace = true

crates/terminal/Cargo.toml 🔗

@@ -17,20 +17,20 @@ theme = { path = "../theme" }
 util = { path = "../util" }
 alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
 procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
-smallvec = { workspace = true }
-smol = "1.2.5"
+smallvec.workspace = true
+smol.workspace = true
 mio-extras = "2.0.6"
-futures = "0.3"
-ordered-float = "2.1.1"
+futures.workspace = true
+ordered-float.workspace = true
 itertools = "0.10"
 dirs = "4.0.0"
 shellexpand = "2.1.0"
 libc = "0.2"
-anyhow = "1"
-thiserror = "1.0"
-lazy_static = "1.4.0"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+anyhow.workspace = true
+thiserror.workspace = true
+lazy_static.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
 
 [dev-dependencies]
-rand = "0.8.5"
+rand.workspace = true

crates/terminal/src/terminal.rs 🔗

@@ -31,6 +31,7 @@ use mappings::mouse::{
 };
 
 use procinfo::LocalProcessInfo;
+use serde::{Deserialize, Serialize};
 use settings::{AlternateScroll, Settings, Shell, TerminalBlink};
 use util::truncate_and_trailoff;
 
@@ -113,7 +114,7 @@ impl EventListener for ZedListener {
     }
 }
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
 pub struct TerminalSize {
     pub cell_width: f32,
     pub line_height: f32,
@@ -441,7 +442,7 @@ impl TerminalBuilder {
     }
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct IndexedCell {
     pub point: Point,
     pub cell: Cell,
@@ -664,6 +665,7 @@ impl Terminal {
                         self.last_content.size,
                         term.grid().display_offset(),
                     );
+
                     let side = mouse_side(*position, self.last_content.size);
 
                     selection.update(point, side);
@@ -1024,6 +1026,8 @@ impl Terminal {
                 self.last_content.size,
                 self.last_content.display_offset,
             );
+
+            // Use .opposite so that selection is inclusive of the cell clicked.
             let side = mouse_side(position, self.last_content.size);
 
             let selection_type = match e.click_count {
@@ -1074,7 +1078,7 @@ impl Terminal {
 
             //Hyperlinks
             if self.selection_phase == SelectionPhase::Ended {
-                let mouse_cell_index = content_index_for_mouse(position, &self.last_content);
+                let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size);
                 if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() {
                     cx.platform().open_url(link.uri());
                 } else {
@@ -1254,17 +1258,16 @@ fn all_search_matches<'a, T>(
     RegexIter::new(start, end, AlacDirection::Right, term, regex)
 }
 
-fn content_index_for_mouse<'a>(pos: Vector2F, content: &'a TerminalContent) -> usize {
-    let col = min(
-        (pos.x() / content.size.cell_width()) as usize,
-        content.size.columns() - 1,
-    ) as usize;
-    let line = min(
-        (pos.y() / content.size.line_height()) as usize,
-        content.size.screen_lines() - 1,
-    ) as usize;
-
-    line * content.size.columns() + col
+fn content_index_for_mouse(pos: Vector2F, size: &TerminalSize) -> usize {
+    let col = (pos.x() / size.cell_width()).round() as usize;
+
+    let clamped_col = min(col, size.columns() - 1);
+
+    let row = (pos.y() / size.line_height()).round() as usize;
+
+    let clamped_row = min(row, size.screen_lines() - 1);
+
+    clamped_row * size.columns() + clamped_col
 }
 
 #[cfg(test)]
@@ -1274,17 +1277,19 @@ mod tests {
         term::cell::Cell,
     };
     use gpui::geometry::vector::vec2f;
-    use rand::{rngs::ThreadRng, thread_rng, Rng};
+    use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng};
 
     use crate::{content_index_for_mouse, IndexedCell, TerminalContent, TerminalSize};
 
     #[test]
-    fn test_mouse_to_cell() {
+    fn test_mouse_to_cell_test() {
         let mut rng = thread_rng();
+        const ITERATIONS: usize = 10;
+        const PRECISION: usize = 1000;
 
-        for _ in 0..10 {
-            let viewport_cells = rng.gen_range(5..50);
-            let cell_size = rng.gen_range(5.0..20.0);
+        for _ in 0..ITERATIONS {
+            let viewport_cells = rng.gen_range(15..20);
+            let cell_size = rng.gen_range(5 * PRECISION..20 * PRECISION) as f32 / PRECISION as f32;
 
             let size = crate::TerminalSize {
                 cell_width: cell_size,
@@ -1293,26 +1298,27 @@ mod tests {
                 width: cell_size * (viewport_cells as f32),
             };
 
-            let (content, cells) = create_terminal_content(size, &mut rng);
+            let cells = get_cells(size, &mut rng);
+            let content = convert_cells_to_content(size, &cells);
 
-            for i in 0..(viewport_cells - 1) {
-                let i = i as usize;
-                for j in 0..(viewport_cells - 1) {
-                    let j = j as usize;
-                    let min_row = i as f32 * cell_size;
-                    let max_row = (i + 1) as f32 * cell_size;
-                    let min_col = j as f32 * cell_size;
-                    let max_col = (j + 1) as f32 * cell_size;
+            for row in 0..(viewport_cells - 1) {
+                let row = row as usize;
+                for col in 0..(viewport_cells - 1) {
+                    let col = col as usize;
+
+                    let row_offset = rng.gen_range(0..PRECISION) as f32 / PRECISION as f32;
+                    let col_offset = rng.gen_range(0..PRECISION) as f32 / PRECISION as f32;
 
                     let mouse_pos = vec2f(
-                        rng.gen_range(min_row..max_row),
-                        rng.gen_range(min_col..max_col),
+                        col as f32 * cell_size + col_offset,
+                        row as f32 * cell_size + row_offset,
                     );
 
-                    assert_eq!(
-                        content.cells[content_index_for_mouse(mouse_pos, &content)].c,
-                        cells[j][i]
-                    );
+                    let content_index = content_index_for_mouse(mouse_pos, &content.size);
+                    let mouse_cell = content.cells[content_index].c;
+                    let real_cell = cells[row][col];
+
+                    assert_eq!(mouse_cell, real_cell);
                 }
             }
         }
@@ -1329,29 +1335,40 @@ mod tests {
             width: 100.,
         };
 
-        let (content, cells) = create_terminal_content(size, &mut rng);
+        let cells = get_cells(size, &mut rng);
+        let content = convert_cells_to_content(size, &cells);
 
         assert_eq!(
-            content.cells[content_index_for_mouse(vec2f(-10., -10.), &content)].c,
+            content.cells[content_index_for_mouse(vec2f(-10., -10.), &content.size)].c,
             cells[0][0]
         );
         assert_eq!(
-            content.cells[content_index_for_mouse(vec2f(1000., 1000.), &content)].c,
+            content.cells[content_index_for_mouse(vec2f(1000., 1000.), &content.size)].c,
             cells[9][9]
         );
     }
 
-    fn create_terminal_content(
-        size: TerminalSize,
-        rng: &mut ThreadRng,
-    ) -> (TerminalContent, Vec<Vec<char>>) {
-        let mut ic = Vec::new();
+    fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec<Vec<char>> {
         let mut cells = Vec::new();
 
-        for row in 0..((size.height() / size.line_height()) as usize) {
+        for _ in 0..((size.height() / size.line_height()) as usize) {
             let mut row_vec = Vec::new();
-            for col in 0..((size.width() / size.cell_width()) as usize) {
-                let cell_char = rng.gen();
+            for _ in 0..((size.width() / size.cell_width()) as usize) {
+                let cell_char = rng.sample(Alphanumeric) as char;
+                row_vec.push(cell_char)
+            }
+            cells.push(row_vec)
+        }
+
+        cells
+    }
+
+    fn convert_cells_to_content(size: TerminalSize, cells: &Vec<Vec<char>>) -> TerminalContent {
+        let mut ic = Vec::new();
+
+        for row in 0..cells.len() {
+            for col in 0..cells[row].len() {
+                let cell_char = cells[row][col];
                 ic.push(IndexedCell {
                     point: Point::new(Line(row as i32), Column(col)),
                     cell: Cell {
@@ -1359,18 +1376,13 @@ mod tests {
                         ..Default::default()
                     },
                 });
-                row_vec.push(cell_char)
             }
-            cells.push(row_vec)
         }
 
-        (
-            TerminalContent {
-                cells: ic,
-                size,
-                ..Default::default()
-            },
-            cells,
-        )
+        TerminalContent {
+            cells: ic,
+            size,
+            ..Default::default()
+        }
     }
 }

crates/terminal_view/Cargo.toml 🔗

@@ -21,20 +21,20 @@ workspace = { path = "../workspace" }
 db = { path = "../db" }
 procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
 terminal = { path = "../terminal" }
-smallvec = { workspace = true }
-smol = "1.2.5"
+smallvec.workspace = true
+smol.workspace = true
 mio-extras = "2.0.6"
-futures = "0.3"
-ordered-float = "2.1.1"
+futures.workspace = true
+ordered-float.workspace = true
 itertools = "0.10"
 dirs = "4.0.0"
 shellexpand = "2.1.0"
 libc = "0.2"
-anyhow = "1"
-thiserror = "1.0"
-lazy_static = "1.4.0"
-serde = { workspace = true }
-serde_derive = { workspace = true }
+anyhow.workspace = true
+thiserror.workspace = true
+lazy_static.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
 
 
 
@@ -43,4 +43,4 @@ gpui = { path = "../gpui", features = ["test-support"] }
 client = { path = "../client", features = ["test-support"]}
 project = { path = "../project", features = ["test-support"]}
 workspace = { path = "../workspace", features = ["test-support"] }
-rand = "0.8.5"
+rand.workspace = true

crates/terminal_view/src/terminal_element.rs 🔗

@@ -46,6 +46,7 @@ pub struct LayoutState {
     mode: TermMode,
     display_offset: usize,
     hyperlink_tooltip: Option<AnyElement<TerminalView>>,
+    gutter: f32,
 }
 
 ///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
@@ -572,10 +573,14 @@ impl Element<TerminalView> for TerminalElement {
         let text_style = TerminalElement::make_text_style(font_cache, settings);
         let selection_color = settings.theme.editor.selection.selection;
         let match_color = settings.theme.search.match_background;
+        let gutter;
         let dimensions = {
             let line_height = text_style.font_size * settings.terminal_line_height();
             let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
-            TerminalSize::new(line_height, cell_width, constraint.max)
+            gutter = cell_width;
+
+            let size = constraint.max - vec2f(gutter, 0.);
+            TerminalSize::new(line_height, cell_width, size)
         };
 
         let search_matches = if let Some(terminal_model) = self.terminal.upgrade(cx) {
@@ -713,6 +718,7 @@ impl Element<TerminalView> for TerminalElement {
                 mode: *mode,
                 display_offset: *display_offset,
                 hyperlink_tooltip,
+                gutter,
             },
         )
     }
@@ -732,7 +738,7 @@ impl Element<TerminalView> for TerminalElement {
         let clip_bounds = Some(visible_bounds);
 
         scene.paint_layer(clip_bounds, |scene| {
-            let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
+            let origin = bounds.origin() + vec2f(layout.gutter, 0.);
 
             // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
             self.attach_mouse_handlers(scene, origin, visible_bounds, layout.mode, cx);

crates/text/Cargo.toml 🔗

@@ -17,21 +17,21 @@ collections = { path = "../collections" }
 fs = { path = "../fs" }
 rope = { path = "../rope" }
 sum_tree = { path = "../sum_tree" }
-anyhow = "1.0.38"
-digest = { version = "0.9", features = ["std"] }
-lazy_static = "1.4"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11"
-postage = { workspace = true }
-rand = { version = "0.8.3", optional = true }
-smallvec = { workspace = true }
 util = { path = "../util" }
-regex = "1.5"
 
+anyhow.workspace = true
+digest = { version = "0.9", features = ["std"] }
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+rand = { workspace = true, optional = true }
+smallvec.workspace = true
+regex.workspace = true
 
 [dev-dependencies]
 collections = { path = "../collections", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
-ctor = "0.1"
-env_logger = "0.9"
-rand = "0.8.3"
+ctor.workspace = true
+env_logger.workspace = true
+rand.workspace = true

crates/theme/Cargo.toml 🔗

@@ -10,11 +10,11 @@ doctest = false
 
 [dependencies]
 gpui = { path = "../gpui" }
-anyhow = "1.0.38"
+anyhow.workspace = true
 indexmap = "1.6.2"
-parking_lot = "0.11.1"
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+parking_lot.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 serde_path_to_error = "0.1.4"
 toml = "0.5"

crates/theme/src/ui.rs 🔗

@@ -27,28 +27,40 @@ pub struct CheckboxStyle {
     pub hovered_and_checked: ContainerStyle,
 }
 
-pub fn checkbox<Tag: 'static, V: View>(
+pub fn checkbox<Tag, V, F>(
     label: &'static str,
     style: &CheckboxStyle,
     checked: bool,
+    id: usize,
     cx: &mut ViewContext<V>,
-    change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
-) -> MouseEventHandler<Tag, V> {
+    change: F,
+) -> MouseEventHandler<Tag, V>
+where
+    Tag: 'static,
+    V: View,
+    F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
+{
     let label = Label::new(label, style.label.text.clone())
         .contained()
         .with_style(style.label.container);
-
-    checkbox_with_label(label, style, checked, cx, change)
+    checkbox_with_label(label, style, checked, id, cx, change)
 }
 
-pub fn checkbox_with_label<Tag: 'static, D: Element<V>, V: View>(
+pub fn checkbox_with_label<Tag, D, V, F>(
     label: D,
     style: &CheckboxStyle,
     checked: bool,
+    id: usize,
     cx: &mut ViewContext<V>,
-    change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
-) -> MouseEventHandler<Tag, V> {
-    MouseEventHandler::new(0, cx, |state, _| {
+    change: F,
+) -> MouseEventHandler<Tag, V>
+where
+    Tag: 'static,
+    D: Element<V>,
+    V: View,
+    F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
+{
+    MouseEventHandler::new(id, cx, |state, _| {
         let indicator = if checked {
             svg(&style.icon)
         } else {
@@ -75,8 +87,8 @@ pub fn checkbox_with_label<Tag: 'static, D: Element<V>, V: View>(
             .with_child(label)
             .align_children_center()
     })
-    .on_click(platform::MouseButton::Left, move |_, _, cx| {
-        change(!checked, cx)
+    .on_click(platform::MouseButton::Left, move |_, view, cx| {
+        change(view, !checked, cx)
     })
     .with_cursor_style(platform::CursorStyle::PointingHand)
 }

crates/theme_selector/Cargo.toml 🔗

@@ -18,7 +18,7 @@ settings = { path = "../settings" }
 staff_mode = { path = "../staff_mode" }
 workspace = { path = "../workspace" }
 util = { path = "../util" }
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-postage = { workspace = true }
-smol = "1.2.5"
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+smol.workspace = true

crates/theme_testbench/Cargo.toml 🔗

@@ -16,4 +16,4 @@ settings = { path = "../settings" }
 workspace = { path = "../workspace" }
 project = { path = "../project" }
 
-smallvec = { workspace = true }
+smallvec.workspace = true

crates/util/Cargo.toml 🔗

@@ -12,21 +12,21 @@ doctest = true
 test-support = ["tempdir", "git2"]
 
 [dependencies]
-anyhow = "1.0.38"
+anyhow.workspace = true
 backtrace = "0.3"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-lazy_static = "1.4.0"
-futures = "0.3"
+log.workspace = true
+lazy_static.workspace = true
+futures.workspace = true
 isahc = "1.7"
-smol = "1.2.5"
+smol.workspace = true
 url = "2.2"
-rand = { workspace = true }
-tempdir = { version = "0.3.7", optional = true }
-serde = { workspace = true }
-serde_json = { workspace = true }
+rand.workspace = true
+tempdir = { workspace = true, optional = true }
+serde.workspace = true
+serde_json.workspace = true
 git2 = { version = "0.15", default-features = false, optional = true }
 dirs = "3.0"
 
 [dev-dependencies]
-tempdir = { version = "0.3.7" }
+tempdir.workspace = true
 git2 = { version = "0.15", default-features = false }

crates/vim/Cargo.toml 🔗

@@ -12,16 +12,16 @@ doctest = false
 neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
 
 [dependencies]
-serde = { workspace = true }
-serde_derive = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
 itertools = "0.10"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 
 async-compat = { version = "0.2.1", "optional" = true }
-async-trait = { version = "0.1", "optional" = true }
+async-trait = { workspace = true, "optional" = true }
 nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
 tokio = { version = "1.15", "optional" = true }
-serde_json = { workspace = true }
+serde_json.workspace = true
 
 assets = { path = "../assets" }
 collections = { path = "../collections" }
@@ -35,8 +35,8 @@ workspace = { path = "../workspace" }
 
 [dev-dependencies]
 indoc = "1.0.4"
-parking_lot = "0.11.1"
-lazy_static = "1.4"
+parking_lot.workspace = true
+lazy_static.workspace = true
 
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }

crates/welcome/Cargo.toml 🔗

@@ -11,8 +11,8 @@ path = "src/welcome.rs"
 test-support = []
 
 [dependencies]
-anyhow = "1.0.38"
-log = "0.4"
+anyhow.workspace = true
+log.workspace = true
 editor = { path = "../editor" }
 fuzzy = { path = "../fuzzy" }
 gpui = { path = "../gpui" }
@@ -24,4 +24,4 @@ theme = { path = "../theme" }
 theme_selector = { path = "../theme_selector" }
 util = { path = "../util" }
 picker = { path = "../picker" }
-workspace = { path = "../workspace" }
+workspace = { path = "../workspace" }

crates/welcome/src/welcome.rs 🔗

@@ -126,7 +126,7 @@ impl View for WelcomePage {
                 .with_child(
                     Flex::column()
                         .with_child(
-                            theme::ui::checkbox_with_label::<Metrics, _, Self>(
+                            theme::ui::checkbox_with_label::<Metrics, _, Self, _>(
                                 Flex::column()
                                     .with_child(
                                         Label::new(
@@ -146,8 +146,9 @@ impl View for WelcomePage {
                                     ),
                                 &theme.welcome.checkbox,
                                 metrics,
+                                0,
                                 cx,
-                                |checked, cx| {
+                                |_, checked, cx| {
                                     SettingsFile::update(cx, move |file| {
                                         file.telemetry.set_metrics(checked)
                                     })
@@ -157,12 +158,13 @@ impl View for WelcomePage {
                             .with_style(theme.welcome.checkbox_container),
                         )
                         .with_child(
-                            theme::ui::checkbox::<Diagnostics, Self>(
+                            theme::ui::checkbox::<Diagnostics, Self, _>(
                                 "Send crash reports",
                                 &theme.welcome.checkbox,
                                 diagnostics,
+                                0,
                                 cx,
-                                |checked, cx| {
+                                |_, checked, cx| {
                                     SettingsFile::update(cx, move |file| {
                                         file.telemetry.set_diagnostics(checked)
                                     })

crates/workspace/Cargo.toml 🔗

@@ -35,20 +35,19 @@ settings = { path = "../settings" }
 terminal = { path = "../terminal" }
 theme = { path = "../theme" }
 util = { path = "../util" }
+
 async-recursion = "1.0.0"
 bincode = "1.2.1"
-anyhow = "1.0.38"
-futures = "0.3"
-lazy_static = "1.4"
-env_logger = "0.9.1"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
-parking_lot = "0.11.1"
-postage = { workspace = true }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
-smallvec = { workspace = true }
-indoc = "1.0.4"
+anyhow.workspace = true
+futures.workspace = true
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smallvec.workspace = true
 uuid = { version = "1.1.2", features = ["v4"] }
 
 [dev-dependencies]
@@ -59,3 +58,6 @@ project = { path = "../project", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 fs = { path = "../fs", features = ["test-support"] }
 db = { path = "../db", features = ["test-support"] }
+
+indoc = "1.0.4"
+env_logger.workspace = true

crates/workspace/src/workspace.rs 🔗

@@ -2784,9 +2784,7 @@ fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAp
             workspace.show_notification_once(0, cx, |cx| {
                 cx.add_view(|_| {
                     MessageNotification::new(
-                        indoc::indoc! {"
-                            Failed to load any database file :(
-                        "},
+                        "Failed to load any database file.",
                         OsOpen::new("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()),
                         "Click to let us know about this error"
                     )
@@ -2800,11 +2798,7 @@ fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAp
                         let backup_path = backup_path.to_string_lossy();
                         MessageNotification::new(
                             format!(
-                                indoc::indoc! {"
-                                Database file was corrupted :(
-                                Old database backed up to:
-                                {}
-                                "},
+                                "Database file was corrupted. Old database backed up to {}",
                                 backup_path
                             ),
                             OsOpen::new(backup_path.to_string()),

crates/zed/Cargo.toml 🔗

@@ -46,6 +46,7 @@ journal = { path = "../journal" }
 language = { path = "../language" }
 language_selector = { path = "../language_selector" }
 lsp = { path = "../lsp" }
+lsp_log = { path = "../lsp_log" }
 node_runtime = { path = "../node_runtime" }
 outline = { path = "../outline" }
 plugin_runtime = { path = "../plugin_runtime" }
@@ -66,40 +67,41 @@ util = { path = "../util" }
 vim = { path = "../vim" }
 workspace = { path = "../workspace" }
 welcome = { path = "../welcome" }
-anyhow = "1.0.38"
+
+anyhow.workspace = true
 async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
 async-tar = "0.4.2"
 async-recursion = "0.3"
-async-trait = "0.1"
+async-trait.workspace = true
 backtrace = "0.3"
 chrono = "0.4"
 ctor = "0.1.20"
 easy-parallel = "3.1.0"
-env_logger = "0.9"
-futures = "0.3"
+env_logger.workspace = true
+futures.workspace = true
 ignore = "0.4"
 image = "0.23"
 indexmap = "1.6.2"
 isahc = "1.7"
-lazy_static = "1.4.0"
+lazy_static.workspace = true
 libc = "0.2"
-log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+log.workspace = true
 num_cpus = "1.13.0"
-parking_lot = "0.11.1"
-postage = { workspace = true }
-rand = "0.8.3"
-regex = "1.5"
+parking_lot.workspace = true
+postage.workspace = true
+rand.workspace = true
+regex.workspace = true
 rsa = "0.4"
 rust-embed = { version = "6.3", features = ["include-exclude"] }
-serde = { workspace = true }
-serde_derive = { workspace = true }
-serde_json = { workspace = true }
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
 serde_path_to_error = "0.1.4"
 simplelog = "0.9"
-smallvec = { workspace = true }
-smol = "1.2.5"
-tempdir = { version = "0.3.7" }
-thiserror = "1.0.29"
+smallvec.workspace = true
+smol.workspace = true
+tempdir.workspace = true
+thiserror.workspace = true
 tiny_http = "0.8"
 toml = "0.5"
 tree-sitter = "0.20"
@@ -109,13 +111,12 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev
 tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" }
 tree-sitter-embedded-template = "0.20.0"
 tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
-tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" }
+tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
 tree-sitter-rust = "0.20.3"
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
 tree-sitter-python = "0.20.2"
 tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
 tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
-
 tree-sitter-ruby = "0.20.0"
 tree-sitter-html = "0.19.0"
 tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"}
@@ -140,9 +141,7 @@ text = { path = "../text", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
 
-env_logger = "0.9"
-serde_json = { workspace = true }
-unindent = "0.1.7"
+unindent.workspace = true
 
 [package.metadata.bundle-dev]
 icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]

crates/zed/src/languages/rust.rs 🔗

@@ -133,7 +133,8 @@ impl LspAdapter for RustLspAdapter {
                 });
             }
             Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
-                if completion.detail.is_some() =>
+                if completion.detail.is_some()
+                    && completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>
             {
                 let detail = completion.detail.as_ref().unwrap();
                 let name = &completion.label;

crates/zed/src/zed.rs 🔗

@@ -262,6 +262,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
     );
     activity_indicator::init(cx);
     copilot_button::init(cx);
+    lsp_log::init(cx);
     call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
     settings::KeymapFileContent::load_defaults(cx);
 }
@@ -273,7 +274,7 @@ pub fn initialize_workspace(
 ) {
     let workspace_handle = cx.handle();
     cx.subscribe(&workspace_handle, {
-        move |_, _, event, cx| {
+        move |workspace, _, event, cx| {
             if let workspace::Event::PaneAdded(pane) = event {
                 pane.update(cx, |pane, cx| {
                     pane.toolbar().update(cx, |toolbar, cx| {
@@ -287,6 +288,10 @@ pub fn initialize_workspace(
                         toolbar.add_item(submit_feedback_button, cx);
                         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(workspace.project().clone())
+                        });
+                        toolbar.add_item(lsp_log_item, cx);
                     })
                 });
             }