since_v0_6_0.rs

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