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