1use serde_json::json;
2use std::fs;
3use zed::LanguageServerId;
4use zed_extension_api::{self as zed, Result, settings::LspSettings};
5
6struct SnippetExtension {
7 cached_binary_path: Option<String>,
8}
9
10impl SnippetExtension {
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) = worktree.which("simple-completion-language-server") {
17 return Ok(path);
18 }
19
20 if let Some(path) = &self.cached_binary_path
21 && fs::metadata(path).is_ok_and(|stat| stat.is_file())
22 {
23 return Ok(path.clone());
24 }
25
26 zed::set_language_server_installation_status(
27 language_server_id,
28 &zed::LanguageServerInstallationStatus::CheckingForUpdate,
29 );
30 let release = zed::latest_github_release(
31 "zed-industries/simple-completion-language-server",
32 zed::GithubReleaseOptions {
33 require_assets: true,
34 pre_release: false,
35 },
36 )?;
37
38 let (platform, arch) = zed::current_platform();
39 let asset_name = format!(
40 "simple-completion-language-server-{arch}-{os}.tar.gz",
41 arch = match arch {
42 zed::Architecture::Aarch64 => "aarch64",
43 zed::Architecture::X86 => "x86",
44 zed::Architecture::X8664 => "x86_64",
45 },
46 os = match platform {
47 zed::Os::Mac => "apple-darwin",
48 zed::Os::Linux => "unknown-linux-gnu",
49 zed::Os::Windows => "pc-windows-msvc",
50 },
51 );
52
53 let asset = release
54 .assets
55 .iter()
56 .find(|asset| asset.name == asset_name)
57 .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
58
59 let version_dir = format!("simple-completion-language-server-{}", release.version);
60 let binary_path = format!("{version_dir}/simple-completion-language-server");
61
62 if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) {
63 zed::set_language_server_installation_status(
64 language_server_id,
65 &zed::LanguageServerInstallationStatus::Downloading,
66 );
67
68 zed::download_file(
69 &asset.download_url,
70 &version_dir,
71 zed::DownloadedFileType::GzipTar,
72 )
73 .map_err(|e| format!("failed to download file: {e}"))?;
74
75 let entries =
76 fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
77 for entry in entries {
78 let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
79 if entry.file_name().to_str() != Some(&version_dir) {
80 fs::remove_dir_all(entry.path()).ok();
81 }
82 }
83 }
84
85 self.cached_binary_path = Some(binary_path.clone());
86 Ok(binary_path)
87 }
88}
89
90impl zed::Extension for SnippetExtension {
91 fn new() -> Self {
92 Self {
93 cached_binary_path: None,
94 }
95 }
96
97 fn language_server_command(
98 &mut self,
99 language_server_id: &LanguageServerId,
100 worktree: &zed::Worktree,
101 ) -> Result<zed::Command> {
102 Ok(zed::Command {
103 command: self.language_server_binary_path(language_server_id, worktree)?,
104 args: vec![],
105 env: vec![("SCLS_CONFIG_SUBDIRECTORY".to_owned(), "zed".to_owned())],
106 })
107 }
108
109 fn language_server_workspace_configuration(
110 &mut self,
111 server_id: &LanguageServerId,
112 worktree: &zed_extension_api::Worktree,
113 ) -> Result<Option<zed_extension_api::serde_json::Value>> {
114 let settings = LspSettings::for_worktree(server_id.as_ref(), worktree)
115 .ok()
116 .and_then(|lsp_settings| lsp_settings.settings)
117 .unwrap_or_else(|| {
118 json!({
119 "max_completion_items": 20,
120 "snippets_first": true,
121 "feature_words": false,
122 "feature_snippets": true,
123 // We disable `feature_paths` by default, because it's bad UX to assume that any `/` that is typed
124 // is the start of a path.
125 "feature_paths": false
126 })
127 });
128 Ok(Some(settings))
129 }
130}
131
132zed::register_extension!(SnippetExtension);