1use crate::context_editor::ContextEditor;
2use anyhow::Result;
3pub use assistant_slash_command::SlashCommand;
4use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
5use editor::{CompletionProvider, Editor};
6use fuzzy::{match_strings, StringMatchCandidate};
7use gpui::{App, Context, Entity, Task, WeakEntity, Window};
8use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
9use parking_lot::Mutex;
10use project::CompletionIntent;
11use rope::Point;
12use std::{
13 cell::RefCell,
14 ops::Range,
15 rc::Rc,
16 sync::{
17 atomic::{AtomicBool, Ordering::SeqCst},
18 Arc,
19 },
20};
21use workspace::Workspace;
22
23pub struct SlashCommandCompletionProvider {
24 cancel_flag: Mutex<Arc<AtomicBool>>,
25 slash_commands: Arc<SlashCommandWorkingSet>,
26 editor: Option<WeakEntity<ContextEditor>>,
27 workspace: Option<WeakEntity<Workspace>>,
28}
29
30impl SlashCommandCompletionProvider {
31 pub fn new(
32 slash_commands: Arc<SlashCommandWorkingSet>,
33 editor: Option<WeakEntity<ContextEditor>>,
34 workspace: Option<WeakEntity<Workspace>>,
35 ) -> Self {
36 Self {
37 cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
38 slash_commands,
39 editor,
40 workspace,
41 }
42 }
43
44 fn complete_command_name(
45 &self,
46 command_name: &str,
47 command_range: Range<Anchor>,
48 name_range: Range<Anchor>,
49 window: &mut Window,
50 cx: &mut App,
51 ) -> Task<Result<Vec<project::Completion>>> {
52 let slash_commands = self.slash_commands.clone();
53 let candidates = slash_commands
54 .command_names(cx)
55 .into_iter()
56 .enumerate()
57 .map(|(ix, def)| StringMatchCandidate::new(ix, &def))
58 .collect::<Vec<_>>();
59 let command_name = command_name.to_string();
60 let editor = self.editor.clone();
61 let workspace = self.workspace.clone();
62 window.spawn(cx, |mut cx| async move {
63 let matches = match_strings(
64 &candidates,
65 &command_name,
66 true,
67 usize::MAX,
68 &Default::default(),
69 cx.background_executor().clone(),
70 )
71 .await;
72
73 cx.update(|_, cx| {
74 matches
75 .into_iter()
76 .filter_map(|mat| {
77 let command = slash_commands.command(&mat.string, cx)?;
78 let mut new_text = mat.string.clone();
79 let requires_argument = command.requires_argument();
80 let accepts_arguments = command.accepts_arguments();
81 if requires_argument || accepts_arguments {
82 new_text.push(' ');
83 }
84
85 let confirm =
86 editor
87 .clone()
88 .zip(workspace.clone())
89 .map(|(editor, workspace)| {
90 let command_name = mat.string.clone();
91 let command_range = command_range.clone();
92 let editor = editor.clone();
93 let workspace = workspace.clone();
94 Arc::new(
95 move |intent: CompletionIntent,
96 window: &mut Window,
97 cx: &mut App| {
98 if !requires_argument
99 && (!accepts_arguments || intent.is_complete())
100 {
101 editor
102 .update(cx, |editor, cx| {
103 editor.run_command(
104 command_range.clone(),
105 &command_name,
106 &[],
107 true,
108 workspace.clone(),
109 window,
110 cx,
111 );
112 })
113 .ok();
114 false
115 } else {
116 requires_argument || accepts_arguments
117 }
118 },
119 ) as Arc<_>
120 });
121 Some(project::Completion {
122 old_range: name_range.clone(),
123 documentation: Some(Documentation::SingleLine(command.description())),
124 new_text,
125 label: command.label(cx),
126 server_id: LanguageServerId(0),
127 lsp_completion: Default::default(),
128 confirm,
129 resolved: true,
130 })
131 })
132 .collect()
133 })
134 })
135 }
136
137 #[allow(clippy::too_many_arguments)]
138 fn complete_command_argument(
139 &self,
140 command_name: &str,
141 arguments: &[String],
142 command_range: Range<Anchor>,
143 argument_range: Range<Anchor>,
144 last_argument_range: Range<Anchor>,
145 window: &mut Window,
146 cx: &mut App,
147 ) -> Task<Result<Vec<project::Completion>>> {
148 let new_cancel_flag = Arc::new(AtomicBool::new(false));
149 let mut flag = self.cancel_flag.lock();
150 flag.store(true, SeqCst);
151 *flag = new_cancel_flag.clone();
152 if let Some(command) = self.slash_commands.command(command_name, cx) {
153 let completions = command.complete_argument(
154 arguments,
155 new_cancel_flag.clone(),
156 self.workspace.clone(),
157 window,
158 cx,
159 );
160 let command_name: Arc<str> = command_name.into();
161 let editor = self.editor.clone();
162 let workspace = self.workspace.clone();
163 let arguments = arguments.to_vec();
164 cx.background_executor().spawn(async move {
165 Ok(completions
166 .await?
167 .into_iter()
168 .map(|new_argument| {
169 let confirm =
170 editor
171 .clone()
172 .zip(workspace.clone())
173 .map(|(editor, workspace)| {
174 Arc::new({
175 let mut completed_arguments = arguments.clone();
176 if new_argument.replace_previous_arguments {
177 completed_arguments.clear();
178 } else {
179 completed_arguments.pop();
180 }
181 completed_arguments.push(new_argument.new_text.clone());
182
183 let command_range = command_range.clone();
184 let command_name = command_name.clone();
185 move |intent: CompletionIntent,
186 window: &mut Window,
187 cx: &mut App| {
188 if new_argument.after_completion.run()
189 || intent.is_complete()
190 {
191 editor
192 .update(cx, |editor, cx| {
193 editor.run_command(
194 command_range.clone(),
195 &command_name,
196 &completed_arguments,
197 true,
198 workspace.clone(),
199 window,
200 cx,
201 );
202 })
203 .ok();
204 false
205 } else {
206 !new_argument.after_completion.run()
207 }
208 }
209 }) as Arc<_>
210 });
211
212 let mut new_text = new_argument.new_text.clone();
213 if new_argument.after_completion == AfterCompletion::Continue {
214 new_text.push(' ');
215 }
216
217 project::Completion {
218 old_range: if new_argument.replace_previous_arguments {
219 argument_range.clone()
220 } else {
221 last_argument_range.clone()
222 },
223 label: new_argument.label,
224 new_text,
225 documentation: None,
226 server_id: LanguageServerId(0),
227 lsp_completion: Default::default(),
228 confirm,
229 resolved: true,
230 }
231 })
232 .collect())
233 })
234 } else {
235 Task::ready(Ok(Vec::new()))
236 }
237 }
238}
239
240impl CompletionProvider for SlashCommandCompletionProvider {
241 fn completions(
242 &self,
243 buffer: &Entity<Buffer>,
244 buffer_position: Anchor,
245 _: editor::CompletionContext,
246 window: &mut Window,
247 cx: &mut Context<Editor>,
248 ) -> Task<Result<Vec<project::Completion>>> {
249 let Some((name, arguments, command_range, last_argument_range)) =
250 buffer.update(cx, |buffer, _cx| {
251 let position = buffer_position.to_point(buffer);
252 let line_start = Point::new(position.row, 0);
253 let mut lines = buffer.text_for_range(line_start..position).lines();
254 let line = lines.next()?;
255 let call = SlashCommandLine::parse(line)?;
256
257 let command_range_start = Point::new(position.row, call.name.start as u32 - 1);
258 let command_range_end = Point::new(
259 position.row,
260 call.arguments.last().map_or(call.name.end, |arg| arg.end) as u32,
261 );
262 let command_range = buffer.anchor_after(command_range_start)
263 ..buffer.anchor_after(command_range_end);
264
265 let name = line[call.name.clone()].to_string();
266 let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last()
267 {
268 let last_arg_start =
269 buffer.anchor_after(Point::new(position.row, argument.start as u32));
270 let first_arg_start = call.arguments.first().expect("we have the last element");
271 let first_arg_start =
272 buffer.anchor_after(Point::new(position.row, first_arg_start.start as u32));
273 let arguments = call
274 .arguments
275 .iter()
276 .filter_map(|argument| Some(line.get(argument.clone())?.to_string()))
277 .collect::<Vec<_>>();
278 let argument_range = first_arg_start..buffer_position;
279 (
280 Some((arguments, argument_range)),
281 last_arg_start..buffer_position,
282 )
283 } else {
284 let start =
285 buffer.anchor_after(Point::new(position.row, call.name.start as u32));
286 (None, start..buffer_position)
287 };
288
289 Some((name, arguments, command_range, last_argument_range))
290 })
291 else {
292 return Task::ready(Ok(Vec::new()));
293 };
294
295 if let Some((arguments, argument_range)) = arguments {
296 self.complete_command_argument(
297 &name,
298 &arguments,
299 command_range,
300 argument_range,
301 last_argument_range,
302 window,
303 cx,
304 )
305 } else {
306 self.complete_command_name(&name, command_range, last_argument_range, window, cx)
307 }
308 }
309
310 fn resolve_completions(
311 &self,
312 _: Entity<Buffer>,
313 _: Vec<usize>,
314 _: Rc<RefCell<Box<[project::Completion]>>>,
315 _: &mut Context<Editor>,
316 ) -> Task<Result<bool>> {
317 Task::ready(Ok(true))
318 }
319
320 fn is_completion_trigger(
321 &self,
322 buffer: &Entity<Buffer>,
323 position: language::Anchor,
324 _text: &str,
325 _trigger_in_words: bool,
326 cx: &mut Context<Editor>,
327 ) -> bool {
328 let buffer = buffer.read(cx);
329 let position = position.to_point(buffer);
330 let line_start = Point::new(position.row, 0);
331 let mut lines = buffer.text_for_range(line_start..position).lines();
332 if let Some(line) = lines.next() {
333 SlashCommandLine::parse(line).is_some()
334 } else {
335 false
336 }
337 }
338
339 fn sort_completions(&self) -> bool {
340 false
341 }
342}