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