You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
11 KiB
383 lines
11 KiB
use std::sync::mpsc;
|
|
|
|
use std::io::Write;
|
|
|
|
use crossterm::{
|
|
cursor::{MoveTo, MoveToNextLine},
|
|
event::{Event, KeyCode, KeyModifiers, MouseEvent},
|
|
queue,
|
|
style::Print,
|
|
terminal::{Clear, ClearType},
|
|
};
|
|
|
|
use fern::colors::{Color, ColoredLevelConfig};
|
|
|
|
mod color_message;
|
|
use color_message::print_line;
|
|
|
|
// TODO: Cleanup
|
|
|
|
pub fn setup(
|
|
properties: &crate::server_properties::ServerProperties,
|
|
) -> Result<(Close, Stdin<String>), fern::InitError> {
|
|
let (stdout, stdin, close, join_handle) = TerminalHandler::new(
|
|
|x| {
|
|
if x == "sv_cheats" {
|
|
log::info!("CHEATS ENABLED")
|
|
}
|
|
x
|
|
},
|
|
properties.use_colors,
|
|
);
|
|
|
|
// let simple_formatter = |out: fern::FormatCallback, message: _, record: &log::Record| {
|
|
// out.finish(format_args!(
|
|
// "{}[{}][{}] {}",
|
|
// chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
|
// record.level(),
|
|
// record.target(),
|
|
// message,
|
|
// ))
|
|
// };
|
|
let stdout_logger = if properties.use_colors {
|
|
colored_formatter(fern::Dispatch::new())
|
|
} else {
|
|
simple_formatter(fern::Dispatch::new())
|
|
}
|
|
.chain(stdout);
|
|
let file = simple_formatter(fern::Dispatch::new()).chain(
|
|
std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open("output.log")?,
|
|
);
|
|
|
|
fern::Dispatch::new()
|
|
.level(log::LevelFilter::Debug)
|
|
.level_for("h2::codec", log::LevelFilter::Info)
|
|
.level_for("tokio::codec", log::LevelFilter::Info)
|
|
.level_for("sqlx::query", log::LevelFilter::Info)
|
|
.chain(stdout_logger)
|
|
.chain(file)
|
|
.apply()?;
|
|
log::info!("Saving output to output.log");
|
|
// std::process::exit(1);
|
|
Ok((
|
|
Close {
|
|
close,
|
|
handle: join_handle,
|
|
},
|
|
stdin,
|
|
))
|
|
}
|
|
|
|
fn simple_formatter(d: fern::Dispatch) -> fern::Dispatch {
|
|
d.format(
|
|
|out: fern::FormatCallback, message: _, record: &log::Record| {
|
|
out.finish(format_args!(
|
|
"{}[{}][{}] {}",
|
|
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
|
record.level(),
|
|
record.target(),
|
|
message,
|
|
))
|
|
},
|
|
)
|
|
}
|
|
|
|
fn colored_formatter(d: fern::Dispatch) -> fern::Dispatch {
|
|
let colors_line = ColoredLevelConfig::new()
|
|
.error(Color::Red)
|
|
.warn(Color::Yellow)
|
|
// we actually don't need to specify the color for debug and info, they are white by default
|
|
.info(Color::White)
|
|
.debug(Color::BrightBlack)
|
|
// depending on the terminals color scheme, this is the same as the background color
|
|
.trace(Color::BrightBlack);
|
|
let colors_level = colors_line
|
|
.clone()
|
|
.info(Color::Green)
|
|
.debug(Color::BrightBlack);
|
|
d.format(
|
|
move |out: fern::FormatCallback, message: _, record: &log::Record| {
|
|
out.finish(format_args!(
|
|
"{colors_line}{}[{}{colors_line}][{}] {}\x1B[0m",
|
|
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
|
colors_level.clone().color(record.level()),
|
|
record.target(),
|
|
message,
|
|
colors_line = format!(
|
|
"\x1B[{}m",
|
|
colors_line.clone().get_color(&record.level()).to_fg_str()
|
|
),
|
|
// colors_level = colors_level.clone().get_color(&record.level()).to_fg_str(),
|
|
))
|
|
},
|
|
)
|
|
}
|
|
|
|
pub type Stdout = mpsc::Sender<String>;
|
|
pub type Stdin<T> = mpsc::Receiver<T>;
|
|
|
|
struct TerminalHandler<T, F>
|
|
where
|
|
T: Send + 'static,
|
|
F: Fn(String) -> T + Send + 'static,
|
|
{
|
|
stdout: mpsc::Receiver<String>,
|
|
stdin: mpsc::Sender<T>,
|
|
close: mpsc::Receiver<()>,
|
|
handler: F,
|
|
lines: LimitedVec<String>,
|
|
command: String,
|
|
use_colors: bool,
|
|
}
|
|
|
|
impl<T, F> TerminalHandler<T, F>
|
|
where
|
|
T: Send + 'static,
|
|
F: Fn(String) -> T + Send + 'static,
|
|
{
|
|
fn new(
|
|
handler: F,
|
|
use_colors: bool,
|
|
) -> (
|
|
Stdout,
|
|
Stdin<T>,
|
|
mpsc::Sender<()>,
|
|
std::thread::JoinHandle<()>,
|
|
) {
|
|
crossterm::terminal::enable_raw_mode().unwrap();
|
|
let mut stdout = std::io::stdout();
|
|
queue!(stdout, crossterm::event::EnableMouseCapture).unwrap();
|
|
let (stdout_send, stdout_recv) = mpsc::channel();
|
|
let (stdin_send, stdin_recv) = mpsc::channel();
|
|
let (close_send, close_recv) = mpsc::channel();
|
|
let join_handle = std::thread::spawn(move || {
|
|
let mut s = Self {
|
|
stdout: stdout_recv,
|
|
stdin: stdin_send,
|
|
close: close_recv,
|
|
handler,
|
|
lines: LimitedVec::new(crossterm::terminal::size().unwrap().1 as usize - 1),
|
|
command: String::new(),
|
|
use_colors,
|
|
};
|
|
loop {
|
|
if s.iterate() {
|
|
break;
|
|
}
|
|
}
|
|
cleanup();
|
|
});
|
|
(stdout_send, stdin_recv, close_send, join_handle)
|
|
}
|
|
|
|
fn iterate(&mut self) -> bool {
|
|
let mut updated = false;
|
|
match self.close.try_recv() {
|
|
Err(mpsc::TryRecvError::Empty) => (),
|
|
_ => return true,
|
|
}
|
|
while is_event_available() {
|
|
match crossterm::event::read().unwrap() {
|
|
Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1,
|
|
Event::Key(k) => match (k.code, k.modifiers) {
|
|
(KeyCode::Char('c'), KeyModifiers::CONTROL) => {
|
|
cleanup();
|
|
std::process::exit(0)
|
|
}
|
|
(k, _m) => match k {
|
|
KeyCode::Char(c) => self.command.push(c),
|
|
KeyCode::Backspace => {
|
|
self.command.pop();
|
|
}
|
|
KeyCode::Enter => {
|
|
log::info!(target: "command", " >> {}", self.command);
|
|
match self.stdin.send((self.handler)(self.command.clone())) {
|
|
Ok(_) => (),
|
|
Err(e) => log::error!("{}", e),
|
|
};
|
|
self.command = String::new();
|
|
}
|
|
KeyCode::Esc => {
|
|
self.lines.scroll_to_bottom();
|
|
}
|
|
_ => (),
|
|
},
|
|
},
|
|
Event::Mouse(m) => match m {
|
|
MouseEvent::ScrollUp(_, _, _) => self.lines.scroll_up(),
|
|
MouseEvent::ScrollDown(_, _, _) => self.lines.scroll_down(),
|
|
_ => (),
|
|
},
|
|
}
|
|
|
|
updated = true;
|
|
}
|
|
while let Some(x) = match self.stdout.try_recv() {
|
|
Err(mpsc::TryRecvError::Empty) => None,
|
|
Err(mpsc::TryRecvError::Disconnected) => return true,
|
|
Ok(v) => Some(v),
|
|
} {
|
|
for line in x.split('\n') {
|
|
if !line.is_empty() {
|
|
self.lines.push(line.trim_end().to_string());
|
|
}
|
|
}
|
|
|
|
updated = true;
|
|
}
|
|
let mut stdout = std::io::stdout();
|
|
// println!("Got stdout");
|
|
if updated {
|
|
queue!(stdout, Clear(ClearType::All), MoveTo(0, 0)).unwrap();
|
|
let mut i = 0;
|
|
let size = crossterm::terminal::size().unwrap();
|
|
for v in self.lines.iter(self.use_colors) {
|
|
print_line(&v, self.use_colors, &mut stdout);
|
|
queue!(stdout, MoveToNextLine(1)).unwrap();
|
|
i += 1;
|
|
if i >= size.1 as usize - 1 {
|
|
break;
|
|
}
|
|
// println!("{}", v);
|
|
}
|
|
queue!(
|
|
stdout,
|
|
MoveTo(0, size.1),
|
|
// Clear(ClearType::CurrentLine),
|
|
Print(&format!(" >> {}", self.command))
|
|
)
|
|
.unwrap();
|
|
stdout.flush().unwrap();
|
|
}
|
|
false
|
|
}
|
|
}
|
|
|
|
fn cleanup() {
|
|
let mut stdout = std::io::stdout();
|
|
queue!(
|
|
stdout,
|
|
Clear(ClearType::All),
|
|
MoveTo(0, 0),
|
|
crossterm::event::DisableMouseCapture
|
|
)
|
|
.unwrap();
|
|
crossterm::terminal::disable_raw_mode().unwrap();
|
|
stdout.flush().unwrap();
|
|
println!("Exiting server, output has been saved to output.log");
|
|
}
|
|
|
|
impl<T, F> Drop for TerminalHandler<T, F>
|
|
where
|
|
T: Send + 'static,
|
|
F: Fn(String) -> T + Send + 'static,
|
|
{
|
|
fn drop(&mut self) {
|
|
queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap();
|
|
}
|
|
}
|
|
|
|
fn is_event_available() -> bool {
|
|
crossterm::event::poll(std::time::Duration::from_millis(10))
|
|
.expect("Error polling for terminal events")
|
|
}
|
|
|
|
pub struct Close {
|
|
close: mpsc::Sender<()>,
|
|
handle: std::thread::JoinHandle<()>,
|
|
}
|
|
|
|
impl Close {
|
|
pub fn close(self) {
|
|
self.close.send(()).unwrap();
|
|
self.handle.join().unwrap();
|
|
println!("CLOSING");
|
|
queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap();
|
|
}
|
|
}
|
|
|
|
struct LimitedVec<T> {
|
|
inner: Vec<T>,
|
|
size: usize,
|
|
pos: usize,
|
|
}
|
|
|
|
impl<T> LimitedVec<T> {
|
|
fn new(size: usize) -> Self {
|
|
Self {
|
|
inner: Vec::with_capacity(size),
|
|
size,
|
|
pos: 0,
|
|
}
|
|
}
|
|
|
|
fn push(&mut self, v: T) {
|
|
// while self.inner.len() >= self.size {
|
|
// self.inner.remove(0);
|
|
// }
|
|
if self.pos != 0 {
|
|
self.pos += 1;
|
|
}
|
|
self.inner.push(v);
|
|
// assert!(self.inner.len() <= self.size)
|
|
}
|
|
|
|
fn iter<'a>(&'a self, use_colors: bool) -> Box<dyn Iterator<Item = String> + 'a>
|
|
where
|
|
T: std::fmt::Display,
|
|
{
|
|
let start = if self.inner.len() < self.size {
|
|
0
|
|
} else {
|
|
self.inner
|
|
.len()
|
|
.checked_sub(self.size)
|
|
.and_then(|x| x.checked_sub(self.pos))
|
|
.unwrap_or(0)
|
|
};
|
|
let mut end = self.inner.len().checked_sub(self.pos).unwrap_or(0);
|
|
if end < self.inner.len() {
|
|
end = self.inner.len()
|
|
}
|
|
let iter = self.inner[start..end].iter();
|
|
|
|
let map = iter.enumerate().map(move |(i, x)| {
|
|
format!(
|
|
"{}{}",
|
|
if use_colors {
|
|
format!(
|
|
"\x1B[{}m{:>5}\x1B[0m ",
|
|
fern::colors::Color::BrightBlack.to_fg_str(),
|
|
start + i
|
|
)
|
|
} else {
|
|
String::new()
|
|
},
|
|
x
|
|
)
|
|
});
|
|
Box::new(map)
|
|
}
|
|
|
|
fn scroll_up(&mut self) {
|
|
if self.pos + self.size < self.inner.len() {
|
|
// log::info!("SCROLLING UP");
|
|
self.pos += 1;
|
|
}
|
|
}
|
|
|
|
fn scroll_down(&mut self) {
|
|
if self.pos > 0 {
|
|
// log::info!("SCROLLING DOWN");
|
|
self.pos -= 1;
|
|
}
|
|
}
|
|
|
|
fn scroll_to_bottom(&mut self) {
|
|
self.pos = 0;
|
|
}
|
|
}
|
|
|