1use std::fs;
2use zed::lsp::CompletionKind;
3use zed::{CodeLabel, CodeLabelSpan, LanguageServerId};
4use zed_extension_api::{self as zed, Result};
5
6struct LuaExtension {
7 cached_binary_path: Option<String>,
8}
9
10impl LuaExtension {
11 fn language_server_binary_path(
12 &mut self,
13 language_server_id: &LanguageServerId,
14 worktree: &zed::Worktree,
15 ) -> Result<String> {
16 if let Some(path) = &self.cached_binary_path {
17 if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
18 return Ok(path.clone());
19 }
20 }
21
22 if let Some(path) = worktree.which("lua-language-server") {
23 self.cached_binary_path = Some(path.clone());
24 return Ok(path);
25 }
26
27 zed::set_language_server_installation_status(
28 &language_server_id,
29 &zed::LanguageServerInstallationStatus::CheckingForUpdate,
30 );
31 let release = zed::latest_github_release(
32 "LuaLS/lua-language-server",
33 zed::GithubReleaseOptions {
34 require_assets: true,
35 pre_release: false,
36 },
37 )?;
38
39 let (platform, arch) = zed::current_platform();
40 let asset_name = format!(
41 "lua-language-server-{version}-{os}-{arch}.tar.gz",
42 version = release.version,
43 os = match platform {
44 zed::Os::Mac => "darwin",
45 zed::Os::Linux => "linux",
46 zed::Os::Windows => "win32",
47 },
48 arch = match arch {
49 zed::Architecture::Aarch64 => "arm64",
50 zed::Architecture::X8664 => "x86_64",
51 zed::Architecture::X86 => return Err("unsupported platform x86".into()),
52 },
53 );
54
55 let asset = release
56 .assets
57 .iter()
58 .find(|asset| asset.name == asset_name)
59 .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
60
61 let version_dir = format!("lua-language-server-{}", release.version);
62 let binary_path = format!("{version_dir}/bin/lua-language-server");
63
64 if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
65 zed::set_language_server_installation_status(
66 &language_server_id,
67 &zed::LanguageServerInstallationStatus::Downloading,
68 );
69
70 zed::download_file(
71 &asset.download_url,
72 &version_dir,
73 zed::DownloadedFileType::GzipTar,
74 )
75 .map_err(|e| format!("failed to download file: {e}"))?;
76
77 let entries =
78 fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
79 for entry in entries {
80 let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
81 if entry.file_name().to_str() != Some(&version_dir) {
82 fs::remove_dir_all(&entry.path()).ok();
83 }
84 }
85 }
86
87 self.cached_binary_path = Some(binary_path.clone());
88 Ok(binary_path)
89 }
90}
91
92impl zed::Extension for LuaExtension {
93 fn new() -> Self {
94 Self {
95 cached_binary_path: None,
96 }
97 }
98
99 fn language_server_command(
100 &mut self,
101 language_server_id: &LanguageServerId,
102 worktree: &zed::Worktree,
103 ) -> Result<zed::Command> {
104 Ok(zed::Command {
105 command: self.language_server_binary_path(language_server_id, worktree)?,
106 args: Default::default(),
107 env: Default::default(),
108 })
109 }
110
111 fn label_for_completion(
112 &self,
113 _language_server_id: &LanguageServerId,
114 completion: zed::lsp::Completion,
115 ) -> Option<CodeLabel> {
116 match completion.kind? {
117 CompletionKind::Method | CompletionKind::Function => {
118 let name_len = completion.label.find('(').unwrap_or(completion.label.len());
119 Some(CodeLabel {
120 spans: vec![CodeLabelSpan::code_range(0..completion.label.len())],
121 filter_range: (0..name_len).into(),
122 code: completion.label,
123 })
124 }
125 CompletionKind::Field => Some(CodeLabel {
126 spans: vec![CodeLabelSpan::literal(
127 completion.label.clone(),
128 Some("property".into()),
129 )],
130 filter_range: (0..completion.label.len()).into(),
131 code: Default::default(),
132 }),
133 _ => None,
134 }
135 }
136
137 fn label_for_symbol(
138 &self,
139 _language_server_id: &LanguageServerId,
140 symbol: zed::lsp::Symbol,
141 ) -> Option<CodeLabel> {
142 let prefix = "let a = ";
143 let suffix = match symbol.kind {
144 zed::lsp::SymbolKind::Method => "()",
145 _ => "",
146 };
147 let code = format!("{prefix}{}{suffix}", symbol.name);
148 Some(CodeLabel {
149 spans: vec![CodeLabelSpan::code_range(
150 prefix.len()..code.len() - suffix.len(),
151 )],
152 filter_range: (0..symbol.name.len()).into(),
153 code,
154 })
155 }
156}
157
158zed::register_extension!(LuaExtension);