Improve lsp log viewer's behavior in the presence of LSP restarts

Mikayla created

Improve settings interface to local LSP

Change summary

Cargo.lock                              |  1 
assets/settings/default.json            |  6 +-
crates/language_tools/src/lsp_log.rs    | 50 +++++++++++++++++++++++++-
crates/zed/Cargo.toml                   |  1 
crates/zed/src/languages.rs             |  3 +
crates/zed/src/languages/elixir_next.rs | 25 +++++++++----
6 files changed, 71 insertions(+), 15 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9868,6 +9868,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "settings",
+ "shellexpand",
  "simplelog",
  "smallvec",
  "smol",

assets/settings/default.json 🔗

@@ -384,11 +384,11 @@
     //         "next": "off"
     //  2. Use a bundled version of the next Next LS LSP server
     //         "next": "on",
-    //  3. Use a locally running version of the next Next LS LSP server,
-    //     on a specific port:
+    //  3. Use a local build of the next Next LS LSP server:
     //         "next": {
     //           "local": {
-    //             "port": 4000
+    //             "path": "~/next-ls/bin/start",
+    //             "arguments": ["--stdio"]
     //            }
     //          },
     //

crates/language_tools/src/lsp_log.rs 🔗

@@ -8,8 +8,8 @@ use gpui::{
         ParentElement, Stack,
     },
     platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, View, ViewContext,
-    ViewHandle, WeakModelHandle,
+    AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription, View,
+    ViewContext, ViewHandle, WeakModelHandle,
 };
 use language::{Buffer, LanguageServerId, LanguageServerName};
 use lsp::IoKind;
@@ -52,10 +52,12 @@ pub struct LspLogView {
     current_server_id: Option<LanguageServerId>,
     is_showing_rpc_trace: bool,
     project: ModelHandle<Project>,
+    _log_store_subscription: Subscription,
 }
 
 pub struct LspLogToolbarItemView {
     log_view: Option<ViewHandle<LspLogView>>,
+    _log_view_subscription: Option<Subscription>,
     menu_open: bool,
 }
 
@@ -346,12 +348,49 @@ impl LspLogView {
             .get(&project.downgrade())
             .and_then(|project| project.servers.keys().copied().next());
         let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""));
+        let _log_store_subscription = cx.observe(&log_store, |this, store, cx| {
+            (|| -> Option<()> {
+                let project_state = store.read(cx).projects.get(&this.project.downgrade())?;
+                if let Some(current_lsp) = this.current_server_id {
+                    if !project_state.servers.contains_key(&current_lsp) {
+                        if let Some(server) = project_state.servers.iter().next() {
+                            if this.is_showing_rpc_trace {
+                                this.show_rpc_trace_for_server(*server.0, cx)
+                            } else {
+                                this.show_logs_for_server(*server.0, cx)
+                            }
+                        } else {
+                            this.current_server_id = None;
+                            this.editor.update(cx, |editor, cx| {
+                                editor.set_read_only(false);
+                                editor.clear(cx);
+                                editor.set_read_only(true);
+                            });
+                            cx.notify();
+                        }
+                    }
+                } else {
+                    if let Some(server) = project_state.servers.iter().next() {
+                        if this.is_showing_rpc_trace {
+                            this.show_rpc_trace_for_server(*server.0, cx)
+                        } else {
+                            this.show_logs_for_server(*server.0, cx)
+                        }
+                    }
+                }
+
+                Some(())
+            })();
+
+            cx.notify();
+        });
         let mut this = Self {
             editor: Self::editor_for_buffer(project.clone(), buffer, cx),
             project,
             log_store,
             current_server_id: None,
             is_showing_rpc_trace: false,
+            _log_store_subscription,
         };
         if let Some(server_id) = server_id {
             this.show_logs_for_server(server_id, cx);
@@ -556,18 +595,22 @@ impl ToolbarItemView for LspLogToolbarItemView {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn ItemHandle>,
-        _: &mut ViewContext<Self>,
+        cx: &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());
+                self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
+                    cx.notify();
+                }));
                 return ToolbarItemLocation::PrimaryLeft {
                     flex: Some((1., false)),
                 };
             }
         }
         self.log_view = None;
+        self._log_view_subscription = None;
         ToolbarItemLocation::Hidden
     }
 }
@@ -697,6 +740,7 @@ impl LspLogToolbarItemView {
         Self {
             menu_open: false,
             log_view: None,
+            _log_view_subscription: None,
         }
     }
 

crates/zed/Cargo.toml 🔗

@@ -62,6 +62,7 @@ rpc = { path = "../rpc" }
 settings = { path = "../settings" }
 feature_flags = { path = "../feature_flags" }
 sum_tree = { path = "../sum_tree" }
+shellexpand = "2.1.0"
 text = { path = "../text" }
 terminal_view = { path = "../terminal_view" }
 theme = { path = "../theme" }

crates/zed/src/languages.rs 🔗

@@ -79,11 +79,12 @@ pub fn init(
             vec![Arc::new(elixir::ElixirLspAdapter)],
         ),
         elixir_next::ElixirNextSetting::On => todo!(),
-        elixir_next::ElixirNextSetting::Local { path } => language(
+        elixir_next::ElixirNextSetting::Local { path, arguments } => language(
             "elixir",
             tree_sitter_elixir::language(),
             vec![Arc::new(elixir_next::LocalNextLspAdapter {
                 path: path.clone(),
+                arguments: arguments.clone(),
             })],
         ),
     }

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

@@ -5,7 +5,7 @@ use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use settings::Setting;
-use std::{any::Any, path::PathBuf, sync::Arc};
+use std::{any::Any, ops::Deref, path::PathBuf, sync::Arc};
 
 #[derive(Clone, Serialize, Deserialize, JsonSchema)]
 pub struct ElixirSettings {
@@ -17,7 +17,10 @@ pub struct ElixirSettings {
 pub enum ElixirNextSetting {
     Off,
     On,
-    Local { path: String },
+    Local {
+        path: String,
+        arguments: Vec<String>,
+    },
 }
 
 #[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
@@ -44,6 +47,7 @@ impl Setting for ElixirSettings {
 
 pub struct LocalNextLspAdapter {
     pub path: String,
+    pub arguments: Vec<String>,
 }
 
 #[async_trait]
@@ -69,9 +73,10 @@ impl LspAdapter for LocalNextLspAdapter {
         _: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Result<LanguageServerBinary> {
+        let path = shellexpand::full(&self.path)?;
         Ok(LanguageServerBinary {
-            path: self.path.clone().into(),
-            arguments: vec!["--stdio".into()],
+            path: PathBuf::from(path.deref()),
+            arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
         })
     }
 
@@ -80,19 +85,22 @@ impl LspAdapter for LocalNextLspAdapter {
         _: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
+        let path = shellexpand::full(&self.path).ok()?;
         Some(LanguageServerBinary {
-            path: self.path.clone().into(),
-            arguments: vec!["--stdio".into()],
+            path: PathBuf::from(path.deref()),
+            arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
         })
     }
 
     async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
+        let path = shellexpand::full(&self.path).ok()?;
         Some(LanguageServerBinary {
-            path: self.path.clone().into(),
-            arguments: vec!["--stdio".into()],
+            path: PathBuf::from(path.deref()),
+            arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
         })
     }
 
+    // TODO:
     async fn label_for_completion(
         &self,
         completion: &lsp::CompletionItem,
@@ -147,6 +155,7 @@ impl LspAdapter for LocalNextLspAdapter {
         None
     }
 
+    // TODO:
     async fn label_for_symbol(
         &self,
         name: &str,