From 3bded16ddc728a3e64f8cfc1412e20fb09540685 Mon Sep 17 00:00:00 2001 From: Anders Fischer-Nielsen Date: Thu, 23 Apr 2026 10:08:31 +0200 Subject: [PATCH 01/11] Update extensions.toml --- extension.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extension.toml b/extension.toml index 2b8504a..05a0a52 100644 --- a/extension.toml +++ b/extension.toml @@ -74,6 +74,10 @@ languages = [ "EEx" = "eex" "HEEx" = "heex" +[debug_adapters.elixir-ls] +name = "ElixirLS" +schema_path = "schemas/elixir_debug_adapter.json" + [grammars.elixir] repository = "https://github.com/elixir-lang/tree-sitter-elixir" commit = "450a8194f5a66561135962cfc8d7545a27b61c4c" From caa58c0fd70c0ab820249930c96dfebc1b2ce9bc Mon Sep 17 00:00:00 2001 From: Anders Fischer-Nielsen Date: Thu, 23 Apr 2026 10:08:52 +0200 Subject: [PATCH 02/11] Add schemas --- schemas/elixir_debug_adapter.json | 80 +++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 schemas/elixir_debug_adapter.json diff --git a/schemas/elixir_debug_adapter.json b/schemas/elixir_debug_adapter.json new file mode 100644 index 0000000..a945d67 --- /dev/null +++ b/schemas/elixir_debug_adapter.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ElixirLS Debug Adapter Configuration", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "mix_task", + "description": "Must be 'mix_task' for ElixirLS" + }, + "request": { + "type": "string", + "enum": ["launch", "attach"], + "description": "Whether to launch a new process or attach to an existing one" + }, + "name": { + "type": "string", + "description": "Display name for this debug configuration" + }, + "task": { + "type": "string", + "description": "Mix task to run (e.g. 'test', 'phx.server', 'run')" + }, + "taskArgs": { + "type": "array", + "items": { "type": "string" }, + "description": "Arguments passed to the Mix task" + }, + "projectDir": { + "type": "string", + "description": "Absolute path to the directory containing mix.exs" + }, + "startApps": { + "type": "boolean", + "description": "Run 'mix app.start' before the debugger (default: false)" + }, + "requireFiles": { + "type": "array", + "items": { "type": "string" }, + "description": "Extra .exs files to require and interpret before debugging" + }, + "excludeModules": { + "type": "array", + "items": { "type": "string" }, + "description": "Modules that should NOT be interpreted (e.g. NIFs)" + }, + "debugAutoInterpretAllModules": { + "type": "boolean", + "description": "Automatically interpret all loaded modules (default: true)" + }, + "debugInterpretModulesPatterns": { + "type": "array", + "items": { "type": "string" }, + "description": "Glob patterns for modules to interpret" + }, + "exitAfterTaskReturns": { + "type": "boolean", + "description": "End the debug session when the task returns (default: true)" + }, + "env": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "Environment variables to set for the debugged process" + }, + "stackTraceMode": { + "type": "string", + "enum": ["all", "no_tail", "false"], + "description": "Argument passed to :int.stack_trace/1" + }, + "noDebug": { + "type": "boolean", + "description": "Run the Mix task without debugging (default: false)" + }, + "breakOnDbg": { + "type": "boolean", + "description": "Automatically break on Kernel.dbg/2 calls (default: true)" + } + }, + "required": ["type", "request"] +} From 68a2d093678315f3230688b1b2d6c20a9c24b150 Mon Sep 17 00:00:00 2001 From: Anders Fischer-Nielsen Date: Thu, 23 Apr 2026 10:09:46 +0200 Subject: [PATCH 03/11] Wire up DAP --- src/elixir.rs | 98 ++++++++++++++++++++++++++++++- src/language_servers/elixir_ls.rs | 63 ++++++++++++++++---- 2 files changed, 149 insertions(+), 12 deletions(-) diff --git a/src/elixir.rs b/src/elixir.rs index 35f1868..b8f7aea 100644 --- a/src/elixir.rs +++ b/src/elixir.rs @@ -1,9 +1,11 @@ mod language_servers; use zed_extension_api::{ - self as zed, CodeLabel, LanguageServerId, Result, Worktree, + self as zed, CodeLabel, DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, + DebugTaskDefinition, LanguageServerId, Result, StartDebuggingRequestArguments, + StartDebuggingRequestArgumentsRequest, Worktree, lsp::{Completion, Symbol}, - serde_json::Value, + serde_json::{self, Value, json}, }; use crate::language_servers::{Dexter, ElixirLs, Expert, Lexical, NextLs}; @@ -148,6 +150,98 @@ impl zed::Extension for ElixirExtension { _ => None, } } + + fn get_dap_binary( + &mut self, + _adapter_name: String, + config: DebugTaskDefinition, + user_provided_debug_adapter_path: Option, + worktree: &Worktree, + ) -> Result { + let binary_path = if let Some(path) = user_provided_debug_adapter_path { + path + } else { + self.elixir_ls + .get_or_insert_with(ElixirLs::new) + .get_debug_adapter_path(worktree)? + }; + + let request = serde_json::from_str::(&config.config) + .ok() + .and_then(|v| { + v.get("request") + .and_then(|r| r.as_str()) + .map(str::to_string) + }) + .unwrap_or_else(|| "launch".to_string()); + + let request_kind = if request == "attach" { + StartDebuggingRequestArgumentsRequest::Attach + } else { + StartDebuggingRequestArgumentsRequest::Launch + }; + + Ok(DebugAdapterBinary { + command: Some(binary_path), + arguments: vec![], + envs: vec![], + cwd: None, + connection: None, + request_args: StartDebuggingRequestArguments { + configuration: config.config, + request: request_kind, + }, + }) + } + + fn dap_request_kind( + &mut self, + _adapter_name: String, + config: Value, + ) -> Result { + match config.get("request").and_then(|v| v.as_str()) { + Some("attach") => Ok(StartDebuggingRequestArgumentsRequest::Attach), + _ => Ok(StartDebuggingRequestArgumentsRequest::Launch), + } + } + + fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { + let adapter_config = match config.request { + DebugRequest::Launch(launch) => { + let env: serde_json::Map = launch + .envs + .into_iter() + .map(|(k, v)| (k, Value::String(v))) + .collect(); + + let mut cfg = json!({ + "type": "mix_task", + "request": "launch", + "task": launch.program, + "taskArgs": launch.args, + "env": env, + }); + + if let Some(cwd) = launch.cwd { + cfg["projectDir"] = Value::String(cwd); + } + + cfg + } + DebugRequest::Attach(_) => json!({ + "type": "mix_task", + "request": "attach", + }), + }; + + Ok(DebugScenario { + label: config.label, + adapter: config.adapter, + build: None, + config: adapter_config.to_string(), + tcp_connection: None, + }) + } } zed::register_extension!(ElixirExtension); diff --git a/src/language_servers/elixir_ls.rs b/src/language_servers/elixir_ls.rs index bfba8f4..03173ca 100644 --- a/src/language_servers/elixir_ls.rs +++ b/src/language_servers/elixir_ls.rs @@ -15,6 +15,7 @@ struct ElixirLsBinary { pub struct ElixirLs { cached_binary_path: Option, + cached_debug_adapter_path: Option, } impl ElixirLs { @@ -23,6 +24,7 @@ impl ElixirLs { pub fn new() -> Self { Self { cached_binary_path: None, + cached_debug_adapter_path: None, } } @@ -31,7 +33,7 @@ impl ElixirLs { language_server_id: &LanguageServerId, worktree: &Worktree, ) -> Result { - let elixir_ls = self.language_server_binary(language_server_id, worktree)?; + let elixir_ls = self.language_server_binary(Some(language_server_id), worktree)?; Ok(zed::Command { command: elixir_ls.path, @@ -40,9 +42,36 @@ impl ElixirLs { }) } + pub fn get_debug_adapter_path(&mut self, worktree: &Worktree) -> Result { + if let Some(debug_path) = &self.cached_debug_adapter_path { + if fs::metadata(debug_path).is_ok_and(|stat| stat.is_file()) { + return Self::make_absolute(debug_path); + } + } + + self.language_server_binary(None, worktree)?; + + let path = self + .cached_debug_adapter_path + .as_deref() + .ok_or_else(|| "debug adapter path could not be determined".to_string())?; + + Self::make_absolute(path) + } + + fn make_absolute(path: &str) -> Result { + let p = std::path::Path::new(path); + if p.is_absolute() { + return Ok(path.to_string()); + } + std::env::current_dir() + .map(|cwd| cwd.join(p).to_string_lossy().to_string()) + .map_err(|e| format!("failed to resolve debug adapter path: {e}")) + } + fn language_server_binary( &mut self, - language_server_id: &LanguageServerId, + language_server_id: Option<&LanguageServerId>, worktree: &Worktree, ) -> Result { let (platform, _arch) = zed::current_platform(); @@ -58,6 +87,9 @@ impl ElixirLs { let debug_adapter = format!("debug_adapter.{extension}"); if let Some(binary_path) = config::get_binary_path(&binary_settings) { + self.cached_debug_adapter_path = std::path::Path::new(&binary_path) + .parent() + .map(|p| p.join(&debug_adapter).to_string_lossy().to_string()); return Ok(ElixirLsBinary { path: binary_path, args: binary_args, @@ -65,6 +97,9 @@ impl ElixirLs { } if let Some(binary_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { + self.cached_debug_adapter_path = std::path::Path::new(&binary_path) + .parent() + .map(|p| p.join(&debug_adapter).to_string_lossy().to_string()); return Ok(ElixirLsBinary { path: binary_path, args: binary_args, @@ -80,10 +115,12 @@ impl ElixirLs { }); } - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::CheckingForUpdate, - ); + if let Some(id) = language_server_id { + zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + } let release = match zed::latest_github_release( "elixir-lsp/elixir-ls", @@ -97,6 +134,9 @@ impl ElixirLs { if let Some(binary_path) = util::find_existing_binary(Self::LANGUAGE_SERVER_ID, &binary_name) { + self.cached_debug_adapter_path = std::path::Path::new(&binary_path) + .parent() + .map(|p| p.join(&debug_adapter).to_string_lossy().to_string()); self.cached_binary_path = Some(binary_path.clone()); return Ok(ElixirLsBinary { path: binary_path, @@ -125,10 +165,12 @@ impl ElixirLs { let debug_path = format!("{}/{}", version_dir, debug_adapter); if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { - zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); + if let Some(id) = language_server_id { + zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + } zed::download_file( &asset.download_url, @@ -144,6 +186,7 @@ impl ElixirLs { util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &version_dir)?; } + self.cached_debug_adapter_path = Some(debug_path); self.cached_binary_path = Some(binary_path.clone()); Ok(ElixirLsBinary { path: binary_path, From 2c8bd169e77ff955fe2f310139c4138be8d5481b Mon Sep 17 00:00:00 2001 From: Anders Fischer-Nielsen Date: Mon, 27 Apr 2026 12:15:58 +0200 Subject: [PATCH 04/11] Simplify schemas Co-authored-by: AltCode --- extension.toml | 2 +- schemas/elixir_debug_adapter.json | 42 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/extension.toml b/extension.toml index 05a0a52..d2b4d93 100644 --- a/extension.toml +++ b/extension.toml @@ -74,7 +74,7 @@ languages = [ "EEx" = "eex" "HEEx" = "heex" -[debug_adapters.elixir-ls] +[debug_adapters.ElixirLS] name = "ElixirLS" schema_path = "schemas/elixir_debug_adapter.json" diff --git a/schemas/elixir_debug_adapter.json b/schemas/elixir_debug_adapter.json index a945d67..d9d6b16 100644 --- a/schemas/elixir_debug_adapter.json +++ b/schemas/elixir_debug_adapter.json @@ -3,46 +3,44 @@ "title": "ElixirLS Debug Adapter Configuration", "type": "object", "properties": { - "type": { - "type": "string", - "const": "mix_task", - "description": "Must be 'mix_task' for ElixirLS" - }, "request": { "type": "string", "enum": ["launch", "attach"], - "description": "Whether to launch a new process or attach to an existing one" - }, - "name": { - "type": "string", - "description": "Display name for this debug configuration" + "description": "Whether to launch a new process or attach to an existing one", + "default": "launch" }, "task": { "type": "string", - "description": "Mix task to run (e.g. 'test', 'phx.server', 'run')" + "examples": ["run", "test", "phx.server"], + "description": "Mix task to invoke (e.g. `run`, `test`, `phx.server`)" }, "taskArgs": { "type": "array", "items": { "type": "string" }, - "description": "Arguments passed to the Mix task" + "description": "Arguments passed to the Mix task", + "default": [] }, "projectDir": { "type": "string", - "description": "Absolute path to the directory containing mix.exs" + "description": "Absolute path to the directory containing mix.exs", + "default": "$ZED_WORKTREE_ROOT" }, "startApps": { "type": "boolean", - "description": "Run 'mix app.start' before the debugger (default: false)" + "description": "Run `mix app.start` before the debugger (default: false)" }, "requireFiles": { "type": "array", "items": { "type": "string" }, - "description": "Extra .exs files to require and interpret before debugging" + "examples": [["test/**/test_helper.exs", "test/**/*_test.exs"]], + "default": [], + "description": "Extra `.exs` files to require and interpret before debugging" }, "excludeModules": { "type": "array", "items": { "type": "string" }, - "description": "Modules that should NOT be interpreted (e.g. NIFs)" + "description": "Modules that should NOT be interpreted (e.g. NIFs)", + "default": [] }, "debugAutoInterpretAllModules": { "type": "boolean", @@ -51,7 +49,8 @@ "debugInterpretModulesPatterns": { "type": "array", "items": { "type": "string" }, - "description": "Glob patterns for modules to interpret" + "description": "Glob patterns for modules to interpret", + "default": [] }, "exitAfterTaskReturns": { "type": "boolean", @@ -60,12 +59,13 @@ "env": { "type": "object", "additionalProperties": { "type": "string" }, - "description": "Environment variables to set for the debugged process" + "description": "Environment variables to set for the debugged process", + "default": {} }, "stackTraceMode": { "type": "string", "enum": ["all", "no_tail", "false"], - "description": "Argument passed to :int.stack_trace/1" + "description": "Argument passed to `:int.stack_trace/1` (default: no_tail)" }, "noDebug": { "type": "boolean", @@ -73,8 +73,8 @@ }, "breakOnDbg": { "type": "boolean", - "description": "Automatically break on Kernel.dbg/2 calls (default: true)" + "description": "Automatically break on `Kernel.dbg/2` calls (default: true)" } }, - "required": ["type", "request"] + "required": ["request", "task", "projectDir"] } From db98aa6a42cbf4f802ae71fb3f27b8019fd24d2f Mon Sep 17 00:00:00 2001 From: Anders Fischer-Nielsen Date: Mon, 27 Apr 2026 12:16:49 +0200 Subject: [PATCH 05/11] Simplify JSON mapping Co-authored-by: AltCode --- src/elixir.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/elixir.rs b/src/elixir.rs index b8f7aea..f038355 100644 --- a/src/elixir.rs +++ b/src/elixir.rs @@ -1,11 +1,13 @@ mod language_servers; +use std::str::FromStr; + use zed_extension_api::{ self as zed, CodeLabel, DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition, LanguageServerId, Result, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, Worktree, lsp::{Completion, Symbol}, - serde_json::{self, Value, json}, + serde_json::{Map, Value, json}, }; use crate::language_servers::{Dexter, ElixirLs, Expert, Lexical, NextLs}; @@ -166,21 +168,6 @@ impl zed::Extension for ElixirExtension { .get_debug_adapter_path(worktree)? }; - let request = serde_json::from_str::(&config.config) - .ok() - .and_then(|v| { - v.get("request") - .and_then(|r| r.as_str()) - .map(str::to_string) - }) - .unwrap_or_else(|| "launch".to_string()); - - let request_kind = if request == "attach" { - StartDebuggingRequestArgumentsRequest::Attach - } else { - StartDebuggingRequestArgumentsRequest::Launch - }; - Ok(DebugAdapterBinary { command: Some(binary_path), arguments: vec![], @@ -188,8 +175,14 @@ impl zed::Extension for ElixirExtension { cwd: None, connection: None, request_args: StartDebuggingRequestArguments { - configuration: config.config, - request: request_kind, + configuration: config.config.clone(), + request: self + .dap_request_kind( + _adapter_name, + Value::from_str(&config.config) + .map_err(|err| format!("Invalid JSON configuration: {err}"))?, + ) + .map_err(|err| format!("Failed to determine debug request kind: {err}"))?, }, }) } @@ -201,18 +194,25 @@ impl zed::Extension for ElixirExtension { ) -> Result { match config.get("request").and_then(|v| v.as_str()) { Some("attach") => Ok(StartDebuggingRequestArgumentsRequest::Attach), - _ => Ok(StartDebuggingRequestArgumentsRequest::Launch), + Some("launch") => Ok(StartDebuggingRequestArgumentsRequest::Launch), + Some(value) => Err(format!( + "Unexpected value for `request` key in ElixirLS debug adapter configuration: {value:?}" + )), + None => Err( + "Missing required `request` field in ElixirLS debug adapter configuration" + .to_string(), + ), } } fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { let adapter_config = match config.request { DebugRequest::Launch(launch) => { - let env: serde_json::Map = launch + let env: Map = launch .envs .into_iter() .map(|(k, v)| (k, Value::String(v))) - .collect(); + .collect::>(); let mut cfg = json!({ "type": "mix_task", From e6f7698898cdb0bc7a95f122ca8809fed12d3811 Mon Sep 17 00:00:00 2001 From: Anders Fischer-Nielsen Date: Mon, 27 Apr 2026 12:21:09 +0200 Subject: [PATCH 06/11] Rethink ElixirLS download handling --- src/language_servers/elixir_ls.rs | 133 ++++++++++++++---------------- 1 file changed, 62 insertions(+), 71 deletions(-) diff --git a/src/language_servers/elixir_ls.rs b/src/language_servers/elixir_ls.rs index 03173ca..47309c7 100644 --- a/src/language_servers/elixir_ls.rs +++ b/src/language_servers/elixir_ls.rs @@ -15,7 +15,6 @@ struct ElixirLsBinary { pub struct ElixirLs { cached_binary_path: Option, - cached_debug_adapter_path: Option, } impl ElixirLs { @@ -24,7 +23,6 @@ impl ElixirLs { pub fn new() -> Self { Self { cached_binary_path: None, - cached_debug_adapter_path: None, } } @@ -33,47 +31,39 @@ impl ElixirLs { language_server_id: &LanguageServerId, worktree: &Worktree, ) -> Result { - let elixir_ls = self.language_server_binary(Some(language_server_id), worktree)?; - + let binary = self.language_server_binary(language_server_id, worktree)?; Ok(zed::Command { - command: elixir_ls.path, - args: elixir_ls.args, + command: binary.path, + args: binary.args, env: Default::default(), }) } pub fn get_debug_adapter_path(&mut self, worktree: &Worktree) -> Result { - if let Some(debug_path) = &self.cached_debug_adapter_path { - if fs::metadata(debug_path).is_ok_and(|stat| stat.is_file()) { - return Self::make_absolute(debug_path); - } - } - - self.language_server_binary(None, worktree)?; - - let path = self - .cached_debug_adapter_path - .as_deref() - .ok_or_else(|| "debug adapter path could not be determined".to_string())?; - - Self::make_absolute(path) + let (_, debug_adapter_path) = self.download_elixir_ls(None, worktree)?; + Ok(debug_adapter_path) } - fn make_absolute(path: &str) -> Result { - let p = std::path::Path::new(path); - if p.is_absolute() { - return Ok(path.to_string()); - } - std::env::current_dir() - .map(|cwd| cwd.join(p).to_string_lossy().to_string()) - .map_err(|e| format!("failed to resolve debug adapter path: {e}")) + fn language_server_binary( + &mut self, + language_server_id: &LanguageServerId, + worktree: &Worktree, + ) -> Result { + let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); + let args = config::get_binary_args(&binary_settings).unwrap_or_default(); + let (language_server_path, _) = + self.download_elixir_ls(Some(language_server_id), worktree)?; + Ok(ElixirLsBinary { + path: language_server_path, + args, + }) } - fn language_server_binary( + fn download_elixir_ls( &mut self, language_server_id: Option<&LanguageServerId>, worktree: &Worktree, - ) -> Result { + ) -> Result<(String, String)> { let (platform, _arch) = zed::current_platform(); let extension = match platform { zed::Os::Mac | zed::Os::Linux => "sh", @@ -81,38 +71,34 @@ impl ElixirLs { }; let binary_name = format!("language_server.{extension}"); - let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); - let binary_args = config::get_binary_args(&binary_settings).unwrap_or_default(); + let debug_adapter_name = format!("debug_adapter.{extension}"); let launch_script = format!("launch.{extension}"); - let debug_adapter = format!("debug_adapter.{extension}"); + let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); - if let Some(binary_path) = config::get_binary_path(&binary_settings) { - self.cached_debug_adapter_path = std::path::Path::new(&binary_path) + if let Some(language_server_path) = config::get_binary_path(&binary_settings) { + let debug_adapter_path = std::path::Path::new(&language_server_path) .parent() - .map(|p| p.join(&debug_adapter).to_string_lossy().to_string()); - return Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }); + .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) + .ok_or_else(|| "failed to determine debug adapter path".to_string())?; + return Ok((language_server_path, debug_adapter_path)); } - if let Some(binary_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { - self.cached_debug_adapter_path = std::path::Path::new(&binary_path) + if let Some(language_server_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { + let debug_adapter_path = std::path::Path::new(&language_server_path) .parent() - .map(|p| p.join(&debug_adapter).to_string_lossy().to_string()); - return Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }); + .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) + .ok_or_else(|| "failed to determine debug adapter path".to_string())?; + return Ok((language_server_path, debug_adapter_path)); } - if let Some(binary_path) = &self.cached_binary_path - && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) + if let Some(language_server_path) = &self.cached_binary_path + && fs::metadata(language_server_path).is_ok_and(|stat| stat.is_file()) { - return Ok(ElixirLsBinary { - path: binary_path.clone(), - args: binary_args, - }); + let debug_adapter_path = std::path::Path::new(language_server_path) + .parent() + .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) + .ok_or_else(|| "failed to determine debug adapter path".to_string())?; + return Ok((language_server_path.clone(), debug_adapter_path)); } if let Some(id) = language_server_id { @@ -131,17 +117,19 @@ impl ElixirLs { ) { Ok(release) => release, Err(_) => { - if let Some(binary_path) = + if let Some(ls_path) = util::find_existing_binary(Self::LANGUAGE_SERVER_ID, &binary_name) { - self.cached_debug_adapter_path = std::path::Path::new(&binary_path) - .parent() - .map(|p| p.join(&debug_adapter).to_string_lossy().to_string()); - self.cached_binary_path = Some(binary_path.clone()); - return Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }); + let absolute_language_server_path = fs::canonicalize(format!("./{ls_path}")) + .map(|p| p.to_string_lossy().to_string()) + .map_err(|e| format!("failed to get absolute language server path: {e}"))?; + let absolute_debug_adapter_path = + std::path::Path::new(&absolute_language_server_path) + .parent() + .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) + .ok_or_else(|| "failed to determine debug adapter path".to_string())?; + self.cached_binary_path = Some(absolute_language_server_path.clone()); + return Ok((absolute_language_server_path, absolute_debug_adapter_path)); } return Err("failed to download latest github release".to_string()); } @@ -160,9 +148,9 @@ impl ElixirLs { .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; let version_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, release.version); - let binary_path = format!("{}/{}", version_dir, binary_name); - let launch_path = format!("{}/{}", version_dir, launch_script); - let debug_path = format!("{}/{}", version_dir, debug_adapter); + let binary_path = format!("{version_dir}/{binary_name}"); + let launch_path = format!("{version_dir}/{launch_script}"); + let debug_path = format!("{version_dir}/{debug_adapter_name}"); if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { if let Some(id) = language_server_id { @@ -186,12 +174,15 @@ impl ElixirLs { util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &version_dir)?; } - self.cached_debug_adapter_path = Some(debug_path); - self.cached_binary_path = Some(binary_path.clone()); - Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }) + let absolute_language_server_path = fs::canonicalize(format!("./{binary_path}")) + .map(|p| p.to_string_lossy().to_string()) + .map_err(|e| format!("failed to get absolute language server path: {e}"))?; + let absolute_debug_adapter_path = fs::canonicalize(format!("./{debug_path}")) + .map(|p| p.to_string_lossy().to_string()) + .map_err(|e| format!("failed to get absolute debug adapter path: {e}"))?; + + self.cached_binary_path = Some(absolute_language_server_path.clone()); + Ok((absolute_language_server_path, absolute_debug_adapter_path)) } pub fn language_server_initialization_options( From 2bad1a674b20e00c0cb031a176c8d7ee6c428076 Mon Sep 17 00:00:00 2001 From: AltCode Date: Mon, 27 Apr 2026 14:23:17 +0200 Subject: [PATCH 07/11] Further split download handling --- src/language_servers/elixir_ls.rs | 156 +++++++++++++++--------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/src/language_servers/elixir_ls.rs b/src/language_servers/elixir_ls.rs index 47309c7..5ae4f50 100644 --- a/src/language_servers/elixir_ls.rs +++ b/src/language_servers/elixir_ls.rs @@ -14,7 +14,8 @@ struct ElixirLsBinary { } pub struct ElixirLs { - cached_binary_path: Option, + cached_lsp_binary_path: Option, + cached_dap_binary_path: Option, } impl ElixirLs { @@ -22,7 +23,8 @@ impl ElixirLs { pub fn new() -> Self { Self { - cached_binary_path: None, + cached_lsp_binary_path: None, + cached_dap_binary_path: None, } } @@ -31,17 +33,24 @@ impl ElixirLs { language_server_id: &LanguageServerId, worktree: &Worktree, ) -> Result { - let binary = self.language_server_binary(language_server_id, worktree)?; + let elixir_ls = self.language_server_binary(language_server_id, worktree)?; + Ok(zed::Command { - command: binary.path, - args: binary.args, + command: elixir_ls.path, + args: elixir_ls.args, env: Default::default(), }) } - pub fn get_debug_adapter_path(&mut self, worktree: &Worktree) -> Result { - let (_, debug_adapter_path) = self.download_elixir_ls(None, worktree)?; - Ok(debug_adapter_path) + pub fn get_debug_adapter_path(&mut self, _worktree: &Worktree) -> Result { + if let Some(binary_path) = &self.cached_dap_binary_path + && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) + { + return Ok(binary_path.clone()); + } + + let (_, binary_path) = self.download_elixir_ls(None)?; + Ok(binary_path) } fn language_server_binary( @@ -50,60 +59,55 @@ impl ElixirLs { worktree: &Worktree, ) -> Result { let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); - let args = config::get_binary_args(&binary_settings).unwrap_or_default(); - let (language_server_path, _) = - self.download_elixir_ls(Some(language_server_id), worktree)?; + let binary_args = config::get_binary_args(&binary_settings).unwrap_or_default(); + + if let Some(binary_path) = config::get_binary_path(&binary_settings) { + return Ok(ElixirLsBinary { + path: binary_path, + args: binary_args, + }); + } + + if let Some(binary_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { + return Ok(ElixirLsBinary { + path: binary_path, + args: binary_args, + }); + } + + if let Some(binary_path) = &self.cached_lsp_binary_path + && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) + { + return Ok(ElixirLsBinary { + path: binary_path.clone(), + args: binary_args, + }); + } + + let (binary_path, _) = self.download_elixir_ls(Some(language_server_id))?; Ok(ElixirLsBinary { - path: language_server_path, - args, + path: binary_path, + args: binary_args, }) } fn download_elixir_ls( &mut self, language_server_id: Option<&LanguageServerId>, - worktree: &Worktree, ) -> Result<(String, String)> { let (platform, _arch) = zed::current_platform(); let extension = match platform { - zed::Os::Mac | zed::Os::Linux => "sh", - zed::Os::Windows => "bat", + zed::Os::Mac | zed::Os::Linux => ".sh", + zed::Os::Windows => ".bat", }; - let binary_name = format!("language_server.{extension}"); - let debug_adapter_name = format!("debug_adapter.{extension}"); - let launch_script = format!("launch.{extension}"); - let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); - - if let Some(language_server_path) = config::get_binary_path(&binary_settings) { - let debug_adapter_path = std::path::Path::new(&language_server_path) - .parent() - .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) - .ok_or_else(|| "failed to determine debug adapter path".to_string())?; - return Ok((language_server_path, debug_adapter_path)); - } - - if let Some(language_server_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { - let debug_adapter_path = std::path::Path::new(&language_server_path) - .parent() - .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) - .ok_or_else(|| "failed to determine debug adapter path".to_string())?; - return Ok((language_server_path, debug_adapter_path)); - } - - if let Some(language_server_path) = &self.cached_binary_path - && fs::metadata(language_server_path).is_ok_and(|stat| stat.is_file()) - { - let debug_adapter_path = std::path::Path::new(language_server_path) - .parent() - .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) - .ok_or_else(|| "failed to determine debug adapter path".to_string())?; - return Ok((language_server_path.clone(), debug_adapter_path)); - } + let language_server = format!("language_server{extension}"); + let launch_script = format!("launch{extension}"); + let debug_adapter = format!("debug_adapter{extension}"); - if let Some(id) = language_server_id { + if let Some(language_server_id) = language_server_id { zed::set_language_server_installation_status( - id, + language_server_id, &zed::LanguageServerInstallationStatus::CheckingForUpdate, ); } @@ -117,19 +121,17 @@ impl ElixirLs { ) { Ok(release) => release, Err(_) => { - if let Some(ls_path) = - util::find_existing_binary(Self::LANGUAGE_SERVER_ID, &binary_name) - { - let absolute_language_server_path = fs::canonicalize(format!("./{ls_path}")) - .map(|p| p.to_string_lossy().to_string()) - .map_err(|e| format!("failed to get absolute language server path: {e}"))?; - let absolute_debug_adapter_path = - std::path::Path::new(&absolute_language_server_path) + if let Some(lsp_binary_path) = + util::find_existing_binary(Self::LANGUAGE_SERVER_ID, &language_server) + && let Some(dap_binary_path) = + fs::canonicalize(format!("./{}", lsp_binary_path)) + .map_err(|e| format!("failed to resolve debug adapter path: {e}"))? .parent() - .map(|p| p.join(&debug_adapter_name).to_string_lossy().to_string()) - .ok_or_else(|| "failed to determine debug adapter path".to_string())?; - self.cached_binary_path = Some(absolute_language_server_path.clone()); - return Ok((absolute_language_server_path, absolute_debug_adapter_path)); + .map(|path| path.join(debug_adapter).to_string_lossy().to_string()) + { + self.cached_lsp_binary_path = Some(lsp_binary_path.clone()); + self.cached_dap_binary_path = Some(dap_binary_path.clone()); + return Ok((lsp_binary_path, dap_binary_path)); } return Err("failed to download latest github release".to_string()); } @@ -148,14 +150,14 @@ impl ElixirLs { .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; let version_dir = format!("{}-{}", Self::LANGUAGE_SERVER_ID, release.version); - let binary_path = format!("{version_dir}/{binary_name}"); - let launch_path = format!("{version_dir}/{launch_script}"); - let debug_path = format!("{version_dir}/{debug_adapter_name}"); + let lsp_binary_path = format!("{}/{}", version_dir, language_server); + let launch_binary_path = format!("{}/{}", version_dir, launch_script); + let dap_binary_path = format!("{}/{}", version_dir, debug_adapter); - if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { - if let Some(id) = language_server_id { + if !fs::metadata(&lsp_binary_path).is_ok_and(|stat| stat.is_file()) { + if let Some(language_server_id) = language_server_id { zed::set_language_server_installation_status( - id, + language_server_id, &zed::LanguageServerInstallationStatus::Downloading, ); } @@ -167,22 +169,20 @@ impl ElixirLs { ) .map_err(|e| format!("failed to download file: {e}"))?; - zed::make_file_executable(&binary_path)?; - zed::make_file_executable(&launch_path)?; - zed::make_file_executable(&debug_path)?; + zed::make_file_executable(&lsp_binary_path)?; + zed::make_file_executable(&launch_binary_path)?; + zed::make_file_executable(&dap_binary_path)?; util::remove_outdated_versions(Self::LANGUAGE_SERVER_ID, &version_dir)?; } - let absolute_language_server_path = fs::canonicalize(format!("./{binary_path}")) - .map(|p| p.to_string_lossy().to_string()) - .map_err(|e| format!("failed to get absolute language server path: {e}"))?; - let absolute_debug_adapter_path = fs::canonicalize(format!("./{debug_path}")) - .map(|p| p.to_string_lossy().to_string()) - .map_err(|e| format!("failed to get absolute debug adapter path: {e}"))?; - - self.cached_binary_path = Some(absolute_language_server_path.clone()); - Ok((absolute_language_server_path, absolute_debug_adapter_path)) + let dap_binary_path = fs::canonicalize(format!("./{}", dap_binary_path)) + .map_err(|e| format!("failed to resolve debug adapter path: {e}"))? + .to_string_lossy() + .to_string(); + self.cached_lsp_binary_path = Some(lsp_binary_path.clone()); + self.cached_dap_binary_path = Some(dap_binary_path.clone()); + Ok((lsp_binary_path, dap_binary_path)) } pub fn language_server_initialization_options( From 353cb2efd28a5e6a545bc8612808152018e87f38 Mon Sep 17 00:00:00 2001 From: AltCode Date: Mon, 27 Apr 2026 14:37:08 +0200 Subject: [PATCH 08/11] Adjust DAP schema - Remove name key and use the default location instead - Make `requireFiles` example the default instead - Reorganize fields --- .../ElixirLS.json | 27 +++++++++---------- extension.toml | 2 -- 2 files changed, 13 insertions(+), 16 deletions(-) rename schemas/elixir_debug_adapter.json => debug_adapter_schemas/ElixirLS.json (84%) diff --git a/schemas/elixir_debug_adapter.json b/debug_adapter_schemas/ElixirLS.json similarity index 84% rename from schemas/elixir_debug_adapter.json rename to debug_adapter_schemas/ElixirLS.json index d9d6b16..640ecf9 100644 --- a/schemas/elixir_debug_adapter.json +++ b/debug_adapter_schemas/ElixirLS.json @@ -6,8 +6,8 @@ "request": { "type": "string", "enum": ["launch", "attach"], - "description": "Whether to launch a new process or attach to an existing one", - "default": "launch" + "default": "launch", + "description": "Whether to launch a new process or attach to an existing one" }, "task": { "type": "string", @@ -17,13 +17,13 @@ "taskArgs": { "type": "array", "items": { "type": "string" }, - "description": "Arguments passed to the Mix task", - "default": [] + "default": [], + "description": "Arguments passed to the Mix task" }, "projectDir": { "type": "string", - "description": "Absolute path to the directory containing mix.exs", - "default": "$ZED_WORKTREE_ROOT" + "default": "$ZED_WORKTREE_ROOT", + "description": "Absolute path to the directory containing `mix.exs`" }, "startApps": { "type": "boolean", @@ -32,15 +32,14 @@ "requireFiles": { "type": "array", "items": { "type": "string" }, - "examples": [["test/**/test_helper.exs", "test/**/*_test.exs"]], - "default": [], + "default": ["test/**/test_helper.exs", "test/**/*_test.exs"], "description": "Extra `.exs` files to require and interpret before debugging" }, "excludeModules": { "type": "array", "items": { "type": "string" }, - "description": "Modules that should NOT be interpreted (e.g. NIFs)", - "default": [] + "default": [], + "description": "Modules that should NOT be interpreted (e.g. NIFs)" }, "debugAutoInterpretAllModules": { "type": "boolean", @@ -49,8 +48,8 @@ "debugInterpretModulesPatterns": { "type": "array", "items": { "type": "string" }, - "description": "Glob patterns for modules to interpret", - "default": [] + "default": [], + "description": "Glob patterns for modules to interpret" }, "exitAfterTaskReturns": { "type": "boolean", @@ -59,8 +58,8 @@ "env": { "type": "object", "additionalProperties": { "type": "string" }, - "description": "Environment variables to set for the debugged process", - "default": {} + "default": {}, + "description": "Environment variables to set for the debugged process" }, "stackTraceMode": { "type": "string", diff --git a/extension.toml b/extension.toml index d2b4d93..8910411 100644 --- a/extension.toml +++ b/extension.toml @@ -75,8 +75,6 @@ languages = [ "HEEx" = "heex" [debug_adapters.ElixirLS] -name = "ElixirLS" -schema_path = "schemas/elixir_debug_adapter.json" [grammars.elixir] repository = "https://github.com/elixir-lang/tree-sitter-elixir" From aa63ff105e09184a4d1d984d826038eb909894b9 Mon Sep 17 00:00:00 2001 From: AltCode Date: Mon, 27 Apr 2026 15:06:33 +0200 Subject: [PATCH 09/11] Move DAP implementation into `elixir_ls` module This is to future-proof for the potential addition of more debug adapters (e.g. an official one) --- src/elixir.rs | 107 ++++++++---------------------- src/language_servers/elixir_ls.rs | 99 +++++++++++++++++++++++++-- 2 files changed, 122 insertions(+), 84 deletions(-) diff --git a/src/elixir.rs b/src/elixir.rs index f038355..dd9f5da 100644 --- a/src/elixir.rs +++ b/src/elixir.rs @@ -1,13 +1,10 @@ mod language_servers; -use std::str::FromStr; - use zed_extension_api::{ - self as zed, CodeLabel, DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, - DebugTaskDefinition, LanguageServerId, Result, StartDebuggingRequestArguments, - StartDebuggingRequestArgumentsRequest, Worktree, + self as zed, CodeLabel, DebugAdapterBinary, DebugConfig, DebugScenario, DebugTaskDefinition, + LanguageServerId, Result, StartDebuggingRequestArgumentsRequest, Worktree, lsp::{Completion, Symbol}, - serde_json::{Map, Value, json}, + serde_json::Value, }; use crate::language_servers::{Dexter, ElixirLs, Expert, Lexical, NextLs}; @@ -155,92 +152,42 @@ impl zed::Extension for ElixirExtension { fn get_dap_binary( &mut self, - _adapter_name: String, + adapter_name: String, config: DebugTaskDefinition, user_provided_debug_adapter_path: Option, worktree: &Worktree, - ) -> Result { - let binary_path = if let Some(path) = user_provided_debug_adapter_path { - path - } else { - self.elixir_ls + ) -> Result { + match adapter_name.as_str() { + ElixirLs::DEBUG_ADAPTER_NAME => self + .elixir_ls .get_or_insert_with(ElixirLs::new) - .get_debug_adapter_path(worktree)? - }; - - Ok(DebugAdapterBinary { - command: Some(binary_path), - arguments: vec![], - envs: vec![], - cwd: None, - connection: None, - request_args: StartDebuggingRequestArguments { - configuration: config.config.clone(), - request: self - .dap_request_kind( - _adapter_name, - Value::from_str(&config.config) - .map_err(|err| format!("Invalid JSON configuration: {err}"))?, - ) - .map_err(|err| format!("Failed to determine debug request kind: {err}"))?, - }, - }) + .get_dap_binary(config, user_provided_debug_adapter_path, worktree), + adapter_name => Err(format!("unknown debug adapter: {adapter_name}")), + } } fn dap_request_kind( &mut self, - _adapter_name: String, + adapter_name: String, config: Value, - ) -> Result { - match config.get("request").and_then(|v| v.as_str()) { - Some("attach") => Ok(StartDebuggingRequestArgumentsRequest::Attach), - Some("launch") => Ok(StartDebuggingRequestArgumentsRequest::Launch), - Some(value) => Err(format!( - "Unexpected value for `request` key in ElixirLS debug adapter configuration: {value:?}" - )), - None => Err( - "Missing required `request` field in ElixirLS debug adapter configuration" - .to_string(), - ), + ) -> Result { + match adapter_name.as_str() { + ElixirLs::DEBUG_ADAPTER_NAME => self + .elixir_ls + .get_or_insert_with(ElixirLs::new) + .dap_request_kind(config), + adapter_name => Err(format!("unknown debug adapter: {adapter_name}")), } } - fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { - let adapter_config = match config.request { - DebugRequest::Launch(launch) => { - let env: Map = launch - .envs - .into_iter() - .map(|(k, v)| (k, Value::String(v))) - .collect::>(); - - let mut cfg = json!({ - "type": "mix_task", - "request": "launch", - "task": launch.program, - "taskArgs": launch.args, - "env": env, - }); - - if let Some(cwd) = launch.cwd { - cfg["projectDir"] = Value::String(cwd); - } - - cfg - } - DebugRequest::Attach(_) => json!({ - "type": "mix_task", - "request": "attach", - }), - }; - - Ok(DebugScenario { - label: config.label, - adapter: config.adapter, - build: None, - config: adapter_config.to_string(), - tcp_connection: None, - }) + fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { + match config.adapter.as_str() { + ElixirLs::DEBUG_ADAPTER_NAME => self + .elixir_ls + .get_or_insert_with(ElixirLs::new) + .dap_config_to_scenario(config), + adapter_name => Err(format!("unknown debug adapter: {adapter_name}")), + } } } diff --git a/src/language_servers/elixir_ls.rs b/src/language_servers/elixir_ls.rs index 5ae4f50..28983ec 100644 --- a/src/language_servers/elixir_ls.rs +++ b/src/language_servers/elixir_ls.rs @@ -1,9 +1,11 @@ -use std::fs; +use std::{fs, str::FromStr}; use zed_extension_api::{ - self as zed, CodeLabel, CodeLabelSpan, LanguageServerId, Result, Worktree, + self as zed, CodeLabel, CodeLabelSpan, DebugAdapterBinary, DebugConfig, DebugRequest, + DebugScenario, DebugTaskDefinition, LanguageServerId, Result, StartDebuggingRequestArguments, + StartDebuggingRequestArgumentsRequest, Worktree, lsp::{Completion, CompletionKind, Symbol, SymbolKind}, - serde_json::{Value, json}, + serde_json::{Map, Value, json}, }; use crate::language_servers::{config, util}; @@ -20,6 +22,7 @@ pub struct ElixirLs { impl ElixirLs { pub const LANGUAGE_SERVER_ID: &'static str = "elixir-ls"; + pub const DEBUG_ADAPTER_NAME: &'static str = "ElixirLS"; pub fn new() -> Self { Self { @@ -42,7 +45,42 @@ impl ElixirLs { }) } - pub fn get_debug_adapter_path(&mut self, _worktree: &Worktree) -> Result { + pub fn get_dap_binary( + &mut self, + config: DebugTaskDefinition, + user_provided_debug_adapter_path: Option, + _worktree: &Worktree, + ) -> Result { + let elixir_ls = self.debug_adapter_binary(user_provided_debug_adapter_path)?; + + let request = self + .dap_request_kind( + Value::from_str(&config.config) + .map_err(|err| format!("Invalid JSON configuration: {err}"))?, + ) + .map_err(|err| format!("Failed to determine debug request kind: {err}"))?; + + Ok(DebugAdapterBinary { + command: Some(elixir_ls), + arguments: vec![], + envs: vec![], + cwd: None, + connection: None, + request_args: StartDebuggingRequestArguments { + configuration: config.config, + request: request, + }, + }) + } + + fn debug_adapter_binary( + &mut self, + user_provided_debug_adapter_path: Option, + ) -> Result { + if let Some(binary_path) = user_provided_debug_adapter_path { + return Ok(binary_path); + } + if let Some(binary_path) = &self.cached_dap_binary_path && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) { @@ -185,6 +223,59 @@ impl ElixirLs { Ok((lsp_binary_path, dap_binary_path)) } + pub fn dap_request_kind( + &mut self, + config: Value, + ) -> Result { + match config.get("request").and_then(|v| v.as_str()) { + Some("attach") => Ok(StartDebuggingRequestArgumentsRequest::Attach), + Some("launch") => Ok(StartDebuggingRequestArgumentsRequest::Launch), + Some(value) => Err(format!( + "Unexpected value for `request` key in ElixirLS debug adapter configuration: {value:?}" + )), + None => Err( + "Missing required `request` field in ElixirLS debug adapter configuration" + .to_string(), + ), + } + } + + pub fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { + let adapter_config = match config.request { + DebugRequest::Launch(launch) => { + let env = launch + .envs + .into_iter() + .map(|(k, v)| (k, Value::String(v))) + .collect::>(); + + let mut cfg = json!({ + "request": "launch", + "task": launch.program, + "taskArgs": launch.args, + "env": env, + }); + + if let Some(cwd) = launch.cwd { + cfg["projectDir"] = Value::String(cwd); + } + + cfg + } + DebugRequest::Attach(_) => json!({ + "request": "attach", + }), + }; + + Ok(DebugScenario { + label: config.label, + adapter: config.adapter, + build: None, + config: adapter_config.to_string(), + tcp_connection: None, + }) + } + pub fn language_server_initialization_options( &mut self, _worktree: &Worktree, From 94ffb63b7c46b8eb2bf5f3b45f095e2f36a52c39 Mon Sep 17 00:00:00 2001 From: AltCode Date: Tue, 28 Apr 2026 01:29:24 +0200 Subject: [PATCH 10/11] Reorganize `elixir_ls` module functions --- src/language_servers/elixir_ls.rs | 282 +++++++++++++++--------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/src/language_servers/elixir_ls.rs b/src/language_servers/elixir_ls.rs index 28983ec..9277e65 100644 --- a/src/language_servers/elixir_ls.rs +++ b/src/language_servers/elixir_ls.rs @@ -31,104 +31,6 @@ impl ElixirLs { } } - pub fn language_server_command( - &mut self, - language_server_id: &LanguageServerId, - worktree: &Worktree, - ) -> Result { - let elixir_ls = self.language_server_binary(language_server_id, worktree)?; - - Ok(zed::Command { - command: elixir_ls.path, - args: elixir_ls.args, - env: Default::default(), - }) - } - - pub fn get_dap_binary( - &mut self, - config: DebugTaskDefinition, - user_provided_debug_adapter_path: Option, - _worktree: &Worktree, - ) -> Result { - let elixir_ls = self.debug_adapter_binary(user_provided_debug_adapter_path)?; - - let request = self - .dap_request_kind( - Value::from_str(&config.config) - .map_err(|err| format!("Invalid JSON configuration: {err}"))?, - ) - .map_err(|err| format!("Failed to determine debug request kind: {err}"))?; - - Ok(DebugAdapterBinary { - command: Some(elixir_ls), - arguments: vec![], - envs: vec![], - cwd: None, - connection: None, - request_args: StartDebuggingRequestArguments { - configuration: config.config, - request: request, - }, - }) - } - - fn debug_adapter_binary( - &mut self, - user_provided_debug_adapter_path: Option, - ) -> Result { - if let Some(binary_path) = user_provided_debug_adapter_path { - return Ok(binary_path); - } - - if let Some(binary_path) = &self.cached_dap_binary_path - && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) - { - return Ok(binary_path.clone()); - } - - let (_, binary_path) = self.download_elixir_ls(None)?; - Ok(binary_path) - } - - fn language_server_binary( - &mut self, - language_server_id: &LanguageServerId, - worktree: &Worktree, - ) -> Result { - let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); - let binary_args = config::get_binary_args(&binary_settings).unwrap_or_default(); - - if let Some(binary_path) = config::get_binary_path(&binary_settings) { - return Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }); - } - - if let Some(binary_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { - return Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }); - } - - if let Some(binary_path) = &self.cached_lsp_binary_path - && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) - { - return Ok(ElixirLsBinary { - path: binary_path.clone(), - args: binary_args, - }); - } - - let (binary_path, _) = self.download_elixir_ls(Some(language_server_id))?; - Ok(ElixirLsBinary { - path: binary_path, - args: binary_args, - }) - } - fn download_elixir_ls( &mut self, language_server_id: Option<&LanguageServerId>, @@ -223,56 +125,55 @@ impl ElixirLs { Ok((lsp_binary_path, dap_binary_path)) } - pub fn dap_request_kind( + pub fn language_server_command( &mut self, - config: Value, - ) -> Result { - match config.get("request").and_then(|v| v.as_str()) { - Some("attach") => Ok(StartDebuggingRequestArgumentsRequest::Attach), - Some("launch") => Ok(StartDebuggingRequestArgumentsRequest::Launch), - Some(value) => Err(format!( - "Unexpected value for `request` key in ElixirLS debug adapter configuration: {value:?}" - )), - None => Err( - "Missing required `request` field in ElixirLS debug adapter configuration" - .to_string(), - ), - } + language_server_id: &LanguageServerId, + worktree: &Worktree, + ) -> Result { + let elixir_ls = self.language_server_binary(language_server_id, worktree)?; + + Ok(zed::Command { + command: elixir_ls.path, + args: elixir_ls.args, + env: Default::default(), + }) } - pub fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { - let adapter_config = match config.request { - DebugRequest::Launch(launch) => { - let env = launch - .envs - .into_iter() - .map(|(k, v)| (k, Value::String(v))) - .collect::>(); + fn language_server_binary( + &mut self, + language_server_id: &LanguageServerId, + worktree: &Worktree, + ) -> Result { + let binary_settings = config::get_binary_settings(Self::LANGUAGE_SERVER_ID, worktree); + let binary_args = config::get_binary_args(&binary_settings).unwrap_or_default(); - let mut cfg = json!({ - "request": "launch", - "task": launch.program, - "taskArgs": launch.args, - "env": env, - }); + if let Some(binary_path) = config::get_binary_path(&binary_settings) { + return Ok(ElixirLsBinary { + path: binary_path, + args: binary_args, + }); + } - if let Some(cwd) = launch.cwd { - cfg["projectDir"] = Value::String(cwd); - } + if let Some(binary_path) = worktree.which(Self::LANGUAGE_SERVER_ID) { + return Ok(ElixirLsBinary { + path: binary_path, + args: binary_args, + }); + } - cfg - } - DebugRequest::Attach(_) => json!({ - "request": "attach", - }), - }; + if let Some(binary_path) = &self.cached_lsp_binary_path + && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) + { + return Ok(ElixirLsBinary { + path: binary_path.clone(), + args: binary_args, + }); + } - Ok(DebugScenario { - label: config.label, - adapter: config.adapter, - build: None, - config: adapter_config.to_string(), - tcp_connection: None, + let (binary_path, _) = self.download_elixir_ls(Some(language_server_id))?; + Ok(ElixirLsBinary { + path: binary_path, + args: binary_args, }) } @@ -417,4 +318,103 @@ impl ElixirLs { code, }) } + + pub fn get_dap_binary( + &mut self, + config: DebugTaskDefinition, + user_provided_debug_adapter_path: Option, + _worktree: &Worktree, + ) -> Result { + let elixir_ls = self.debug_adapter_binary(user_provided_debug_adapter_path)?; + + let request = self + .dap_request_kind( + Value::from_str(&config.config) + .map_err(|err| format!("Invalid JSON configuration: {err}"))?, + ) + .map_err(|err| format!("Failed to determine debug request kind: {err}"))?; + + Ok(DebugAdapterBinary { + command: Some(elixir_ls), + arguments: vec![], + envs: vec![], + cwd: None, + connection: None, + request_args: StartDebuggingRequestArguments { + configuration: config.config, + request: request, + }, + }) + } + + fn debug_adapter_binary( + &mut self, + user_provided_debug_adapter_path: Option, + ) -> Result { + if let Some(binary_path) = user_provided_debug_adapter_path { + return Ok(binary_path); + } + + if let Some(binary_path) = &self.cached_dap_binary_path + && fs::metadata(binary_path).is_ok_and(|stat| stat.is_file()) + { + return Ok(binary_path.clone()); + } + + let (_, binary_path) = self.download_elixir_ls(None)?; + Ok(binary_path) + } + + pub fn dap_request_kind( + &mut self, + config: Value, + ) -> Result { + match config.get("request").and_then(|v| v.as_str()) { + Some("attach") => Ok(StartDebuggingRequestArgumentsRequest::Attach), + Some("launch") => Ok(StartDebuggingRequestArgumentsRequest::Launch), + Some(value) => Err(format!( + "Unexpected value for `request` key in ElixirLS debug adapter configuration: {value:?}" + )), + None => Err( + "Missing required `request` field in ElixirLS debug adapter configuration" + .to_string(), + ), + } + } + + pub fn dap_config_to_scenario(&mut self, config: DebugConfig) -> Result { + let adapter_config = match config.request { + DebugRequest::Launch(launch) => { + let env = launch + .envs + .into_iter() + .map(|(k, v)| (k, Value::String(v))) + .collect::>(); + + let mut cfg = json!({ + "request": "launch", + "task": launch.program, + "taskArgs": launch.args, + "env": env, + }); + + if let Some(cwd) = launch.cwd { + cfg["projectDir"] = Value::String(cwd); + } + + cfg + } + DebugRequest::Attach(_) => json!({ + "request": "attach", + }), + }; + + Ok(DebugScenario { + label: config.label, + adapter: config.adapter, + build: None, + config: adapter_config.to_string(), + tcp_connection: None, + }) + } } From 1ed9894bad14095f46624ec148322094e57fd898 Mon Sep 17 00:00:00 2001 From: AltCode Date: Tue, 28 Apr 2026 10:06:45 +0200 Subject: [PATCH 11/11] Fix clippy error --- src/language_servers/elixir_ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language_servers/elixir_ls.rs b/src/language_servers/elixir_ls.rs index 9277e65..14fa884 100644 --- a/src/language_servers/elixir_ls.rs +++ b/src/language_servers/elixir_ls.rs @@ -342,7 +342,7 @@ impl ElixirLs { connection: None, request_args: StartDebuggingRequestArguments { configuration: config.config, - request: request, + request, }, }) }