From d46dbb9708c8726de5e2676d709e50f66bba9b4b Mon Sep 17 00:00:00 2001 From: Syniis Date: Sat, 27 Jan 2024 13:41:29 +0100 Subject: [PATCH 1/4] Incremental item path autocomplete --- common/src/cmd.rs | 18 ++++++++++++++++-- voxygen/src/cmd.rs | 12 ++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index fa18862587..02c4267727 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -537,7 +537,7 @@ impl ServerChatCommand { ), ServerChatCommand::GiveItem => cmd( vec![ - Enum("item", ITEM_SPECS.clone(), Required), + AssetPath("item", ITEM_SPECS.clone(), Required), Integer("num", 1, Optional), ], "Give yourself some items.\nFor an example or to auto complete use Tab.", @@ -647,7 +647,7 @@ impl ServerChatCommand { ), ServerChatCommand::MakeNpc => cmd( vec![ - Enum("entity_config", ENTITY_CONFIGS.clone(), Required), + AssetPath("entity_config", ENTITY_CONFIGS.clone(), Required), Integer("num", 1, Optional), ], "Spawn entity from config near you.\nFor an example or to auto complete use Tab.", @@ -1079,6 +1079,7 @@ impl ServerChatCommand { ArgumentSpec::Message(_) => "{/.*/}", ArgumentSpec::SubCommand => "{} {/.*/}", ArgumentSpec::Enum(_, _, _) => "{}", + ArgumentSpec::AssetPath(_, _, _) => "{}", ArgumentSpec::Boolean(_, _, _) => "{}", ArgumentSpec::Flag(_) => "{}", }) @@ -1146,6 +1147,11 @@ pub enum ArgumentSpec { /// * Predefined string completions /// * whether it's optional Enum(&'static str, Vec, Requirement), + /// The argument is an asset path. The associated values are + /// * label + /// * List of all asset paths as strings for completion + /// * whether it's optional + AssetPath(&'static str, Vec, Requirement), /// The argument is likely a boolean. The associated values are /// * label /// * suggested tab-completion @@ -1222,6 +1228,13 @@ impl ArgumentSpec { format!("[{}]", label) } }, + ArgumentSpec::AssetPath(label, _, req) => { + if &Requirement::Required == req { + format!("<{}>", label) + } else { + format!("[{}]", label) + } + }, ArgumentSpec::Boolean(label, _, req) => { if &Requirement::Required == req { format!("<{}>", label) @@ -1246,6 +1259,7 @@ impl ArgumentSpec { | ArgumentSpec::Command(r) | ArgumentSpec::Message(r) | ArgumentSpec::Enum(_, _, r) + | ArgumentSpec::AssetPath(_, _, r) | ArgumentSpec::Boolean(_, _, r) => *r, ArgumentSpec::Flag(_) => Requirement::Optional, ArgumentSpec::SubCommand => Requirement::Required, diff --git a/voxygen/src/cmd.rs b/voxygen/src/cmd.rs index 9dffba8372..01f40339a4 100644 --- a/voxygen/src/cmd.rs +++ b/voxygen/src/cmd.rs @@ -17,6 +17,7 @@ use common::{ uuid::Uuid, }; use common_net::sync::WorldSyncExt; +use itertools::Itertools; use levenshtein::levenshtein; use specs::{Join, WorldExt}; use strum::{EnumIter, IntoEnumIterator}; @@ -100,6 +101,7 @@ impl ClientChatCommand { ArgumentSpec::Message(_) => "{/.*/}", ArgumentSpec::SubCommand => "{} {/.*/}", ArgumentSpec::Enum(_, _, _) => "{}", + ArgumentSpec::AssetPath(_, _, _) => "{}", ArgumentSpec::Boolean(_, _, _) => "{}", ArgumentSpec::Flag(_) => "{}", }) @@ -591,6 +593,16 @@ impl TabComplete for ArgumentSpec { .filter(|string| string.starts_with(part)) .map(|c| c.to_string()) .collect(), + ArgumentSpec::AssetPath(_, paths, _) => { + let depth = part.split('.').count(); + paths + .iter() + .map(|path| path.as_str().split('.').take(depth).join(".")) + .dedup() + .filter(|string| string.starts_with(part)) + .map(|c| c.to_string()) + .collect() + }, ArgumentSpec::Boolean(_, part, _) => ["true", "false"] .iter() .filter(|string| string.starts_with(part)) From b97e27df9a181dcc22e02fdf2a93a1f0a959b87d Mon Sep 17 00:00:00 2001 From: Syniis Date: Sat, 27 Jan 2024 15:44:03 +0100 Subject: [PATCH 2/4] Allow specifiying prefix for AssetPath command argument --- common/src/cmd.rs | 18 ++++++++++++------ voxygen/src/cmd.rs | 13 ++++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 02c4267727..235cbe7182 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -537,7 +537,7 @@ impl ServerChatCommand { ), ServerChatCommand::GiveItem => cmd( vec![ - AssetPath("item", ITEM_SPECS.clone(), Required), + AssetPath("item", "common.items", ITEM_SPECS.clone(), Required), Integer("num", 1, Optional), ], "Give yourself some items.\nFor an example or to auto complete use Tab.", @@ -647,7 +647,12 @@ impl ServerChatCommand { ), ServerChatCommand::MakeNpc => cmd( vec![ - AssetPath("entity_config", ENTITY_CONFIGS.clone(), Required), + AssetPath( + "entity_config", + "common.entity", + ENTITY_CONFIGS.clone(), + Required, + ), Integer("num", 1, Optional), ], "Spawn entity from config near you.\nFor an example or to auto complete use Tab.", @@ -1079,7 +1084,7 @@ impl ServerChatCommand { ArgumentSpec::Message(_) => "{/.*/}", ArgumentSpec::SubCommand => "{} {/.*/}", ArgumentSpec::Enum(_, _, _) => "{}", - ArgumentSpec::AssetPath(_, _, _) => "{}", + ArgumentSpec::AssetPath(_, _, _, _) => "{}", ArgumentSpec::Boolean(_, _, _) => "{}", ArgumentSpec::Flag(_) => "{}", }) @@ -1149,9 +1154,10 @@ pub enum ArgumentSpec { Enum(&'static str, Vec, Requirement), /// The argument is an asset path. The associated values are /// * label + /// * Path prefix shared by all assets /// * List of all asset paths as strings for completion /// * whether it's optional - AssetPath(&'static str, Vec, Requirement), + AssetPath(&'static str, &'static str, Vec, Requirement), /// The argument is likely a boolean. The associated values are /// * label /// * suggested tab-completion @@ -1228,7 +1234,7 @@ impl ArgumentSpec { format!("[{}]", label) } }, - ArgumentSpec::AssetPath(label, _, req) => { + ArgumentSpec::AssetPath(label, _, _, req) => { if &Requirement::Required == req { format!("<{}>", label) } else { @@ -1259,7 +1265,7 @@ impl ArgumentSpec { | ArgumentSpec::Command(r) | ArgumentSpec::Message(r) | ArgumentSpec::Enum(_, _, r) - | ArgumentSpec::AssetPath(_, _, r) + | ArgumentSpec::AssetPath(_, _, _, r) | ArgumentSpec::Boolean(_, _, r) => *r, ArgumentSpec::Flag(_) => Requirement::Optional, ArgumentSpec::SubCommand => Requirement::Required, diff --git a/voxygen/src/cmd.rs b/voxygen/src/cmd.rs index 01f40339a4..4b618a947c 100644 --- a/voxygen/src/cmd.rs +++ b/voxygen/src/cmd.rs @@ -101,7 +101,7 @@ impl ClientChatCommand { ArgumentSpec::Message(_) => "{/.*/}", ArgumentSpec::SubCommand => "{} {/.*/}", ArgumentSpec::Enum(_, _, _) => "{}", - ArgumentSpec::AssetPath(_, _, _) => "{}", + ArgumentSpec::AssetPath(_, _, _, _) => "{}", ArgumentSpec::Boolean(_, _, _) => "{}", ArgumentSpec::Flag(_) => "{}", }) @@ -221,6 +221,9 @@ fn preproccess_command( } else if matches!(cmd_args.last(), Some(ArgumentSpec::SubCommand)) { could_be_entity_target = true; } + if let Some(ArgumentSpec::AssetPath(_, prefix, _, _)) = cmd_args.get(i) { + *arg = prefix.to_string() + "." + arg; + } if could_be_entity_target && arg.starts_with(ClientEntityTarget::PREFIX) { let target_str = arg.trim_start_matches(ClientEntityTarget::PREFIX); let target = ClientEntityTarget::iter() @@ -593,11 +596,15 @@ impl TabComplete for ArgumentSpec { .filter(|string| string.starts_with(part)) .map(|c| c.to_string()) .collect(), - ArgumentSpec::AssetPath(_, paths, _) => { + ArgumentSpec::AssetPath(_, prefix, paths, _) => { let depth = part.split('.').count(); paths .iter() - .map(|path| path.as_str().split('.').take(depth).join(".")) + .filter_map(|path| { + path.as_str() + .strip_prefix(&(prefix.to_string() + ".")) + .map(|stripped| stripped.split('.').take(depth).join(".")) + }) .dedup() .filter(|string| string.starts_with(part)) .map(|c| c.to_string()) From 488922ac94243cec4c7bc797ca7c43bd4871ca0d Mon Sep 17 00:00:00 2001 From: Syniis Date: Sat, 27 Jan 2024 16:16:30 +0100 Subject: [PATCH 3/4] Cleaner code --- common/src/cmd.rs | 4 ++-- voxygen/src/cmd.rs | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 235cbe7182..e67074a5de 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -537,7 +537,7 @@ impl ServerChatCommand { ), ServerChatCommand::GiveItem => cmd( vec![ - AssetPath("item", "common.items", ITEM_SPECS.clone(), Required), + AssetPath("item", "common.items.", ITEM_SPECS.clone(), Required), Integer("num", 1, Optional), ], "Give yourself some items.\nFor an example or to auto complete use Tab.", @@ -649,7 +649,7 @@ impl ServerChatCommand { vec![ AssetPath( "entity_config", - "common.entity", + "common.entity.", ENTITY_CONFIGS.clone(), Required, ), diff --git a/voxygen/src/cmd.rs b/voxygen/src/cmd.rs index 4b618a947c..27c5611cfc 100644 --- a/voxygen/src/cmd.rs +++ b/voxygen/src/cmd.rs @@ -222,7 +222,7 @@ fn preproccess_command( could_be_entity_target = true; } if let Some(ArgumentSpec::AssetPath(_, prefix, _, _)) = cmd_args.get(i) { - *arg = prefix.to_string() + "." + arg; + *arg = prefix.to_string() + arg; } if could_be_entity_target && arg.starts_with(ClientEntityTarget::PREFIX) { let target_str = arg.trim_start_matches(ClientEntityTarget::PREFIX); @@ -597,17 +597,14 @@ impl TabComplete for ArgumentSpec { .map(|c| c.to_string()) .collect(), ArgumentSpec::AssetPath(_, prefix, paths, _) => { - let depth = part.split('.').count(); + let part_with_prefix = prefix.to_string() + part; + let depth = part_with_prefix.split('.').count(); paths .iter() - .filter_map(|path| { - path.as_str() - .strip_prefix(&(prefix.to_string() + ".")) - .map(|stripped| stripped.split('.').take(depth).join(".")) - }) + .map(|path| path.as_str().split('.').take(depth).join(".")) .dedup() - .filter(|string| string.starts_with(part)) - .map(|c| c.to_string()) + .filter(|string| string.starts_with(&part_with_prefix)) + .filter_map(|c| Some(c.strip_prefix(prefix)?.to_string())) .collect() }, ArgumentSpec::Boolean(_, part, _) => ["true", "false"] From 04acb82d755415855b79309cb14e1eb331e8a28e Mon Sep 17 00:00:00 2001 From: Syniis Date: Sat, 27 Jan 2024 17:34:25 +0100 Subject: [PATCH 4/4] Fix asset prefix not working with sudo --- voxygen/src/cmd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/voxygen/src/cmd.rs b/voxygen/src/cmd.rs index 27c5611cfc..f1ff2a4cba 100644 --- a/voxygen/src/cmd.rs +++ b/voxygen/src/cmd.rs @@ -212,6 +212,9 @@ fn preproccess_command( break; } }, + ArgumentSpec::AssetPath(_, prefix, _, _) => { + *arg = prefix.to_string() + arg; + }, _ => {}, } if matches!(arg_spec.requirement(), Requirement::Required) { @@ -221,9 +224,6 @@ fn preproccess_command( } else if matches!(cmd_args.last(), Some(ArgumentSpec::SubCommand)) { could_be_entity_target = true; } - if let Some(ArgumentSpec::AssetPath(_, prefix, _, _)) = cmd_args.get(i) { - *arg = prefix.to_string() + arg; - } if could_be_entity_target && arg.starts_with(ClientEntityTarget::PREFIX) { let target_str = arg.trim_start_matches(ClientEntityTarget::PREFIX); let target = ClientEntityTarget::iter()