since_v0_6_0.rs

  1use crate::wasm_host::wit::since_v0_6_0::{
  2    dap::{
  3        StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, TcpArguments,
  4        TcpArgumentsTemplate,
  5    },
  6    slash_command::SlashCommandOutputSection,
  7};
  8use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
  9use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
 10use ::http_client::{AsyncBody, HttpRequestExt};
 11use ::settings::{Settings, WorktreeId};
 12use anyhow::{Context as _, Result, bail};
 13use async_compression::futures::bufread::GzipDecoder;
 14use async_tar::Archive;
 15use async_trait::async_trait;
 16use extension::{
 17    ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
 18};
 19use futures::{AsyncReadExt, lock::Mutex};
 20use futures::{FutureExt as _, io::BufReader};
 21use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
 22use project::project_settings::ProjectSettings;
 23use semantic_version::SemanticVersion;
 24use std::{
 25    env,
 26    net::Ipv4Addr,
 27    path::{Path, PathBuf},
 28    sync::{Arc, OnceLock},
 29};
 30use util::{archive::extract_zip, maybe};
 31use wasmtime::component::{Linker, Resource};
 32
 33pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
 34pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
 35
 36wasmtime::component::bindgen!({
 37    async: true,
 38    trappable_imports: true,
 39    path: "../extension_api/wit/since_v0.6.0",
 40    with: {
 41         "worktree": ExtensionWorktree,
 42         "project": ExtensionProject,
 43         "key-value-store": ExtensionKeyValueStore,
 44         "zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
 45    },
 46});
 47
 48pub use self::zed::extension::*;
 49
 50mod settings {
 51    include!(concat!(env!("OUT_DIR"), "/since_v0.6.0/settings.rs"));
 52}
 53
 54pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
 55pub type ExtensionProject = Arc<dyn ProjectDelegate>;
 56pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
 57pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
 58
 59pub fn linker() -> &'static Linker<WasmState> {
 60    static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
 61    LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
 62}
 63
 64impl From<Range> for std::ops::Range<usize> {
 65    fn from(range: Range) -> Self {
 66        let start = range.start as usize;
 67        let end = range.end as usize;
 68        start..end
 69    }
 70}
 71
 72impl From<Command> for extension::Command {
 73    fn from(value: Command) -> Self {
 74        Self {
 75            command: value.command,
 76            args: value.args,
 77            env: value.env,
 78        }
 79    }
 80}
 81
 82impl From<StartDebuggingRequestArgumentsRequest>
 83    for extension::StartDebuggingRequestArgumentsRequest
 84{
 85    fn from(value: StartDebuggingRequestArgumentsRequest) -> Self {
 86        match value {
 87            StartDebuggingRequestArgumentsRequest::Launch => Self::Launch,
 88            StartDebuggingRequestArgumentsRequest::Attach => Self::Attach,
 89        }
 90    }
 91}
 92impl TryFrom<StartDebuggingRequestArguments> for extension::StartDebuggingRequestArguments {
 93    type Error = anyhow::Error;
 94
 95    fn try_from(value: StartDebuggingRequestArguments) -> Result<Self, Self::Error> {
 96        Ok(Self {
 97            configuration: serde_json::from_str(&value.configuration)?,
 98            request: value.request.into(),
 99        })
100    }
101}
102impl From<TcpArguments> for extension::TcpArguments {
103    fn from(value: TcpArguments) -> Self {
104        Self {
105            host: value.host.into(),
106            port: value.port,
107            timeout: value.timeout,
108        }
109    }
110}
111
112impl From<extension::TcpArgumentsTemplate> for TcpArgumentsTemplate {
113    fn from(value: extension::TcpArgumentsTemplate) -> Self {
114        Self {
115            host: value.host.map(Ipv4Addr::to_bits),
116            port: value.port,
117            timeout: value.timeout,
118        }
119    }
120}
121
122impl TryFrom<extension::DebugTaskDefinition> for DebugTaskDefinition {
123    type Error = anyhow::Error;
124    fn try_from(value: extension::DebugTaskDefinition) -> Result<Self, Self::Error> {
125        Ok(Self {
126            label: value.label.to_string(),
127            adapter: value.adapter.to_string(),
128            config: value.config.to_string(),
129            tcp_connection: value.tcp_connection.map(Into::into),
130        })
131    }
132}
133
134impl TryFrom<DebugAdapterBinary> for extension::DebugAdapterBinary {
135    type Error = anyhow::Error;
136    fn try_from(value: DebugAdapterBinary) -> Result<Self, Self::Error> {
137        Ok(Self {
138            command: value.command,
139            arguments: value.arguments,
140            envs: value.envs.into_iter().collect(),
141            cwd: value.cwd.map(|s| s.into()),
142            connection: value.connection.map(Into::into),
143            request_args: value.request_args.try_into()?,
144        })
145    }
146}
147
148impl From<CodeLabel> for extension::CodeLabel {
149    fn from(value: CodeLabel) -> Self {
150        Self {
151            code: value.code,
152            spans: value.spans.into_iter().map(Into::into).collect(),
153            filter_range: value.filter_range.into(),
154        }
155    }
156}
157
158impl From<CodeLabelSpan> for extension::CodeLabelSpan {
159    fn from(value: CodeLabelSpan) -> Self {
160        match value {
161            CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
162            CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
163        }
164    }
165}
166
167impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
168    fn from(value: CodeLabelSpanLiteral) -> Self {
169        Self {
170            text: value.text,
171            highlight_name: value.highlight_name,
172        }
173    }
174}
175
176impl From<extension::Completion> for Completion {
177    fn from(value: extension::Completion) -> Self {
178        Self {
179            label: value.label,
180            label_details: value.label_details.map(Into::into),
181            detail: value.detail,
182            kind: value.kind.map(Into::into),
183            insert_text_format: value.insert_text_format.map(Into::into),
184        }
185    }
186}
187
188impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
189    fn from(value: extension::CompletionLabelDetails) -> Self {
190        Self {
191            detail: value.detail,
192            description: value.description,
193        }
194    }
195}
196
197impl From<extension::CompletionKind> for CompletionKind {
198    fn from(value: extension::CompletionKind) -> Self {
199        match value {
200            extension::CompletionKind::Text => Self::Text,
201            extension::CompletionKind::Method => Self::Method,
202            extension::CompletionKind::Function => Self::Function,
203            extension::CompletionKind::Constructor => Self::Constructor,
204            extension::CompletionKind::Field => Self::Field,
205            extension::CompletionKind::Variable => Self::Variable,
206            extension::CompletionKind::Class => Self::Class,
207            extension::CompletionKind::Interface => Self::Interface,
208            extension::CompletionKind::Module => Self::Module,
209            extension::CompletionKind::Property => Self::Property,
210            extension::CompletionKind::Unit => Self::Unit,
211            extension::CompletionKind::Value => Self::Value,
212            extension::CompletionKind::Enum => Self::Enum,
213            extension::CompletionKind::Keyword => Self::Keyword,
214            extension::CompletionKind::Snippet => Self::Snippet,
215            extension::CompletionKind::Color => Self::Color,
216            extension::CompletionKind::File => Self::File,
217            extension::CompletionKind::Reference => Self::Reference,
218            extension::CompletionKind::Folder => Self::Folder,
219            extension::CompletionKind::EnumMember => Self::EnumMember,
220            extension::CompletionKind::Constant => Self::Constant,
221            extension::CompletionKind::Struct => Self::Struct,
222            extension::CompletionKind::Event => Self::Event,
223            extension::CompletionKind::Operator => Self::Operator,
224            extension::CompletionKind::TypeParameter => Self::TypeParameter,
225            extension::CompletionKind::Other(value) => Self::Other(value),
226        }
227    }
228}
229
230impl From<extension::InsertTextFormat> for InsertTextFormat {
231    fn from(value: extension::InsertTextFormat) -> Self {
232        match value {
233            extension::InsertTextFormat::PlainText => Self::PlainText,
234            extension::InsertTextFormat::Snippet => Self::Snippet,
235            extension::InsertTextFormat::Other(value) => Self::Other(value),
236        }
237    }
238}
239
240impl From<extension::Symbol> for Symbol {
241    fn from(value: extension::Symbol) -> Self {
242        Self {
243            kind: value.kind.into(),
244            name: value.name,
245        }
246    }
247}
248
249impl From<extension::SymbolKind> for SymbolKind {
250    fn from(value: extension::SymbolKind) -> Self {
251        match value {
252            extension::SymbolKind::File => Self::File,
253            extension::SymbolKind::Module => Self::Module,
254            extension::SymbolKind::Namespace => Self::Namespace,
255            extension::SymbolKind::Package => Self::Package,
256            extension::SymbolKind::Class => Self::Class,
257            extension::SymbolKind::Method => Self::Method,
258            extension::SymbolKind::Property => Self::Property,
259            extension::SymbolKind::Field => Self::Field,
260            extension::SymbolKind::Constructor => Self::Constructor,
261            extension::SymbolKind::Enum => Self::Enum,
262            extension::SymbolKind::Interface => Self::Interface,
263            extension::SymbolKind::Function => Self::Function,
264            extension::SymbolKind::Variable => Self::Variable,
265            extension::SymbolKind::Constant => Self::Constant,
266            extension::SymbolKind::String => Self::String,
267            extension::SymbolKind::Number => Self::Number,
268            extension::SymbolKind::Boolean => Self::Boolean,
269            extension::SymbolKind::Array => Self::Array,
270            extension::SymbolKind::Object => Self::Object,
271            extension::SymbolKind::Key => Self::Key,
272            extension::SymbolKind::Null => Self::Null,
273            extension::SymbolKind::EnumMember => Self::EnumMember,
274            extension::SymbolKind::Struct => Self::Struct,
275            extension::SymbolKind::Event => Self::Event,
276            extension::SymbolKind::Operator => Self::Operator,
277            extension::SymbolKind::TypeParameter => Self::TypeParameter,
278            extension::SymbolKind::Other(value) => Self::Other(value),
279        }
280    }
281}
282
283impl From<extension::SlashCommand> for SlashCommand {
284    fn from(value: extension::SlashCommand) -> Self {
285        Self {
286            name: value.name,
287            description: value.description,
288            tooltip_text: value.tooltip_text,
289            requires_argument: value.requires_argument,
290        }
291    }
292}
293
294impl From<SlashCommandOutput> for extension::SlashCommandOutput {
295    fn from(value: SlashCommandOutput) -> Self {
296        Self {
297            text: value.text,
298            sections: value.sections.into_iter().map(Into::into).collect(),
299        }
300    }
301}
302
303impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
304    fn from(value: SlashCommandOutputSection) -> Self {
305        Self {
306            range: value.range.start as usize..value.range.end as usize,
307            label: value.label,
308        }
309    }
310}
311
312impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
313    fn from(value: SlashCommandArgumentCompletion) -> Self {
314        Self {
315            label: value.label,
316            new_text: value.new_text,
317            run_command: value.run_command,
318        }
319    }
320}
321
322impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
323    type Error = anyhow::Error;
324
325    fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
326        let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
327            .context("Failed to parse settings_schema")?;
328
329        Ok(Self {
330            installation_instructions: value.installation_instructions,
331            default_settings: value.default_settings,
332            settings_schema,
333        })
334    }
335}
336
337impl HostKeyValueStore for WasmState {
338    async fn insert(
339        &mut self,
340        kv_store: Resource<ExtensionKeyValueStore>,
341        key: String,
342        value: String,
343    ) -> wasmtime::Result<Result<(), String>> {
344        let kv_store = self.table.get(&kv_store)?;
345        kv_store.insert(key, value).await.to_wasmtime_result()
346    }
347
348    async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
349        // We only ever hand out borrows of key-value stores.
350        Ok(())
351    }
352}
353
354impl HostProject for WasmState {
355    async fn worktree_ids(
356        &mut self,
357        project: Resource<ExtensionProject>,
358    ) -> wasmtime::Result<Vec<u64>> {
359        let project = self.table.get(&project)?;
360        Ok(project.worktree_ids())
361    }
362
363    async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
364        // We only ever hand out borrows of projects.
365        Ok(())
366    }
367}
368
369impl HostWorktree for WasmState {
370    async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
371        let delegate = self.table.get(&delegate)?;
372        Ok(delegate.id())
373    }
374
375    async fn root_path(
376        &mut self,
377        delegate: Resource<Arc<dyn WorktreeDelegate>>,
378    ) -> wasmtime::Result<String> {
379        let delegate = self.table.get(&delegate)?;
380        Ok(delegate.root_path())
381    }
382
383    async fn read_text_file(
384        &mut self,
385        delegate: Resource<Arc<dyn WorktreeDelegate>>,
386        path: String,
387    ) -> wasmtime::Result<Result<String, String>> {
388        let delegate = self.table.get(&delegate)?;
389        Ok(delegate
390            .read_text_file(path.into())
391            .await
392            .map_err(|error| error.to_string()))
393    }
394
395    async fn shell_env(
396        &mut self,
397        delegate: Resource<Arc<dyn WorktreeDelegate>>,
398    ) -> wasmtime::Result<EnvVars> {
399        let delegate = self.table.get(&delegate)?;
400        Ok(delegate.shell_env().await.into_iter().collect())
401    }
402
403    async fn which(
404        &mut self,
405        delegate: Resource<Arc<dyn WorktreeDelegate>>,
406        binary_name: String,
407    ) -> wasmtime::Result<Option<String>> {
408        let delegate = self.table.get(&delegate)?;
409        Ok(delegate.which(binary_name).await)
410    }
411
412    async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
413        // We only ever hand out borrows of worktrees.
414        Ok(())
415    }
416}
417
418impl common::Host for WasmState {}
419
420impl http_client::Host for WasmState {
421    async fn fetch(
422        &mut self,
423        request: http_client::HttpRequest,
424    ) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
425        maybe!(async {
426            let url = &request.url;
427            let request = convert_request(&request)?;
428            let mut response = self.host.http_client.send(request).await?;
429
430            if response.status().is_client_error() || response.status().is_server_error() {
431                bail!("failed to fetch '{url}': status code {}", response.status())
432            }
433            convert_response(&mut response).await
434        })
435        .await
436        .to_wasmtime_result()
437    }
438
439    async fn fetch_stream(
440        &mut self,
441        request: http_client::HttpRequest,
442    ) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
443        let request = convert_request(&request)?;
444        let response = self.host.http_client.send(request);
445        maybe!(async {
446            let response = response.await?;
447            let stream = Arc::new(Mutex::new(response));
448            let resource = self.table.push(stream)?;
449            Ok(resource)
450        })
451        .await
452        .to_wasmtime_result()
453    }
454}
455
456impl http_client::HostHttpResponseStream for WasmState {
457    async fn next_chunk(
458        &mut self,
459        resource: Resource<ExtensionHttpResponseStream>,
460    ) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
461        let stream = self.table.get(&resource)?.clone();
462        maybe!(async move {
463            let mut response = stream.lock().await;
464            let mut buffer = vec![0; 8192]; // 8KB buffer
465            let bytes_read = response.body_mut().read(&mut buffer).await?;
466            if bytes_read == 0 {
467                Ok(None)
468            } else {
469                buffer.truncate(bytes_read);
470                Ok(Some(buffer))
471            }
472        })
473        .await
474        .to_wasmtime_result()
475    }
476
477    async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
478        Ok(())
479    }
480}
481
482impl From<http_client::HttpMethod> for ::http_client::Method {
483    fn from(value: http_client::HttpMethod) -> Self {
484        match value {
485            http_client::HttpMethod::Get => Self::GET,
486            http_client::HttpMethod::Post => Self::POST,
487            http_client::HttpMethod::Put => Self::PUT,
488            http_client::HttpMethod::Delete => Self::DELETE,
489            http_client::HttpMethod::Head => Self::HEAD,
490            http_client::HttpMethod::Options => Self::OPTIONS,
491            http_client::HttpMethod::Patch => Self::PATCH,
492        }
493    }
494}
495
496fn convert_request(
497    extension_request: &http_client::HttpRequest,
498) -> anyhow::Result<::http_client::Request<AsyncBody>> {
499    let mut request = ::http_client::Request::builder()
500        .method(::http_client::Method::from(extension_request.method))
501        .uri(&extension_request.url)
502        .follow_redirects(match extension_request.redirect_policy {
503            http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
504            http_client::RedirectPolicy::FollowLimit(limit) => {
505                ::http_client::RedirectPolicy::FollowLimit(limit)
506            }
507            http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
508        });
509    for (key, value) in &extension_request.headers {
510        request = request.header(key, value);
511    }
512    let body = extension_request
513        .body
514        .clone()
515        .map(AsyncBody::from)
516        .unwrap_or_default();
517    request.body(body).map_err(anyhow::Error::from)
518}
519
520async fn convert_response(
521    response: &mut ::http_client::Response<AsyncBody>,
522) -> anyhow::Result<http_client::HttpResponse> {
523    let mut extension_response = http_client::HttpResponse {
524        body: Vec::new(),
525        headers: Vec::new(),
526    };
527
528    for (key, value) in response.headers() {
529        extension_response
530            .headers
531            .push((key.to_string(), value.to_str().unwrap_or("").to_string()));
532    }
533
534    response
535        .body_mut()
536        .read_to_end(&mut extension_response.body)
537        .await?;
538
539    Ok(extension_response)
540}
541
542impl nodejs::Host for WasmState {
543    async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
544        self.host
545            .node_runtime
546            .binary_path()
547            .await
548            .map(|path| path.to_string_lossy().to_string())
549            .to_wasmtime_result()
550    }
551
552    async fn npm_package_latest_version(
553        &mut self,
554        package_name: String,
555    ) -> wasmtime::Result<Result<String, String>> {
556        self.host
557            .node_runtime
558            .npm_package_latest_version(&package_name)
559            .await
560            .to_wasmtime_result()
561    }
562
563    async fn npm_package_installed_version(
564        &mut self,
565        package_name: String,
566    ) -> wasmtime::Result<Result<Option<String>, String>> {
567        self.host
568            .node_runtime
569            .npm_package_installed_version(&self.work_dir(), &package_name)
570            .await
571            .to_wasmtime_result()
572    }
573
574    async fn npm_install_package(
575        &mut self,
576        package_name: String,
577        version: String,
578    ) -> wasmtime::Result<Result<(), String>> {
579        self.host
580            .node_runtime
581            .npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
582            .await
583            .to_wasmtime_result()
584    }
585}
586
587#[async_trait]
588impl lsp::Host for WasmState {}
589
590impl From<::http_client::github::GithubRelease> for github::GithubRelease {
591    fn from(value: ::http_client::github::GithubRelease) -> Self {
592        Self {
593            version: value.tag_name,
594            assets: value.assets.into_iter().map(Into::into).collect(),
595        }
596    }
597}
598
599impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
600    fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
601        Self {
602            name: value.name,
603            download_url: value.browser_download_url,
604        }
605    }
606}
607
608impl github::Host for WasmState {
609    async fn latest_github_release(
610        &mut self,
611        repo: String,
612        options: github::GithubReleaseOptions,
613    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
614        maybe!(async {
615            let release = ::http_client::github::latest_github_release(
616                &repo,
617                options.require_assets,
618                options.pre_release,
619                self.host.http_client.clone(),
620            )
621            .await?;
622            Ok(release.into())
623        })
624        .await
625        .to_wasmtime_result()
626    }
627
628    async fn github_release_by_tag_name(
629        &mut self,
630        repo: String,
631        tag: String,
632    ) -> wasmtime::Result<Result<github::GithubRelease, String>> {
633        maybe!(async {
634            let release = ::http_client::github::get_release_by_tag_name(
635                &repo,
636                &tag,
637                self.host.http_client.clone(),
638            )
639            .await?;
640            Ok(release.into())
641        })
642        .await
643        .to_wasmtime_result()
644    }
645}
646
647impl platform::Host for WasmState {
648    async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
649        Ok((
650            match env::consts::OS {
651                "macos" => platform::Os::Mac,
652                "linux" => platform::Os::Linux,
653                "windows" => platform::Os::Windows,
654                _ => panic!("unsupported os"),
655            },
656            match env::consts::ARCH {
657                "aarch64" => platform::Architecture::Aarch64,
658                "x86" => platform::Architecture::X86,
659                "x86_64" => platform::Architecture::X8664,
660                _ => panic!("unsupported architecture"),
661            },
662        ))
663    }
664}
665
666impl From<std::process::Output> for process::Output {
667    fn from(output: std::process::Output) -> Self {
668        Self {
669            status: output.status.code(),
670            stdout: output.stdout,
671            stderr: output.stderr,
672        }
673    }
674}
675
676impl process::Host for WasmState {
677    async fn run_command(
678        &mut self,
679        command: process::Command,
680    ) -> wasmtime::Result<Result<process::Output, String>> {
681        maybe!(async {
682            self.manifest.allow_exec(&command.command, &command.args)?;
683
684            let output = util::command::new_smol_command(command.command.as_str())
685                .args(&command.args)
686                .envs(command.env)
687                .output()
688                .await?;
689
690            Ok(output.into())
691        })
692        .await
693        .to_wasmtime_result()
694    }
695}
696
697#[async_trait]
698impl slash_command::Host for WasmState {}
699
700#[async_trait]
701impl context_server::Host for WasmState {}
702
703impl dap::Host for WasmState {
704    async fn resolve_tcp_template(
705        &mut self,
706        template: TcpArgumentsTemplate,
707    ) -> wasmtime::Result<Result<TcpArguments, String>> {
708        maybe!(async {
709            let (host, port, timeout) =
710                ::dap::configure_tcp_connection(task::TcpArgumentsTemplate {
711                    port: template.port,
712                    host: template.host.map(Ipv4Addr::from_bits),
713                    timeout: template.timeout,
714                })
715                .await?;
716            Ok(TcpArguments {
717                port,
718                host: host.to_bits(),
719                timeout,
720            })
721        })
722        .await
723        .to_wasmtime_result()
724    }
725}
726
727impl ExtensionImports for WasmState {
728    async fn get_settings(
729        &mut self,
730        location: Option<self::SettingsLocation>,
731        category: String,
732        key: Option<String>,
733    ) -> wasmtime::Result<Result<String, String>> {
734        self.on_main_thread(|cx| {
735            async move {
736                let location = location
737                    .as_ref()
738                    .map(|location| ::settings::SettingsLocation {
739                        worktree_id: WorktreeId::from_proto(location.worktree_id),
740                        path: Path::new(&location.path),
741                    });
742
743                cx.update(|cx| match category.as_str() {
744                    "language" => {
745                        let key = key.map(|k| LanguageName::new(&k));
746                        let settings = AllLanguageSettings::get(location, cx).language(
747                            location,
748                            key.as_ref(),
749                            cx,
750                        );
751                        Ok(serde_json::to_string(&settings::LanguageSettings {
752                            tab_size: settings.tab_size,
753                        })?)
754                    }
755                    "lsp" => {
756                        let settings = key
757                            .and_then(|key| {
758                                ProjectSettings::get(location, cx)
759                                    .lsp
760                                    .get(&::lsp::LanguageServerName::from_proto(key))
761                            })
762                            .cloned()
763                            .unwrap_or_default();
764                        Ok(serde_json::to_string(&settings::LspSettings {
765                            binary: settings.binary.map(|binary| settings::CommandSettings {
766                                path: binary.path,
767                                arguments: binary.arguments,
768                                env: binary.env,
769                            }),
770                            settings: settings.settings,
771                            initialization_options: settings.initialization_options,
772                        })?)
773                    }
774                    "context_servers" => {
775                        let configuration = key
776                            .and_then(|key| {
777                                ProjectSettings::get(location, cx)
778                                    .context_servers
779                                    .get(key.as_str())
780                            })
781                            .cloned()
782                            .unwrap_or_default();
783                        Ok(serde_json::to_string(&settings::ContextServerSettings {
784                            command: configuration.command.map(|command| {
785                                settings::CommandSettings {
786                                    path: Some(command.path),
787                                    arguments: Some(command.args),
788                                    env: command.env.map(|env| env.into_iter().collect()),
789                                }
790                            }),
791                            settings: configuration.settings,
792                        })?)
793                    }
794                    _ => {
795                        bail!("Unknown settings category: {}", category);
796                    }
797                })
798            }
799            .boxed_local()
800        })
801        .await?
802        .to_wasmtime_result()
803    }
804
805    async fn set_language_server_installation_status(
806        &mut self,
807        server_name: String,
808        status: LanguageServerInstallationStatus,
809    ) -> wasmtime::Result<()> {
810        let status = match status {
811            LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
812            LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
813            LanguageServerInstallationStatus::None => BinaryStatus::None,
814            LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
815        };
816
817        self.host
818            .proxy
819            .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
820
821        Ok(())
822    }
823
824    async fn download_file(
825        &mut self,
826        url: String,
827        path: String,
828        file_type: DownloadedFileType,
829    ) -> wasmtime::Result<Result<(), String>> {
830        maybe!(async {
831            let path = PathBuf::from(path);
832            let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
833
834            self.host.fs.create_dir(&extension_work_dir).await?;
835
836            let destination_path = self
837                .host
838                .writeable_path_from_extension(&self.manifest.id, &path)?;
839
840            let mut response = self
841                .host
842                .http_client
843                .get(&url, Default::default(), true)
844                .await
845                .context("downloading release")?;
846
847            anyhow::ensure!(
848                response.status().is_success(),
849                "download failed with status {}",
850                response.status().to_string()
851            );
852            let body = BufReader::new(response.body_mut());
853
854            match file_type {
855                DownloadedFileType::Uncompressed => {
856                    futures::pin_mut!(body);
857                    self.host
858                        .fs
859                        .create_file_with(&destination_path, body)
860                        .await?;
861                }
862                DownloadedFileType::Gzip => {
863                    let body = GzipDecoder::new(body);
864                    futures::pin_mut!(body);
865                    self.host
866                        .fs
867                        .create_file_with(&destination_path, body)
868                        .await?;
869                }
870                DownloadedFileType::GzipTar => {
871                    let body = GzipDecoder::new(body);
872                    futures::pin_mut!(body);
873                    self.host
874                        .fs
875                        .extract_tar_file(&destination_path, Archive::new(body))
876                        .await?;
877                }
878                DownloadedFileType::Zip => {
879                    futures::pin_mut!(body);
880                    extract_zip(&destination_path, body)
881                        .await
882                        .with_context(|| format!("unzipping {path:?} archive"))?;
883                }
884            }
885
886            Ok(())
887        })
888        .await
889        .to_wasmtime_result()
890    }
891
892    async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
893        #[allow(unused)]
894        let path = self
895            .host
896            .writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
897
898        #[cfg(unix)]
899        {
900            use std::fs::{self, Permissions};
901            use std::os::unix::fs::PermissionsExt;
902
903            return fs::set_permissions(&path, Permissions::from_mode(0o755))
904                .with_context(|| format!("setting permissions for path {path:?}"))
905                .to_wasmtime_result();
906        }
907
908        #[cfg(not(unix))]
909        Ok(Ok(()))
910    }
911}