Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 151 additions & 136 deletions src/v.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,158 +3,173 @@ use zed::{CodeLabel, CodeLabelSpan, LanguageServerId};
use zed_extension_api::{self as zed, Result};

struct VExtension {
current_version: String,
cached_binary_path: Option<String>
current_version: String,
cached_binary_path: Option<String>,
}

// -------------------- fallback: local vls --------------------
fn try_local_install<T>(err: T, worktree: &zed::Worktree) -> Result<String, T> {
if let Some(path) = worktree.which("v-analyzer") {
return Ok(path);
}
return Err(err);
// 先尝试 ~/.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);

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)
}

// -------------------- main resolver --------------------
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<String> {
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, |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::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 => "darwin",
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 != 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();
}
}
}

selff.cached_binary_path = Some(asset_name.clone());
selff.current_version = release.version;
Ok(asset_name)
}

// -------------------- VExtension impl --------------------
impl VExtension {
fn language_server_binary_path(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
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<String> {
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<zed::Command> {
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<zed::CodeLabel> {
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<zed::Command> {
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<zed::CodeLabel> {
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<String> {
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);