1use std::fs;
2
3use zed::lsp::{Completion, CompletionKind, Symbol};
4use zed::{CodeLabel, CodeLabelSpan, LanguageServerId};
5use zed_extension_api::settings::LspSettings;
6use zed_extension_api::{self as zed, Result};
7
8pub struct LexicalBinary {
9 pub path: String,
10 pub args: Option<Vec<String>>,
11}
12
13pub struct Lexical {
14 cached_binary_path: Option<String>,
15}
16
17impl Lexical {
18 pub const LANGUAGE_SERVER_ID: &'static str = "lexical";
19
20 pub fn new() -> Self {
21 Self {
22 cached_binary_path: None,
23 }
24 }
25
26 pub fn language_server_binary(
27 &mut self,
28 language_server_id: &LanguageServerId,
29 worktree: &zed::Worktree,
30 ) -> Result<LexicalBinary> {
31 let binary_settings = LspSettings::for_worktree("lexical", worktree)
32 .ok()
33 .and_then(|lsp_settings| lsp_settings.binary);
34 let binary_args = binary_settings
35 .as_ref()
36 .and_then(|binary_settings| binary_settings.arguments.clone());
37
38 if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
39 return Ok(LexicalBinary {
40 path,
41 args: binary_args,
42 });
43 }
44
45 if let Some(path) = worktree.which("lexical") {
46 return Ok(LexicalBinary {
47 path,
48 args: binary_args,
49 });
50 }
51
52 if let Some(path) = &self.cached_binary_path {
53 if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
54 return Ok(LexicalBinary {
55 path: path.clone(),
56 args: binary_args,
57 });
58 }
59 }
60
61 zed::set_language_server_installation_status(
62 language_server_id,
63 &zed::LanguageServerInstallationStatus::CheckingForUpdate,
64 );
65 let release = zed::latest_github_release(
66 "lexical-lsp/lexical",
67 zed::GithubReleaseOptions {
68 require_assets: true,
69 pre_release: false,
70 },
71 )?;
72
73 let asset_name = format!("lexical-{version}.zip", version = release.version);
74
75 let asset = release
76 .assets
77 .iter()
78 .find(|asset| asset.name == asset_name)
79 .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
80
81 let version_dir = format!("lexical-{}", release.version);
82 let binary_path = format!("{version_dir}/lexical/bin/start_lexical.sh");
83
84 if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
85 zed::set_language_server_installation_status(
86 language_server_id,
87 &zed::LanguageServerInstallationStatus::Downloading,
88 );
89
90 zed::download_file(
91 &asset.download_url,
92 &version_dir,
93 zed::DownloadedFileType::Zip,
94 )
95 .map_err(|e| format!("failed to download file: {e}"))?;
96
97 zed::make_file_executable(&binary_path)?;
98 zed::make_file_executable(&format!("{version_dir}/lexical/bin/debug_shell.sh"))?;
99 zed::make_file_executable(&format!("{version_dir}/lexical/priv/port_wrapper.sh"))?;
100
101 let entries =
102 fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
103 for entry in entries {
104 let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
105 if entry.file_name().to_str() != Some(&version_dir) {
106 fs::remove_dir_all(entry.path()).ok();
107 }
108 }
109 }
110
111 self.cached_binary_path = Some(binary_path.clone());
112 Ok(LexicalBinary {
113 path: binary_path,
114 args: binary_args,
115 })
116 }
117
118 pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {
119 match completion.kind? {
120 CompletionKind::Module
121 | CompletionKind::Class
122 | CompletionKind::Interface
123 | CompletionKind::Struct => {
124 let name = completion.label;
125 let defmodule = "defmodule ";
126 let code = format!("{defmodule}{name}");
127
128 Some(CodeLabel {
129 code,
130 spans: vec![CodeLabelSpan::code_range(
131 defmodule.len()..defmodule.len() + name.len(),
132 )],
133 filter_range: (0..name.len()).into(),
134 })
135 }
136 CompletionKind::Function | CompletionKind::Constant => {
137 let name = completion.label;
138 let def = "def ";
139 let code = format!("{def}{name}");
140
141 Some(CodeLabel {
142 code,
143 spans: vec![CodeLabelSpan::code_range(def.len()..def.len() + name.len())],
144 filter_range: (0..name.len()).into(),
145 })
146 }
147 CompletionKind::Operator => {
148 let name = completion.label;
149 let def_a = "def a ";
150 let code = format!("{def_a}{name} b");
151
152 Some(CodeLabel {
153 code,
154 spans: vec![CodeLabelSpan::code_range(
155 def_a.len()..def_a.len() + name.len(),
156 )],
157 filter_range: (0..name.len()).into(),
158 })
159 }
160 _ => None,
161 }
162 }
163
164 pub fn label_for_symbol(&self, _symbol: Symbol) -> Option<CodeLabel> {
165 None
166 }
167}