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