ZIm/crates/language_extension/src/extension_lsp_adapter.rs
Finn Evers a391d67366
supermaven_api: Ensure downloaded Supermaven binary has executable permissions set (#32576)
Closes #32068
Closes #15653

Not entirely sure that it fixes the latter issue, but I am fairly
certain given the comments in #32068 and the available logs in the
issue.

This PR fixes an issue where the Supermaven provider would not leave the
"Initializing" stage. This happened due to the downloaded binary missing
executable permissions. The change here ensures that freshly downloaded
binaries as well as existing binaries downloaded by Zed have executable
permissions set. I decided on also adding this for the latter since
existing downloads would continue to be broken and Supermaven does not
seem to change versions often given the logs provided by users.


While I was at it, I also added a `make_file_executable` to the util
crate mirroring the method of the `zed_extensions_api` and refactored
existing usages where possible to use that method instead. This makes
the code slightly more readable in my opinion, yet adds a method to
non-unix systems that practically does nothing. I can revert this should
that be preferred.


Release Notes:

- Fixed an issue where the Supermaven completion provider would not
leave the "Initializing" stage.

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-06-17 14:39:45 +00:00

636 lines
22 KiB
Rust

use std::any::Any;
use std::ops::Range;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use collections::HashMap;
use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
use fs::Fs;
use futures::{Future, FutureExt};
use gpui::AsyncApp;
use language::{
BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LanguageServerStatusUpdate,
LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
use serde::Serialize;
use serde_json::Value;
use util::{ResultExt, fs::make_file_executable, maybe};
use crate::LanguageServerRegistryProxy;
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
#[async_trait]
impl WorktreeDelegate for WorktreeDelegateAdapter {
fn id(&self) -> u64 {
self.0.worktree_id().to_proto()
}
fn root_path(&self) -> String {
self.0.worktree_root_path().to_string_lossy().to_string()
}
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
self.0.read_text_file(path).await
}
async fn which(&self, binary_name: String) -> Option<String> {
self.0
.which(binary_name.as_ref())
.await
.map(|path| path.to_string_lossy().to_string())
}
async fn shell_env(&self) -> Vec<(String, String)> {
self.0.shell_env().await.into_iter().collect()
}
}
impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
fn register_language_server(
&self,
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language: LanguageName,
) {
self.language_registry.register_lsp_adapter(
language.clone(),
Arc::new(ExtensionLspAdapter::new(
extension,
language_server_id,
language,
)),
);
}
fn remove_language_server(
&self,
language: &LanguageName,
language_server_id: &LanguageServerName,
) {
self.language_registry
.remove_lsp_adapter(language, language_server_id);
}
fn update_language_server_status(
&self,
language_server_id: LanguageServerName,
status: BinaryStatus,
) {
self.language_registry.update_lsp_status(
language_server_id,
LanguageServerStatusUpdate::Binary(status),
);
}
}
struct ExtensionLspAdapter {
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language_name: LanguageName,
}
impl ExtensionLspAdapter {
fn new(
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language_name: LanguageName,
) -> Self {
Self {
extension,
language_server_id,
language_name,
}
}
}
#[async_trait(?Send)]
impl LspAdapter for ExtensionLspAdapter {
fn name(&self) -> LanguageServerName {
self.language_server_id.clone()
}
fn get_language_server_command<'a>(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
async move {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let command = self
.extension
.language_server_command(
self.language_server_id.clone(),
self.language_name.clone(),
delegate,
)
.await?;
let path = self.extension.path_from_extension(command.command.as_ref());
// TODO: This should now be done via the `zed::make_file_executable` function in
// Zed extension API, but we're leaving these existing usages in place temporarily
// to avoid any compatibility issues between Zed and the extension versions.
//
// We can remove once the following extension versions no longer see any use:
// - toml@0.0.2
// - zig@0.0.1
if ["toml", "zig"].contains(&self.extension.manifest().id.as_ref())
&& path.starts_with(&self.extension.work_dir())
{
make_file_executable(&path)
.await
.context("failed to set file permissions")?;
}
Ok(LanguageServerBinary {
path,
arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
env: Some(command.env.into_iter().collect()),
})
}
.boxed_local()
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
unreachable!("get_language_server_command is overridden")
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
unreachable!("get_language_server_command is overridden")
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
unreachable!("get_language_server_command is overridden")
}
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
let code_action_kinds = self
.extension
.manifest()
.language_servers
.get(&self.language_server_id)
.and_then(|server| server.code_action_kinds.clone());
code_action_kinds.or(Some(vec![
CodeActionKind::EMPTY,
CodeActionKind::QUICKFIX,
CodeActionKind::REFACTOR,
CodeActionKind::REFACTOR_EXTRACT,
CodeActionKind::SOURCE,
]))
}
fn language_ids(&self) -> HashMap<String, String> {
// TODO: The language IDs can be provided via the language server options
// in `extension.toml now but we're leaving these existing usages in place temporarily
// to avoid any compatibility issues between Zed and the extension versions.
//
// We can remove once the following extension versions no longer see any use:
// - php@0.0.1
if self.extension.manifest().id.as_ref() == "php" {
return HashMap::from_iter([("PHP".into(), "php".into())]);
}
self.extension
.manifest()
.language_servers
.get(&self.language_server_id)
.map(|server| server.language_ids.clone())
.unwrap_or_default()
}
async fn initialization_options(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let json_options = self
.extension
.language_server_initialization_options(
self.language_server_id.clone(),
self.language_name.clone(),
delegate,
)
.await?;
Ok(if let Some(json_options) = json_options {
serde_json::from_str(&json_options).with_context(|| {
format!("failed to parse initialization_options from extension: {json_options}")
})?
} else {
None
})
}
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncApp,
) -> Result<Value> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let json_options: Option<String> = self
.extension
.language_server_workspace_configuration(self.language_server_id.clone(), delegate)
.await?;
Ok(if let Some(json_options) = json_options {
serde_json::from_str(&json_options).with_context(|| {
format!("failed to parse workspace_configuration from extension: {json_options}")
})?
} else {
serde_json::json!({})
})
}
async fn additional_initialization_options(
self: Arc<Self>,
target_language_server_id: LanguageServerName,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let json_options: Option<String> = self
.extension
.language_server_additional_initialization_options(
self.language_server_id.clone(),
target_language_server_id.clone(),
delegate,
)
.await?;
Ok(if let Some(json_options) = json_options {
serde_json::from_str(&json_options).with_context(|| {
format!(
"failed to parse additional_initialization_options from extension: {json_options}"
)
})?
} else {
None
})
}
async fn additional_workspace_configuration(
self: Arc<Self>,
target_language_server_id: LanguageServerName,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncApp,
) -> Result<Option<serde_json::Value>> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let json_options: Option<String> = self
.extension
.language_server_additional_workspace_configuration(
self.language_server_id.clone(),
target_language_server_id.clone(),
delegate,
)
.await?;
Ok(if let Some(json_options) = json_options {
serde_json::from_str(&json_options).with_context(|| {
format!("failed to parse additional_workspace_configuration from extension: {json_options}")
})?
} else {
None
})
}
async fn labels_for_completions(
self: Arc<Self>,
completions: &[lsp::CompletionItem],
language: &Arc<Language>,
) -> Result<Vec<Option<CodeLabel>>> {
let completions = completions
.iter()
.cloned()
.map(lsp_completion_to_extension)
.collect::<Vec<_>>();
let labels = self
.extension
.labels_for_completions(self.language_server_id.clone(), completions)
.await?;
Ok(labels_from_extension(labels, language))
}
async fn labels_for_symbols(
self: Arc<Self>,
symbols: &[(String, lsp::SymbolKind)],
language: &Arc<Language>,
) -> Result<Vec<Option<CodeLabel>>> {
let symbols = symbols
.iter()
.cloned()
.map(|(name, kind)| extension::Symbol {
name,
kind: lsp_symbol_kind_to_extension(kind),
})
.collect::<Vec<_>>();
let labels = self
.extension
.labels_for_symbols(self.language_server_id.clone(), symbols)
.await?;
Ok(labels_from_extension(labels, language))
}
}
fn labels_from_extension(
labels: Vec<Option<extension::CodeLabel>>,
language: &Arc<Language>,
) -> Vec<Option<CodeLabel>> {
labels
.into_iter()
.map(|label| {
let label = label?;
let runs = if label.code.is_empty() {
Vec::new()
} else {
language.highlight_text(&label.code.as_str().into(), 0..label.code.len())
};
build_code_label(&label, &runs, language)
})
.collect()
}
fn build_code_label(
label: &extension::CodeLabel,
parsed_runs: &[(Range<usize>, HighlightId)],
language: &Arc<Language>,
) -> Option<CodeLabel> {
let mut text = String::new();
let mut runs = vec![];
for span in &label.spans {
match span {
extension::CodeLabelSpan::CodeRange(range) => {
let code_span = &label.code.get(range.clone())?;
let mut input_ix = range.start;
let mut output_ix = text.len();
for (run_range, id) in parsed_runs {
if run_range.start >= range.end {
break;
}
if run_range.end <= input_ix {
continue;
}
if run_range.start > input_ix {
let len = run_range.start - input_ix;
output_ix += len;
input_ix += len;
}
let len = range.end.min(run_range.end) - input_ix;
runs.push((output_ix..output_ix + len, *id));
output_ix += len;
input_ix += len;
}
text.push_str(code_span);
}
extension::CodeLabelSpan::Literal(span) => {
let highlight_id = language
.grammar()
.zip(span.highlight_name.as_ref())
.and_then(|(grammar, highlight_name)| {
grammar.highlight_id_for_name(highlight_name)
})
.unwrap_or_default();
let ix = text.len();
runs.push((ix..ix + span.text.len(), highlight_id));
text.push_str(&span.text);
}
}
}
let filter_range = label.filter_range.clone();
text.get(filter_range.clone())?;
Some(CodeLabel {
text,
runs,
filter_range,
})
}
fn lsp_completion_to_extension(value: lsp::CompletionItem) -> extension::Completion {
extension::Completion {
label: value.label,
label_details: value
.label_details
.map(lsp_completion_item_label_details_to_extension),
detail: value.detail,
kind: value.kind.map(lsp_completion_item_kind_to_extension),
insert_text_format: value
.insert_text_format
.map(lsp_insert_text_format_to_extension),
}
}
fn lsp_completion_item_label_details_to_extension(
value: lsp::CompletionItemLabelDetails,
) -> extension::CompletionLabelDetails {
extension::CompletionLabelDetails {
detail: value.detail,
description: value.description,
}
}
fn lsp_completion_item_kind_to_extension(
value: lsp::CompletionItemKind,
) -> extension::CompletionKind {
match value {
lsp::CompletionItemKind::TEXT => extension::CompletionKind::Text,
lsp::CompletionItemKind::METHOD => extension::CompletionKind::Method,
lsp::CompletionItemKind::FUNCTION => extension::CompletionKind::Function,
lsp::CompletionItemKind::CONSTRUCTOR => extension::CompletionKind::Constructor,
lsp::CompletionItemKind::FIELD => extension::CompletionKind::Field,
lsp::CompletionItemKind::VARIABLE => extension::CompletionKind::Variable,
lsp::CompletionItemKind::CLASS => extension::CompletionKind::Class,
lsp::CompletionItemKind::INTERFACE => extension::CompletionKind::Interface,
lsp::CompletionItemKind::MODULE => extension::CompletionKind::Module,
lsp::CompletionItemKind::PROPERTY => extension::CompletionKind::Property,
lsp::CompletionItemKind::UNIT => extension::CompletionKind::Unit,
lsp::CompletionItemKind::VALUE => extension::CompletionKind::Value,
lsp::CompletionItemKind::ENUM => extension::CompletionKind::Enum,
lsp::CompletionItemKind::KEYWORD => extension::CompletionKind::Keyword,
lsp::CompletionItemKind::SNIPPET => extension::CompletionKind::Snippet,
lsp::CompletionItemKind::COLOR => extension::CompletionKind::Color,
lsp::CompletionItemKind::FILE => extension::CompletionKind::File,
lsp::CompletionItemKind::REFERENCE => extension::CompletionKind::Reference,
lsp::CompletionItemKind::FOLDER => extension::CompletionKind::Folder,
lsp::CompletionItemKind::ENUM_MEMBER => extension::CompletionKind::EnumMember,
lsp::CompletionItemKind::CONSTANT => extension::CompletionKind::Constant,
lsp::CompletionItemKind::STRUCT => extension::CompletionKind::Struct,
lsp::CompletionItemKind::EVENT => extension::CompletionKind::Event,
lsp::CompletionItemKind::OPERATOR => extension::CompletionKind::Operator,
lsp::CompletionItemKind::TYPE_PARAMETER => extension::CompletionKind::TypeParameter,
_ => extension::CompletionKind::Other(extract_int(value)),
}
}
fn lsp_insert_text_format_to_extension(
value: lsp::InsertTextFormat,
) -> extension::InsertTextFormat {
match value {
lsp::InsertTextFormat::PLAIN_TEXT => extension::InsertTextFormat::PlainText,
lsp::InsertTextFormat::SNIPPET => extension::InsertTextFormat::Snippet,
_ => extension::InsertTextFormat::Other(extract_int(value)),
}
}
fn lsp_symbol_kind_to_extension(value: lsp::SymbolKind) -> extension::SymbolKind {
match value {
lsp::SymbolKind::FILE => extension::SymbolKind::File,
lsp::SymbolKind::MODULE => extension::SymbolKind::Module,
lsp::SymbolKind::NAMESPACE => extension::SymbolKind::Namespace,
lsp::SymbolKind::PACKAGE => extension::SymbolKind::Package,
lsp::SymbolKind::CLASS => extension::SymbolKind::Class,
lsp::SymbolKind::METHOD => extension::SymbolKind::Method,
lsp::SymbolKind::PROPERTY => extension::SymbolKind::Property,
lsp::SymbolKind::FIELD => extension::SymbolKind::Field,
lsp::SymbolKind::CONSTRUCTOR => extension::SymbolKind::Constructor,
lsp::SymbolKind::ENUM => extension::SymbolKind::Enum,
lsp::SymbolKind::INTERFACE => extension::SymbolKind::Interface,
lsp::SymbolKind::FUNCTION => extension::SymbolKind::Function,
lsp::SymbolKind::VARIABLE => extension::SymbolKind::Variable,
lsp::SymbolKind::CONSTANT => extension::SymbolKind::Constant,
lsp::SymbolKind::STRING => extension::SymbolKind::String,
lsp::SymbolKind::NUMBER => extension::SymbolKind::Number,
lsp::SymbolKind::BOOLEAN => extension::SymbolKind::Boolean,
lsp::SymbolKind::ARRAY => extension::SymbolKind::Array,
lsp::SymbolKind::OBJECT => extension::SymbolKind::Object,
lsp::SymbolKind::KEY => extension::SymbolKind::Key,
lsp::SymbolKind::NULL => extension::SymbolKind::Null,
lsp::SymbolKind::ENUM_MEMBER => extension::SymbolKind::EnumMember,
lsp::SymbolKind::STRUCT => extension::SymbolKind::Struct,
lsp::SymbolKind::EVENT => extension::SymbolKind::Event,
lsp::SymbolKind::OPERATOR => extension::SymbolKind::Operator,
lsp::SymbolKind::TYPE_PARAMETER => extension::SymbolKind::TypeParameter,
_ => extension::SymbolKind::Other(extract_int(value)),
}
}
fn extract_int<T: Serialize>(value: T) -> i32 {
maybe!({
let kind = serde_json::to_value(&value)?;
serde_json::from_value(kind)
})
.log_err()
.unwrap_or(-1)
}
#[test]
fn test_build_code_label() {
use util::test::marked_text_ranges;
let (code, code_ranges) = marked_text_ranges(
"«const» «a»: «fn»(«Bcd»(«Efgh»)) -> «Ijklm» = pqrs.tuv",
false,
);
let code_runs = code_ranges
.into_iter()
.map(|range| (range, HighlightId(0)))
.collect::<Vec<_>>();
let label = build_code_label(
&extension::CodeLabel {
spans: vec![
extension::CodeLabelSpan::CodeRange(code.find("pqrs").unwrap()..code.len()),
extension::CodeLabelSpan::CodeRange(
code.find(": fn").unwrap()..code.find(" = ").unwrap(),
),
],
filter_range: 0.."pqrs.tuv".len(),
code,
},
&code_runs,
&language::PLAIN_TEXT,
)
.unwrap();
let (label_text, label_ranges) =
marked_text_ranges("pqrs.tuv: «fn»(«Bcd»(«Efgh»)) -> «Ijklm»", false);
let label_runs = label_ranges
.into_iter()
.map(|range| (range, HighlightId(0)))
.collect::<Vec<_>>();
assert_eq!(
label,
CodeLabel {
text: label_text,
runs: label_runs,
filter_range: label.filter_range.clone()
}
)
}
#[test]
fn test_build_code_label_with_invalid_ranges() {
use util::test::marked_text_ranges;
let (code, code_ranges) = marked_text_ranges("const «a»: «B» = '🏀'", false);
let code_runs = code_ranges
.into_iter()
.map(|range| (range, HighlightId(0)))
.collect::<Vec<_>>();
// A span uses a code range that is invalid because it starts inside of
// a multi-byte character.
let label = build_code_label(
&extension::CodeLabel {
spans: vec![
extension::CodeLabelSpan::CodeRange(
code.find('B').unwrap()..code.find(" = ").unwrap(),
),
extension::CodeLabelSpan::CodeRange((code.find('🏀').unwrap() + 1)..code.len()),
],
filter_range: 0.."B".len(),
code,
},
&code_runs,
&language::PLAIN_TEXT,
);
assert!(label.is_none());
// Filter range extends beyond actual text
let label = build_code_label(
&extension::CodeLabel {
spans: vec![extension::CodeLabelSpan::Literal(
extension::CodeLabelSpanLiteral {
text: "abc".into(),
highlight_name: Some("type".into()),
},
)],
filter_range: 0..5,
code: String::new(),
},
&code_runs,
&language::PLAIN_TEXT,
);
assert!(label.is_none());
}