1use anyhow::Result;
2use async_trait::async_trait;
3use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
4use gpui::{App, Task, WeakEntity, Window};
5use language::{BufferSnapshot, LspAdapterDelegate};
6use std::sync::{Arc, atomic::AtomicBool};
7use ui::prelude::*;
8use util::rel_path::RelPath;
9use workspace::Workspace;
10
11use crate::{
12 ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
13 SlashCommandRegistry, SlashCommandResult,
14};
15
16pub fn init(cx: &mut App) {
17 let proxy = ExtensionHostProxy::default_global(cx);
18 proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
19 slash_command_registry: SlashCommandRegistry::global(cx),
20 });
21}
22
23struct SlashCommandRegistryProxy {
24 slash_command_registry: Arc<SlashCommandRegistry>,
25}
26
27impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
28 fn register_slash_command(
29 &self,
30 extension: Arc<dyn Extension>,
31 command: extension::SlashCommand,
32 ) {
33 self.slash_command_registry
34 .register_command(ExtensionSlashCommand::new(extension, command), false)
35 }
36
37 fn unregister_slash_command(&self, command_name: Arc<str>) {
38 self.slash_command_registry
39 .unregister_command_by_name(&command_name)
40 }
41}
42
43/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
44struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
45
46#[async_trait]
47impl WorktreeDelegate for WorktreeDelegateAdapter {
48 fn id(&self) -> u64 {
49 self.0.worktree_id().to_proto()
50 }
51
52 fn root_path(&self) -> String {
53 self.0.worktree_root_path().to_string_lossy().into_owned()
54 }
55
56 async fn read_text_file(&self, path: &RelPath) -> Result<String> {
57 self.0.read_text_file(path).await
58 }
59
60 async fn which(&self, binary_name: String) -> Option<String> {
61 self.0
62 .which(binary_name.as_ref())
63 .await
64 .map(|path| path.to_string_lossy().into_owned())
65 }
66
67 async fn shell_env(&self) -> Vec<(String, String)> {
68 self.0.shell_env().await.into_iter().collect()
69 }
70}
71
72pub struct ExtensionSlashCommand {
73 extension: Arc<dyn Extension>,
74 command: extension::SlashCommand,
75}
76
77impl ExtensionSlashCommand {
78 pub fn new(extension: Arc<dyn Extension>, command: extension::SlashCommand) -> Self {
79 Self { extension, command }
80 }
81}
82
83impl SlashCommand for ExtensionSlashCommand {
84 fn name(&self) -> String {
85 self.command.name.clone()
86 }
87
88 fn description(&self) -> String {
89 self.command.description.clone()
90 }
91
92 fn menu_text(&self) -> String {
93 self.command.tooltip_text.clone()
94 }
95
96 fn requires_argument(&self) -> bool {
97 self.command.requires_argument
98 }
99
100 fn complete_argument(
101 self: Arc<Self>,
102 arguments: &[String],
103 _cancel: Arc<AtomicBool>,
104 _workspace: Option<WeakEntity<Workspace>>,
105 _window: &mut Window,
106 cx: &mut App,
107 ) -> Task<Result<Vec<ArgumentCompletion>>> {
108 let command = self.command.clone();
109 let arguments = arguments.to_owned();
110 cx.background_spawn(async move {
111 let completions = self
112 .extension
113 .complete_slash_command_argument(command, arguments)
114 .await?;
115
116 anyhow::Ok(
117 completions
118 .into_iter()
119 .map(|completion| ArgumentCompletion {
120 label: completion.label.into(),
121 new_text: completion.new_text,
122 replace_previous_arguments: false,
123 after_completion: completion.run_command.into(),
124 })
125 .collect(),
126 )
127 })
128 }
129
130 fn run(
131 self: Arc<Self>,
132 arguments: &[String],
133 _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
134 _context_buffer: BufferSnapshot,
135 _workspace: WeakEntity<Workspace>,
136 delegate: Option<Arc<dyn LspAdapterDelegate>>,
137 _window: &mut Window,
138 cx: &mut App,
139 ) -> Task<SlashCommandResult> {
140 let command = self.command.clone();
141 let arguments = arguments.to_owned();
142 let output = cx.background_spawn(async move {
143 let delegate =
144 delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _);
145 let output = self
146 .extension
147 .run_slash_command(command, arguments, delegate)
148 .await?;
149
150 anyhow::Ok(output)
151 });
152 cx.foreground_executor().spawn(async move {
153 let output = output.await?;
154 Ok(SlashCommandOutput {
155 text: output.text,
156 sections: output
157 .sections
158 .into_iter()
159 .map(|section| SlashCommandOutputSection {
160 range: section.range,
161 icon: IconName::Code,
162 label: section.label.into(),
163 metadata: None,
164 })
165 .collect(),
166 run_commands_in_text: false,
167 }
168 .into_event_stream())
169 })
170 }
171}