c.rs

  1use anyhow::{anyhow, Context, Result};
  2use async_trait::async_trait;
  3use futures::StreamExt;
  4pub use language2::*;
  5use lsp2::LanguageServerBinary;
  6use smol::fs::{self, File};
  7use std::{any::Any, path::PathBuf, sync::Arc};
  8use util::{
  9    fs::remove_matching,
 10    github::{latest_github_release, GitHubLspBinaryVersion},
 11    ResultExt,
 12};
 13
 14pub struct CLspAdapter;
 15
 16#[async_trait]
 17impl super::LspAdapter for CLspAdapter {
 18    async fn name(&self) -> LanguageServerName {
 19        LanguageServerName("clangd".into())
 20    }
 21
 22    fn short_name(&self) -> &'static str {
 23        "clangd"
 24    }
 25
 26    async fn fetch_latest_server_version(
 27        &self,
 28        delegate: &dyn LspAdapterDelegate,
 29    ) -> Result<Box<dyn 'static + Send + Any>> {
 30        let release = latest_github_release("clangd/clangd", false, delegate.http_client()).await?;
 31        let asset_name = format!("clangd-mac-{}.zip", release.name);
 32        let asset = release
 33            .assets
 34            .iter()
 35            .find(|asset| asset.name == asset_name)
 36            .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
 37        let version = GitHubLspBinaryVersion {
 38            name: release.name,
 39            url: asset.browser_download_url.clone(),
 40        };
 41        Ok(Box::new(version) as Box<_>)
 42    }
 43
 44    async fn fetch_server_binary(
 45        &self,
 46        version: Box<dyn 'static + Send + Any>,
 47        container_dir: PathBuf,
 48        delegate: &dyn LspAdapterDelegate,
 49    ) -> Result<LanguageServerBinary> {
 50        let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
 51        let zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
 52        let version_dir = container_dir.join(format!("clangd_{}", version.name));
 53        let binary_path = version_dir.join("bin/clangd");
 54
 55        if fs::metadata(&binary_path).await.is_err() {
 56            let mut response = delegate
 57                .http_client()
 58                .get(&version.url, Default::default(), true)
 59                .await
 60                .context("error downloading release")?;
 61            let mut file = File::create(&zip_path).await?;
 62            if !response.status().is_success() {
 63                Err(anyhow!(
 64                    "download failed with status {}",
 65                    response.status().to_string()
 66                ))?;
 67            }
 68            futures::io::copy(response.body_mut(), &mut file).await?;
 69
 70            let unzip_status = smol::process::Command::new("unzip")
 71                .current_dir(&container_dir)
 72                .arg(&zip_path)
 73                .output()
 74                .await?
 75                .status;
 76            if !unzip_status.success() {
 77                Err(anyhow!("failed to unzip clangd archive"))?;
 78            }
 79
 80            remove_matching(&container_dir, |entry| entry != version_dir).await;
 81        }
 82
 83        Ok(LanguageServerBinary {
 84            path: binary_path,
 85            arguments: vec![],
 86        })
 87    }
 88
 89    async fn cached_server_binary(
 90        &self,
 91        container_dir: PathBuf,
 92        _: &dyn LspAdapterDelegate,
 93    ) -> Option<LanguageServerBinary> {
 94        get_cached_server_binary(container_dir).await
 95    }
 96
 97    async fn installation_test_binary(
 98        &self,
 99        container_dir: PathBuf,
100    ) -> Option<LanguageServerBinary> {
101        get_cached_server_binary(container_dir)
102            .await
103            .map(|mut binary| {
104                binary.arguments = vec!["--help".into()];
105                binary
106            })
107    }
108
109    async fn label_for_completion(
110        &self,
111        completion: &lsp2::CompletionItem,
112        language: &Arc<Language>,
113    ) -> Option<CodeLabel> {
114        let label = completion
115            .label
116            .strip_prefix('•')
117            .unwrap_or(&completion.label)
118            .trim();
119
120        match completion.kind {
121            Some(lsp2::CompletionItemKind::FIELD) if completion.detail.is_some() => {
122                let detail = completion.detail.as_ref().unwrap();
123                let text = format!("{} {}", detail, label);
124                let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
125                let runs = language.highlight_text(&source, 11..11 + text.len());
126                return Some(CodeLabel {
127                    filter_range: detail.len() + 1..text.len(),
128                    text,
129                    runs,
130                });
131            }
132            Some(lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE)
133                if completion.detail.is_some() =>
134            {
135                let detail = completion.detail.as_ref().unwrap();
136                let text = format!("{} {}", detail, label);
137                let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
138                return Some(CodeLabel {
139                    filter_range: detail.len() + 1..text.len(),
140                    text,
141                    runs,
142                });
143            }
144            Some(lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD)
145                if completion.detail.is_some() =>
146            {
147                let detail = completion.detail.as_ref().unwrap();
148                let text = format!("{} {}", detail, label);
149                let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
150                return Some(CodeLabel {
151                    filter_range: detail.len() + 1..text.rfind('(').unwrap_or(text.len()),
152                    text,
153                    runs,
154                });
155            }
156            Some(kind) => {
157                let highlight_name = match kind {
158                    lsp2::CompletionItemKind::STRUCT
159                    | lsp2::CompletionItemKind::INTERFACE
160                    | lsp2::CompletionItemKind::CLASS
161                    | lsp2::CompletionItemKind::ENUM => Some("type"),
162                    lsp2::CompletionItemKind::ENUM_MEMBER => Some("variant"),
163                    lsp2::CompletionItemKind::KEYWORD => Some("keyword"),
164                    lsp2::CompletionItemKind::VALUE | lsp2::CompletionItemKind::CONSTANT => {
165                        Some("constant")
166                    }
167                    _ => None,
168                };
169                if let Some(highlight_id) = language
170                    .grammar()
171                    .and_then(|g| g.highlight_id_for_name(highlight_name?))
172                {
173                    let mut label = CodeLabel::plain(label.to_string(), None);
174                    label.runs.push((
175                        0..label.text.rfind('(').unwrap_or(label.text.len()),
176                        highlight_id,
177                    ));
178                    return Some(label);
179                }
180            }
181            _ => {}
182        }
183        Some(CodeLabel::plain(label.to_string(), None))
184    }
185
186    async fn label_for_symbol(
187        &self,
188        name: &str,
189        kind: lsp2::SymbolKind,
190        language: &Arc<Language>,
191    ) -> Option<CodeLabel> {
192        let (text, filter_range, display_range) = match kind {
193            lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
194                let text = format!("void {} () {{}}", name);
195                let filter_range = 0..name.len();
196                let display_range = 5..5 + name.len();
197                (text, filter_range, display_range)
198            }
199            lsp2::SymbolKind::STRUCT => {
200                let text = format!("struct {} {{}}", name);
201                let filter_range = 7..7 + name.len();
202                let display_range = 0..filter_range.end;
203                (text, filter_range, display_range)
204            }
205            lsp2::SymbolKind::ENUM => {
206                let text = format!("enum {} {{}}", name);
207                let filter_range = 5..5 + name.len();
208                let display_range = 0..filter_range.end;
209                (text, filter_range, display_range)
210            }
211            lsp2::SymbolKind::INTERFACE | lsp2::SymbolKind::CLASS => {
212                let text = format!("class {} {{}}", name);
213                let filter_range = 6..6 + name.len();
214                let display_range = 0..filter_range.end;
215                (text, filter_range, display_range)
216            }
217            lsp2::SymbolKind::CONSTANT => {
218                let text = format!("const int {} = 0;", name);
219                let filter_range = 10..10 + name.len();
220                let display_range = 0..filter_range.end;
221                (text, filter_range, display_range)
222            }
223            lsp2::SymbolKind::MODULE => {
224                let text = format!("namespace {} {{}}", name);
225                let filter_range = 10..10 + name.len();
226                let display_range = 0..filter_range.end;
227                (text, filter_range, display_range)
228            }
229            lsp2::SymbolKind::TYPE_PARAMETER => {
230                let text = format!("typename {} {{}};", name);
231                let filter_range = 9..9 + name.len();
232                let display_range = 0..filter_range.end;
233                (text, filter_range, display_range)
234            }
235            _ => return None,
236        };
237
238        Some(CodeLabel {
239            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
240            text: text[display_range].to_string(),
241            filter_range,
242        })
243    }
244}
245
246async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
247    (|| async move {
248        let mut last_clangd_dir = None;
249        let mut entries = fs::read_dir(&container_dir).await?;
250        while let Some(entry) = entries.next().await {
251            let entry = entry?;
252            if entry.file_type().await?.is_dir() {
253                last_clangd_dir = Some(entry.path());
254            }
255        }
256        let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
257        let clangd_bin = clangd_dir.join("bin/clangd");
258        if clangd_bin.exists() {
259            Ok(LanguageServerBinary {
260                path: clangd_bin,
261                arguments: vec![],
262            })
263        } else {
264            Err(anyhow!(
265                "missing clangd binary in directory {:?}",
266                clangd_dir
267            ))
268        }
269    })()
270    .await
271    .log_err()
272}
273
274#[cfg(test)]
275mod tests {
276    use gpui2::{Context, TestAppContext};
277    use language2::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
278    use settings2::SettingsStore;
279    use std::num::NonZeroU32;
280
281    #[gpui2::test]
282    async fn test_c_autoindent(cx: &mut TestAppContext) {
283        // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
284        cx.update(|cx| {
285            let test_settings = SettingsStore::test(cx);
286            cx.set_global(test_settings);
287            language2::init(cx);
288            cx.update_global::<SettingsStore, _>(|store, cx| {
289                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
290                    s.defaults.tab_size = NonZeroU32::new(2);
291                });
292            });
293        });
294        let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
295
296        cx.build_model(|cx| {
297            let mut buffer =
298                Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
299
300            // empty function
301            buffer.edit([(0..0, "int main() {}")], None, cx);
302
303            // indent inside braces
304            let ix = buffer.len() - 1;
305            buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
306            assert_eq!(buffer.text(), "int main() {\n  \n}");
307
308            // indent body of single-statement if statement
309            let ix = buffer.len() - 2;
310            buffer.edit([(ix..ix, "if (a)\nb;")], Some(AutoindentMode::EachLine), cx);
311            assert_eq!(buffer.text(), "int main() {\n  if (a)\n    b;\n}");
312
313            // indent inside field expression
314            let ix = buffer.len() - 3;
315            buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx);
316            assert_eq!(buffer.text(), "int main() {\n  if (a)\n    b\n      .c;\n}");
317
318            buffer
319        });
320    }
321}