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}