mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(command): add suggestion providers and their support in argument nodes and fix list_suggestions method (#2004)
* added suggestion providers and their support in argument nodes * renamed argument suggesting methods * fixed custom suggestion provider to make them work * fixed formatting * make `SuggestionProvider` use a lifetime * added comment * fixed lifetime issue * used suggestion results of suggestion providers * switched to using `SuggestionsBuilder` instead of `&mut SuggestionsBuilder` for `list_suggestions`
This commit is contained in:
@@ -6,6 +6,7 @@ use crate::command::node::detached::{
|
||||
use crate::command::node::{
|
||||
Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements,
|
||||
};
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
@@ -119,6 +120,7 @@ pub struct RequiredArgumentBuilder {
|
||||
common: CommonArgumentBuilder,
|
||||
name: Cow<'static, str>,
|
||||
argument_type: Arc<dyn AnyArgumentType>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
}
|
||||
|
||||
mod private {
|
||||
@@ -356,8 +358,22 @@ impl RequiredArgumentBuilder {
|
||||
common: CommonArgumentBuilder::new(),
|
||||
name: name.into(),
|
||||
argument_type: Arc::new(arg_type),
|
||||
suggestion_provider: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
||||
#[must_use]
|
||||
pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self {
|
||||
self.suggests_arc(Arc::new(provider))
|
||||
}
|
||||
|
||||
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
||||
#[must_use]
|
||||
pub fn suggests_arc(mut self, provider: Arc<dyn SuggestionProvider>) -> Self {
|
||||
self.suggestion_provider = Some(provider);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgumentBuilder<LiteralDetachedNode> for LiteralArgumentBuilder {
|
||||
@@ -410,6 +426,7 @@ impl ArgumentBuilder<ArgumentDetachedNode> for RequiredArgumentBuilder {
|
||||
self.common.target,
|
||||
self.common.modifier,
|
||||
self.common.forks,
|
||||
self.suggestion_provider,
|
||||
);
|
||||
node.children = self.common.arguments;
|
||||
node
|
||||
|
||||
@@ -42,7 +42,7 @@ pub trait ArgumentType: Send + Sync {
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
_context: &CommandContext,
|
||||
_suggestions_builder: &mut SuggestionsBuilder,
|
||||
_suggestions_builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send>> {
|
||||
Box::pin(async move { Suggestions::empty() })
|
||||
}
|
||||
@@ -102,7 +102,7 @@ pub trait AnyArgumentType: Sealed + Send + Sync {
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
suggestions_builder: &mut SuggestionsBuilder,
|
||||
suggestions_builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send>>;
|
||||
|
||||
/// Returns the Java client-side parser used for this argument type.
|
||||
@@ -154,7 +154,7 @@ impl<U: ArgumentType<Item = T>, T: Send + Sync + 'static> AnyArgumentType for U
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: &CommandContext,
|
||||
suggestions_builder: &mut SuggestionsBuilder,
|
||||
suggestions_builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send>> {
|
||||
self.list_suggestions(context, suggestions_builder)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
use pumpkin_protocol::{
|
||||
codec::var_int::VarInt,
|
||||
java::client::play::{CCommands, ProtoNode, ProtoNodeType},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::tree::{Node, NodeType};
|
||||
use crate::server::Server;
|
||||
use crate::{
|
||||
@@ -14,6 +8,12 @@ use crate::{
|
||||
},
|
||||
entity::player::Player,
|
||||
};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_protocol::{
|
||||
codec::var_int::VarInt,
|
||||
java::client::play::{CCommands, ProtoNode, ProtoNodeType},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn send_c_commands_packet(
|
||||
@@ -151,7 +151,15 @@ pub async fn send_c_commands_packet(
|
||||
name: &argument_attached_node.meta.name,
|
||||
is_executable: argument_attached_node.owned.command.is_some(),
|
||||
parser: arg_type.client_side_parser(),
|
||||
override_suggestion_type: arg_type.override_suggestion_providers(),
|
||||
override_suggestion_type: if argument_attached_node
|
||||
.meta
|
||||
.suggestion_provider
|
||||
.is_some()
|
||||
{
|
||||
Some(SuggestionProviders::AskServer)
|
||||
} else {
|
||||
arg_type.override_suggestion_providers()
|
||||
},
|
||||
redirect_target,
|
||||
restricted: !satisfies_requirements,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::command::node::{
|
||||
ArgumentNodeMetadata, Command, CommandNodeMetadata, LiteralNodeMetadata, NodeMetadata,
|
||||
OwnedNodeData, RedirectModifier, Redirection, Requirements,
|
||||
};
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::num::NonZero;
|
||||
@@ -150,6 +151,7 @@ impl ArgumentDetachedNode {
|
||||
redirect: Option<Redirection>,
|
||||
modifier: RedirectModifier,
|
||||
forks: bool,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
owned: OwnedNodeData {
|
||||
@@ -161,7 +163,7 @@ impl ArgumentDetachedNode {
|
||||
},
|
||||
children: FxHashMap::default(),
|
||||
redirect,
|
||||
meta: ArgumentNodeMetadata::new(name, argument_type),
|
||||
meta: ArgumentNodeMetadata::new(name, argument_type, suggestion_provider),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,14 +490,15 @@ impl CommandDispatcher {
|
||||
let mut futures = Vec::with_capacity(capacity);
|
||||
|
||||
let context = context.build(truncated_input);
|
||||
let mut provided_suggestions = Vec::new();
|
||||
|
||||
for child in children {
|
||||
let mut builder = SuggestionsBuilder::new(truncated_input, start);
|
||||
let builder = SuggestionsBuilder::new(truncated_input, start);
|
||||
|
||||
let future: Pin<Box<dyn Future<Output = Suggestions> + Send>> =
|
||||
let future: Option<Pin<Box<dyn Future<Output = Suggestions> + Send>>> =
|
||||
match self.tree.classify_id(child) {
|
||||
NodeIdClassification::Root => Box::pin(async { Suggestions::empty() }),
|
||||
NodeIdClassification::Literal(literal_node_id) => Box::pin(async move {
|
||||
NodeIdClassification::Root => Some(Box::pin(async { Suggestions::empty() })),
|
||||
NodeIdClassification::Literal(literal_node_id) => Some(Box::pin(async move {
|
||||
let node = &self.tree[literal_node_id];
|
||||
if node
|
||||
.meta
|
||||
@@ -508,8 +509,8 @@ impl CommandDispatcher {
|
||||
} else {
|
||||
Suggestions::empty()
|
||||
}
|
||||
}),
|
||||
NodeIdClassification::Command(command_node_id) => Box::pin(async move {
|
||||
})),
|
||||
NodeIdClassification::Command(command_node_id) => Some(Box::pin(async move {
|
||||
let node = &self.tree[command_node_id];
|
||||
if node
|
||||
.meta
|
||||
@@ -520,19 +521,27 @@ impl CommandDispatcher {
|
||||
} else {
|
||||
Suggestions::empty()
|
||||
}
|
||||
}),
|
||||
})),
|
||||
NodeIdClassification::Argument(argument_node_id) => {
|
||||
let node = &self.tree[argument_node_id];
|
||||
node.meta
|
||||
.argument_type
|
||||
.list_suggestions(&context, &mut builder)
|
||||
if let Some(provider) = &node.meta.suggestion_provider {
|
||||
// For custom suggestions sent by the server, we simply
|
||||
// wait instead of adding the future to join.
|
||||
provided_suggestions.push(provider.suggest(&context, builder).await);
|
||||
None
|
||||
} else {
|
||||
Some(node.meta.argument_type.list_suggestions(&context, builder))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
futures.push(future);
|
||||
if let Some(future) = future {
|
||||
futures.push(future);
|
||||
}
|
||||
}
|
||||
|
||||
let suggestions = future::join_all(futures).await;
|
||||
let mut suggestions = future::join_all(futures).await;
|
||||
suggestions.append(&mut provided_suggestions);
|
||||
Suggestions::merge(full_input, suggestions)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::command::context::command_source::CommandSource;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::node::attached::NodeId;
|
||||
use crate::command::node::detached::GlobalNodeId;
|
||||
use crate::command::suggestion::provider::SuggestionProvider;
|
||||
use std::borrow::Cow;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -218,16 +219,19 @@ impl CommandNodeMetadata {
|
||||
pub struct ArgumentNodeMetadata {
|
||||
pub name: Cow<'static, str>,
|
||||
pub argument_type: Arc<dyn AnyArgumentType>,
|
||||
pub suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
}
|
||||
|
||||
impl ArgumentNodeMetadata {
|
||||
pub fn new(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
argument_type: Arc<dyn AnyArgumentType>,
|
||||
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
argument_type,
|
||||
suggestion_provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod provider;
|
||||
pub mod suggestions;
|
||||
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
24
pumpkin/src/command/suggestion/provider.rs
Normal file
24
pumpkin/src/command/suggestion/provider.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use std::pin::Pin;
|
||||
|
||||
/// The [`Suggestions`] future given by a [`SuggestionProvider`].
|
||||
pub type SuggestionProviderResult<'a> = Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>>;
|
||||
|
||||
/// A trait allowing an object to provide suggestions using a
|
||||
/// [`CommandContext`] and [`SuggestionsBuilder`].
|
||||
pub trait SuggestionProvider: Send + Sync {
|
||||
/// Uses a [`CommandContext`] and [`SuggestionsBuilder`] to suggest.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `context`: The context to use for building the suggestions.
|
||||
/// - `builder`: The builder to consume for the suggestions.
|
||||
///
|
||||
/// # Returns
|
||||
/// The [`Suggestions`] representing the suggested items.
|
||||
fn suggest<'a>(
|
||||
&'a self,
|
||||
context: &'a CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> SuggestionProviderResult<'a>;
|
||||
}
|
||||
Reference in New Issue
Block a user