10 changed files with 294 additions and 8 deletions
@ -1,4 +1,5 @@ |
|||||
/target |
/target |
||||
Cargo.lock |
Cargo.lock |
||||
db.sqlite* |
db.sqlite* |
||||
/games |
/games |
||||
|
output.log |
||||
@ -0,0 +1,257 @@ |
|||||
|
use std::sync::mpsc; |
||||
|
|
||||
|
use std::io::Write; |
||||
|
|
||||
|
use crossterm::{ |
||||
|
cursor::{MoveTo, MoveToNextLine}, |
||||
|
event::{Event, KeyCode, KeyModifiers, MouseEvent}, |
||||
|
queue, |
||||
|
style::Print, |
||||
|
terminal::{Clear, ClearType}, |
||||
|
}; |
||||
|
|
||||
|
pub fn setup() -> Result<(Close, Stdin<String>), fern::InitError> { |
||||
|
let (stdout, stdin, close, join_handle) = TerminalHandler::new(|x| { |
||||
|
if x == "sv_cheats" { |
||||
|
log::info!("CHEATS ENABLED") |
||||
|
} |
||||
|
x |
||||
|
}); |
||||
|
fern::Dispatch::new() |
||||
|
.format(|out, message, record| { |
||||
|
out.finish(format_args!( |
||||
|
"{}[{}][{}] {}", |
||||
|
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), |
||||
|
record.target(), |
||||
|
record.level(), |
||||
|
message |
||||
|
)) |
||||
|
}) |
||||
|
.level(log::LevelFilter::Debug) |
||||
|
.chain(stdout) |
||||
|
.chain( |
||||
|
std::fs::OpenOptions::new() |
||||
|
.write(true) |
||||
|
.create(true) |
||||
|
.truncate(true) |
||||
|
.open("output.log")?, |
||||
|
) |
||||
|
.apply()?; |
||||
|
log::info!("TEST1"); |
||||
|
log::info!("TEST2"); |
||||
|
// std::process::exit(1);
|
||||
|
Ok((Close{close, handle: join_handle}, stdin)) |
||||
|
} |
||||
|
|
||||
|
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, |
||||
|
} |
||||
|
|
||||
|
impl<T, F> TerminalHandler<T, F> |
||||
|
where |
||||
|
T: Send + 'static, |
||||
|
F: Fn(String) -> T + Send + 'static, |
||||
|
{ |
||||
|
fn new( |
||||
|
handler: F, |
||||
|
) -> ( |
||||
|
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(), |
||||
|
}; |
||||
|
loop { |
||||
|
if s.iterate() { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
queue!(stdout, crossterm::event::DisableMouseCapture).unwrap(); |
||||
|
crossterm::terminal::disable_raw_mode().ok(); |
||||
|
println!("Closing printing thread"); |
||||
|
}); |
||||
|
(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) => { |
||||
|
let mut stdout = std::io::stdout(); |
||||
|
queue!(stdout, crossterm::event::DisableMouseCapture).unwrap(); |
||||
|
crossterm::terminal::disable_raw_mode().unwrap(); |
||||
|
stdout.flush().unwrap(); |
||||
|
println!("Exiting server"); |
||||
|
std::process::exit(0) |
||||
|
} |
||||
|
(k, _m) => { |
||||
|
match k { |
||||
|
KeyCode::Char(c) => self.command.push(c), |
||||
|
KeyCode::Backspace => {self.command.pop();}, |
||||
|
KeyCode::Enter => { |
||||
|
match self.stdin.send((self.handler)(self.command.clone())) { |
||||
|
Ok(_) => (), |
||||
|
Err(e) => log::error!("{}", e), |
||||
|
}; |
||||
|
self.lines.push(format!(" >> {}", self.command)); |
||||
|
self.command = String::new(); |
||||
|
} |
||||
|
_ => () |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
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(); |
||||
|
for v in self.lines.iter() { |
||||
|
queue!(stdout, Print(v), MoveToNextLine(1)).unwrap(); |
||||
|
// println!("{}", v);
|
||||
|
} |
||||
|
let size = crossterm::terminal::size().unwrap(); |
||||
|
queue!(stdout, MoveTo(0, size.1), Clear(ClearType::CurrentLine), Print(&self.command)).unwrap(); |
||||
|
stdout.flush().unwrap(); |
||||
|
} |
||||
|
false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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(&self) -> std::slice::Iter<T> { |
||||
|
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() |
||||
|
} |
||||
|
self.inner[start..end].iter() |
||||
|
} |
||||
|
|
||||
|
fn scroll_up(&mut self) { |
||||
|
if self.pos + 1 < self.inner.len() { |
||||
|
self.pos += 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn scroll_down(&mut self) { |
||||
|
if self.pos > 0 { |
||||
|
self.pos -= 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue