#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]
#![deny(trivial_casts)]
use std::cmp;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::path::Path;
mod draw;
use draw::draw;
use draw::get_absolute_draw_indices;
mod find;
use find::FindOptSubset;
extern crate ncurses;
use ncurses::*;
extern crate getopts;
use getopts::Options;
extern crate pest;
#[macro_use]
extern crate pest_derive;
use pest::Parser;
#[derive(Parser)]
#[grammar = "cmd.pest"]
struct IdentParser;
extern crate memmem;
use memmem::{Searcher, TwoWaySearcher};
#[derive(PartialEq, Copy, Clone)]
pub enum CursorSelects {
LeftNibble,
RightNibble,
AsciiChar,
}
#[derive(Copy, Clone)]
pub struct CursorState {
pos: usize,
sel: CursorSelects,
}
fn print_usage(program: &str, opts: Options) {
let brief = format!("Usage: {} FILENAME [options]", program);
print!("{}", opts.usage(&brief));
}
fn main() {
let mut buf = vec![];
let mut cursor = CursorState {
pos: 0,
sel: CursorSelects::LeftNibble,
};
let mut screenoffset: usize = 0;
const COLS: usize = 16;
let mut command = String::new();
let mut lastcommand = String::new();
let mut autoparse = String::new();
let mut infoline = String::new();
initscr();
let screenheight = getmaxy(stdscr()) as usize;
cbreak();
noecho();
start_color();
use_default_colors();
init_pair(1, COLOR_GREEN, -1);
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts.optflag("h", "help", "print this help menu");
opts.optflag("v", "version", "print the version");
let arg_matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
endwin();
println!("{}", f.to_string());
print_usage(&program, opts);
return;
}
};
if arg_matches.opt_present("h") {
endwin();
print_usage(&program, opts);
return;
}
if arg_matches.opt_present("v") {
endwin();
println!("Name: {}", env!("CARGO_PKG_NAME"));
println!("Version: {}", env!("CARGO_PKG_VERSION"));
println!("Repository: {}", env!("CARGO_PKG_REPOSITORY"));
return;
}
if !has_colors() {
endwin();
println!("Your terminal does not support color!\n");
return;
}
let arg_filename = match arg_matches.free.is_empty() {
true => String::new(),
false => arg_matches.free[0].clone(),
};
if arg_filename.is_empty() {
endwin();
println!("FILENAME is empty!\n");
print_usage(&program, opts);
return;
}
let path = Path::new(&arg_filename);
let mut file = match OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
{
Err(why) => {
endwin();
println!("Could not open {}: {}", path.display(), why.to_string());
return;
}
Ok(file) => file,
};
file.read_to_end(&mut buf)
.ok()
.expect("File could not be read.");
let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
draw(
&buf[draw_range.0..draw_range.1],
COLS,
&command,
&mut infoline,
cursor,
screenoffset,
);
let mut quitnow = false;
while quitnow == false {
if autoparse.is_empty() {
let key = std::char::from_u32(getch() as u32).unwrap();
command.push_str(&key.clone().to_string());
} else {
command.push(autoparse.chars().nth(0).unwrap());
autoparse.remove(0);
}
let parsethisstring = command.clone();
let commands = IdentParser::parse(Rule::cmd_list, &parsethisstring)
.unwrap_or_else(|e| panic!("{}", e));
let mut clear = true;
let mut save = false;
for cmd in commands {
match cmd.as_rule() {
Rule::down => {
if cursor.pos + COLS < buf.len() {
cursor.pos += COLS;
} else {
cursor.pos = buf.len().saturating_sub(1);
}
}
Rule::up => {
if cursor.pos >= COLS {
cursor.pos -= COLS;
}
}
Rule::left => {
if cursor.sel == CursorSelects::AsciiChar {
if cursor.pos > 0 {
cursor.pos -= 1;
}
} else if cursor.sel == CursorSelects::RightNibble {
cursor.sel = CursorSelects::LeftNibble;
} else if cursor.sel == CursorSelects::LeftNibble {
if cursor.pos > 0 {
cursor.sel = CursorSelects::RightNibble;
cursor.pos -= 1;
}
}
}
Rule::right => {
if cursor.sel == CursorSelects::AsciiChar {
if cursor.pos + 1 < buf.len() {
cursor.pos += 1;
}
} else if cursor.sel == CursorSelects::LeftNibble {
cursor.sel = CursorSelects::RightNibble;
} else if cursor.sel == CursorSelects::RightNibble {
if cursor.pos + 1 < buf.len() {
cursor.sel = CursorSelects::LeftNibble;
cursor.pos += 1;
}
}
}
Rule::start => {
cursor.pos -= cursor.pos % COLS; if cursor.sel == CursorSelects::RightNibble {
cursor.sel = CursorSelects::LeftNibble;
}
}
Rule::end => {
if cursor.pos - (cursor.pos % COLS) + (COLS - 1) < buf.len() {
cursor.pos = cursor.pos - (cursor.pos % COLS) + (COLS - 1);
} else {
cursor.pos = buf.len().saturating_sub(1);
}
if cursor.sel == CursorSelects::LeftNibble {
cursor.sel = CursorSelects::RightNibble;
}
}
Rule::bottom => {
cursor.pos = buf.len().saturating_sub(1);
cursor.pos -= cursor.pos % COLS; }
Rule::replace => {
clear = false;
}
Rule::replacement => {
let key = command.chars().last().unwrap_or('x');
if cursor.sel == CursorSelects::AsciiChar {
if cursor.pos >= buf.len() {
buf.insert(cursor.pos, 0);
}
buf[cursor.pos] = key as u8;
} else {
let mask = if cursor.sel == CursorSelects::LeftNibble {
0x0F
} else {
0xF0
};
let shift = if cursor.sel == CursorSelects::LeftNibble {
4
} else {
0
};
if cursor.pos >= buf.len() {
buf.insert(cursor.pos, 0);
}
if let Some(c) = key.to_digit(16) {
buf[cursor.pos] = buf[cursor.pos] & mask | (c as u8) << shift;
}
}
lastcommand = command.clone();
clear = true;
}
Rule::replaceend => {
clear = true;
}
Rule::remove => {
if buf.len() > 0 && cursor.pos < buf.len() {
buf.remove(cursor.pos);
}
if cursor.pos >= buf.len() {
cursor.pos = cursor.pos.saturating_sub(1);
}
lastcommand = command.clone();
}
Rule::dd => {
let amount: usize = cmd.as_str().parse().unwrap_or(1);
if cursor.pos < buf.len() {
let startofline = cursor.pos - cursor.pos % COLS;
let mut endofline = cursor.pos - (cursor.pos % COLS) + (COLS * amount);
endofline = cmp::min(endofline, buf.len());
buf.drain(startofline..endofline);
if cursor.pos >= buf.len() {
cursor.pos = buf.len().saturating_sub(1);
}
}
lastcommand = command.clone();
clear = true;
}
Rule::insert => {
clear = false;
}
Rule::insertstuff => {
let key = command.chars().last().unwrap_or('x');
if cursor.sel == CursorSelects::LeftNibble {
if let Some(c) = key.to_digit(16) {
buf.insert(cursor.pos, (c as u8) << 4);
cursor.sel = CursorSelects::RightNibble;
}
} else if cursor.sel == CursorSelects::RightNibble {
if cursor.pos == buf.len() {
buf.insert(cursor.pos, 0);
}
if let Some(c) = key.to_digit(16) {
buf[cursor.pos] = buf[cursor.pos] & 0xF0 | c as u8;
cursor.sel = CursorSelects::LeftNibble;
cursor.pos += 1;
}
} else if cursor.sel == CursorSelects::AsciiChar {
buf.insert(cursor.pos, key as u8);
cursor.pos += 1;
}
clear = false;
}
Rule::insertend => {
lastcommand = command.clone();
clear = true;
}
Rule::jumpascii => {
if cursor.sel == CursorSelects::AsciiChar {
cursor.sel = CursorSelects::LeftNibble;
} else {
cursor.sel = CursorSelects::AsciiChar;
}
}
Rule::helpfile => {
command.pop();
command.push_str("No helpfile yet");
clear = false;
}
Rule::repeat => {
autoparse = lastcommand.clone();
}
Rule::gg => {
let linenr: usize = cmd.as_str().parse().unwrap_or(0);
cursor.pos = linenr * COLS; if cursor.pos > buf.len() {
cursor.pos = buf.len();
}
cursor.pos -= cursor.pos % COLS; clear = true;
}
Rule::backspace => {
command.pop();
command.pop();
clear = false;
}
Rule::saveandexit => {
save = true;
quitnow = true;
}
Rule::exit => quitnow = true,
Rule::save => save = true,
Rule::escape => (),
_ => (),
}
for inner_cmd in cmd.into_inner() {
match inner_cmd.as_rule() {
Rule::searchstr => {
let search = inner_cmd.as_str().as_bytes();
let foundpos = TwoWaySearcher::new(&search);
cursor.pos = foundpos.search_in(&buf).unwrap_or(cursor.pos);
}
Rule::searchbytes => {
let search = inner_cmd.as_str().as_bytes();
let mut needle = vec![];
for i in 0..search.len() {
let nibble = match search[i] as u8 {
c @ 48..=57 => c - 48, b'x' => 0x10, b'X' => 0x10, c @ b'a'..=b'f' => c - 87,
c @ b'A'..=b'F' => c - 55,
_ => panic!("Should not get to this position!"),
};
needle.push(nibble);
}
cursor.pos = buf.find_subset(&needle).unwrap_or(cursor.pos);
}
Rule::gatherone => clear = false,
_ => {
command.push_str(&format!("no rule for {:?} ", inner_cmd.as_rule()));
clear = false;
}
};
}
if save {
if path.exists() {
let mut file = match OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
{
Err(why) => {
panic!("Could not open {}: {}", path.display(), why.to_string())
}
Ok(file) => file,
};
file.seek(SeekFrom::Start(0))
.ok()
.expect("Filepointer could not be set to 0");
file.write_all(&mut buf)
.ok()
.expect("File could not be written.");
file.set_len(buf.len() as u64)
.ok()
.expect("File could not be set to correct lenght.");
command.push_str("File saved!");
} else {
command.push_str("Careful, file could not be saved!");
}
save = false;
}
if clear {
command.clear();
}
if cursor.pos > (screenheight + screenoffset - 1) * COLS - 1 {
screenoffset = 2 + cursor.pos / COLS - screenheight;
}
if cursor.pos < screenoffset * COLS {
screenoffset = cursor.pos / COLS;
}
}
let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
draw(
&buf[draw_range.0..draw_range.1],
COLS,
&command,
&mut infoline,
cursor,
screenoffset,
);
}
refresh();
endwin();
}