From 4fe34429eeeb6d06ba68d084da37474f61a6ff63 Mon Sep 17 00:00:00 2001 From: Andy Wortman Date: Sun, 9 Jul 2017 03:25:57 -0700 Subject: rewrite stuff to handle arrow key input, wip buffering/windowing for render --- Cargo.toml | 2 +- main.rs | 262 +++++++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 204 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 673a8a6..ea084a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,5 @@ test = false bench = false [dependencies] -"terminal_size" = "0.1.7" "termios" = "0.2.2" +"termion" = "1.4.0" diff --git a/main.rs b/main.rs index fa22374..1b935c8 100644 --- a/main.rs +++ b/main.rs @@ -1,28 +1,34 @@ +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; +//use std::path::Path; extern crate termios; use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr}; -extern crate terminal_size; -use terminal_size::{Width, Height, terminal_size}; +extern crate termion; +use termion::{color, input}; +use termion::input::TermRead; +use termion::event::{Event, Key}; fn main() { println!("hello"); - if let Some((Width(w), Height(h))) = terminal_size() { - if let Some(arg1) = env::args().nth(1) { - println!("You would like to work with {}.", arg1); - launch_interface(w, h, &arg1); - } else { - usage(); + 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); } - } else { - println!("Wasn't able to get terminal dimensions for some reason"); } } @@ -30,80 +36,215 @@ fn usage() { println!("usage: this_thing filename"); } -fn launch_interface(w: u16, h: u16, filename: &String) { - println!("Launching interface with width {} and height {}...", w, h); +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); + } + } +} - // 4 = columns per group, 3 = xx xx xx xx , ... - // + (x + 7) / 8 is for ascii at the end of line, rounded up to 8 characters - // width = 8-20 + (3 * 4) * x + (x + 7) / 8 - // 8 * width = 87 + 57x - // x = (8 * width - 87) / 57 - let hex_cols = (w * 8 - 87) / 57; - println!("0x________: {} {}", hex_cols, hex_cols); +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); - tcsetattr(0, TCSANOW, &mut new_termios).unwrap(); - interface_loop(w, h, filename); + 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, &termios).unwrap(); + tcsetattr(0, TCSANOW, &state.old_term).unwrap(); }, Err(e) => - println!("{} does not exist.", filename), + println!("{} does not exist. ({})", filename, e), } } -fn interface_loop(w: u16, h: u16, filename: &String) { - let mut cursor: u64 = 0; - let mut buffer = [0; 0x200]; - populate_buf(filename, cursor, &mut buffer); - render_interface(w, h, cursor, &buffer); +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 byte in stdin.lock().bytes() { - match byte { - Ok(value) => { - render_interface(w, h, cursor, &buffer); - }, - Err(e) => { - println!("Err: {}", e); +// 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 } + 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(w: u16, h: u16, cursor: u64, buffer: &[u8]) { - println!("Dimensions: {}x{} Cursor: {} cursor_pos: ({}, {})", w, h, cursor, 0, 0); - let addr_width = 8; - // w = 10 + 1 + 1 + 4 * 3 * x + x + 1 + 4 * x - // w = 13 + 17 * x - // w - 13 / 17 = x - let cols = (w - 13) / 17; - let col_width = 4; +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 mut idx: usize = 0; - for i in 1..(h-1) { + 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 col in 0..cols { - for c in 0..col_width { - if (idx < buffer.len()) { + 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(); @@ -113,7 +254,7 @@ fn render_interface(w: u16, h: u16, cursor: u64, buffer: &[u8]) { write!(&mut hex_line, " ").unwrap(); } - let lineaddr = ((i as u64) - 1) * (cols as u64 * 4); + 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 { @@ -123,18 +264,21 @@ fn render_interface(w: u16, h: u16, cursor: u64, buffer: &[u8]) { } } -fn populate_buf(arg: &String, cursor: u64, buffer: &mut [u8]) { +fn populate_buf(arg: &String, seek: u64, count: u64) -> Vec { match fs::File::open(&arg) { Ok(mut fd) => { /*..*/ - fd.seek(SeekFrom::Start(cursor)); - fd.read(buffer); - for b in buffer.into_iter() { - print!("{:02x}", b); - } - println!(""); + fd.seek(SeekFrom::Start(seek)).unwrap(); + fd + .bytes() + .take(count as usize) + .map(|r| r.unwrap()) // lazy + .collect() }, Err(_) => - println!("Failed to open {}", arg) + { + println!("Failed to open {}", arg); + panic!(); + } } } -- cgit v1.1