Skip to main content

steel_core/command/
api.rs

1//! Public startup command-registration API.
2
3use std::{error::Error, fmt, sync::Arc};
4
5use glam::DVec3;
6use steel_protocol::packets::game::{
7    ArgumentType as ProtocolArgumentType, SuggestionType as ProtocolSuggestionType,
8};
9use steel_utils::{DowncastType, Identifier};
10use text_components::TextComponent;
11
12use super::{
13    brigadier::{
14        ArgumentType, CommandNodeBuilder, CommandRequirement, CommandSyntaxError,
15        CommandSyntaxErrorKind, ReaderCursor, StringReader, SuggestionsBuilder,
16    },
17    execution::{
18        CommandArgumentSource, CommandPermissionSource, CommandResultSuspension,
19        CommandResultSuspensionPoll, CommandSource as InternalCommandSource,
20        CommandSuspensionOrder, SteelArgumentParser, SteelArgumentSuggestionContext,
21        SteelArgumentType, SteelCommandContext, SteelCommandRuntime,
22    },
23    registration::{
24        CommandDispatcherBuilder, CommandRegistration as InternalCommandRegistration,
25        CommandRegistrationError as InternalCommandRegistrationError,
26    },
27};
28use crate::{
29    entity::SharedEntity,
30    permission::{PermissionExpr, PermissionState},
31    player::Player,
32    server::Server,
33    world::World,
34};
35
36/// A command declaration collected before the server constructs its dispatcher atomically.
37pub struct CommandRegistration {
38    inner: InternalCommandRegistration<InternalCommandSource>,
39}
40
41impl CommandRegistration {
42    /// Declares a command whose literal root must match the path of `id`.
43    pub fn new(id: Identifier, factory: impl FnOnce() -> CommandNode + Send + 'static) -> Self {
44        Self {
45            inner: InternalCommandRegistration::new(id, move |_| factory().inner),
46        }
47    }
48
49    /// Adds an unqualified alias owned by this command.
50    #[must_use]
51    pub fn alias(mut self, alias: impl Into<Box<str>>) -> Self {
52        self.inner = self.inner.alias(alias);
53        self
54    }
55
56    /// Allows an unset root permission while still respecting an explicit deny.
57    #[must_use]
58    pub fn default_access(mut self) -> Self {
59        self.inner = self.inner.default_access();
60        self
61    }
62
63    /// Replaces the permission expression derived from the command ID.
64    #[must_use]
65    pub fn permission(mut self, permission: PermissionExpr) -> Self {
66        self.inner = self.inner.permission(permission);
67        self
68    }
69
70    /// Allows a literal path through a permission derived from the command ID.
71    #[must_use]
72    pub fn subcommand_permission<I, T>(mut self, path: I) -> Self
73    where
74        I: IntoIterator<Item = T>,
75        T: Into<Box<str>>,
76    {
77        self.inner = self.inner.subcommand_permission(path);
78        self
79    }
80}
81
82/// Additional command declarations supplied before server startup.
83pub struct CommandRegistry {
84    inner: CommandDispatcherBuilder<InternalCommandSource>,
85}
86
87impl CommandRegistry {
88    /// Creates an empty extension registry. Built-in commands are added separately by the server.
89    #[must_use]
90    pub fn new() -> Self {
91        Self {
92            inner: CommandDispatcherBuilder::new(),
93        }
94    }
95
96    /// Declares a non-command permission for discovery and autocomplete.
97    pub fn declare_permission(
98        &mut self,
99        permission: impl Into<String>,
100    ) -> Result<&mut Self, CommandRegistrationError> {
101        self.inner
102            .declare_permission(permission)
103            .map_err(CommandRegistrationError::from)?;
104        Ok(self)
105    }
106
107    /// Adds one command declaration to this startup registry.
108    pub fn register(
109        &mut self,
110        registration: CommandRegistration,
111    ) -> Result<&mut Self, CommandRegistrationError> {
112        self.inner
113            .register(registration.inner)
114            .map_err(CommandRegistrationError::from)?;
115        Ok(self)
116    }
117
118    pub(crate) fn into_inner(self) -> CommandDispatcherBuilder<InternalCommandSource> {
119        self.inner
120    }
121}
122
123impl Default for CommandRegistry {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129/// A command registration failed validation or collided by stable owner ID.
130#[derive(Debug)]
131pub struct CommandRegistrationError {
132    inner: InternalCommandRegistrationError,
133}
134
135impl From<InternalCommandRegistrationError> for CommandRegistrationError {
136    fn from(inner: InternalCommandRegistrationError) -> Self {
137        Self { inner }
138    }
139}
140
141impl fmt::Display for CommandRegistrationError {
142    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143        self.inner.fmt(formatter)
144    }
145}
146
147impl Error for CommandRegistrationError {
148    fn source(&self) -> Option<&(dyn Error + 'static)> {
149        Some(&self.inner)
150    }
151}
152
153/// One literal or argument node in a server command declaration.
154pub struct CommandNode {
155    inner: CommandNodeBuilder<InternalCommandSource, SteelCommandRuntime>,
156}
157
158impl CommandNode {
159    /// Creates a literal node.
160    #[must_use]
161    pub fn literal(name: impl Into<Box<str>>) -> Self {
162        Self {
163            inner: CommandNodeBuilder::literal(name),
164        }
165    }
166
167    /// Creates a typed argument node.
168    #[must_use]
169    pub fn argument(name: impl Into<Box<str>>, argument: CommandArgument) -> Self {
170        Self {
171            inner: CommandNodeBuilder::argument(name, argument.inner),
172        }
173    }
174
175    /// Adds a child while preserving declaration order.
176    #[must_use]
177    pub fn then(mut self, child: Self) -> Self {
178        self.inner = self.inner.then(child.inner);
179        self
180    }
181
182    /// Attaches a synchronous terminal executor.
183    #[must_use]
184    pub fn executes(
185        mut self,
186        executor: impl for<'context> Fn(&CommandContext<'context>) -> Result<i32, CommandError>
187        + Send
188        + Sync
189        + 'static,
190    ) -> Self {
191        self.inner = self.inner.executes(move |context| {
192            executor(&CommandContext { inner: context }).map_err(CommandError::into_inner)
193        });
194        self
195    }
196
197    /// Attaches a terminal executor whose result is produced across server ticks.
198    #[must_use]
199    pub fn executes_suspended<T>(
200        mut self,
201        executor: impl for<'context> Fn(&CommandContext<'context>) -> Result<T, CommandError>
202        + Send
203        + Sync
204        + 'static,
205    ) -> Self
206    where
207        T: SuspendedCommand,
208    {
209        self.inner = self.inner.executes_suspended(move |context| {
210            executor(&CommandContext { inner: context })
211                .map(ExternalCommandSuspension)
212                .map_err(CommandError::into_inner)
213        });
214        self
215    }
216
217    /// Adds a non-permission source requirement used for tree visibility and execution.
218    #[must_use]
219    pub fn requires(
220        mut self,
221        requirement: impl for<'source> Fn(CommandSource<'source>) -> bool + Send + Sync + 'static,
222    ) -> Self {
223        self.inner = self
224            .inner
225            .requires(CommandRequirement::contextual(move |source| {
226                requirement(CommandSource { inner: source })
227            }));
228        self
229    }
230}
231
232/// Creates a literal command node.
233#[must_use]
234pub fn literal(name: impl Into<Box<str>>) -> CommandNode {
235    CommandNode::literal(name)
236}
237
238/// Creates a typed argument command node.
239#[must_use]
240pub fn argument(name: impl Into<Box<str>>, argument: CommandArgument) -> CommandNode {
241    CommandNode::argument(name, argument)
242}
243
244/// A parsed command invocation exposed to an extension executor.
245#[derive(Clone, Copy)]
246pub struct CommandContext<'context> {
247    inner: &'context SteelCommandContext<InternalCommandSource>,
248}
249
250impl<'context> CommandContext<'context> {
251    /// Returns the execution source.
252    #[must_use]
253    pub fn source(self) -> CommandSource<'context> {
254        CommandSource {
255            inner: self.inner.source(),
256        }
257    }
258
259    /// Returns a parsed custom value by its deterministic concrete type key.
260    #[must_use]
261    pub fn value<T: DowncastType>(self, name: &str) -> Option<&'context T> {
262        self.inner.argument(name)?.downcast_ref::<T>()
263    }
264
265    #[must_use]
266    /// Returns a parsed boolean, or `None` when the named argument has another type.
267    pub fn boolean(self, name: &str) -> Option<bool> {
268        self.inner.boolean(name)
269    }
270
271    #[must_use]
272    /// Returns a parsed 32-bit integer.
273    pub fn integer(self, name: &str) -> Option<i32> {
274        self.inner.integer(name)
275    }
276
277    #[must_use]
278    /// Returns a parsed 64-bit integer.
279    pub fn long(self, name: &str) -> Option<i64> {
280        self.inner.long(name)
281    }
282
283    #[must_use]
284    /// Returns a parsed 32-bit floating-point value.
285    pub fn float(self, name: &str) -> Option<f32> {
286        self.inner.float(name)
287    }
288
289    #[must_use]
290    /// Returns a parsed 64-bit floating-point value.
291    pub fn double(self, name: &str) -> Option<f64> {
292        self.inner.double(name)
293    }
294
295    #[must_use]
296    /// Returns a parsed word, phrase, or greedy string.
297    pub fn string(self, name: &str) -> Option<&'context str> {
298        self.inner.string(name)
299    }
300
301    /// Returns a parsed configured domain name.
302    #[must_use]
303    pub fn domain(self, name: &str) -> Option<&'context str> {
304        self.inner.domain(name)
305    }
306
307    /// Resolves a parsed loaded-world argument against the current source domain.
308    pub fn world(self, name: &str) -> Result<Arc<World>, CommandError> {
309        let Some(world) = self.inner.world_argument(name) else {
310            return Err(CommandError::from(format!(
311                "missing parsed world argument '{name}'"
312            )));
313        };
314        world
315            .resolve(self.inner.source())
316            .map_err(CommandError::from)
317    }
318
319    /// Resolves a player selector and requires at least one result.
320    pub fn players(self, name: &str) -> Result<Vec<Arc<Player>>, CommandError> {
321        self.inner.players(name).map_err(CommandError::from)
322    }
323
324    /// Resolves a single player selector.
325    pub fn player(self, name: &str) -> Result<Arc<Player>, CommandError> {
326        self.inner.player(name).map_err(CommandError::from)
327    }
328
329    /// Resolves an entity selector and requires at least one result.
330    pub fn entities(self, name: &str) -> Result<Vec<SharedEntity>, CommandError> {
331        self.inner.entities(name).map_err(CommandError::from)
332    }
333
334    /// Resolves a single entity selector.
335    pub fn entity(self, name: &str) -> Result<SharedEntity, CommandError> {
336        self.inner.entity(name).map_err(CommandError::from)
337    }
338}
339
340/// Read-only execution source and feedback operations available to extension commands.
341#[derive(Clone, Copy)]
342pub struct CommandSource<'source> {
343    inner: &'source InternalCommandSource,
344}
345
346impl<'source> CommandSource<'source> {
347    /// Returns the current execution player, if any.
348    #[must_use]
349    pub const fn player(self) -> Option<&'source Arc<Player>> {
350        self.inner.player()
351    }
352
353    /// Returns the current execution entity, if any.
354    #[must_use]
355    pub const fn entity(self) -> Option<&'source SharedEntity> {
356        self.inner.entity()
357    }
358
359    /// Returns the current execution world.
360    #[must_use]
361    pub const fn world(self) -> &'source Arc<World> {
362        self.inner.world()
363    }
364
365    /// Returns the owning server.
366    #[must_use]
367    pub const fn server(self) -> &'source Arc<Server> {
368        self.inner.server()
369    }
370
371    /// Returns the current execution position.
372    #[must_use]
373    pub const fn position(self) -> DVec3 {
374        self.inner.position()
375    }
376
377    /// Returns the current execution yaw and pitch.
378    #[must_use]
379    pub const fn rotation(self) -> (f32, f32) {
380        self.inner.rotation()
381    }
382
383    /// Resolves a permission against the authorization snapshot captured at command start.
384    #[must_use]
385    pub fn permission_state(self, permission: &PermissionExpr) -> Option<PermissionState> {
386        CommandPermissionSource::permission_state(self.inner, permission)
387    }
388
389    /// Sends success feedback and optionally applies vanilla administrative broadcasting.
390    pub fn send_success(self, message: &TextComponent, broadcast_to_admins: bool) {
391        self.inner.send_success(message, broadcast_to_admins);
392    }
393
394    /// Sends red failure feedback to the original sender.
395    pub fn send_failure(self, message: TextComponent) {
396        self.inner.send_failure(message);
397    }
398}
399
400/// A command parsing or execution error with vanilla-style feedback.
401#[derive(Debug)]
402pub struct CommandError {
403    inner: CommandSyntaxError,
404}
405
406impl CommandError {
407    /// Creates an execution error from a rich feedback component.
408    #[must_use]
409    pub fn new(message: impl Into<TextComponent>) -> Self {
410        Self {
411            inner: CommandSyntaxError::dynamic(message),
412        }
413    }
414
415    fn into_inner(self) -> CommandSyntaxError {
416        self.inner
417    }
418}
419
420impl From<CommandSyntaxError> for CommandError {
421    fn from(inner: CommandSyntaxError) -> Self {
422        Self { inner }
423    }
424}
425
426impl From<String> for CommandError {
427    fn from(message: String) -> Self {
428        Self::new(TextComponent::plain(message))
429    }
430}
431
432impl From<&str> for CommandError {
433    fn from(message: &str) -> Self {
434        Self::new(TextComponent::plain(message.to_owned()))
435    }
436}
437
438impl From<TextComponent> for CommandError {
439    fn from(message: TextComponent) -> Self {
440        Self::new(message)
441    }
442}
443
444impl fmt::Display for CommandError {
445    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
446        self.inner.fmt(formatter)
447    }
448}
449
450impl Error for CommandError {
451    fn source(&self) -> Option<&(dyn Error + 'static)> {
452        Some(&self.inner)
453    }
454}
455
456/// An argument parser accepted by a public command node.
457pub struct CommandArgument {
458    inner: SteelArgumentType,
459}
460
461impl CommandArgument {
462    /// Creates a lowercase boolean argument.
463    #[must_use]
464    pub fn boolean() -> Self {
465        Self::primitive(ArgumentType::bool())
466    }
467
468    /// Creates a bounded signed 32-bit integer argument.
469    #[must_use]
470    pub fn integer(minimum: i32, maximum: i32) -> Self {
471        Self::primitive(ArgumentType::integer(minimum, maximum))
472    }
473
474    /// Creates a bounded signed 64-bit integer argument.
475    #[must_use]
476    pub fn long(minimum: i64, maximum: i64) -> Self {
477        Self::primitive(ArgumentType::long(minimum, maximum))
478    }
479
480    /// Creates a bounded 32-bit floating-point argument.
481    #[must_use]
482    pub fn float(minimum: f32, maximum: f32) -> Self {
483        Self::primitive(ArgumentType::float(minimum, maximum))
484    }
485
486    /// Creates a bounded 64-bit floating-point argument.
487    #[must_use]
488    pub fn double(minimum: f64, maximum: f64) -> Self {
489        Self::primitive(ArgumentType::double(minimum, maximum))
490    }
491
492    /// Creates a single unquoted word argument.
493    #[must_use]
494    pub fn word() -> Self {
495        Self::primitive(ArgumentType::word())
496    }
497
498    /// Creates a quoted or unquoted phrase argument.
499    #[must_use]
500    pub fn string() -> Self {
501        Self::primitive(ArgumentType::string())
502    }
503
504    /// Creates an argument that consumes the remaining command input.
505    #[must_use]
506    pub fn greedy_string() -> Self {
507        Self::primitive(ArgumentType::greedy_string())
508    }
509
510    /// Creates a single-entity selector argument.
511    #[must_use]
512    pub fn entity() -> Self {
513        Self {
514            inner: SteelArgumentType::entity(),
515        }
516    }
517
518    /// Creates a multiple-entity selector argument.
519    #[must_use]
520    pub fn entities() -> Self {
521        Self {
522            inner: SteelArgumentType::entities(),
523        }
524    }
525
526    /// Creates a single-player selector argument.
527    #[must_use]
528    pub fn player() -> Self {
529        Self {
530            inner: SteelArgumentType::player(),
531        }
532    }
533
534    /// Creates a multiple-player selector argument.
535    #[must_use]
536    pub fn players() -> Self {
537        Self {
538            inner: SteelArgumentType::players(),
539        }
540    }
541
542    /// Creates a configured Steel domain argument.
543    #[must_use]
544    pub fn domain() -> Self {
545        Self {
546            inner: SteelArgumentType::domain(),
547        }
548    }
549
550    /// Creates a loaded-world argument.
551    #[must_use]
552    pub fn world() -> Self {
553        Self {
554            inner: SteelArgumentType::world(),
555        }
556    }
557
558    /// Erases a keyed extension parser without using `Any` or `TypeId`.
559    #[must_use]
560    pub fn custom<P>(parser: P) -> Self
561    where
562        P: CommandArgumentParser,
563    {
564        Self {
565            inner: SteelArgumentType::new(parser),
566        }
567    }
568
569    fn primitive(argument: ArgumentType) -> Self {
570        Self {
571            inner: SteelArgumentType::from(argument),
572        }
573    }
574}
575
576/// Typed, deterministically keyed parser contract for extension arguments.
577pub trait CommandArgumentParser:
578    DowncastType + fmt::Debug + PartialEq + Send + Sync + 'static
579{
580    /// Concrete keyed value retained in the parsed command context.
581    type Value: DowncastType + fmt::Debug + Send + Sync + 'static;
582
583    /// Parses one value from the reader's current cursor.
584    fn parse(
585        &self,
586        reader: &mut CommandReader<'_, '_>,
587        source: CommandParserSource<'_>,
588    ) -> Result<Self::Value, CommandError>;
589
590    /// Adds context-aware completions for a partially entered value.
591    fn list_suggestions(
592        &self,
593        _context: CommandSuggestionContext<'_>,
594        _suggestions: &mut CommandSuggestions<'_, '_>,
595    ) {
596    }
597
598    /// Returns the vanilla command-tree parser and optional server suggestion provider.
599    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>);
600}
601
602impl<P> SteelArgumentParser for P
603where
604    P: CommandArgumentParser,
605{
606    type Value = P::Value;
607
608    fn parse(
609        &self,
610        reader: &mut StringReader<'_>,
611        source: &dyn CommandArgumentSource,
612    ) -> Result<Self::Value, CommandSyntaxError> {
613        CommandArgumentParser::parse(
614            self,
615            &mut CommandReader { inner: reader },
616            CommandParserSource { inner: source },
617        )
618        .map_err(CommandError::into_inner)
619    }
620
621    fn list_suggestions(
622        &self,
623        context: &dyn SteelArgumentSuggestionContext,
624        builder: &mut SuggestionsBuilder<'_>,
625    ) {
626        CommandArgumentParser::list_suggestions(
627            self,
628            CommandSuggestionContext { inner: context },
629            &mut CommandSuggestions { inner: builder },
630        );
631    }
632
633    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
634        CommandArgumentParser::protocol_argument(self)
635    }
636}
637
638/// Cursor checkpoint for a public custom argument reader.
639#[derive(Clone, Copy)]
640pub struct CommandReaderCursor(ReaderCursor);
641
642/// Cursor-aware input available to custom argument parsers.
643pub struct CommandReader<'reader, 'input> {
644    inner: &'reader mut StringReader<'input>,
645}
646
647impl<'input> CommandReader<'_, 'input> {
648    /// Returns the complete command input.
649    #[must_use]
650    pub const fn input(&self) -> &'input str {
651        self.inner.input()
652    }
653
654    /// Returns the current UTF-8 byte cursor.
655    #[must_use]
656    pub const fn cursor(&self) -> usize {
657        self.inner.byte_cursor()
658    }
659
660    /// Returns the unconsumed input.
661    #[must_use]
662    pub fn remaining(&self) -> &'input str {
663        self.inner.remaining()
664    }
665
666    /// Peeks at the next Unicode scalar without consuming it.
667    #[must_use]
668    pub fn peek(&self) -> Option<char> {
669        self.inner.peek()
670    }
671
672    /// Consumes and returns the next Unicode scalar.
673    pub fn read(&mut self) -> Option<char> {
674        self.inner.read()
675    }
676
677    /// Consumes Java-compatible command whitespace.
678    pub fn skip_whitespace(&mut self) {
679        self.inner.skip_whitespace();
680    }
681
682    /// Reads one unquoted Brigadier string.
683    pub fn read_unquoted_string(&mut self) -> &'input str {
684        self.inner.read_unquoted_string()
685    }
686
687    /// Reads one quoted or unquoted Brigadier string.
688    pub fn read_string(&mut self) -> Result<String, CommandError> {
689        self.inner.read_string().map_err(CommandError::from)
690    }
691
692    /// Reads a signed 32-bit integer.
693    pub fn read_integer(&mut self) -> Result<i32, CommandError> {
694        self.inner.read_int().map_err(CommandError::from)
695    }
696
697    /// Reads a signed 64-bit integer.
698    pub fn read_long(&mut self) -> Result<i64, CommandError> {
699        self.inner.read_long().map_err(CommandError::from)
700    }
701
702    /// Reads a 32-bit floating-point value.
703    pub fn read_float(&mut self) -> Result<f32, CommandError> {
704        self.inner.read_float().map_err(CommandError::from)
705    }
706
707    /// Reads a 64-bit floating-point value.
708    pub fn read_double(&mut self) -> Result<f64, CommandError> {
709        self.inner.read_double().map_err(CommandError::from)
710    }
711
712    /// Reads a lowercase boolean.
713    pub fn read_boolean(&mut self) -> Result<bool, CommandError> {
714        self.inner.read_boolean().map_err(CommandError::from)
715    }
716
717    /// Consumes one required symbol.
718    pub fn expect(&mut self, expected: char) -> Result<(), CommandError> {
719        self.inner.expect(expected).map_err(CommandError::from)
720    }
721
722    /// Captures the current cursor for speculative parsing.
723    #[must_use]
724    pub const fn checkpoint(&self) -> CommandReaderCursor {
725        CommandReaderCursor(self.inner.checkpoint())
726    }
727
728    /// Restores a previously captured cursor.
729    pub const fn restore(&mut self, checkpoint: CommandReaderCursor) {
730        self.inner.restore(checkpoint.0);
731    }
732
733    /// Creates an error carrying the reader's current Brigadier context.
734    #[must_use]
735    pub fn error(&self, message: impl Into<TextComponent>) -> CommandError {
736        CommandError::from(
737            self.inner
738                .error(CommandSyntaxErrorKind::Dynamic(Box::new(message.into()))),
739        )
740    }
741}
742
743#[cfg(test)]
744mod tests {
745    use super::CommandReader;
746    use crate::command::brigadier::StringReader;
747
748    #[test]
749    fn command_reader_cursor_is_a_utf8_byte_offset() {
750        let mut inner = StringReader::new("é🦀");
751        let mut reader = CommandReader { inner: &mut inner };
752
753        assert_eq!(reader.read(), Some('é'));
754        assert_eq!(reader.cursor(), 2);
755        assert_eq!(reader.read(), Some('🦀'));
756        assert_eq!(reader.cursor(), 6);
757        assert_eq!(&reader.input()[..reader.cursor()], "é🦀");
758    }
759}
760
761/// Read-only source facts available while a custom argument is parsed.
762#[derive(Clone, Copy)]
763pub struct CommandParserSource<'source> {
764    inner: &'source dyn CommandArgumentSource,
765}
766
767impl CommandParserSource<'_> {
768    /// Returns configured domain names.
769    #[must_use]
770    pub fn domain_names(&self) -> Vec<&str> {
771        self.inner.domain_names()
772    }
773
774    /// Returns world names visible to the current command source.
775    #[must_use]
776    pub fn world_names(&self) -> Vec<String> {
777        self.inner.command_world_names()
778    }
779
780    /// Returns player names visible to selectors in the source domain.
781    #[must_use]
782    pub fn player_names(&self) -> Vec<String> {
783        self.inner.selector_player_names()
784    }
785
786    /// Returns configured permission group names.
787    #[must_use]
788    pub fn permission_group_names(&self) -> Vec<String> {
789        self.inner.permission_group_names()
790    }
791}
792
793/// Prior parsed values and source facts available to custom suggestions.
794#[derive(Clone, Copy)]
795pub struct CommandSuggestionContext<'context> {
796    inner: &'context dyn SteelArgumentSuggestionContext,
797}
798
799impl<'context> CommandSuggestionContext<'context> {
800    /// Returns source facts for context-aware completion.
801    #[must_use]
802    pub fn source(&self) -> CommandParserSource<'context> {
803        CommandParserSource {
804            inner: self.inner.source(),
805        }
806    }
807
808    /// Returns a previously parsed custom value by deterministic concrete type key.
809    #[must_use]
810    pub fn value<T: DowncastType>(&self, name: &str) -> Option<&'context T> {
811        self.inner.argument(name)?.downcast_ref::<T>()
812    }
813}
814
815/// Completion builder available to custom argument parsers.
816pub struct CommandSuggestions<'builder, 'input> {
817    inner: &'builder mut SuggestionsBuilder<'input>,
818}
819
820impl CommandSuggestions<'_, '_> {
821    /// Returns the partial input being completed.
822    #[must_use]
823    pub fn remaining(&self) -> &str {
824        self.inner.remaining()
825    }
826
827    /// Adds a textual completion.
828    pub fn suggest(&mut self, text: impl Into<Box<str>>) {
829        self.inner.suggest(text);
830    }
831
832    /// Adds a textual completion with a rich tooltip.
833    pub fn suggest_with_tooltip(&mut self, text: impl Into<Box<str>>, tooltip: TextComponent) {
834        self.inner.suggest_with_tooltip(text, tooltip);
835    }
836
837    /// Adds an integer completion with Brigadier's numeric ordering.
838    pub fn suggest_integer(&mut self, value: i32) {
839        self.inner.suggest_integer(value);
840    }
841}
842
843/// Poll result for a public command whose result is produced across ticks.
844pub enum SuspendedCommandPoll {
845    /// The command remains suspended.
846    Pending,
847    /// The command completed with a result or execution error.
848    Ready(Result<i32, CommandError>),
849}
850
851/// Cross-tick command work owned and cancelled by Steel's command scheduler.
852pub trait SuspendedCommand: Send + 'static {
853    /// Returns which later top-level commands must wait for this work.
854    fn order(&self) -> CommandSuspensionOrder {
855        CommandSuspensionOrder::Source
856    }
857
858    /// Polls the command once from the server tick.
859    fn poll(&mut self) -> SuspendedCommandPoll;
860
861    /// Cancels retained external work when the command or server stops.
862    fn cancel(&mut self) {}
863}
864
865struct ExternalCommandSuspension<T>(T);
866
867impl<T> CommandResultSuspension for ExternalCommandSuspension<T>
868where
869    T: SuspendedCommand,
870{
871    fn order(&self) -> CommandSuspensionOrder {
872        self.0.order()
873    }
874
875    fn poll(&mut self) -> CommandResultSuspensionPoll {
876        match self.0.poll() {
877            SuspendedCommandPoll::Pending => CommandResultSuspensionPoll::Pending,
878            SuspendedCommandPoll::Ready(result) => {
879                CommandResultSuspensionPoll::Ready(result.map_err(CommandError::into_inner))
880            }
881        }
882    }
883
884    fn cancel(&mut self) {
885        self.0.cancel();
886    }
887}