Skip to content

Commit

Permalink
Better help command (#365)
Browse files Browse the repository at this point in the history
* Make formatting more easy to read

* Implement recursive extra field

* Nicer help command

* Make command help menu nicer

* Fix clippy issues
  • Loading branch information
vyPal authored Dec 2, 2024
1 parent 5be43e2 commit c52b770
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 40 deletions.
26 changes: 14 additions & 12 deletions pumpkin-core/src/text/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,22 +186,24 @@ impl<'a> TextComponent<'a> {
style: &'a Style<'a>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "extra")]
extra: Vec<&'a TempStruct<'a>>,
extra: Vec<TempStruct<'a>>,
}
let temp_extra: Vec<TempStruct> = self
.extra
.iter()
.map(|x| TempStruct {
text: &x.content,
style: &x.style,
extra: vec![],
})
.collect();
let temp_extra_refs: Vec<&TempStruct> = temp_extra.iter().collect();
fn convert_extra<'a>(extra: &'a [TextComponent<'a>]) -> Vec<TempStruct<'a>> {
extra
.iter()
.map(|x| TempStruct {
text: &x.content,
style: &x.style,
extra: convert_extra(&x.extra),
})
.collect()
}

let temp_extra = convert_extra(&self.extra);
let astruct = TempStruct {
text: &self.content,
style: &self.style,
extra: temp_extra_refs,
extra: temp_extra,
};
// dbg!(&serde_json::to_string(&astruct));
// dbg!(pumpkin_nbt::serializer::to_bytes_unnamed(&astruct).unwrap().to_vec());
Expand Down
203 changes: 176 additions & 27 deletions pumpkin/src/command/commands/cmd_help.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use async_trait::async_trait;
use num_traits::ToPrimitive;
use pumpkin_core::text::click::ClickEvent;
use pumpkin_core::text::color::{Color, NamedColor};
use pumpkin_core::text::TextComponent;

use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::arg_command::CommandTreeArgumentConsumer;
use crate::command::args::{Arg, ConsumedArgs};
use crate::command::args::{Arg, ConsumedArgs, FindArgDefaultName};
use crate::command::dispatcher::CommandError;
use crate::command::dispatcher::CommandError::InvalidConsumption;
use crate::command::tree::{Command, CommandTree};
use crate::command::tree_builder::argument;
use crate::command::tree_builder::{argument, argument_default_name};
use crate::command::{CommandExecutor, CommandSender};
use crate::server::Server;

Expand All @@ -16,6 +20,11 @@ const DESCRIPTION: &str = "Print a help message.";

const ARG_COMMAND: &str = "command";

const COMMANDS_PER_PAGE: i32 = 7;

static PAGE_NUMBER_CONSUMER: BoundedNumArgumentConsumer<i32> =
BoundedNumArgumentConsumer::new().name("page").min(1);

struct CommandHelpExecutor;

#[async_trait]
Expand All @@ -30,14 +39,59 @@ impl CommandExecutor for CommandHelpExecutor {
return Err(InvalidConsumption(Some(ARG_COMMAND.into())));
};

sender
.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree
)))
.await;
let command_names = tree.names.join(", /");
let usage = format!("{tree}");
let description = tree.description;

let header_text = format!(" Help - /{} ", tree.names[0]);

let mut message = TextComponent::text("")
.add_child(
TextComponent::text_string("-".repeat((52 - header_text.len()) / 2) + " ")
.color_named(NamedColor::Yellow),
)
.add_child(TextComponent::text_string(header_text.clone()))
.add_child(
TextComponent::text_string(
" ".to_owned().to_string() + &"-".repeat((52 - header_text.len()) / 2) + "\n",
)
.color_named(NamedColor::Yellow),
)
.add_child(
TextComponent::text("Command: ")
.color_named(NamedColor::Aqua)
.add_child(
TextComponent::text_string(format!("/{command_names}"))
.color_named(NamedColor::Gold)
.bold(),
)
.add_child(TextComponent::text("\n").color_named(NamedColor::White))
.click_event(ClickEvent::SuggestCommand(
format!("/{}", tree.names[0]).into(),
)),
)
.add_child(
TextComponent::text("Description: ")
.color_named(NamedColor::Aqua)
.add_child(
TextComponent::text_string(format!("{description}\n"))
.color_named(NamedColor::White),
),
)
.add_child(
TextComponent::text("Usage: ")
.color_named(NamedColor::Aqua)
.add_child(
TextComponent::text_string(format!("{usage}\n"))
.color_named(NamedColor::White),
)
.click_event(ClickEvent::SuggestCommand(format!("{tree}").into())),
);

message = message
.add_child(TextComponent::text_string("-".repeat(52)).color_named(NamedColor::Yellow));
sender.send_message(message).await;
Ok(())
}
Expand All @@ -51,26 +105,120 @@ impl CommandExecutor for BaseHelpExecutor {
&self,
sender: &mut CommandSender<'a>,
server: &Server,
_args: &ConsumedArgs<'a>,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let mut keys: Vec<&str> = server.command_dispatcher.commands.keys().copied().collect();
keys.sort_unstable();

for key in keys {
let Command::Tree(tree) = &server.command_dispatcher.commands[key] else {
continue;
};

sender
.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree
)))
.await;
let page_number = match PAGE_NUMBER_CONSUMER.find_arg_default_name(args) {
Err(_) => 1,
Ok(Ok(number)) => number,
Ok(Err(())) => {
sender
.send_message(
TextComponent::text("Invalid page number.")
.color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
}
};

let mut commands: Vec<&CommandTree> = server
.command_dispatcher
.commands
.values()
.filter_map(|cmd| match cmd {
Command::Tree(tree) => Some(tree),
Command::Alias(_) => None,
})
.collect();

commands.sort_by(|a, b| a.names[0].cmp(b.names[0]));

let total_pages =
(commands.len().to_i32().unwrap() + COMMANDS_PER_PAGE - 1) / COMMANDS_PER_PAGE;
let page = page_number.min(total_pages);

let start = (page - 1) * COMMANDS_PER_PAGE;
let end = start + COMMANDS_PER_PAGE;
let page_commands =
&commands[start as usize..end.min(commands.len().to_i32().unwrap()) as usize];

let arrow_left = if page > 1 {
let cmd = format!("/help {}", page - 1);
TextComponent::text("<<<")
.color(Color::Named(NamedColor::Aqua))
.click_event(ClickEvent::RunCommand(cmd.into()))
} else {
TextComponent::text("<<<").color(Color::Named(NamedColor::Gray))
};

let arrow_right = if page < total_pages {
let cmd = format!("/help {}", page + 1);
TextComponent::text(">>>")
.color(Color::Named(NamedColor::Aqua))
.click_event(ClickEvent::RunCommand(cmd.into()))
} else {
TextComponent::text(">>>").color(Color::Named(NamedColor::Gray))
};

let header_text = format!(" Help - Page {page}/{total_pages} ");

let mut message = TextComponent::text("")
.add_child(
TextComponent::text_string("-".repeat((52 - header_text.len() - 3) / 2) + " ")
.color_named(NamedColor::Yellow),
)
.add_child(arrow_left.clone())
.add_child(TextComponent::text_string(header_text.clone()))
.add_child(arrow_right.clone())
.add_child(
TextComponent::text_string(
" ".to_owned().to_string()
+ &"-".repeat((52 - header_text.len() - 3) / 2)
+ "\n",
)
.color_named(NamedColor::Yellow),
);

for tree in page_commands {
message = message.add_child(
TextComponent::text_string("/".to_owned() + &tree.names.join(", /"))
.color_named(NamedColor::Gold)
.add_child(TextComponent::text(" - ").color_named(NamedColor::Yellow))
.add_child(
TextComponent::text_string(tree.description.to_owned() + "\n")
.color_named(NamedColor::White),
)
.add_child(TextComponent::text(" Usage: ").color_named(NamedColor::Yellow))
.add_child(
TextComponent::text_string(format!("{tree}"))
.color_named(NamedColor::White),
)
.add_child(TextComponent::text("\n").color_named(NamedColor::White))
.click_event(ClickEvent::SuggestCommand(
format!("/{}", tree.names[0]).into(),
)),
);
}

let footer_text = format!(" Page {page}/{total_pages} ");

message = message
.add_child(
TextComponent::text_string("-".repeat((52 - footer_text.len() - 3) / 2) + " ")
.color_named(NamedColor::Yellow),
)
.add_child(arrow_left)
.add_child(TextComponent::text_string(footer_text.clone()))
.add_child(arrow_right)
.add_child(
TextComponent::text_string(
" ".to_owned().to_string() + &"-".repeat((52 - footer_text.len() - 3) / 2),
)
.color_named(NamedColor::Yellow),
);

sender.send_message(message).await;

Ok(())
}
}
Expand All @@ -80,5 +228,6 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
.with_child(
argument(ARG_COMMAND, &CommandTreeArgumentConsumer).execute(&CommandHelpExecutor),
)
.with_child(argument_default_name(&PAGE_NUMBER_CONSUMER).execute(&BaseHelpExecutor))
.execute(&BaseHelpExecutor)
}
2 changes: 1 addition & 1 deletion pumpkin/src/command/tree_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl Display for CommandTree<'_> {
}

for node in iter {
f.write_char('|')?;
f.write_str(" | ")?;
node.fmt(f)?;
}

Expand Down

0 comments on commit c52b770

Please sign in to comment.