1use super::*;
2use crate::command::execution::suggestions::{matches_substring, matches_substring_ignore_case};
3
4fn selector_suggestions(allow_selectors: bool) -> Vec<&'static str> {
5 if !allow_selectors {
6 return Vec::new();
7 }
8 vec!["@a", "@e", "@p", "@r", "@s", "@n"]
9}
10
11struct SelectorSuggestionData {
12 allow_selectors: bool,
13 allow_advanced: bool,
14 player_names: Vec<String>,
15 team_names: Vec<String>,
16}
17
18pub(crate) fn suggest_entity_selector<S>(
19 builder: &mut SuggestionsBuilder<'_>,
20 source: &S,
21 single: bool,
22 players_only: bool,
23) where
24 S: CommandArgumentSource + ?Sized,
25{
26 let data = SelectorSuggestionData {
27 allow_selectors: allow_selectors(source),
28 allow_advanced: allow_advanced_selectors(source),
29 player_names: source.selector_player_names(),
30 team_names: source.selector_team_names(),
31 };
32 for suggestion in
33 selector_argument_suggestions(builder.remaining(), players_only, single, &data)
34 {
35 builder.suggest(suggestion);
36 }
37}
38
39fn selector_argument_suggestions(
40 prefix: &str,
41 players_only: bool,
42 single: bool,
43 data: &SelectorSuggestionData,
44) -> Vec<String> {
45 if !prefix.starts_with('@') {
46 return selector_root_suggestions(prefix, players_only, single, data);
47 }
48
49 let mut chars = prefix.chars();
50 if chars.next() != Some('@') {
51 return Vec::new();
52 }
53 let Some(selector_type) = chars.next() else {
54 return selector_root_suggestions(prefix, players_only, single, data);
55 };
56 if !selector_type_allowed_for_suggestions(selector_type) {
57 return selector_root_suggestions(prefix, players_only, single, data);
58 }
59 if chars.next().is_some_and(|ch| ch != '[') {
60 return selector_root_suggestions(prefix, players_only, single, data);
61 }
62
63 if let Some(option_start) = prefix.find('[') {
64 if !data.allow_advanced {
65 return Vec::new();
66 }
67 return selector_option_suggestions(prefix, selector_type, option_start, data);
68 }
69
70 if !data.allow_advanced {
71 return selector_root_suggestions(prefix, players_only, single, data);
72 }
73 let open_options = format!("@{selector_type}[");
74 if open_options.starts_with(prefix) {
75 vec![open_options]
76 } else {
77 selector_root_suggestions(prefix, players_only, single, data)
78 }
79}
80
81fn selector_root_suggestions(
82 prefix: &str,
83 _players_only: bool,
84 _single: bool,
85 data: &SelectorSuggestionData,
86) -> Vec<String> {
87 let mut suggestions = selector_suggestions(data.allow_selectors)
88 .into_iter()
89 .filter(|selector| selector.starts_with(prefix))
90 .map(str::to_owned)
91 .collect::<Vec<_>>();
92 suggestions.extend(
93 data.player_names
94 .iter()
95 .filter(|name| matches_substring_ignore_case(prefix, name))
96 .cloned(),
97 );
98 suggestions
99}
100
101const fn selector_type_allowed_for_suggestions(selector_type: char) -> bool {
102 matches!(selector_type, 'a' | 'e' | 'n' | 'p' | 'r' | 's')
103}
104
105fn selector_option_suggestions(
106 prefix: &str,
107 selector_type: char,
108 option_start: usize,
109 data: &SelectorSuggestionData,
110) -> Vec<String> {
111 if selector_options_have_top_level_close(&prefix[option_start + 1..]) {
112 return Vec::new();
113 }
114
115 let option_prefix = &prefix[..=option_start];
116 let inside = &prefix[option_start + 1..];
117 let (completed_entries, current_entry) = split_current_selector_option_entry(inside);
118 let expression_prefix = format!("{option_prefix}{completed_entries}");
119 if let Some((key, value_prefix)) = current_entry.split_once('=') {
120 let value_expression_prefix = format!("{expression_prefix}{key}=");
121 let mut suggestions = selector_option_value_suggestions(
122 &value_expression_prefix,
123 key.trim(),
124 value_prefix,
125 completed_entries,
126 data,
127 );
128 suggestions.retain(|suggestion| suggestion != prefix);
129 if selector_option_entry_is_complete(selector_type, inside) {
130 suggestions.extend(selector_option_delimiter_suggestions(prefix));
131 }
132 return suggestions;
133 }
134
135 let used_set_once_options = completed_set_once_selector_options(completed_entries);
136 let mut suggestions = Vec::new();
137 if completed_entries.is_empty() && current_entry.trim().is_empty() {
138 suggestions.push(format!("{option_prefix}]"));
139 }
140 suggestions.extend(
141 SELECTOR_OPTION_KEYS
142 .iter()
143 .copied()
144 .filter(|key| selector_option_supported_for_suggestions(key))
145 .filter(|key| selector_option_available_for_type(key, selector_type))
146 .filter(|key| !used_set_once_options.iter().any(|used| used == key))
147 .filter(|key| selector_option_available_for_completed_entries(key, completed_entries))
148 .filter(|key| matches_substring_ignore_case(current_entry.trim_start(), key))
149 .map(|key| format!("{expression_prefix}{key}=")),
150 );
151 suggestions
152}
153
154fn selector_option_entry_is_complete(selector_type: char, inside: &str) -> bool {
155 parse_selector_plan_with_permissions(&format!("@{selector_type}[{inside}]"), true, true).is_ok()
156}
157
158fn selector_option_delimiter_suggestions(prefix: &str) -> Vec<String> {
159 [',', ']']
160 .iter()
161 .map(|delimiter| format!("{prefix}{delimiter}"))
162 .collect()
163}
164
165fn selector_option_supported_for_suggestions(key: &str) -> bool {
166 !UNSUPPORTED_SELECTOR_OPTION_KEYS.contains(&key)
167}
168
169fn selector_options_have_top_level_close(input: &str) -> bool {
170 let mut state = SelectorSuggestionSplitState::default();
171 for (_, ch) in input.char_indices() {
172 if state.accepts_top_level_close(ch) {
173 return true;
174 }
175 }
176 false
177}
178
179fn split_current_selector_option_entry(input: &str) -> (&str, &str) {
180 let mut state = SelectorSuggestionSplitState::default();
181 let mut separator = None;
182 for (index, ch) in input.char_indices() {
183 if state.accepts_top_level_separator(ch) {
184 separator = Some(index);
185 }
186 }
187
188 separator.map_or(("", input), |index| (&input[..=index], &input[index + 1..]))
189}
190
191fn selector_option_entries(input: &str) -> Vec<&str> {
192 let mut entries = Vec::new();
193 let mut state = SelectorSuggestionSplitState::default();
194 let mut entry_start = 0;
195 for (index, ch) in input.char_indices() {
196 if state.accepts_top_level_separator(ch) {
197 let entry = input[entry_start..index].trim();
198 if !entry.is_empty() {
199 entries.push(entry);
200 }
201 entry_start = index + ch.len_utf8();
202 }
203 }
204
205 let entry = input[entry_start..].trim();
206 if !entry.is_empty() {
207 entries.push(entry);
208 }
209 entries
210}
211
212#[derive(Default)]
213struct SelectorSuggestionSplitState {
214 depth: usize,
215 quote: Option<char>,
216 escaping: bool,
217}
218
219impl SelectorSuggestionSplitState {
220 const fn accepts_top_level_separator(&mut self, ch: char) -> bool {
221 self.accepts_top_level_char(ch, ',')
222 }
223
224 const fn accepts_top_level_close(&mut self, ch: char) -> bool {
225 self.accepts_top_level_char(ch, ']')
226 }
227
228 const fn accepts_top_level_char(&mut self, ch: char, target: char) -> bool {
229 if let Some(quote) = self.quote {
230 if self.escaping {
231 self.escaping = false;
232 return false;
233 }
234 if ch == '\\' {
235 self.escaping = true;
236 return false;
237 }
238 if ch == quote {
239 self.quote = None;
240 }
241 return false;
242 }
243
244 match ch {
245 '"' | '\'' => self.quote = Some(ch),
246 '{' | '[' | '(' => self.depth = self.depth.saturating_add(1),
247 ']' if self.depth == 0 => return target == ']',
248 '}' | ')' | ']' => self.depth = self.depth.saturating_sub(1),
249 _ if ch == target && self.depth == 0 => return true,
250 _ => {}
251 }
252 false
253 }
254}
255
256fn completed_set_once_selector_options(completed_entries: &str) -> Vec<&str> {
257 selector_option_entries(completed_entries)
258 .into_iter()
259 .filter_map(|entry| entry.split_once('=').map(|(key, _)| key.trim()))
260 .filter(|key| SET_ONCE_SELECTOR_OPTIONS.contains(key))
261 .collect()
262}
263
264fn selector_option_available_for_type(key: &str, selector_type: char) -> bool {
265 !matches!((key, selector_type), ("limit" | "sort", 's'))
266}
267
268fn selector_option_available_for_completed_entries(key: &str, completed_entries: &str) -> bool {
269 match key {
270 "name" | "gamemode" | "team" => completed_invertable_option_state(completed_entries, key)
271 .suggestion_mode()
272 .allows_any(),
273 "type" => completed_entity_type_suggestion_state(completed_entries)
274 .mode
275 .allows_any(),
276 _ => true,
277 }
278}
279
280fn selector_option_value_suggestions(
281 expression_prefix: &str,
282 key: &str,
283 value_prefix: &str,
284 completed_entries: &str,
285 data: &SelectorSuggestionData,
286) -> Vec<String> {
287 match key {
288 "sort" => prefixed_values(
289 expression_prefix,
290 value_prefix,
291 [SORT_NEAREST, SORT_FURTHEST, SORT_RANDOM, SORT_ARBITRARY],
292 ),
293 "gamemode" => invertible_prefixed_values(
294 expression_prefix,
295 value_prefix,
296 GAME_MODE_SUGGESTIONS,
297 completed_invertable_option_state(completed_entries, key).suggestion_mode(),
298 ),
299 "type" => entity_type_suggestions(
300 expression_prefix,
301 value_prefix,
302 &completed_entity_type_suggestion_state(completed_entries),
303 ),
304 "team" => team_suggestions(
305 expression_prefix,
306 value_prefix,
307 data,
308 completed_invertable_option_state(completed_entries, key).suggestion_mode(),
309 ),
310 _ => Vec::new(),
311 }
312}
313
314fn completed_invertable_option_state(completed_entries: &str, key: &str) -> InvertableOptionState {
315 let mut state = InvertableOptionState::default();
316 for value in completed_option_values(completed_entries, key) {
317 let _ = state.parse_element(value.trim_start().starts_with('!'), key);
318 }
319 state
320}
321
322fn completed_option_values<'a>(
323 completed_entries: &'a str,
324 key: &'a str,
325) -> impl Iterator<Item = &'a str> {
326 selector_option_entries(completed_entries)
327 .into_iter()
328 .filter_map(|entry| entry.split_once('='))
329 .filter(move |(entry_key, _)| entry_key.trim() == key)
330 .map(|(_, value)| value.trim())
331 .filter(|value| !value.is_empty())
332}
333
334fn prefixed_values<const N: usize>(
335 expression_prefix: &str,
336 value_prefix: &str,
337 values: [&'static str; N],
338) -> Vec<String> {
339 values
340 .into_iter()
341 .filter(|value| value.starts_with(value_prefix))
342 .map(|value| format!("{expression_prefix}{value}"))
343 .collect()
344}
345
346fn invertible_prefixed_values(
347 expression_prefix: &str,
348 value_prefix: &str,
349 values: &[&'static str],
350 mode: InvertableSuggestionMode,
351) -> Vec<String> {
352 let mut suggestions = Vec::new();
353 for value in values {
354 if mode.allows_positive() {
355 push_prefixed_value(&mut suggestions, expression_prefix, value_prefix, value);
356 }
357 if mode.allows_negative() {
358 push_prefixed_value(
359 &mut suggestions,
360 expression_prefix,
361 value_prefix,
362 &format!("!{value}"),
363 );
364 }
365 }
366 suggestions
367}
368
369fn push_prefixed_value(
370 suggestions: &mut Vec<String>,
371 expression_prefix: &str,
372 value_prefix: &str,
373 value: &str,
374) {
375 if matches_substring_ignore_case(value_prefix, value) {
376 suggestions.push(format!("{expression_prefix}{value}"));
377 }
378}
379
380#[derive(Clone, Debug)]
381struct EntityTypeSuggestionState {
382 mode: InvertableSuggestionMode,
383 tags_seen: Vec<Identifier>,
384}
385
386fn completed_entity_type_suggestion_state(completed_entries: &str) -> EntityTypeSuggestionState {
387 let mut state = InvertableOptionState::default();
388 let mut tags_seen = Vec::new();
389 for value in completed_option_values(completed_entries, "type") {
390 let value = value.trim_start();
391 if let Some(tag) = value.strip_prefix("!#").or_else(|| value.strip_prefix('#')) {
392 if let Some(tag) = parse_resource_identifier_value(tag)
393 && !tags_seen.iter().any(|seen| seen == &tag)
394 {
395 tags_seen.push(tag);
396 }
397 state.negative_seen = true;
398 } else {
399 let _ = state.parse_element(value.starts_with('!'), "type");
400 }
401 }
402
403 EntityTypeSuggestionState {
404 mode: state.suggestion_mode(),
405 tags_seen,
406 }
407}
408
409fn entity_type_suggestions(
410 expression_prefix: &str,
411 value_prefix: &str,
412 state: &EntityTypeSuggestionState,
413) -> Vec<String> {
414 if !state.mode.allows_any() {
415 return Vec::new();
416 }
417
418 let mut suggestions = Vec::new();
419 push_entity_type_tag_suggestions(&mut suggestions, expression_prefix, value_prefix, "", state);
420 push_entity_type_tag_suggestions(
421 &mut suggestions,
422 expression_prefix,
423 value_prefix,
424 "!",
425 state,
426 );
427 if value_prefix.starts_with('#') || value_prefix.starts_with("!#") {
428 return suggestions;
429 }
430
431 if state.mode.allows_positive() {
432 push_entity_type_id_suggestions(&mut suggestions, expression_prefix, value_prefix, "");
433 }
434 if state.mode.allows_negative() {
435 push_entity_type_id_suggestions(&mut suggestions, expression_prefix, value_prefix, "!");
436 }
437
438 suggestions
439}
440
441fn push_entity_type_id_suggestions(
442 suggestions: &mut Vec<String>,
443 expression_prefix: &str,
444 value_prefix: &str,
445 inversion: &str,
446) {
447 let resource_prefix = if inversion.is_empty() {
448 if value_prefix.starts_with('!') || value_prefix.starts_with('#') {
449 return;
450 }
451 value_prefix
452 } else if let Some(prefix) = value_prefix.strip_prefix(inversion) {
453 prefix
454 } else if inversion.starts_with(value_prefix) {
455 ""
456 } else {
457 return;
458 };
459
460 let stripped_prefix = resource_prefix
461 .strip_prefix("minecraft:")
462 .unwrap_or(resource_prefix);
463 suggestions.extend(
464 REGISTRY
465 .entity_types
466 .iter()
467 .map(|(_, entity_type)| entity_type.key.to_string())
468 .filter(|key| {
469 let text = key.strip_prefix("minecraft:").unwrap_or(key);
470 matches_substring(stripped_prefix, text)
471 })
472 .map(|key| format!("{expression_prefix}{inversion}{key}")),
473 );
474}
475
476fn push_entity_type_tag_suggestions(
477 suggestions: &mut Vec<String>,
478 expression_prefix: &str,
479 value_prefix: &str,
480 inversion: &str,
481 state: &EntityTypeSuggestionState,
482) {
483 let marker = format!("{inversion}#");
484 if !marker.starts_with(value_prefix) && !value_prefix.starts_with(&marker) {
485 return;
486 }
487
488 let tag_prefix = value_prefix.strip_prefix(&marker).unwrap_or_default();
489 let tag_prefix = tag_prefix.strip_prefix("minecraft:").unwrap_or(tag_prefix);
490 let mut tag_keys = REGISTRY.entity_types.tag_keys().collect::<Vec<_>>();
491 tag_keys.sort_by(|left, right| {
492 left.namespace
493 .cmp(&right.namespace)
494 .then_with(|| left.path.cmp(&right.path))
495 });
496 suggestions.extend(
497 tag_keys
498 .into_iter()
499 .filter(|key| !state.tags_seen.iter().any(|seen| seen == *key))
500 .filter(|key| {
501 if key.namespace == Identifier::VANILLA_NAMESPACE {
502 return matches_substring(tag_prefix, &key.path);
503 }
504
505 let text = key.to_string();
506 matches_substring(tag_prefix, &text)
507 })
508 .map(|key| format!("{expression_prefix}{marker}{key}")),
509 );
510}
511
512fn team_suggestions(
513 expression_prefix: &str,
514 value_prefix: &str,
515 data: &SelectorSuggestionData,
516 mode: InvertableSuggestionMode,
517) -> Vec<String> {
518 let mut suggestions = Vec::new();
519 for team_name in &data.team_names {
520 if mode.allows_positive() {
521 push_prefixed_value(&mut suggestions, expression_prefix, value_prefix, team_name);
522 }
523 if mode.allows_negative() {
524 push_prefixed_value(
525 &mut suggestions,
526 expression_prefix,
527 value_prefix,
528 &format!("!{team_name}"),
529 );
530 }
531 }
532 suggestions
533}