mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Fixed player list tab completion
This commit is contained in:
parent
b486de28ac
commit
9d118b55a0
121
client/src/cmd.rs
Normal file
121
client/src/cmd.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use crate::Client;
|
||||
use common::cmd::*;
|
||||
|
||||
trait TabComplete {
|
||||
fn complete(&self, part: &str, client: &Client) -> Vec<String>;
|
||||
}
|
||||
|
||||
impl TabComplete for ArgumentSpec {
|
||||
fn complete(&self, part: &str, client: &Client) -> Vec<String> {
|
||||
match self {
|
||||
ArgumentSpec::PlayerName(_) => complete_player(part, &client),
|
||||
ArgumentSpec::Float(_, x, _) => {
|
||||
if part.is_empty() {
|
||||
vec![format!("{:.1}", x)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
ArgumentSpec::Integer(_, x, _) => {
|
||||
if part.is_empty() {
|
||||
vec![format!("{}", x)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
ArgumentSpec::Any(_, _) => vec![],
|
||||
ArgumentSpec::Command(_) => complete_command(part),
|
||||
ArgumentSpec::Message => complete_player(part, &client),
|
||||
ArgumentSpec::SubCommand => complete_command(part),
|
||||
ArgumentSpec::Enum(_, strings, _) => strings
|
||||
.iter()
|
||||
.filter(|string| string.starts_with(part))
|
||||
.map(|c| c.to_string())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_player(part: &str, client: &Client) -> Vec<String> {
|
||||
client
|
||||
.player_list
|
||||
.values()
|
||||
.filter(|alias| alias.starts_with(part))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn complete_command(part: &str) -> Vec<String> {
|
||||
CHAT_COMMANDS
|
||||
.iter()
|
||||
.map(|com| com.keyword())
|
||||
.filter(|kwd| kwd.starts_with(part) || format!("/{}", kwd).starts_with(part))
|
||||
.map(|c| format!("/{}", c))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Get the byte index of the nth word. Used in completing "/sudo p /subcmd"
|
||||
fn nth_word(line: &str, n: usize) -> Option<usize> {
|
||||
let mut is_space = false;
|
||||
let mut j = 0;
|
||||
for (i, c) in line.char_indices() {
|
||||
match (is_space, c.is_whitespace()) {
|
||||
(true, true) => {},
|
||||
(true, false) => {
|
||||
is_space = false;
|
||||
j += 1;
|
||||
},
|
||||
(false, true) => {
|
||||
is_space = true;
|
||||
},
|
||||
(false, false) => {},
|
||||
}
|
||||
if j == n {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn complete(line: &str, client: &Client) -> Vec<String> {
|
||||
let word = if line.chars().last().map_or(true, char::is_whitespace) {
|
||||
""
|
||||
} else {
|
||||
line.split_whitespace().last().unwrap_or("")
|
||||
};
|
||||
if line.chars().next() == Some('/') {
|
||||
let mut iter = line.split_whitespace();
|
||||
let cmd = iter.next().unwrap();
|
||||
let i = iter.count() + if word.is_empty() { 1 } else { 0 };
|
||||
if i == 0 {
|
||||
// Completing chat command name
|
||||
complete_command(word)
|
||||
} else {
|
||||
if let Ok(cmd) = cmd.parse::<ChatCommand>() {
|
||||
if let Some(arg) = cmd.data().args.get(i - 1) {
|
||||
// Complete ith argument
|
||||
arg.complete(word, &client)
|
||||
} else {
|
||||
// Complete past the last argument
|
||||
match cmd.data().args.last() {
|
||||
Some(ArgumentSpec::SubCommand) => {
|
||||
if let Some(index) = nth_word(line, cmd.data().args.len()) {
|
||||
complete(&line[index..], &client)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
Some(ArgumentSpec::Message) => complete_player(word, &client),
|
||||
_ => vec![], // End of command. Nothing to complete
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Completing for unknown chat command
|
||||
complete_player(word, &client)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not completing a command
|
||||
complete_player(word, &client)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#![deny(unsafe_code)]
|
||||
#![feature(label_break_value)]
|
||||
|
||||
pub mod cmd;
|
||||
pub mod error;
|
||||
|
||||
// Reexports
|
||||
|
@ -12,7 +12,7 @@ use std::{
|
||||
fmt,
|
||||
fs::{self, File, ReadDir},
|
||||
io::{BufReader, Read},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
@ -59,6 +59,31 @@ lazy_static! {
|
||||
/// The HashMap where all loaded assets are stored in.
|
||||
pub static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
|
||||
RwLock::new(HashMap::new());
|
||||
|
||||
/// List of item specifiers. Used for tab completing
|
||||
pub static ref ITEM_SPECS: Vec<String> = {
|
||||
let base = ASSETS_PATH.join("common").join("items");
|
||||
let mut items = vec![];
|
||||
fn list_items (path: &Path, base: &Path, mut items: &mut Vec<String>) -> std::io::Result<()>{
|
||||
for entry in std::fs::read_dir(path)? {
|
||||
let path = entry?.path();
|
||||
if path.is_dir(){
|
||||
list_items(&path, &base, &mut items)?;
|
||||
} else {
|
||||
if let Ok(path) = path.strip_prefix(base) {
|
||||
let path = path.to_string_lossy().trim_end_matches(".ron").replace('/', ".");
|
||||
items.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
if list_items(&base, &ASSETS_PATH, &mut items).is_err() {
|
||||
warn!("There was a problem listing some item assets");
|
||||
}
|
||||
items.sort();
|
||||
items
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Remove this function. It's only used in world/ in a really ugly way.To
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::{assets, comp, npc, state::State};
|
||||
use crate::{assets, comp, npc};
|
||||
use lazy_static::lazy_static;
|
||||
use specs::prelude::{Join, WorldExt};
|
||||
use std::{ops::Deref, str::FromStr};
|
||||
|
||||
/// Struct representing a command that a user can run from server chat.
|
||||
@ -110,23 +109,6 @@ lazy_static! {
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
}
|
||||
fn items() -> Vec<String> {
|
||||
if let Ok(assets) = assets::ASSETS.read() {
|
||||
assets
|
||||
.iter()
|
||||
.flat_map(|(k, v)| {
|
||||
if v.is::<comp::item::Item>() {
|
||||
Some(k.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
error!("Assets not found");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatCommand {
|
||||
pub fn data(&self) -> ChatCommandData {
|
||||
@ -143,10 +125,7 @@ impl ChatCommand {
|
||||
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", true),
|
||||
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", true),
|
||||
ChatCommand::DebugColumn => cmd(
|
||||
vec![
|
||||
Integer("x", 15000, Required),
|
||||
Integer("y", 15000, Required),
|
||||
],
|
||||
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
||||
"Prints some debug information about a column",
|
||||
false,
|
||||
),
|
||||
@ -161,7 +140,10 @@ impl ChatCommand {
|
||||
true,
|
||||
),
|
||||
ChatCommand::GiveItem => cmd(
|
||||
vec![Enum("item", items(), Required), Integer("num", 1, Optional)],
|
||||
vec![
|
||||
Enum("item", assets::ITEM_SPECS.clone(), Required),
|
||||
Integer("num", 1, Optional),
|
||||
],
|
||||
"Give yourself some items",
|
||||
true,
|
||||
),
|
||||
@ -444,123 +426,4 @@ impl ArgumentSpec {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn complete(&self, part: &str, state: &State) -> Vec<String> {
|
||||
match self {
|
||||
ArgumentSpec::PlayerName(_) => complete_player(part, &state),
|
||||
ArgumentSpec::Float(_, x, _) => {
|
||||
if part.is_empty() {
|
||||
vec![format!("{:.1}", x)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
ArgumentSpec::Integer(_, x, _) => {
|
||||
if part.is_empty() {
|
||||
vec![format!("{}", x)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
ArgumentSpec::Any(_, _) => vec![],
|
||||
ArgumentSpec::Command(_) => complete_command(part),
|
||||
ArgumentSpec::Message => complete_player(part, &state),
|
||||
ArgumentSpec::SubCommand => complete_command(part),
|
||||
ArgumentSpec::Enum(_, strings, _) => strings
|
||||
.iter()
|
||||
.filter(|string| string.starts_with(part))
|
||||
.map(|c| c.to_string())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_player(part: &str, state: &State) -> Vec<String> {
|
||||
let storage = state.ecs().read_storage::<comp::Player>();
|
||||
let mut iter = storage.join();
|
||||
if let Some(first) = iter.next() {
|
||||
std::iter::once(first)
|
||||
.chain(iter)
|
||||
.filter(|player| player.alias.starts_with(part))
|
||||
.map(|player| player.alias.clone())
|
||||
.collect()
|
||||
} else {
|
||||
vec!["singleplayer".to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_command(part: &str) -> Vec<String> {
|
||||
CHAT_COMMANDS
|
||||
.iter()
|
||||
.map(|com| com.keyword())
|
||||
.filter(|kwd| kwd.starts_with(part) || format!("/{}", kwd).starts_with(part))
|
||||
.map(|c| format!("/{}", c))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Get the byte index of the nth word. Used in completing "/sudo p /subcmd"
|
||||
fn nth_word(line: &str, n: usize) -> Option<usize> {
|
||||
let mut is_space = false;
|
||||
let mut j = 0;
|
||||
for (i, c) in line.char_indices() {
|
||||
match (is_space, c.is_whitespace()) {
|
||||
(true, true) => {},
|
||||
(true, false) => {
|
||||
is_space = false;
|
||||
j += 1;
|
||||
},
|
||||
(false, true) => {
|
||||
is_space = true;
|
||||
},
|
||||
(false, false) => {},
|
||||
}
|
||||
if j == n {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn complete(line: &str, state: &State) -> Vec<String> {
|
||||
let word = if line.chars().last().map_or(true, char::is_whitespace) {
|
||||
""
|
||||
} else {
|
||||
line.split_whitespace().last().unwrap_or("")
|
||||
};
|
||||
if line.chars().next() == Some('/') {
|
||||
let mut iter = line.split_whitespace();
|
||||
let cmd = iter.next().unwrap();
|
||||
let i = iter.count() + if word.is_empty() { 1 } else { 0 };
|
||||
if i == 0 {
|
||||
// Completing chat command name
|
||||
complete_command(word)
|
||||
} else {
|
||||
if let Ok(cmd) = cmd.parse::<ChatCommand>() {
|
||||
if let Some(arg) = cmd.data().args.get(i - 1) {
|
||||
// Complete ith argument
|
||||
arg.complete(word, &state)
|
||||
} else {
|
||||
// Complete past the last argument
|
||||
match cmd.data().args.last() {
|
||||
Some(ArgumentSpec::SubCommand) => {
|
||||
if let Some(index) = nth_word(line, cmd.data().args.len()) {
|
||||
complete(&line[index..], &state)
|
||||
} else {
|
||||
error!("Could not tab-complete SubCommand");
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
Some(ArgumentSpec::Message) => complete_player(word, &state),
|
||||
_ => vec![], // End of command. Nothing to complete
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Completing for unknown chat command
|
||||
complete_player(word, &state)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not completing a command
|
||||
complete_player(word, &state)
|
||||
}
|
||||
}
|
||||
|
@ -724,7 +724,6 @@ fn handle_lantern(
|
||||
args: String,
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
println!("args: '{}', fmt: '{}'", &args, &action.arg_fmt());
|
||||
if let (Some(s), r, g, b) = scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32) {
|
||||
if let Some(light) = server
|
||||
.state
|
||||
|
@ -3,7 +3,7 @@ use super::{
|
||||
META_COLOR, PRIVATE_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR,
|
||||
};
|
||||
use crate::{ui::fonts::ConrodVoxygenFonts, GlobalState};
|
||||
use client::Event as ClientEvent;
|
||||
use client::{cmd, Client, Event as ClientEvent};
|
||||
use common::{msg::validate_chat_msg, ChatType};
|
||||
use conrod_core::{
|
||||
input::Key,
|
||||
@ -68,9 +68,9 @@ impl<'a> Chat<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_tab_completion(mut self, input: String, state: &common::state::State) -> Self {
|
||||
pub fn prepare_tab_completion(mut self, input: String, client: &Client) -> Self {
|
||||
if let Some(index) = input.find('\t') {
|
||||
self.force_completions = Some(common::cmd::complete(&input[..index], &state));
|
||||
self.force_completions = Some(cmd::complete(&input[..index], &client));
|
||||
} else {
|
||||
self.force_completions = None;
|
||||
}
|
||||
|
@ -1727,7 +1727,7 @@ impl Hud {
|
||||
)
|
||||
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
|
||||
.and_then(self.tab_complete.take(), |c, input| {
|
||||
c.prepare_tab_completion(input, &client.state())
|
||||
c.prepare_tab_completion(input, &client)
|
||||
})
|
||||
.and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos))
|
||||
.set(self.ids.chat, ui_widgets)
|
||||
|
Loading…
Reference in New Issue
Block a user