use std::cmp::{min, max}; use std::io; use std::io::{Seek, SeekFrom}; use std::fs; use std::fmt::Write; use std::io::Read; use std::env; //use std::path::Path; extern crate termios; use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr}; extern crate termion; use termion::{color, input}; use termion::input::TermRead; use termion::event::{Event, Key}; fn main() { println!("hello"); match termion::terminal_size() { Ok((w, h)) => { if let Some(arg1) = env::args().nth(1) { println!("You would like to work with {}.", arg1); launch_interface(w, h, arg1.clone()); } else { usage(); } } Err(e) => { println!("{}", e); } } } fn usage() { println!("usage: this_thing filename"); } struct Program { filename: String, filelen: u64, width: u16, row_size: u16, height: u16, seek: u64, cursor: u64, old_term: Termios, new_term: Termios, buf: Vec } impl Program { fn inc_cursor(&mut self, amount: u64) { if self.filelen - self.cursor < amount { self.cursor = self.filelen - 1; } else { self.cursor += amount; } self.recalculate_seek(); } fn dec_cursor(&mut self, amount: u64) { if self.cursor < amount { self.cursor = 0; } else { self.cursor -= amount; } self.recalculate_seek(); } fn recalculate_seek(&mut self) { let old_seek = self.seek; let bufsize = self.height as u64 * self.width as u64; let render_lim = (self.row_size as u64) * (self.height as u64 - 1); // this shouldn't be decided here... if self.cursor + render_lim > self.seek + bufsize { // know a priori that buf size is height * width. TODO: fix. println!("aaaa"); // we've sought past the end, so reset seek to what would have been the start of this // display and it'll all work out from here. self.seek = self.cursor - (self.cursor % self.row_size as u64); } else if self.cursor < self.seek { println!("bbbb"); // we've sought before the start, so reset seek to just before. ideally, populate a // buffer for like 1k before, but lazy. self.seek = self.cursor - (self.cursor % self.row_size as u64); } // repopulate buf.. if self.seek != old_seek { self.buf = populate_buf(&self.filename, self.seek, bufsize); } } } fn launch_interface(w: u16, h: u16, filename: String) { match fs::metadata(&filename) { Ok(fs_meta) => { println!("{} is {} bytes, and is it readable? {}", filename, fs_meta.len(), true); let termios = Termios::from_fd(0).unwrap(); let mut new_termios = termios.clone(); // fix terminal to not echo, thanks new_termios.c_lflag &= !(ICANON | ECHO); let initial_buf = populate_buf(&filename, 0, w as u64 * h as u64); let mut state = Program { filename: filename, filelen: fs_meta.len(), width: w, // let col_width = 4; // w = 10 + 1 + 1 + 4 * 3 * x + x + 1 + 4 * x // w = 13 + 17 * x // w - 13 / 17 = x row_size: ((w - 13) / 17) * 4, height: h, old_term: termios, new_term: new_termios, cursor: 0, seek: 0, buf: initial_buf }; tcsetattr(0, TCSANOW, &state.new_term).unwrap(); interface_loop(&mut state); tcsetattr(0, TCSANOW, &state.old_term).unwrap(); }, Err(e) => println!("{} does not exist. ({})", filename, e), } } fn interface_loop(state: &mut Program) { // let buffer = populate_buf(&state.filename, state.cursor, state.width as u64 * state.height as u64); // sw*h over-estimates by about a factor of.. 4? render_interface(state); let stdin = io::stdin(); // for input in KeyboardInput::from_stdin(&stdin.lock().bytes()) { for input in stdin.events() { match input.unwrap() { Event::Key(Key::Up) => { let amount = state.row_size; // why can't this be inlined? state.dec_cursor(amount as u64); } Event::Key(Key::Down) => { let amount = state.row_size; state.inc_cursor(amount as u64); } Event::Key(Key::Left) => { state.dec_cursor(1); } Event::Key(Key::Right) => { state.inc_cursor(1); } Event::Key(Key::PageDown) => { let amount = (state.row_size as u64) * (state.height as u64 - 1); state.inc_cursor(amount); } Event::Key(Key::PageUp) => { let amount = (state.row_size as u64) * (state.height as u64 - 1); state.dec_cursor(amount); } u => { println!("Got {:?}", u); break; } } render_interface(state); } } /* struct KeyboardInput { } impl KeyboardInput { fn from_stdin(x: &Iterator>) -> KeyboardInput { let x = KeyboardInput { }; x } } impl Iterator for KeyboardInput { type Item = KeyInfo; fn next(&mut self) -> Option { Some(0) } } */ fn ascii_or_dot(c: u8) -> u8 { let u = c as char; if u >= 'a' && u <= 'z' || u >= '0' && u <= '9' || u >= 'A' && u <= 'Z' || u == '~' || u == '!' || u == '@' || u == '#' || u == '$' || u == '%' || u == '^' || u == '&' || u == '*' || u == '(' || u == ')' || u == '_' || u == '+' || u == '{' || u == '}' || u == '|' || u == ':' || u == '"' || u == '>' || u == '?' || u == '-' || u == '=' || u == '[' || u == ']' || u == '\\' || u == ';' || u == '\'' || u == '.' || u == '/' || u == '<' || u == ',' || u == '`' { c } else { '.' as u8 } } fn render_interface(state: &Program) { println!("Dimensions: {}x{} Seek: {}, Cursor: {} - file: {}, {} bytes", state.width, state.height, state.seek, state.cursor, state.filename, state.filelen); //let addr_width = 8; // let ascii = bytes_width * cols; let buffer = &state.buf; let start = (state.cursor - state.seek) as usize; let mut idx: usize = start; let col_width = 4; for i in 1..(state.height-1) { let mut hex_line = String::new(); let mut ascii_line = String::new(); for _ in 0..(state.row_size / 4) { for _ in 0..col_width { if idx < buffer.len() { let chr = buffer[idx]; if idx as u64 + state.seek == state.cursor { write!(&mut hex_line, "\x1b[33m").unwrap(); write!(&mut ascii_line, "\x1b[33m").unwrap(); } write!(&mut hex_line, "{:02x} ", chr).unwrap(); write!(&mut ascii_line, "{}", ascii_or_dot(chr) as char).unwrap(); if idx as u64 + state.seek == state.cursor { write!(&mut hex_line, "\x1b[0m").unwrap(); write!(&mut ascii_line, "\x1b[0m").unwrap(); } } else { write!(&mut hex_line, " ").unwrap(); write!(&mut ascii_line, " ").unwrap(); } idx += 1; } write!(&mut hex_line, " ").unwrap(); } let lineaddr = start as u64 + ((i as u64) - 1) * (state.row_size as u64); let addr = if (lineaddr as usize) < buffer.len() { format!("0x{:08x}:", lineaddr) } else { " ".to_string() }; println!("{} {} {}", addr, hex_line, ascii_line); } } fn populate_buf(arg: &String, seek: u64, count: u64) -> Vec { match fs::File::open(&arg) { Ok(mut fd) => { /*..*/ fd.seek(SeekFrom::Start(seek)).unwrap(); fd .bytes() .take(count as usize) .map(|r| r.unwrap()) // lazy .collect() }, Err(_) => { println!("Failed to open {}", arg); panic!(); } } }