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