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, AppContext as _, Context, Entity, Task, WeakEntity, Window};
8use language::{Anchor, Buffer, ToPoint};
9use parking_lot::Mutex;
10use project::{lsp_store::CompletionDocumentation, CompletionIntent, CompletionSource};
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<Option<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, async move |cx| {
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 Some(
75 matches
76 .into_iter()
77 .filter_map(|mat| {
78 let command = slash_commands.command(&mat.string, cx)?;
79 let mut new_text = mat.string.clone();
80 let requires_argument = command.requires_argument();
81 let accepts_arguments = command.accepts_arguments();
82 if requires_argument || accepts_arguments {
83 new_text.push(' ');
84 }
85
86 let confirm =
87 editor
88 .clone()
89 .zip(workspace.clone())
90 .map(|(editor, workspace)| {
91 let command_name = mat.string.clone();
92 let command_range = command_range.clone();
93 let editor = editor.clone();
94 let workspace = workspace.clone();
95 Arc::new(
96 move |intent: CompletionIntent,
97 window: &mut Window,
98 cx: &mut App| {
99 if !requires_argument
100 && (!accepts_arguments || intent.is_complete())
101 {
102 editor
103 .update(cx, |editor, cx| {
104 editor.run_command(
105 command_range.clone(),
106 &command_name,
107 &[],
108 true,
109 workspace.clone(),
110 window,
111 cx,
112 );
113 })
114 .ok();
115 false
116 } else {
117 requires_argument || accepts_arguments
118 }
119 },
120 ) as Arc<_>
121 });
122 Some(project::Completion {
123 old_range: name_range.clone(),
124 documentation: Some(CompletionDocumentation::SingleLine(
125 command.description().into(),
126 )),
127 new_text,
128 label: command.label(cx),
129 confirm,
130 source: CompletionSource::Custom,
131 })
132 })
133 .collect(),
134 )
135 })
136 })
137 }
138
139 fn complete_command_argument(
140 &self,
141 command_name: &str,
142 arguments: &[String],
143 command_range: Range<Anchor>,
144 argument_range: Range<Anchor>,
145 last_argument_range: Range<Anchor>,
146 window: &mut Window,
147 cx: &mut App,
148 ) -> Task<Result<Option<Vec<project::Completion>>>> {
149 let new_cancel_flag = Arc::new(AtomicBool::new(false));
150 let mut flag = self.cancel_flag.lock();
151 flag.store(true, SeqCst);
152 *flag = new_cancel_flag.clone();
153 if let Some(command) = self.slash_commands.command(command_name, cx) {
154 let completions = command.complete_argument(
155 arguments,
156 new_cancel_flag.clone(),
157 self.workspace.clone(),
158 window,
159 cx,
160 );
161 let command_name: Arc<str> = command_name.into();
162 let editor = self.editor.clone();
163 let workspace = self.workspace.clone();
164 let arguments = arguments.to_vec();
165 cx.background_spawn(async move {
166 Ok(Some(
167 completions
168 .await?
169 .into_iter()
170 .map(|new_argument| {
171 let confirm =
172 editor
173 .clone()
174 .zip(workspace.clone())
175 .map(|(editor, workspace)| {
176 Arc::new({
177 let mut completed_arguments = arguments.clone();
178 if new_argument.replace_previous_arguments {
179 completed_arguments.clear();
180 } else {
181 completed_arguments.pop();
182 }
183 completed_arguments.push(new_argument.new_text.clone());
184
185 let command_range = command_range.clone();
186 let command_name = command_name.clone();
187 move |intent: CompletionIntent,
188 window: &mut Window,
189 cx: &mut App| {
190 if new_argument.after_completion.run()
191 || intent.is_complete()
192 {
193 editor
194 .update(cx, |editor, cx| {
195 editor.run_command(
196 command_range.clone(),
197 &command_name,
198 &completed_arguments,
199 true,
200 workspace.clone(),
201 window,
202 cx,
203 );
204 })
205 .ok();
206 false
207 } else {
208 !new_argument.after_completion.run()
209 }
210 }
211 }) as Arc<_>
212 });
213
214 let mut new_text = new_argument.new_text.clone();
215 if new_argument.after_completion == AfterCompletion::Continue {
216 new_text.push(' ');
217 }
218
219 project::Completion {
220 old_range: if new_argument.replace_previous_arguments {
221 argument_range.clone()
222 } else {
223 last_argument_range.clone()
224 },
225 label: new_argument.label,
226 new_text,
227 documentation: None,
228 confirm,
229 source: CompletionSource::Custom,
230 }
231 })
232 .collect(),
233 ))
234 })
235 } else {
236 Task::ready(Ok(Some(Vec::new())))
237 }
238 }
239}
240
241impl CompletionProvider for SlashCommandCompletionProvider {
242 fn completions(
243 &self,
244 buffer: &Entity<Buffer>,
245 buffer_position: Anchor,
246 _: editor::CompletionContext,
247 window: &mut Window,
248 cx: &mut Context<Editor>,
249 ) -> Task<Result<Option<Vec<project::Completion>>>> {
250 let Some((name, arguments, command_range, last_argument_range)) =
251 buffer.update(cx, |buffer, _cx| {
252 let position = buffer_position.to_point(buffer);
253 let line_start = Point::new(position.row, 0);
254 let mut lines = buffer.text_for_range(line_start..position).lines();
255 let line = lines.next()?;
256 let call = SlashCommandLine::parse(line)?;
257
258 let command_range_start = Point::new(position.row, call.name.start as u32 - 1);
259 let command_range_end = Point::new(
260 position.row,
261 call.arguments.last().map_or(call.name.end, |arg| arg.end) as u32,
262 );
263 let command_range = buffer.anchor_after(command_range_start)
264 ..buffer.anchor_after(command_range_end);
265
266 let name = line[call.name.clone()].to_string();
267 let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last()
268 {
269 let last_arg_start =
270 buffer.anchor_after(Point::new(position.row, argument.start as u32));
271 let first_arg_start = call.arguments.first().expect("we have the last element");
272 let first_arg_start =
273 buffer.anchor_after(Point::new(position.row, first_arg_start.start as u32));
274 let arguments = call
275 .arguments
276 .iter()
277 .filter_map(|argument| Some(line.get(argument.clone())?.to_string()))
278 .collect::<Vec<_>>();
279 let argument_range = first_arg_start..buffer_position;
280 (
281 Some((arguments, argument_range)),
282 last_arg_start..buffer_position,
283 )
284 } else {
285 let start =
286 buffer.anchor_after(Point::new(position.row, call.name.start as u32));
287 (None, start..buffer_position)
288 };
289
290 Some((name, arguments, command_range, last_argument_range))
291 })
292 else {
293 return Task::ready(Ok(Some(Vec::new())));
294 };
295
296 if let Some((arguments, argument_range)) = arguments {
297 self.complete_command_argument(
298 &name,
299 &arguments,
300 command_range,
301 argument_range,
302 last_argument_range,
303 window,
304 cx,
305 )
306 } else {
307 self.complete_command_name(&name, command_range, last_argument_range, window, cx)
308 }
309 }
310
311 fn resolve_completions(
312 &self,
313 _: Entity<Buffer>,
314 _: Vec<usize>,
315 _: Rc<RefCell<Box<[project::Completion]>>>,
316 _: &mut Context<Editor>,
317 ) -> Task<Result<bool>> {
318 Task::ready(Ok(true))
319 }
320
321 fn is_completion_trigger(
322 &self,
323 buffer: &Entity<Buffer>,
324 position: language::Anchor,
325 _text: &str,
326 _trigger_in_words: bool,
327 cx: &mut Context<Editor>,
328 ) -> bool {
329 let buffer = buffer.read(cx);
330 let position = position.to_point(buffer);
331 let line_start = Point::new(position.row, 0);
332 let mut lines = buffer.text_for_range(line_start..position).lines();
333 if let Some(line) = lines.next() {
334 SlashCommandLine::parse(line).is_some()
335 } else {
336 false
337 }
338 }
339
340 fn sort_completions(&self) -> bool {
341 false
342 }
343}