use crate::{app::App, util::parse_offset, widgets::MessageType};
use ratatui::{
Frame,
widgets::{Clear, Paragraph},
};
use crate::{editor::UIState, widgets::Message};
use crate::app::Dz6Error;
use clap::{Parser, Subcommand};
use ratatui::crossterm::event::{Event, KeyCode};
use std::io::Result;
use tui_input::backend::crossterm::EventHandler;
pub struct Commands;
#[derive(Subcommand, Debug)]
enum Command {
Q,
W,
Wq,
X,
Cmt {
offset: String,
comment: String,
},
Set {
option: String,
value: Option<String>,
},
Sel {
start: String,
length: String,
},
}
#[derive(Parser, Debug)]
struct CommandLine {
#[clap(subcommand)]
command: Option<Command>,
}
#[derive(Debug, PartialEq)]
enum OffsetType {
Backward,
Absolute,
Forward,
}
fn try_goto(app: &mut App, offset: &str) {
let offset_direction;
let mut new_offset = offset;
if offset.starts_with('-') {
offset_direction = OffsetType::Backward;
new_offset = &offset[1..];
} else if offset.starts_with('+') {
offset_direction = OffsetType::Forward;
new_offset = &offset[1..];
} else {
offset_direction = OffsetType::Absolute;
}
if let Ok(mut ofs) = parse_offset(&new_offset) {
if offset_direction == OffsetType::Forward {
ofs = app.hex_view.offset.saturating_add(ofs);
} else if offset_direction == OffsetType::Backward {
ofs = app.hex_view.offset.saturating_sub(ofs);
}
if ofs < app.file_info.size {
app.dialog_renderer = None;
app.state = UIState::Normal;
app.goto(ofs);
} else {
app.last_error = Dz6Error {
message: format!(
"Invalid range: {}; maximum offset for this file is {}",
offset,
app.file_info.size.saturating_sub(1)
),
};
app.dialog_renderer = Some(command_error_draw);
}
} else {
app.last_error = Dz6Error {
message: format!("Not an editor command: {}", offset),
};
app.dialog_renderer = Some(command_error_draw);
}
}
pub fn parse_command(app: &mut App, cmdline: &str) {
if cmdline.is_empty() {
app.state = UIState::Normal;
app.dialog_renderer = None;
return;
}
let args = shell_words::split(cmdline).unwrap_or_default();
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
argv.push("dz6");
for s in args.iter() {
argv.push(s.as_str());
}
match CommandLine::try_parse_from(argv) {
Ok(cli) => match cli.command {
Some(Command::Q) => app.running = false,
Some(Command::W) => {
let _ = app.write_to_file();
if app.config.database {
let _ = app.save_database();
}
app.dialog_renderer = None;
app.state = UIState::Normal;
}
Some(Command::Wq) | Some(Command::X) => {
let _ = app.write_to_file();
if app.config.database {
let _ = app.save_database();
}
app.dialog_renderer = None;
app.running = false;
}
Some(Command::Cmt { offset, comment }) => {
if let Ok(mut ofs) = parse_offset(&offset) {
if offset.starts_with('+') {
ofs = ofs.saturating_add(app.hex_view.offset);
}
if ofs < app.file_info.size {
Commands::comment(app, ofs, comment);
app.dialog_renderer = None;
} else {
app.last_error = Dz6Error {
message: format!(
"Invalid range: {}; maximum offset for this file is {}",
cmdline,
app.file_info.size.saturating_sub(1)
),
};
app.dialog_renderer = Some(command_error_draw);
}
} else {
app.last_error = Dz6Error {
message: format!("Invalid argument: {}", offset),
};
app.dialog_renderer = Some(command_error_draw);
}
app.state = UIState::Normal;
}
Some(Command::Set { option, value }) => {
match option.as_str() {
"byteline" => {
if let Some(val) = value {
if let Ok(bpl) = val.parse::<usize>() {
if app.screen.width > 0 {
let max = ((app.screen.width - 9) / 4) as usize;
app.config.hex_mode_bytes_per_line = bpl.min(max - 1);
} else {
app.config.hex_mode_bytes_per_line = bpl.min(64);
}
app.config.hex_mode_bytes_per_line_auto = false;
} else if let Ok(bpl) = val.parse::<String>()
&& bpl == "auto"
{
app.config.hex_mode_bytes_per_line_auto = true;
if app.screen.width > 0 {
let max = ((app.screen.width - 9) / 4) as usize;
app.config.hex_mode_bytes_per_line = max - 1;
}
}
}
app.dialog_renderer = None;
}
"ctrlchar" => {
if let Some(val) = value
&& val.len() == 1
{
let c = val.chars().next().unwrap_or('.');
app.config.hex_mode_non_graphic_char = c;
}
app.dialog_renderer = None;
}
"db" => {
app.config.database = true;
app.dialog_renderer = None;
}
"nodb" => {
app.config.database = false;
app.dialog_renderer = None;
}
"dimctrl" => {
app.config.dim_control_chars = true;
app.dialog_renderer = None;
}
"dimzero" => {
app.config.dim_control_chars = false;
app.config.dim_zeroes = true;
app.dialog_renderer = None;
}
"nodim" => {
app.config.dim_control_chars = false;
app.config.dim_zeroes = false;
app.dialog_renderer = None;
}
"theme" => {
if let Some(val) = value {
match val.as_str() {
"dark" => {
app.config.theme = crate::themes::DARK;
app.dialog_renderer = None;
}
"light" => {
app.config.theme = crate::themes::LIGHT;
app.dialog_renderer = None;
}
_ => {
app.last_error = Dz6Error {
message: format!("Invalid theme: {}", val),
};
app.dialog_renderer = Some(command_error_draw);
}
}
}
}
"wrapscan" => {
app.config.search_wrap = true;
app.dialog_renderer = None;
}
"nowrapscan" => {
app.config.search_wrap = false;
app.dialog_renderer = None;
}
_ => {
app.dialog_renderer = None;
}
}
app.state = UIState::Normal;
}
Some(Command::Sel { start, length }) => {
app.state = UIState::HexSelection;
app.dialog_renderer = None;
if let Ok(st) = parse_offset(&start)
&& let Ok(len) = parse_offset(&length)
{
app.hex_view.selection.start = st;
app.hex_view.selection.end = st.saturating_add(len);
app.goto(st);
}
}
None => {
try_goto(app, cmdline);
}
},
Err(_) => {
try_goto(app, cmdline);
}
}
}
pub fn command_draw(app: &mut App, frame: &mut Frame) {
let para = Paragraph::new(format!(":{}", app.command_input.input.value()));
frame.render_widget(Clear, app.command_area);
frame.render_widget(para, app.command_area);
let x = app.command_input.input.visual_cursor();
frame.set_cursor_position((app.command_area.x + 1 + x as u16, app.command_area.y));
}
pub fn command_events(app: &mut App, event: &Event) -> Result<bool> {
if let Event::Key(key) = event {
match key.code {
KeyCode::Esc => {
app.dialog_renderer = None;
app.state = UIState::Normal;
}
KeyCode::Enter => {
let v = app.command_input.input.value_and_reset();
parse_command(app, &v);
app.command_input.push(v);
}
KeyCode::Up => {
app.command_input.up();
}
KeyCode::Down => {
app.command_input.down();
}
_ => {
app.command_input.input.handle_event(event);
}
}
}
Ok(false)
}
pub fn command_error_draw(app: &mut App, frame: &mut Frame) {
let mut dialog = Message::from(&app.last_error.message);
dialog.kind = MessageType::Error;
dialog.render(app, frame);
app.state = UIState::Error;
}