From fa906169eb743860b25ef1c3f9c7a48977b66bb1 Mon Sep 17 00:00:00 2001 From: Jengro Date: Thu, 6 Nov 2025 12:30:34 +0800 Subject: [PATCH 1/5] vls replace v-analyzer --- src/v.rs | 274 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 138 insertions(+), 136 deletions(-) diff --git a/src/v.rs b/src/v.rs index e855202..4e3edca 100644 --- a/src/v.rs +++ b/src/v.rs @@ -3,158 +3,160 @@ use zed::{CodeLabel, CodeLabelSpan, LanguageServerId}; use zed_extension_api::{self as zed, Result}; struct VExtension { - current_version: String, - cached_binary_path: Option + current_version: String, + cached_binary_path: Option, } fn try_local_install(err: T, worktree: &zed::Worktree) -> Result { - if let Some(path) = worktree.which("v-analyzer") { - return Ok(path); - } - return Err(err); + if let Some(path) = worktree.which("vls") { + return Ok(path); + } + Err(err) } fn language_server_binary_path_no_fallback( - selff: &mut VExtension, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, + selff: &mut VExtension, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, ) -> Result { - if let Some(cache) = selff.cached_binary_path.clone() { - if let Some(local) = worktree.which("v-analyzer") { - if local != cache && fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { - return Ok(cache); - } - } else { - return Ok(cache); - } - } - - let (platform, arch) = zed::current_platform(); - zed::set_language_server_installation_status( - &language_server_id, - &zed::LanguageServerInstallationStatus::CheckingForUpdate, - ); - - let asset_name = format!( - "v-analyzer-{os}-{arch}{extension}", - arch = match arch { - zed::Architecture::Aarch64 => "arm64", - zed::Architecture::X86 => "x86", - zed::Architecture::X8664 => "x86_64", - }, - os = match platform { - zed::Os::Mac => "darwin", - zed::Os::Linux => "linux", - zed::Os::Windows => "windows", - }, - extension = match platform { - zed::Os::Windows => ".exe", - _ => "" - }, - ); - - let release = zed::latest_github_release( - "lv37/v-analyzer", - zed::GithubReleaseOptions { - require_assets: true, - pre_release: false, - }, - )?; - - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; - - - if selff.current_version != asset.download_url || !fs::metadata(&asset_name).map_or(false, |stat| stat.is_file()) { - zed::set_language_server_installation_status( - &language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); - - zed::download_file( - &asset.download_url, - &asset_name, - zed::DownloadedFileType::Uncompressed, - ) - .map_err(|e| format!("failed to download file: {e}"))?; - - zed::make_file_executable(&asset_name)?; - - let entries = - fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; - for entry in entries { - let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; - if entry.file_name().to_str() != Some(&asset_name) { - fs::remove_dir_all(&entry.path()).ok(); - } - } - } - selff.cached_binary_path = Some(asset_name.clone()); - selff.current_version = release.version; - Ok(asset_name) + if let Some(cache) = selff.cached_binary_path.clone() { + if let Some(local) = worktree.which("vls") { + if local != cache && fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { + return Ok(cache); + } + } else { + return Ok(cache); + } + } + + let (platform, arch) = zed::current_platform(); + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + + let asset_name = format!( + "vls-{os}-{arch}{extension}", + arch = match arch { + zed::Architecture::Aarch64 => "arm64", + zed::Architecture::X86 => "x86", + zed::Architecture::X8664 => "x86_64", + }, + os = match platform { + zed::Os::Mac => "macos", + zed::Os::Linux => "linux", + zed::Os::Windows => "windows", + }, + extension = match platform { + zed::Os::Windows => ".exe", + _ => "", + }, + ); + + let release = zed::latest_github_release( + "vlang/vls", + zed::GithubReleaseOptions { + require_assets: true, + pre_release: false, + }, + )?; + + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; + + if selff.current_version != release.version + || !fs::metadata(&asset_name).map_or(false, |stat| stat.is_file()) + { + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &asset_name, + zed::DownloadedFileType::Uncompressed, + ) + .map_err(|e| format!("failed to download file: {e}"))?; + + zed::make_file_executable(&asset_name)?; + + let entries = + fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; + for entry in entries { + let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; + if entry.file_name().to_str() != Some(&asset_name) { + fs::remove_dir_all(&entry.path()).ok(); + } + } + } + + selff.cached_binary_path = Some(asset_name.clone()); + selff.current_version = release.version; + Ok(asset_name) } impl VExtension { - fn language_server_binary_path( - &mut self, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, - ) -> Result { - return language_server_binary_path_no_fallback(self, language_server_id, worktree) - .or_else(|a| try_local_install(a, worktree)); - } + fn language_server_binary_path( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + language_server_binary_path_no_fallback(self, language_server_id, worktree) + .or_else(|a| try_local_install(a, worktree)) + } } impl zed::Extension for VExtension { - fn new() -> Self { - Self { - cached_binary_path: None, - current_version: "".to_string() - } - } - - fn language_server_command( - &mut self, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, - ) -> Result { - Ok(zed::Command { - command: self.language_server_binary_path(language_server_id, worktree)?, - args: vec![], - env: Default::default(), - }) - } - - fn label_for_completion( - &self, - _language_server_id: &LanguageServerId, - completion: zed::lsp::Completion, - ) -> Option { - let (label, start_idx) = match (completion.kind, completion.detail) { - (_, None) => (completion.label, 0), - (Some(zed::lsp::CompletionKind::Function), Some(a)) => (a, 3), - (Some(zed::lsp::CompletionKind::Method), Some(a)) => { - let pure = after_first(&a, ')').unwrap_or(a)[1..].to_string(); - (format!("fn {}", pure), 3) - } - (_, Some(a)) => (format!("{} {}", completion.label, a), 0), - }; - - Some(CodeLabel { - spans: vec![CodeLabelSpan::code_range(start_idx..label.len())], - filter_range: (0..(label.len() - start_idx)).into(), - code: label, - }) - } + fn new() -> Self { + Self { + cached_binary_path: None, + current_version: "".to_string(), + } + } + + fn language_server_command( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + Ok(zed::Command { + command: self.language_server_binary_path(language_server_id, worktree)?, + args: vec![], + env: Default::default(), + }) + } + + fn label_for_completion( + &self, + _language_server_id: &LanguageServerId, + completion: zed::lsp::Completion, + ) -> Option { + let (label, start_idx) = match (completion.kind, completion.detail) { + (_, None) => (completion.label, 0), + (Some(zed::lsp::CompletionKind::Function), Some(a)) => (a, 3), + (Some(zed::lsp::CompletionKind::Method), Some(a)) => { + let pure = after_first(&a, ')').unwrap_or(a)[1..].to_string(); + (format!("fn {}", pure), 3) + } + (_, Some(a)) => (format!("{} {}", completion.label, a), 0), + }; + + Some(CodeLabel { + spans: vec![CodeLabelSpan::code_range(start_idx..label.len())], + filter_range: (0..(label.len() - start_idx)).into(), + code: label, + }) + } } fn after_first(in_string: &str, delim: char) -> Option { - let mut splitter = in_string.splitn(2, delim); - splitter.next()?; - Some(splitter.next()?.to_string()) + let mut splitter = in_string.splitn(2, delim); + splitter.next()?; + Some(splitter.next()?.to_string()) } zed::register_extension!(VExtension); From 1c9994a379e28318402b68dccdff7b93d493ed74 Mon Sep 17 00:00:00 2001 From: Jengro Date: Thu, 6 Nov 2025 15:06:57 +0800 Subject: [PATCH 2/5] fix --- src/v.rs | 105 +++++++++++++++---------------------------------------- 1 file changed, 28 insertions(+), 77 deletions(-) diff --git a/src/v.rs b/src/v.rs index 4e3edca..f222689 100644 --- a/src/v.rs +++ b/src/v.rs @@ -1,3 +1,4 @@ +use std::env; use std::fs; use zed::{CodeLabel, CodeLabelSpan, LanguageServerId}; use zed_extension_api::{self as zed, Result}; @@ -7,96 +8,40 @@ struct VExtension { cached_binary_path: Option, } +// 优先查 PATH,再 fallback 到本地 ~/.vmodules/vls/vls fn try_local_install(err: T, worktree: &zed::Worktree) -> Result { + // 1️⃣ 查 PATH if let Some(path) = worktree.which("vls") { + println!("Using vls from PATH: {}", path); return Ok(path); } + + // 2️⃣ fallback 到 ~/.vmodules/vls/vls + let home = env::var("HOME").unwrap_or_else(|_| ".".to_string()); + let local_path = format!("{}/.vmodules/vls/vls", home); + if fs::metadata(&local_path).map_or(false, |m| m.is_file()) { + println!("Using vls from local path: {}", &local_path); + return Ok(local_path); + } + + // 3️⃣ 全部失败 Err(err) } +// 原来的下载逻辑(可保留,但 Linux/Mac 通常没有二进制,所以不执行) fn language_server_binary_path_no_fallback( selff: &mut VExtension, - language_server_id: &LanguageServerId, - worktree: &zed::Worktree, + _language_server_id: &LanguageServerId, + _worktree: &zed::Worktree, ) -> Result { if let Some(cache) = selff.cached_binary_path.clone() { - if let Some(local) = worktree.which("vls") { - if local != cache && fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { - return Ok(cache); - } - } else { + if fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { + println!("Using cached vls path: {}", &cache); return Ok(cache); } } - let (platform, arch) = zed::current_platform(); - zed::set_language_server_installation_status( - &language_server_id, - &zed::LanguageServerInstallationStatus::CheckingForUpdate, - ); - - let asset_name = format!( - "vls-{os}-{arch}{extension}", - arch = match arch { - zed::Architecture::Aarch64 => "arm64", - zed::Architecture::X86 => "x86", - zed::Architecture::X8664 => "x86_64", - }, - os = match platform { - zed::Os::Mac => "macos", - zed::Os::Linux => "linux", - zed::Os::Windows => "windows", - }, - extension = match platform { - zed::Os::Windows => ".exe", - _ => "", - }, - ); - - let release = zed::latest_github_release( - "vlang/vls", - zed::GithubReleaseOptions { - require_assets: true, - pre_release: false, - }, - )?; - - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; - - if selff.current_version != release.version - || !fs::metadata(&asset_name).map_or(false, |stat| stat.is_file()) - { - zed::set_language_server_installation_status( - &language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, - ); - - zed::download_file( - &asset.download_url, - &asset_name, - zed::DownloadedFileType::Uncompressed, - ) - .map_err(|e| format!("failed to download file: {e}"))?; - - zed::make_file_executable(&asset_name)?; - - let entries = - fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; - for entry in entries { - let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; - if entry.file_name().to_str() != Some(&asset_name) { - fs::remove_dir_all(&entry.path()).ok(); - } - } - } - - selff.cached_binary_path = Some(asset_name.clone()); - selff.current_version = release.version; - Ok(asset_name) + Err("vls not found".to_string()) } impl VExtension { @@ -107,6 +52,10 @@ impl VExtension { ) -> Result { language_server_binary_path_no_fallback(self, language_server_id, worktree) .or_else(|a| try_local_install(a, worktree)) + .map(|path| { + self.cached_binary_path = Some(path.clone()); + path + }) } } @@ -123,8 +72,10 @@ impl zed::Extension for VExtension { language_server_id: &LanguageServerId, worktree: &zed::Worktree, ) -> Result { + let path = self.language_server_binary_path(language_server_id, worktree)?; + println!("Starting language server: {}", &path); Ok(zed::Command { - command: self.language_server_binary_path(language_server_id, worktree)?, + command: path, args: vec![], env: Default::default(), }) @@ -134,7 +85,7 @@ impl zed::Extension for VExtension { &self, _language_server_id: &LanguageServerId, completion: zed::lsp::Completion, - ) -> Option { + ) -> Option { let (label, start_idx) = match (completion.kind, completion.detail) { (_, None) => (completion.label, 0), (Some(zed::lsp::CompletionKind::Function), Some(a)) => (a, 3), From 0b1d980227e413441bcf9f607bde78ce7bf202b8 Mon Sep 17 00:00:00 2001 From: Jengro Date: Fri, 7 Nov 2025 08:55:02 +0800 Subject: [PATCH 3/5] Update v.rs --- src/v.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/v.rs b/src/v.rs index f222689..7e92a17 100644 --- a/src/v.rs +++ b/src/v.rs @@ -1,5 +1,6 @@ use std::env; use std::fs; +use std::path::PathBuf; use zed::{CodeLabel, CodeLabelSpan, LanguageServerId}; use zed_extension_api::{self as zed, Result}; @@ -8,7 +9,7 @@ struct VExtension { cached_binary_path: Option, } -// 优先查 PATH,再 fallback 到本地 ~/.vmodules/vls/vls +// ✅ 平台自动检测 + fallback 逻辑 fn try_local_install(err: T, worktree: &zed::Worktree) -> Result { // 1️⃣ 查 PATH if let Some(path) = worktree.which("vls") { @@ -16,19 +17,32 @@ fn try_local_install(err: T, worktree: &zed::Worktree) -> Result { return Ok(path); } - // 2️⃣ fallback 到 ~/.vmodules/vls/vls - let home = env::var("HOME").unwrap_or_else(|_| ".".to_string()); - let local_path = format!("{}/.vmodules/vls/vls", home); + // 2️⃣ fallback 到 ~/.vmodules/vls/vls 或 Windows 版本 + let home = env::var("HOME") + .or_else(|_| env::var("USERPROFILE")) + .unwrap_or_else(|_| ".".to_string()); + + // ⚙️ 根据系统类型选择可执行文件名 + #[cfg(target_os = "windows")] + let exe_name = "vls.exe"; + #[cfg(not(target_os = "windows"))] + let exe_name = "vls"; + + let mut local_path = PathBuf::from(&home); + local_path.push(".vmodules"); + local_path.push("vls"); + local_path.push(exe_name); + if fs::metadata(&local_path).map_or(false, |m| m.is_file()) { - println!("Using vls from local path: {}", &local_path); - return Ok(local_path); + println!("Using vls from local path: {}", local_path.display()); + return Ok(local_path.display().to_string()); } // 3️⃣ 全部失败 Err(err) } -// 原来的下载逻辑(可保留,但 Linux/Mac 通常没有二进制,所以不执行) +// 原始逻辑(保持不变) fn language_server_binary_path_no_fallback( selff: &mut VExtension, _language_server_id: &LanguageServerId, From c1b8f233023ed7748ae769a235c5b90ab51a20ae Mon Sep 17 00:00:00 2001 From: Jengro Date: Fri, 7 Nov 2025 08:58:23 +0800 Subject: [PATCH 4/5] Update v.rs --- src/v.rs | 98 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/src/v.rs b/src/v.rs index 7e92a17..f51b683 100644 --- a/src/v.rs +++ b/src/v.rs @@ -1,6 +1,7 @@ use std::env; use std::fs; use std::path::PathBuf; +use std::process::Command; use zed::{CodeLabel, CodeLabelSpan, LanguageServerId}; use zed_extension_api::{self as zed, Result}; @@ -9,63 +10,95 @@ struct VExtension { cached_binary_path: Option, } -// ✅ 平台自动检测 + fallback 逻辑 +// --------------------------------------------- +// ✅ 尝试从 PATH 或 ~/.vmodules/vls/vls 查找 +// --------------------------------------------- fn try_local_install(err: T, worktree: &zed::Worktree) -> Result { - // 1️⃣ 查 PATH + // ① 从 PATH 查找 if let Some(path) = worktree.which("vls") { - println!("Using vls from PATH: {}", path); + println!("Using system-installed vls: {}", &path); return Ok(path); } - // 2️⃣ fallback 到 ~/.vmodules/vls/vls 或 Windows 版本 + // ② 从 ~/.vmodules/vls/vls 查找 let home = env::var("HOME") .or_else(|_| env::var("USERPROFILE")) - .unwrap_or_else(|_| ".".to_string()); - - // ⚙️ 根据系统类型选择可执行文件名 - #[cfg(target_os = "windows")] - let exe_name = "vls.exe"; - #[cfg(not(target_os = "windows"))] - let exe_name = "vls"; - - let mut local_path = PathBuf::from(&home); + .unwrap_or_else(|_| ".".into()); + let mut local_path = PathBuf::from(home); local_path.push(".vmodules"); local_path.push("vls"); - local_path.push(exe_name); + local_path.push(if cfg!(target_os = "windows") { + "vls.exe" + } else { + "vls" + }); if fs::metadata(&local_path).map_or(false, |m| m.is_file()) { println!("Using vls from local path: {}", local_path.display()); return Ok(local_path.display().to_string()); } - // 3️⃣ 全部失败 Err(err) } -// 原始逻辑(保持不变) -fn language_server_binary_path_no_fallback( - selff: &mut VExtension, - _language_server_id: &LanguageServerId, - _worktree: &zed::Worktree, -) -> Result { - if let Some(cache) = selff.cached_binary_path.clone() { - if fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { - println!("Using cached vls path: {}", &cache); - return Ok(cache); - } +// --------------------------------------------- +// ✅ 若未找到,则执行 `v install vls` 自动安装 +// --------------------------------------------- +fn ensure_vls_installed(language_server_id: &LanguageServerId) -> Result { + zed::set_language_server_installation_status( + language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + println!("Installing vls via: v install vls ..."); + + let status = Command::new("v") + .args(["install", "vls"]) + .status() + .map_err(|e| format!("failed to spawn 'v install vls': {e}"))?; + + if !status.success() { + return Err("`v install vls` failed".into()); } - Err("vls not found".to_string()) + let home = env::var("HOME") + .or_else(|_| env::var("USERPROFILE")) + .unwrap_or_else(|_| ".".into()); + let mut path = PathBuf::from(home); + path.push(".vmodules"); + path.push("vls"); + path.push(if cfg!(target_os = "windows") { + "vls.exe" + } else { + "vls" + }); + + if fs::metadata(&path).map_or(false, |m| m.is_file()) { + zed::make_file_executable(&path.display().to_string())?; + println!("vls successfully installed at: {}", path.display()); + return Ok(path.display().to_string()); + } + + Err("vls binary not found after installation".into()) } +// --------------------------------------------- +// ✅ 综合逻辑:缓存 + 自动安装 + fallback +// --------------------------------------------- impl VExtension { fn language_server_binary_path( &mut self, language_server_id: &LanguageServerId, worktree: &zed::Worktree, ) -> Result { - language_server_binary_path_no_fallback(self, language_server_id, worktree) - .or_else(|a| try_local_install(a, worktree)) + if let Some(cache) = self.cached_binary_path.clone() { + if fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { + return Ok(cache); + } + } + + try_local_install("vls not found".to_string(), worktree) + .or_else(|_| ensure_vls_installed(language_server_id)) .map(|path| { self.cached_binary_path = Some(path.clone()); path @@ -73,11 +106,14 @@ impl VExtension { } } +// --------------------------------------------- +// ✅ Zed 扩展实现 +// --------------------------------------------- impl zed::Extension for VExtension { fn new() -> Self { Self { cached_binary_path: None, - current_version: "".to_string(), + current_version: String::new(), } } @@ -87,7 +123,7 @@ impl zed::Extension for VExtension { worktree: &zed::Worktree, ) -> Result { let path = self.language_server_binary_path(language_server_id, worktree)?; - println!("Starting language server: {}", &path); + println!("Starting VLS from: {}", &path); Ok(zed::Command { command: path, args: vec![], From c289325c58f0e4ac85fb8656b0dd2d679591df7e Mon Sep 17 00:00:00 2001 From: Jengro Date: Fri, 7 Nov 2025 09:08:10 +0800 Subject: [PATCH 5/5] Support macos and windows --- src/v.rs | 178 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 95 insertions(+), 83 deletions(-) diff --git a/src/v.rs b/src/v.rs index f51b683..3465f50 100644 --- a/src/v.rs +++ b/src/v.rs @@ -1,7 +1,4 @@ -use std::env; use std::fs; -use std::path::PathBuf; -use std::process::Command; use zed::{CodeLabel, CodeLabelSpan, LanguageServerId}; use zed_extension_api::{self as zed, Result}; @@ -10,110 +7,127 @@ struct VExtension { cached_binary_path: Option, } -// --------------------------------------------- -// ✅ 尝试从 PATH 或 ~/.vmodules/vls/vls 查找 -// --------------------------------------------- +// -------------------- fallback: local vls -------------------- fn try_local_install(err: T, worktree: &zed::Worktree) -> Result { - // ① 从 PATH 查找 - if let Some(path) = worktree.which("vls") { - println!("Using system-installed vls: {}", &path); - return Ok(path); - } + // 先尝试 ~/.vmodules/vls/vls + let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".into()); + let local_vls = format!("{home}/.vmodules/vls/vls", home = home_dir); - // ② 从 ~/.vmodules/vls/vls 查找 - let home = env::var("HOME") - .or_else(|_| env::var("USERPROFILE")) - .unwrap_or_else(|_| ".".into()); - let mut local_path = PathBuf::from(home); - local_path.push(".vmodules"); - local_path.push("vls"); - local_path.push(if cfg!(target_os = "windows") { - "vls.exe" - } else { - "vls" - }); - - if fs::metadata(&local_path).map_or(false, |m| m.is_file()) { - println!("Using vls from local path: {}", local_path.display()); - return Ok(local_path.display().to_string()); + if fs::metadata(&local_vls).map_or(false, |s| s.is_file()) { + return Ok(local_vls); } + // 再尝试 PATH 中是否可找到 vls + if let Some(path) = worktree.which("vls") { + return Ok(path); + } Err(err) } -// --------------------------------------------- -// ✅ 若未找到,则执行 `v install vls` 自动安装 -// --------------------------------------------- -fn ensure_vls_installed(language_server_id: &LanguageServerId) -> Result { +// -------------------- main resolver -------------------- +fn language_server_binary_path_no_fallback( + selff: &mut VExtension, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, +) -> Result { + // 复用缓存 + if let Some(cache) = selff.cached_binary_path.clone() { + if let Some(local) = worktree.which("vls") { + if local != cache && fs::metadata(&cache).map_or(false, |s| s.is_file()) { + return Ok(cache); + } + } else { + return Ok(cache); + } + } + + let (platform, arch) = zed::current_platform(); zed::set_language_server_installation_status( - language_server_id, - &zed::LanguageServerInstallationStatus::Downloading, + &language_server_id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, ); - println!("Installing vls via: v install vls ..."); - - let status = Command::new("v") - .args(["install", "vls"]) - .status() - .map_err(|e| format!("failed to spawn 'v install vls': {e}"))?; - - if !status.success() { - return Err("`v install vls` failed".into()); - } + let asset_name = format!( + "vls-{os}-{arch}{extension}", + arch = match arch { + zed::Architecture::Aarch64 => "arm64", + zed::Architecture::X86 => "x86", + zed::Architecture::X8664 => "x86_64", + }, + os = match platform { + zed::Os::Mac => "darwin", + zed::Os::Linux => "linux", + zed::Os::Windows => "windows", + }, + extension = match platform { + zed::Os::Windows => ".exe", + _ => "", + }, + ); - let home = env::var("HOME") - .or_else(|_| env::var("USERPROFILE")) - .unwrap_or_else(|_| ".".into()); - let mut path = PathBuf::from(home); - path.push(".vmodules"); - path.push("vls"); - path.push(if cfg!(target_os = "windows") { - "vls.exe" - } else { - "vls" - }); - - if fs::metadata(&path).map_or(false, |m| m.is_file()) { - zed::make_file_executable(&path.display().to_string())?; - println!("vls successfully installed at: {}", path.display()); - return Ok(path.display().to_string()); + let release = zed::latest_github_release( + "vlang/vls", + zed::GithubReleaseOptions { + require_assets: true, + pre_release: false, + }, + )?; + + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; + + if selff.current_version != asset.download_url + || !fs::metadata(&asset_name).map_or(false, |s| s.is_file()) + { + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &asset_name, + zed::DownloadedFileType::Uncompressed, + ) + .map_err(|e| format!("failed to download file: {e}"))?; + + zed::make_file_executable(&asset_name)?; + + let entries = + fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; + for entry in entries { + let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; + if entry.file_name().to_str() != Some(&asset_name) { + fs::remove_dir_all(&entry.path()).ok(); + } + } } - Err("vls binary not found after installation".into()) + selff.cached_binary_path = Some(asset_name.clone()); + selff.current_version = release.version; + Ok(asset_name) } -// --------------------------------------------- -// ✅ 综合逻辑:缓存 + 自动安装 + fallback -// --------------------------------------------- +// -------------------- VExtension impl -------------------- impl VExtension { fn language_server_binary_path( &mut self, language_server_id: &LanguageServerId, worktree: &zed::Worktree, ) -> Result { - if let Some(cache) = self.cached_binary_path.clone() { - if fs::metadata(&cache).map_or(false, |stat| stat.is_file()) { - return Ok(cache); - } - } - - try_local_install("vls not found".to_string(), worktree) - .or_else(|_| ensure_vls_installed(language_server_id)) - .map(|path| { - self.cached_binary_path = Some(path.clone()); - path - }) + language_server_binary_path_no_fallback(self, language_server_id, worktree) + .or_else(|a| try_local_install(a, worktree)) } } -// --------------------------------------------- -// ✅ Zed 扩展实现 -// --------------------------------------------- impl zed::Extension for VExtension { fn new() -> Self { Self { cached_binary_path: None, - current_version: String::new(), + current_version: "".to_string(), } } @@ -122,10 +136,8 @@ impl zed::Extension for VExtension { language_server_id: &LanguageServerId, worktree: &zed::Worktree, ) -> Result { - let path = self.language_server_binary_path(language_server_id, worktree)?; - println!("Starting VLS from: {}", &path); Ok(zed::Command { - command: path, + command: self.language_server_binary_path(language_server_id, worktree)?, args: vec![], env: Default::default(), }) @@ -135,7 +147,7 @@ impl zed::Extension for VExtension { &self, _language_server_id: &LanguageServerId, completion: zed::lsp::Completion, - ) -> Option { + ) -> Option { let (label, start_idx) = match (completion.kind, completion.detail) { (_, None) => (completion.label, 0), (Some(zed::lsp::CompletionKind::Function), Some(a)) => (a, 3),