extension_lsp_adapter.rs

  1use anyhow::{Context, Result};
  2use async_trait::async_trait;
  3use collections::HashMap;
  4use extension::{Extension, WorktreeDelegate};
  5use futures::{Future, FutureExt};
  6use gpui::AsyncAppContext;
  7use language::{
  8    CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter,
  9    LspAdapterDelegate,
 10};
 11use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
 12use serde::Serialize;
 13use serde_json::Value;
 14use std::ops::Range;
 15use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc};
 16use util::{maybe, ResultExt};
 17
 18/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
 19pub struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
 20
 21#[async_trait]
 22impl WorktreeDelegate for WorktreeDelegateAdapter {
 23    fn id(&self) -> u64 {
 24        self.0.worktree_id().to_proto()
 25    }
 26
 27    fn root_path(&self) -> String {
 28        self.0.worktree_root_path().to_string_lossy().to_string()
 29    }
 30
 31    async fn read_text_file(&self, path: PathBuf) -> Result<String> {
 32        self.0.read_text_file(path).await
 33    }
 34
 35    async fn which(&self, binary_name: String) -> Option<String> {
 36        self.0
 37            .which(binary_name.as_ref())
 38            .await
 39            .map(|path| path.to_string_lossy().to_string())
 40    }
 41
 42    async fn shell_env(&self) -> Vec<(String, String)> {
 43        self.0.shell_env().await.into_iter().collect()
 44    }
 45}
 46
 47pub struct ExtensionLspAdapter {
 48    pub(crate) extension: Arc<dyn Extension>,
 49    pub(crate) language_server_id: LanguageServerName,
 50    pub(crate) language_name: LanguageName,
 51}
 52
 53#[async_trait(?Send)]
 54impl LspAdapter for ExtensionLspAdapter {
 55    fn name(&self) -> LanguageServerName {
 56        self.language_server_id.clone()
 57    }
 58
 59    fn get_language_server_command<'a>(
 60        self: Arc<Self>,
 61        delegate: Arc<dyn LspAdapterDelegate>,
 62        _: LanguageServerBinaryOptions,
 63        _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
 64        _: &'a mut AsyncAppContext,
 65    ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
 66        async move {
 67            let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
 68            let command = self
 69                .extension
 70                .language_server_command(
 71                    self.language_server_id.clone(),
 72                    self.language_name.clone(),
 73                    delegate,
 74                )
 75                .await?;
 76
 77            let path = self.extension.path_from_extension(command.command.as_ref());
 78
 79            // TODO: This should now be done via the `zed::make_file_executable` function in
 80            // Zed extension API, but we're leaving these existing usages in place temporarily
 81            // to avoid any compatibility issues between Zed and the extension versions.
 82            //
 83            // We can remove once the following extension versions no longer see any use:
 84            // - toml@0.0.2
 85            // - zig@0.0.1
 86            if ["toml", "zig"].contains(&self.extension.manifest().id.as_ref())
 87                && path.starts_with(&self.extension.work_dir())
 88            {
 89                #[cfg(not(windows))]
 90                {
 91                    use std::fs::{self, Permissions};
 92                    use std::os::unix::fs::PermissionsExt;
 93
 94                    fs::set_permissions(&path, Permissions::from_mode(0o755))
 95                        .context("failed to set file permissions")?;
 96                }
 97            }
 98
 99            Ok(LanguageServerBinary {
100                path,
101                arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
102                env: Some(command.env.into_iter().collect()),
103            })
104        }
105        .boxed_local()
106    }
107
108    async fn fetch_latest_server_version(
109        &self,
110        _: &dyn LspAdapterDelegate,
111    ) -> Result<Box<dyn 'static + Send + Any>> {
112        unreachable!("get_language_server_command is overridden")
113    }
114
115    async fn fetch_server_binary(
116        &self,
117        _: Box<dyn 'static + Send + Any>,
118        _: PathBuf,
119        _: &dyn LspAdapterDelegate,
120    ) -> Result<LanguageServerBinary> {
121        unreachable!("get_language_server_command is overridden")
122    }
123
124    async fn cached_server_binary(
125        &self,
126        _: PathBuf,
127        _: &dyn LspAdapterDelegate,
128    ) -> Option<LanguageServerBinary> {
129        unreachable!("get_language_server_command is overridden")
130    }
131
132    fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
133        let code_action_kinds = self
134            .extension
135            .manifest()
136            .language_servers
137            .get(&self.language_server_id)
138            .and_then(|server| server.code_action_kinds.clone());
139
140        code_action_kinds.or(Some(vec![
141            CodeActionKind::EMPTY,
142            CodeActionKind::QUICKFIX,
143            CodeActionKind::REFACTOR,
144            CodeActionKind::REFACTOR_EXTRACT,
145            CodeActionKind::SOURCE,
146        ]))
147    }
148
149    fn language_ids(&self) -> HashMap<String, String> {
150        // TODO: The language IDs can be provided via the language server options
151        // in `extension.toml now but we're leaving these existing usages in place temporarily
152        // to avoid any compatibility issues between Zed and the extension versions.
153        //
154        // We can remove once the following extension versions no longer see any use:
155        // - php@0.0.1
156        if self.extension.manifest().id.as_ref() == "php" {
157            return HashMap::from_iter([("PHP".into(), "php".into())]);
158        }
159
160        self.extension
161            .manifest()
162            .language_servers
163            .get(&self.language_server_id)
164            .map(|server| server.language_ids.clone())
165            .unwrap_or_default()
166    }
167
168    async fn initialization_options(
169        self: Arc<Self>,
170        delegate: &Arc<dyn LspAdapterDelegate>,
171    ) -> Result<Option<serde_json::Value>> {
172        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
173        let json_options = self
174            .extension
175            .language_server_initialization_options(
176                self.language_server_id.clone(),
177                self.language_name.clone(),
178                delegate,
179            )
180            .await?;
181        Ok(if let Some(json_options) = json_options {
182            serde_json::from_str(&json_options).with_context(|| {
183                format!("failed to parse initialization_options from extension: {json_options}")
184            })?
185        } else {
186            None
187        })
188    }
189
190    async fn workspace_configuration(
191        self: Arc<Self>,
192        delegate: &Arc<dyn LspAdapterDelegate>,
193        _: Arc<dyn LanguageToolchainStore>,
194        _cx: &mut AsyncAppContext,
195    ) -> Result<Value> {
196        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
197        let json_options: Option<String> = self
198            .extension
199            .language_server_workspace_configuration(self.language_server_id.clone(), delegate)
200            .await?;
201        Ok(if let Some(json_options) = json_options {
202            serde_json::from_str(&json_options).with_context(|| {
203                format!("failed to parse workspace_configuration from extension: {json_options}")
204            })?
205        } else {
206            serde_json::json!({})
207        })
208    }
209
210    async fn labels_for_completions(
211        self: Arc<Self>,
212        completions: &[lsp::CompletionItem],
213        language: &Arc<Language>,
214    ) -> Result<Vec<Option<CodeLabel>>> {
215        let completions = completions
216            .iter()
217            .cloned()
218            .map(lsp_completion_to_extension)
219            .collect::<Vec<_>>();
220
221        let labels = self
222            .extension
223            .labels_for_completions(self.language_server_id.clone(), completions)
224            .await?;
225
226        Ok(labels_from_extension(labels, language))
227    }
228
229    async fn labels_for_symbols(
230        self: Arc<Self>,
231        symbols: &[(String, lsp::SymbolKind)],
232        language: &Arc<Language>,
233    ) -> Result<Vec<Option<CodeLabel>>> {
234        let symbols = symbols
235            .iter()
236            .cloned()
237            .map(|(name, kind)| extension::Symbol {
238                name,
239                kind: lsp_symbol_kind_to_extension(kind),
240            })
241            .collect::<Vec<_>>();
242
243        let labels = self
244            .extension
245            .labels_for_symbols(self.language_server_id.clone(), symbols)
246            .await?;
247
248        Ok(labels_from_extension(
249            labels
250                .into_iter()
251                .map(|label| label.map(Into::into))
252                .collect(),
253            language,
254        ))
255    }
256}
257
258fn labels_from_extension(
259    labels: Vec<Option<extension::CodeLabel>>,
260    language: &Arc<Language>,
261) -> Vec<Option<CodeLabel>> {
262    labels
263        .into_iter()
264        .map(|label| {
265            let label = label?;
266            let runs = if label.code.is_empty() {
267                Vec::new()
268            } else {
269                language.highlight_text(&label.code.as_str().into(), 0..label.code.len())
270            };
271            build_code_label(&label, &runs, language)
272        })
273        .collect()
274}
275
276fn build_code_label(
277    label: &extension::CodeLabel,
278    parsed_runs: &[(Range<usize>, HighlightId)],
279    language: &Arc<Language>,
280) -> Option<CodeLabel> {
281    let mut text = String::new();
282    let mut runs = vec![];
283
284    for span in &label.spans {
285        match span {
286            extension::CodeLabelSpan::CodeRange(range) => {
287                let code_span = &label.code.get(range.clone())?;
288                let mut input_ix = range.start;
289                let mut output_ix = text.len();
290                for (run_range, id) in parsed_runs {
291                    if run_range.start >= range.end {
292                        break;
293                    }
294                    if run_range.end <= input_ix {
295                        continue;
296                    }
297
298                    if run_range.start > input_ix {
299                        let len = run_range.start - input_ix;
300                        output_ix += len;
301                        input_ix += len;
302                    }
303
304                    let len = range.end.min(run_range.end) - input_ix;
305                    runs.push((output_ix..output_ix + len, *id));
306                    output_ix += len;
307                    input_ix += len;
308                }
309
310                text.push_str(code_span);
311            }
312            extension::CodeLabelSpan::Literal(span) => {
313                let highlight_id = language
314                    .grammar()
315                    .zip(span.highlight_name.as_ref())
316                    .and_then(|(grammar, highlight_name)| {
317                        grammar.highlight_id_for_name(highlight_name)
318                    })
319                    .unwrap_or_default();
320                let ix = text.len();
321                runs.push((ix..ix + span.text.len(), highlight_id));
322                text.push_str(&span.text);
323            }
324        }
325    }
326
327    let filter_range = label.filter_range.clone();
328    text.get(filter_range.clone())?;
329    Some(CodeLabel {
330        text,
331        runs,
332        filter_range,
333    })
334}
335
336fn lsp_completion_to_extension(value: lsp::CompletionItem) -> extension::Completion {
337    extension::Completion {
338        label: value.label,
339        label_details: value
340            .label_details
341            .map(lsp_completion_item_label_details_to_extension),
342        detail: value.detail,
343        kind: value.kind.map(lsp_completion_item_kind_to_extension),
344        insert_text_format: value
345            .insert_text_format
346            .map(lsp_insert_text_format_to_extension),
347    }
348}
349
350fn lsp_completion_item_label_details_to_extension(
351    value: lsp::CompletionItemLabelDetails,
352) -> extension::CompletionLabelDetails {
353    extension::CompletionLabelDetails {
354        detail: value.detail,
355        description: value.description,
356    }
357}
358
359fn lsp_completion_item_kind_to_extension(
360    value: lsp::CompletionItemKind,
361) -> extension::CompletionKind {
362    match value {
363        lsp::CompletionItemKind::TEXT => extension::CompletionKind::Text,
364        lsp::CompletionItemKind::METHOD => extension::CompletionKind::Method,
365        lsp::CompletionItemKind::FUNCTION => extension::CompletionKind::Function,
366        lsp::CompletionItemKind::CONSTRUCTOR => extension::CompletionKind::Constructor,
367        lsp::CompletionItemKind::FIELD => extension::CompletionKind::Field,
368        lsp::CompletionItemKind::VARIABLE => extension::CompletionKind::Variable,
369        lsp::CompletionItemKind::CLASS => extension::CompletionKind::Class,
370        lsp::CompletionItemKind::INTERFACE => extension::CompletionKind::Interface,
371        lsp::CompletionItemKind::MODULE => extension::CompletionKind::Module,
372        lsp::CompletionItemKind::PROPERTY => extension::CompletionKind::Property,
373        lsp::CompletionItemKind::UNIT => extension::CompletionKind::Unit,
374        lsp::CompletionItemKind::VALUE => extension::CompletionKind::Value,
375        lsp::CompletionItemKind::ENUM => extension::CompletionKind::Enum,
376        lsp::CompletionItemKind::KEYWORD => extension::CompletionKind::Keyword,
377        lsp::CompletionItemKind::SNIPPET => extension::CompletionKind::Snippet,
378        lsp::CompletionItemKind::COLOR => extension::CompletionKind::Color,
379        lsp::CompletionItemKind::FILE => extension::CompletionKind::File,
380        lsp::CompletionItemKind::REFERENCE => extension::CompletionKind::Reference,
381        lsp::CompletionItemKind::FOLDER => extension::CompletionKind::Folder,
382        lsp::CompletionItemKind::ENUM_MEMBER => extension::CompletionKind::EnumMember,
383        lsp::CompletionItemKind::CONSTANT => extension::CompletionKind::Constant,
384        lsp::CompletionItemKind::STRUCT => extension::CompletionKind::Struct,
385        lsp::CompletionItemKind::EVENT => extension::CompletionKind::Event,
386        lsp::CompletionItemKind::OPERATOR => extension::CompletionKind::Operator,
387        lsp::CompletionItemKind::TYPE_PARAMETER => extension::CompletionKind::TypeParameter,
388        _ => extension::CompletionKind::Other(extract_int(value)),
389    }
390}
391
392fn lsp_insert_text_format_to_extension(
393    value: lsp::InsertTextFormat,
394) -> extension::InsertTextFormat {
395    match value {
396        lsp::InsertTextFormat::PLAIN_TEXT => extension::InsertTextFormat::PlainText,
397        lsp::InsertTextFormat::SNIPPET => extension::InsertTextFormat::Snippet,
398        _ => extension::InsertTextFormat::Other(extract_int(value)),
399    }
400}
401
402fn lsp_symbol_kind_to_extension(value: lsp::SymbolKind) -> extension::SymbolKind {
403    match value {
404        lsp::SymbolKind::FILE => extension::SymbolKind::File,
405        lsp::SymbolKind::MODULE => extension::SymbolKind::Module,
406        lsp::SymbolKind::NAMESPACE => extension::SymbolKind::Namespace,
407        lsp::SymbolKind::PACKAGE => extension::SymbolKind::Package,
408        lsp::SymbolKind::CLASS => extension::SymbolKind::Class,
409        lsp::SymbolKind::METHOD => extension::SymbolKind::Method,
410        lsp::SymbolKind::PROPERTY => extension::SymbolKind::Property,
411        lsp::SymbolKind::FIELD => extension::SymbolKind::Field,
412        lsp::SymbolKind::CONSTRUCTOR => extension::SymbolKind::Constructor,
413        lsp::SymbolKind::ENUM => extension::SymbolKind::Enum,
414        lsp::SymbolKind::INTERFACE => extension::SymbolKind::Interface,
415        lsp::SymbolKind::FUNCTION => extension::SymbolKind::Function,
416        lsp::SymbolKind::VARIABLE => extension::SymbolKind::Variable,
417        lsp::SymbolKind::CONSTANT => extension::SymbolKind::Constant,
418        lsp::SymbolKind::STRING => extension::SymbolKind::String,
419        lsp::SymbolKind::NUMBER => extension::SymbolKind::Number,
420        lsp::SymbolKind::BOOLEAN => extension::SymbolKind::Boolean,
421        lsp::SymbolKind::ARRAY => extension::SymbolKind::Array,
422        lsp::SymbolKind::OBJECT => extension::SymbolKind::Object,
423        lsp::SymbolKind::KEY => extension::SymbolKind::Key,
424        lsp::SymbolKind::NULL => extension::SymbolKind::Null,
425        lsp::SymbolKind::ENUM_MEMBER => extension::SymbolKind::EnumMember,
426        lsp::SymbolKind::STRUCT => extension::SymbolKind::Struct,
427        lsp::SymbolKind::EVENT => extension::SymbolKind::Event,
428        lsp::SymbolKind::OPERATOR => extension::SymbolKind::Operator,
429        lsp::SymbolKind::TYPE_PARAMETER => extension::SymbolKind::TypeParameter,
430        _ => extension::SymbolKind::Other(extract_int(value)),
431    }
432}
433
434fn extract_int<T: Serialize>(value: T) -> i32 {
435    maybe!({
436        let kind = serde_json::to_value(&value)?;
437        serde_json::from_value(kind)
438    })
439    .log_err()
440    .unwrap_or(-1)
441}
442
443#[test]
444fn test_build_code_label() {
445    use util::test::marked_text_ranges;
446
447    let (code, code_ranges) = marked_text_ranges(
448        "«const» «a»: «fn»(«Bcd»(«Efgh»)) -> «Ijklm» = pqrs.tuv",
449        false,
450    );
451    let code_runs = code_ranges
452        .into_iter()
453        .map(|range| (range, HighlightId(0)))
454        .collect::<Vec<_>>();
455
456    let label = build_code_label(
457        &extension::CodeLabel {
458            spans: vec![
459                extension::CodeLabelSpan::CodeRange(code.find("pqrs").unwrap()..code.len()),
460                extension::CodeLabelSpan::CodeRange(
461                    code.find(": fn").unwrap()..code.find(" = ").unwrap(),
462                ),
463            ],
464            filter_range: 0.."pqrs.tuv".len(),
465            code,
466        },
467        &code_runs,
468        &language::PLAIN_TEXT,
469    )
470    .unwrap();
471
472    let (label_text, label_ranges) =
473        marked_text_ranges("pqrs.tuv: «fn»(«Bcd»(«Efgh»)) -> «Ijklm»", false);
474    let label_runs = label_ranges
475        .into_iter()
476        .map(|range| (range, HighlightId(0)))
477        .collect::<Vec<_>>();
478
479    assert_eq!(
480        label,
481        CodeLabel {
482            text: label_text,
483            runs: label_runs,
484            filter_range: label.filter_range.clone()
485        }
486    )
487}
488
489#[test]
490fn test_build_code_label_with_invalid_ranges() {
491    use util::test::marked_text_ranges;
492
493    let (code, code_ranges) = marked_text_ranges("const «a»: «B» = '🏀'", false);
494    let code_runs = code_ranges
495        .into_iter()
496        .map(|range| (range, HighlightId(0)))
497        .collect::<Vec<_>>();
498
499    // A span uses a code range that is invalid because it starts inside of
500    // a multi-byte character.
501    let label = build_code_label(
502        &extension::CodeLabel {
503            spans: vec![
504                extension::CodeLabelSpan::CodeRange(
505                    code.find('B').unwrap()..code.find(" = ").unwrap(),
506                ),
507                extension::CodeLabelSpan::CodeRange((code.find('🏀').unwrap() + 1)..code.len()),
508            ],
509            filter_range: 0.."B".len(),
510            code,
511        },
512        &code_runs,
513        &language::PLAIN_TEXT,
514    );
515    assert!(label.is_none());
516
517    // Filter range extends beyond actual text
518    let label = build_code_label(
519        &extension::CodeLabel {
520            spans: vec![extension::CodeLabelSpan::Literal(
521                extension::CodeLabelSpanLiteral {
522                    text: "abc".into(),
523                    highlight_name: Some("type".into()),
524                },
525            )],
526            filter_range: 0..5,
527            code: String::new(),
528        },
529        &code_runs,
530        &language::PLAIN_TEXT,
531    );
532    assert!(label.is_none());
533}