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}