chore: replaced rustyline-async with rustyline

also added console tab support
fixes https://github.com/Pumpkin-MC/Pumpkin/issues/429
This commit is contained in:
Alexander Medvedev 2026-01-17 21:59:51 +01:00
parent 3fbefafe4d
commit 49490237d4
10 changed files with 465 additions and 241 deletions

297
Cargo.lock generated
View File

@ -49,15 +49,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "ansi-width"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219e3ce6f2611d83b51ec2098a12702112c29e57203a6b0a0929b2cddb486608"
dependencies = [
"unicode-width",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -103,7 +94,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@ -114,7 +105,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@ -281,9 +272,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.52"
version = "1.2.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
dependencies = [
"find-msvc-tools",
"shlex",
@ -310,6 +301,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chacha20"
version = "0.10.0-rc.6"
@ -383,6 +380,21 @@ version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
[[package]]
name = "clipboard-win"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
[[package]]
name = "cmov"
version = "0.5.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96"
[[package]]
name = "colorchoice"
version = "1.0.4"
@ -395,7 +407,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -466,15 +478,6 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cookie"
version = "0.18.1"
@ -635,34 +638,6 @@ dependencies = [
"parking_lot",
]
[[package]]
name = "crossterm"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags",
"crossterm_winapi",
"derive_more",
"document-features",
"futures-core",
"mio",
"parking_lot",
"rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crunchy"
version = "0.2.4"
@ -683,14 +658,14 @@ dependencies = [
[[package]]
name = "crypto-bigint"
version = "0.7.0-rc.10"
version = "0.7.0-rc.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6715836b4946e8585016e80b79c7561476aff3b22f7b756778e7b109d86086c6"
checksum = "37387ceb32048ff590f2cbd24d8b05fffe63c3f69a5cfa089d4f722ca4385a19"
dependencies = [
"ctutils",
"num-traits",
"rand_core 0.10.0-rc-3",
"serdect",
"subtle",
"zeroize",
]
@ -715,15 +690,24 @@ dependencies = [
[[package]]
name = "crypto-primes"
version = "0.7.0-pre.4"
version = "0.7.0-pre.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd9b2855017318a49714c07ee8895b89d3510d54fa6d86be5835de74c389609"
checksum = "e79c98a281f9441200b24e3151407a629bfbe720399186e50516da939195e482"
dependencies = [
"crypto-bigint 0.7.0-rc.10",
"crypto-bigint 0.7.0-rc.18",
"libm",
"rand_core 0.10.0-rc-3",
]
[[package]]
name = "ctutils"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758e5ed90be3c8abff7f9a6f37ab7f6d8c59c2210d448b81f3f508134aec84e4"
dependencies = [
"cmov",
]
[[package]]
name = "dashmap"
version = "6.1.0"
@ -769,28 +753,6 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -876,6 +838,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enum_dispatch"
version = "0.3.13"
@ -924,15 +892,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
name = "error-code"
version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fd-lock"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "ff"
version = "0.13.1"
@ -945,9 +930,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
[[package]]
name = "flate2"
@ -1226,6 +1211,15 @@ dependencies = [
"digest 0.11.0-rc.5",
]
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "http"
version = "1.4.0"
@ -1673,11 +1667,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -2115,7 +2129,7 @@ dependencies = [
"rand",
"rsa",
"rustc-hash",
"rustyline-async",
"rustyline",
"serde",
"serde_json",
"sha1",
@ -2309,6 +2323,16 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.10.0-rc.6"
@ -2418,12 +2442,12 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.10.0-rc.10"
version = "0.10.0-rc.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e499c52862d75a86c0024cc99dcb6d7127d15af3beae7b03573d62fab7ade08a"
checksum = "c9a2b1eacbc34fbaf77f6f1db1385518446008d49b9f9f59dc9d1340fce4ca9e"
dependencies = [
"const-oid 0.10.2",
"crypto-bigint 0.7.0-rc.10",
"crypto-bigint 0.7.0-rc.18",
"crypto-primes",
"digest 0.11.0-rc.5",
"pkcs1",
@ -2432,7 +2456,6 @@ dependencies = [
"sha1",
"signature 3.0.0-rc.6",
"spki 0.8.0-rc.4",
"subtle",
"zeroize",
]
@ -2442,15 +2465,6 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.3"
@ -2461,7 +2475,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -2481,18 +2495,18 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.13.2"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.8"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"ring",
"rustls-pki-types",
@ -2506,18 +2520,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rustyline-async"
version = "0.4.7"
name = "rustyline"
version = "17.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e07ddce8399c61495b405dc94d4f30d01fc1c5e1238f10b9c09940678bc81ab"
checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564"
dependencies = [
"ansi-width",
"crossterm",
"futures-util",
"pin-project",
"thingbuf",
"thiserror",
"bitflags",
"cfg-if",
"clipboard-win",
"fd-lock",
"home",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"windows-sys 0.60.2",
]
[[package]]
@ -2558,12 +2579,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
@ -2685,27 +2700,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
@ -2884,7 +2878,7 @@ dependencies = [
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -2896,16 +2890,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "thingbuf"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "662b54ef6f7b4e71f683dadc787bbb2d8e8ef2f91b682ebed3164a5a7abca905"
dependencies = [
"parking_lot",
"pin-project",
]
[[package]]
name = "thiserror"
version = "2.0.17"
@ -3242,9 +3226,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.1.14"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "untrusted"
@ -3449,7 +3433,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -3473,6 +3457,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"

View File

@ -91,9 +91,9 @@ pumpkin-util = { path = "pumpkin-util" }
pumpkin-world = { path = "pumpkin-world" }
quote = "1.0"
rand = { git = "https://github.com/rust-random/rand"}
rsa = "=0.10.0-rc.10"
rsa = "=0.10.0-rc.12"
rustc-hash = "2.1.1"
rustyline-async = "0.4.7"
rustyline = "17.0.2"
ruzstd = "0.8.2"
serde_json5 = "0.2.1"
sha1 = "=0.11.0-rc.3"

View File

@ -54,7 +54,7 @@ rand.workspace = true
num-bigint.workspace = true
# Console line reading
rustyline-async.workspace = true
rustyline.workspace = true
# encryption
rsa = { workspace = true, features = ["sha1"] }

View File

@ -109,28 +109,31 @@ impl CommandDispatcher {
.await
{
Err(InvalidConsumption(s)) => {
log::trace!(
log::debug!(
"Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"
);
return Vec::new();
}
Err(InvalidRequirement) => {
log::trace!(
log::debug!(
"Error while parsing command \"{cmd}\": a requirement that was expected was not met."
);
return Vec::new();
}
Err(PermissionDenied) => {
log::trace!("Permission denied for command \"{cmd}\"");
log::debug!("Permission denied for command \"{cmd}\"");
return Vec::new();
}
Err(CommandFailed(_)) => {
log::debug!("Command failed");
return Vec::new();
}
Ok(Some(new_suggestions)) => {
suggestions.extend(new_suggestions);
}
Ok(None) => {}
Ok(None) => {
log::debug!("Command none");
}
}
}

View File

@ -121,14 +121,13 @@ impl Goal for MeleeAttackGoal {
Box::pin(async {
let mut target = mob.get_mob_entity().target.lock().await;
if let Some(entity) = target.as_deref() {
if !EntityPredicate::ExceptCreativeOrSpectator
if let Some(entity) = target.as_deref()
&& !EntityPredicate::ExceptCreativeOrSpectator
.test(entity.get_entity())
.await
{
*target = None;
}
}
let mut navigator = mob.get_mob_entity().navigator.lock().await;
navigator.cancel();

View File

@ -54,7 +54,6 @@ impl CreeperEntity {
}
pub async fn set_fuse_speed(&self, speed: i32) {
dbg!(speed);
self.fuse_speed.store(speed, Ordering::Relaxed);
self.mob_entity
.living_entity
@ -64,7 +63,7 @@ impl CreeperEntity {
MetaDataType::Integer,
speed,
)])
.await
.await;
}
}

View File

@ -1,4 +1,4 @@
use std::sync::{Arc};
use std::sync::Arc;
use pumpkin_data::entity::EntityType;

View File

@ -70,7 +70,7 @@ impl MobEntity {
}
pub async fn set_attacking(&self, attacking: bool) {
self.set_mob_flag(Self::ATTACKING_FLAG, attacking).await
self.set_mob_flag(Self::ATTACKING_FLAG, attacking).await;
}
async fn set_mob_flag(&self, flag: u8, value: bool) {

View File

@ -1,7 +1,7 @@
// Not warn event sending macros
#![allow(unused_labels)]
use crate::logging::{GzipRollingLogger, ReadlineLogWrapper};
use crate::logging::{GzipRollingLogger, PumpkinCommandCompleter, ReadlineLogWrapper};
use crate::net::DisconnectReason;
use crate::net::bedrock::BedrockClient;
use crate::net::java::JavaClient;
@ -16,7 +16,9 @@ use pumpkin_macros::send_cancellable;
use pumpkin_protocol::ConnectionState::Play;
use pumpkin_util::permission::{PermissionManager, PermissionRegistry};
use pumpkin_util::text::TextComponent;
use rustyline_async::{Readline, ReadlineEvent};
use rustyline::Editor;
use rustyline::history::FileHistory;
use rustyline::{Config, error::ReadlineError};
use std::collections::HashMap;
use std::io::{Cursor, IsTerminal, stdin};
use std::str::FromStr;
@ -79,7 +81,6 @@ pub fn init_logger(advanced_config: &AdvancedConfiguration) {
config.set_level_color(level, None);
}
} else {
// We are technically logging to a file-like object.
config.set_write_log_enable_colors(true);
}
@ -117,11 +118,25 @@ pub fn init_logger(advanced_config: &AdvancedConfiguration) {
)
};
let (logger, rl): (Box<dyn SharedLogger + 'static>, _) = if advanced_config.commands.use_tty
&& stdin().is_terminal()
{
match Readline::new("$ ".to_owned()) {
Ok((rl, stdout)) => (WriteLogger::new(level, config.build(), stdout), Some(rl)),
let (logger, rl): (
Box<dyn SharedLogger + 'static>,
Option<Editor<PumpkinCommandCompleter, FileHistory>>,
) = if advanced_config.commands.use_tty && stdin().is_terminal() {
let rl_config = Config::builder()
.auto_add_history(true)
.completion_type(rustyline::CompletionType::List)
.edit_mode(rustyline::EditMode::Emacs)
.build();
let helper = PumpkinCommandCompleter::new();
match Editor::with_config(rl_config) {
Ok(mut rl) => {
rl.set_helper(Some(helper));
(
WriteLogger::new(level, config.build(), std::io::stdout()),
Some(rl),
)
}
Err(e) => {
log::warn!(
"Failed to initialize console input ({e}); falling back to simple logger"
@ -330,7 +345,6 @@ impl PumpkinServer {
log::info!("Completed save!");
// Explicitly drop the line reader to return the terminal to the original state.
if let Some((wrapper, _)) = LOGGER_IMPL.wait()
&& let Some(rl) = wrapper.take_readline()
{
@ -499,23 +513,58 @@ async fn setup_stdin_console(server: Arc<Server>) {
});
}
fn setup_console(rl: Readline, server: Arc<Server>) {
// This needs to be async, or it will hog a thread.
server.clone().spawn_task(async move {
let mut rl = rl;
fn setup_console(mut rl: Editor<PumpkinCommandCompleter, FileHistory>, server: Arc<Server>) {
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
if let Some(helper) = rl.helper_mut() {
if let Ok(mut server_lock) = helper.server.write() {
*server_lock = Some(server.clone());
}
let _ = helper.rt.set(tokio::runtime::Handle::current());
}
std::thread::spawn(move || {
while !SHOULD_STOP.load(Ordering::Relaxed) {
let t1 = rl.readline();
let readline = rl.readline("$ ");
match readline {
Ok(line) => {
let _ = rl.add_history_entry(line.clone());
if tx.blocking_send(line).is_err() {
break;
}
}
Err(ReadlineError::Interrupted) => {
log::info!("CTRL-C");
stop_server();
break;
}
Err(ReadlineError::Eof) => {
log::info!("CTRL-D");
stop_server();
break;
}
Err(err) => {
log::error!("Error reading console input: {err}");
break;
}
}
}
if let Some((wrapper, _)) = LOGGER_IMPL.wait() {
wrapper.return_readline(rl);
}
});
server.clone().spawn_task(async move {
while !SHOULD_STOP.load(Ordering::Relaxed) {
let t1 = rx.recv();
let t2 = STOP_INTERRUPT.notified();
let result = select! {
line = t1 => Some(line),
line = t1 => line,
() = t2 => None,
};
let Some(result) = result else { break };
match result {
Ok(ReadlineEvent::Line(line)) => {
if let Some(line) = result {
send_cancellable! {{
ServerCommandEvent::new(line.clone());
@ -525,25 +574,12 @@ fn setup_console(rl: Readline, server: Arc<Server>) {
dispatcher
.handle_command(&command::CommandSender::Console, &server, &line)
.await;
rl.add_history_entry(line).unwrap();
}
}}
}
Ok(ReadlineEvent::Interrupted) => {
stop_server();
break;
}
err => {
log::error!("Console command loop failed!");
log::error!("{err:?}");
} else {
break;
}
}
}
if let Some((wrapper, _)) = LOGGER_IMPL.wait() {
wrapper.return_readline(rl);
}
log::debug!("Stopped console commands task");
});
}

View File

@ -3,14 +3,26 @@
use flate2::write::GzEncoder;
use log::{LevelFilter, Log, Record};
use rustyline_async::Readline;
use rustyline::completion::Completer;
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
use rustyline::history::FileHistory;
use rustyline::validate::Validator;
use rustyline::{Editor, Helper};
use simplelog::{CombinedLogger, Config, SharedLogger, WriteLogger};
use std::borrow::Cow;
use std::collections::VecDeque;
use std::fmt::format;
use std::fs::File;
use std::io::{self, BufWriter};
use std::path::PathBuf;
use std::sync::Arc;
use time::{Duration, OffsetDateTime, UtcOffset};
use crate::command::CommandSender;
use crate::command::tree::NodeType;
use crate::server::Server;
const LOG_DIR: &str = "logs";
const MAX_ATTEMPTS: u32 = 100;
@ -18,7 +30,7 @@ const MAX_ATTEMPTS: u32 = 100;
/// properly flush logs to the output while they happen instead of batched
pub struct ReadlineLogWrapper {
internal: Box<CombinedLogger>,
readline: std::sync::Mutex<Option<Readline>>,
readline: std::sync::Mutex<Option<Editor<PumpkinCommandCompleter, FileHistory>>>,
}
struct GzipRollingLoggerData {
@ -34,6 +46,20 @@ pub struct GzipRollingLogger {
config: Config,
}
impl SharedLogger for GzipRollingLogger {
fn level(&self) -> LevelFilter {
self.log_level
}
fn config(&self) -> Option<&Config> {
Some(&self.config)
}
fn as_log(self: Box<Self>) -> Box<dyn Log> {
Box::new(*self)
}
}
impl GzipRollingLogger {
pub fn new(
log_level: LevelFilter,
@ -190,26 +216,12 @@ impl Log for GzipRollingLogger {
}
}
impl SharedLogger for GzipRollingLogger {
fn level(&self) -> LevelFilter {
self.log_level
}
fn config(&self) -> Option<&Config> {
Some(&self.config)
}
fn as_log(self: Box<Self>) -> Box<dyn Log> {
Box::new(*self)
}
}
impl ReadlineLogWrapper {
#[must_use]
pub fn new(
log: Box<dyn SharedLogger + 'static>,
file_logger: Option<Box<dyn SharedLogger + 'static>>,
rl: Option<Readline>,
rl: Option<Editor<PumpkinCommandCompleter, FileHistory>>,
) -> Self {
let loggers: Vec<Option<Box<dyn SharedLogger + 'static>>> = vec![Some(log), file_logger];
Self {
@ -218,18 +230,15 @@ impl ReadlineLogWrapper {
}
}
pub fn take_readline(&self) -> Option<Readline> {
pub fn take_readline(&self) -> Option<Editor<PumpkinCommandCompleter, FileHistory>> {
self.readline
.lock()
.map_or_else(|_| None, |mut result| result.take())
}
// This isn't really dead code, just for some reason rust thinks that it might be.
// Schroedinger's dead code -> expect warns unfulfilled lint expectation but removing it causes dead_code lint?
#[allow(dead_code)]
pub(crate) fn return_readline(&self, rl: Readline) {
pub(crate) fn return_readline(&self, rl: Editor<PumpkinCommandCompleter, FileHistory>) {
if let Ok(mut result) = self.readline.lock() {
println!("Returned rl");
let _ = result.insert(rl);
}
}
@ -239,23 +248,208 @@ impl ReadlineLogWrapper {
impl Log for ReadlineLogWrapper {
fn log(&self, record: &log::Record) {
self.internal.log(record);
if let Ok(mut lock) = self.readline.lock()
&& let Some(rl) = lock.as_mut()
{
let _ = rl.flush();
}
}
fn flush(&self) {
self.internal.flush();
if let Ok(mut lock) = self.readline.lock()
&& let Some(rl) = lock.as_mut()
{
let _ = rl.flush();
}
}
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.internal.enabled(metadata)
}
}
#[derive(Clone, Default)]
pub struct PumpkinCommandCompleter {
pub server: Arc<std::sync::RwLock<Option<Arc<Server>>>>,
pub rt: Arc<std::sync::OnceLock<tokio::runtime::Handle>>,
}
impl PumpkinCommandCompleter {
#[must_use]
pub fn new() -> Self {
Self {
server: Arc::new(std::sync::RwLock::new(None)),
rt: Arc::new(std::sync::OnceLock::new()),
}
}
}
impl Helper for PumpkinCommandCompleter {}
impl Highlighter for PumpkinCommandCompleter {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
line.find(' ').map_or_else(
|| Cow::Owned(format!("\x1b[1;36m{line}\x1b[0m")),
|first_space| {
let (cmd, args) = line.split_at(first_space);
Cow::Owned(format!("\x1b[1;36m{cmd}\x1b[0m{args}"))
},
)
}
}
impl Hinter for PumpkinCommandCompleter {
type Hint = String;
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
if let Ok((_, candidates)) = self.complete(line, pos, ctx)
&& let Some(first) = candidates.first()
{
let last_word = line.split_whitespace().last().unwrap_or("");
if first.starts_with('<') {
return line.ends_with(' ').then(|| first.clone());
}
if let Some(stripped) = first.strip_prefix(last_word) {
return Some(stripped.to_string());
}
}
None
}
}
impl Validator for PumpkinCommandCompleter {}
impl Completer for PumpkinCommandCompleter {
type Candidate = String;
#[expect(clippy::too_many_lines)]
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let cmd_to_cursor = &line[..pos];
let has_slash = cmd_to_cursor.starts_with('/');
let cmd = if has_slash {
&cmd_to_cursor[1..]
} else {
cmd_to_cursor
};
let Some(handle) = self.rt.get() else {
return Ok((0, Vec::new()));
};
let Ok(server_guard) = self.server.try_read() else {
return Ok((0, Vec::new()));
};
let Some(server) = server_guard.as_ref() else {
return Ok((0, Vec::new()));
};
let parts: Vec<&str> = cmd.split_whitespace().collect();
let ends_with_space = cmd.ends_with(' ');
handle.block_on(async {
let dispatcher = server.command_dispatcher.read().await;
let src = CommandSender::Console;
if parts.is_empty() || (parts.len() == 1 && !ends_with_space) {
let typing = parts.first().unwrap_or(&"");
let candidates = dispatcher
.commands
.keys()
.filter(|k| k.starts_with(typing))
.cloned()
.collect();
return Ok((usize::from(has_slash), candidates));
}
let Some(tree) = dispatcher.get_tree(parts[0]).ok() else {
return Ok((0, Vec::new()));
};
let mut current_indices = tree.children.clone();
let mut word_index = 1;
let walk_limit = if ends_with_space {
parts.len()
} else {
parts.len() - 1
};
while word_index < walk_limit {
let token = parts[word_index];
let mut next_indices = Vec::new();
let mut worklist: VecDeque<usize> = current_indices.iter().copied().collect();
while let Some(idx) = worklist.pop_front() {
let node = &tree.nodes[idx];
match &node.node_type {
NodeType::Require { predicate } => {
if predicate(&src) {
worklist.extend(node.children.iter().copied());
}
}
NodeType::Literal { string } => {
if string.eq_ignore_ascii_case(token) {
next_indices.extend(node.children.iter().copied());
}
}
NodeType::Argument { .. } => {
next_indices.extend(node.children.iter().copied());
}
NodeType::ExecuteLeaf { .. } => {}
}
}
if next_indices.is_empty() {
return Ok((0, Vec::new()));
}
current_indices = next_indices;
word_index += 1;
}
let typing = if ends_with_space {
""
} else {
parts.last().unwrap_or(&"")
};
let mut candidates = Vec::new();
let mut suggestion_worklist: VecDeque<usize> = current_indices.into_iter().collect();
while let Some(idx) = suggestion_worklist.pop_front() {
let node = &tree.nodes[idx];
match &node.node_type {
NodeType::Require { predicate } => {
if predicate(&src) {
suggestion_worklist.extend(node.children.iter().copied());
}
}
NodeType::Literal { string } => {
if string.starts_with(typing) {
candidates.push(string.clone());
}
}
NodeType::Argument { name, consumer } => {
let suggest_future = consumer.suggest(&src, server, typing);
if let Ok(Some(suggestions)) = suggest_future.await {
for s in suggestions {
let s = s.suggestion;
if s.starts_with(typing) {
candidates.push(s);
}
}
} else {
let placeholder = format!("<{name}>");
if placeholder.starts_with(typing) || typing.is_empty() {
candidates.push(placeholder);
}
}
}
NodeType::ExecuteLeaf { executor: _ } => {}
}
}
let last_space = cmd.rfind(' ').map_or(0, |i| i + 1);
Ok((last_space + usize::from(has_slash), candidates))
})
}
}