proto: Add language server support (#18763)

Peter and Marshall Bowers created

Closes #18762

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>

Change summary

Cargo.lock                      |  7 +++
Cargo.toml                      |  1 
extensions/proto/Cargo.toml     | 16 ++++++++
extensions/proto/LICENSE-APACHE |  1 
extensions/proto/extension.toml |  4 ++
extensions/proto/src/proto.rs   | 64 +++++++++++++++++++++++++++++++++++
6 files changed, 93 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -14863,6 +14863,13 @@ dependencies = [
  "zed_extension_api 0.1.0",
 ]
 
+[[package]]
+name = "zed_proto"
+version = "0.1.0"
+dependencies = [
+ "zed_extension_api 0.1.0",
+]
+
 [[package]]
 name = "zed_purescript"
 version = "0.0.1"

Cargo.toml 🔗

@@ -154,6 +154,7 @@ members = [
     "extensions/php",
     "extensions/perplexity",
     "extensions/prisma",
+    "extensions/proto",
     "extensions/purescript",
     "extensions/ruff",
     "extensions/ruby",

extensions/proto/Cargo.toml 🔗

@@ -0,0 +1,16 @@
+[package]
+name = "zed_proto"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/proto.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.1.0"

extensions/proto/extension.toml 🔗

@@ -9,3 +9,7 @@ repository = "https://github.com/zed-industries/zed"
 [grammars.proto]
 repository = "https://github.com/zed-industries/tree-sitter-proto"
 commit = "0848bd30a64be48772e15fbb9d5ba8c0cc5772ad"
+
+[language_servers.protobuf-language-server]
+name = "Protobuf Language Server"
+languages = ["Proto"]

extensions/proto/src/proto.rs 🔗

@@ -0,0 +1,64 @@
+use zed_extension_api::{self as zed, settings::LspSettings, Result};
+
+const PROTOBUF_LANGUAGE_SERVER_NAME: &str = "protobuf-language-server";
+
+struct ProtobufLanguageServerBinary {
+    path: String,
+    args: Option<Vec<String>>,
+}
+
+struct ProtobufExtension;
+
+impl ProtobufExtension {
+    fn language_server_binary(
+        &self,
+        _language_server_id: &zed::LanguageServerId,
+        worktree: &zed::Worktree,
+    ) -> Result<ProtobufLanguageServerBinary> {
+        let binary_settings = LspSettings::for_worktree("protobuf-language-server", worktree)
+            .ok()
+            .and_then(|lsp_settings| lsp_settings.binary);
+        let binary_args = binary_settings
+            .as_ref()
+            .and_then(|binary_settings| binary_settings.arguments.clone());
+
+        if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
+            return Ok(ProtobufLanguageServerBinary {
+                path,
+                args: binary_args,
+            });
+        }
+
+        if let Some(path) = worktree.which(PROTOBUF_LANGUAGE_SERVER_NAME) {
+            return Ok(ProtobufLanguageServerBinary {
+                path,
+                args: binary_args,
+            });
+        }
+
+        Err(format!("{PROTOBUF_LANGUAGE_SERVER_NAME} not found in PATH",))
+    }
+}
+
+impl zed::Extension for ProtobufExtension {
+    fn new() -> Self {
+        Self
+    }
+
+    fn language_server_command(
+        &mut self,
+        language_server_id: &zed_extension_api::LanguageServerId,
+        worktree: &zed_extension_api::Worktree,
+    ) -> zed_extension_api::Result<zed_extension_api::Command> {
+        let binary = self.language_server_binary(language_server_id, worktree)?;
+        Ok(zed::Command {
+            command: binary.path,
+            args: binary
+                .args
+                .unwrap_or_else(|| vec!["-logs".into(), "".into()]),
+            env: Default::default(),
+        })
+    }
+}
+
+zed::register_extension!(ProtobufExtension);