use crate::logger::Logger;
use crossterm::{ExecutableCommand, terminal::ClearType};
use std::io::{Write, BufRead};
use crate::utils::Utils;
use std::path::Path;
use std::fs::{File,OpenOptions};
use std::collections::HashMap;
use crate::consts::*;
use similar::ChangeTag;
use crate::models::{DiffOption, MacroFragment};
use crate::RadError;
pub(crate) struct Debugger {
pub(crate) debug: bool,
pub(crate) log: bool,
pub(crate) debug_switch: DebugSwitch,
pub(crate) line_number: usize,
pub(crate) line_caches: HashMap<usize, String>,
pub(crate) do_yield_diff: bool,
pub(crate) diff_only_change: bool,
pub(crate) diff_original : Option<File>,
pub(crate) diff_processed : Option<File>,
pub(crate) interactive: bool,
prompt_log : Option<String>
}
impl Debugger {
pub fn new() -> Self {
Self {
debug: false,
log: false,
debug_switch: DebugSwitch::NextLine,
line_number: 1,
line_caches: HashMap::new(),
do_yield_diff: false,
diff_only_change : false,
diff_original: None,
diff_processed: None,
interactive : false,
prompt_log : None,
}
}
pub fn enable_diff(&mut self, diff_option : DiffOption) -> Result<(), RadError> {
match diff_option {
DiffOption::None => return Ok(()), DiffOption::Change => self.diff_only_change = true ,
_ => ()
}
self.do_yield_diff = true;
self.diff_original = Some(
OpenOptions::new()
.create(true)
.write(true)
.read(true)
.truncate(true)
.open(Path::new(DIFF_SOURCE_FILE))?
);
self.diff_processed = Some(
OpenOptions::new()
.create(true)
.write(true)
.read(true)
.truncate(true)
.open(Path::new(DIFF_OUT_FILE))?
);
Ok(())
}
pub fn set_interactive(&mut self) {
self.interactive = true;
}
pub fn get_command(&self, log : &str, prompt: Option<&str>) -> Result<String, RadError> {
if self.interactive {
std::io::stdout()
.execute(crossterm::terminal::DisableLineWrap)?;
}
let mut input = String::new();
let prompt = if let Some(content) = prompt { content } else { "" };
eprintln!("{} : {}",Utils::green(&format!("({})", &prompt)), log);
eprint!(">> ");
if self.interactive {
std::io::stdout()
.execute(crossterm::terminal::EnableLineWrap)?;
}
std::io::stdout().flush()?;
let stdin = std::io::stdin();
stdin.lock().read_line(&mut input)?;
if self.interactive {
self.remove_terminal_lines(1 + Utils::count_sentences(log))?;
}
Ok(input)
}
fn remove_terminal_lines(&self, count: usize) -> Result<(), RadError> {
std::io::stdout()
.execute(crossterm::terminal::Clear(ClearType::CurrentLine))?;
for _ in 0..count {
std::io::stdout()
.execute(crossterm::cursor::MoveUp(1))?
.execute(crossterm::terminal::Clear(ClearType::CurrentLine))?;
}
Ok(())
}
pub fn yield_diff(&self, logger: &mut Logger) -> Result<(), RadError> {
if !self.do_yield_diff { return Ok(()); }
let source = std::fs::read_to_string(Path::new(DIFF_SOURCE_FILE))?;
let processed = std::fs::read_to_string(Path::new(DIFF_OUT_FILE))?;
let result = similar::TextDiff::from_lines(&source,&processed);
let mut log: String;
let mut colorfunc : Option<fn(string: &str) -> Box<dyn std::fmt::Display>>;
logger.elog_no_prompt(format!("{0}DIFF : {0}",LINE_ENDING))?;
for change in result.iter_all_changes() {
colorfunc = None;
match change.tag() {
ChangeTag::Delete => {
log = format!("- {}", change);
colorfunc.replace(Utils::red);
}
ChangeTag::Insert => {
log = format!("+ {}", change);
colorfunc.replace(Utils::green);
}
ChangeTag::Equal => {
if !self.diff_only_change {
log = format!(" {}", change);
} else {
log = "".to_owned();
}
}
}
if let Some(func) = colorfunc {
let log = func(&log);
logger.elog_no_prompt(&log)?;
} else {
logger.elog_no_prompt(&log)?;
}
}
Ok(())
}
pub(crate) fn break_point(&mut self, frag: &mut MacroFragment, logger: &mut Logger) -> Result<(), RadError> {
if &frag.name == "BR" {
if self.debug {
if let DebugSwitch::NextBreakPoint(name) = &self.debug_switch {
if name == &frag.args || name == "" {
self.debug_switch = DebugSwitch::NextLine;
}
}
frag.clear();
return Ok(());
}
logger.wlog("Breakpoint in non debug mode")?;
frag.clear();
}
Ok(())
}
pub(crate) fn print_log(&mut self, macro_name: &str, raw_args: &str, frag: &MacroFragment, logger: &mut Logger) -> Result<(), RadError> {
if !self.log { return Ok(());}
let attributes = self.print_macro_attr(frag);
logger.dlog_print(
&format!(
r#"Name = "{}"{}Attr ={}{}Args = "{}"{}---{}"#,
macro_name, LINE_ENDING,
LINE_ENDING,attributes,
raw_args, LINE_ENDING,
LINE_ENDING
)
)?;
Ok(())
}
fn print_macro_attr(&self, frag: &MacroFragment) -> String {
format!(
r#"Greedy : {}{}Pipe : {}{}Literal : {}{}Trimmed : {}{}"#,
frag.greedy, LINE_ENDING,
frag.pipe,LINE_ENDING,
frag.yield_literal,LINE_ENDING,
frag.trimmed,LINE_ENDING
)
}
pub(crate) fn user_input_on_start(&mut self, current_input: &str,logger: &mut Logger) -> Result<(), RadError> {
if self.debug {
let mut log = if let Some(pl) = self.prompt_log.take() { pl }
else { "Default is next. Ctrl + c to exit.".to_owned() };
self.command_loop(&mut log, current_input, None, logger)?;
}
Ok(())
}
fn user_input_prompt(&mut self, frag: &MacroFragment, initial_prompt: &str, logger: &mut Logger) -> Result<(), RadError> {
let mut log = if let Some(pl) = self.prompt_log.take() { pl }
else {
match &self.debug_switch {
&DebugSwitch::NextMacro | &DebugSwitch::StepMacro => {
self.line_caches.get(&logger.get_abs_last_line()).unwrap().to_owned()
}
_ => { self.line_caches.get(&self.line_number).unwrap().to_owned() }
}
};
self.command_loop(&mut log, initial_prompt, Some(frag), logger)?;
Ok(())
}
fn command_loop(&mut self, log: &mut String ,mut prompt: &str, frag: Option<&MacroFragment>, logger: &mut Logger) -> Result<(), RadError> {
let mut do_continue = true;
while do_continue {
let input = self.debug_wait_input(&log, Some(prompt))?;
let input = input.lines().next().unwrap();
do_continue = self.parse_debug_command_and_continue(&input, frag,log, logger)?;
prompt = "output";
}
Ok(())
}
pub fn user_input_on_line(&mut self,frag: &MacroFragment, logger: &mut Logger) -> Result<(), RadError> {
if self.debug {
if let DebugSwitch::NextLine = self.debug_switch {
} else {
return Ok(()); }
self.user_input_prompt(frag, "line", logger)?;
}
Ok(())
}
pub fn user_input_before_macro(&mut self, frag: &MacroFragment, logger: &mut Logger) -> Result<(), RadError> {
if self.debug {
match &self.debug_switch {
&DebugSwitch::UntilMacro => (),
_ => return Ok(()),
}
self.user_input_prompt(frag, "until", logger)?;
}
Ok(())
}
pub fn user_input_on_macro(&mut self, frag: &MacroFragment, logger: &mut Logger) -> Result<(), RadError> {
if self.debug {
match &self.debug_switch {
&DebugSwitch::NextMacro | &DebugSwitch::StepMacro => (),
_ => return Ok(()),
}
self.user_input_prompt(frag, &frag.name, logger)?;
}
Ok(())
}
pub fn user_input_on_step(&mut self, frag: &MacroFragment, logger: &mut Logger) -> Result<(), RadError> {
if self.debug {
if let &DebugSwitch::StepMacro = &self.debug_switch {
} else {
return Ok(()); }
self.user_input_prompt(frag, &frag.name, logger)?;
}
Ok(())
}
pub fn parse_debug_command_and_continue(&mut self, command_input: &str, frag: Option<&MacroFragment>, log: &mut String, logger: &mut Logger) -> Result<bool, RadError> {
let command_input: Vec<&str> = command_input.split(' ').collect();
let command = command_input[0];
let command_args = if command_input.len() == 2 {command_input[1]} else { "" };
match command.to_lowercase().as_str() {
"cl" | "clear" => {
Utils::clear_terminal()?;
return Ok(true);
}
"c" | "continue" => {
self.debug_switch = DebugSwitch::NextBreakPoint(command_args.to_owned());
}
"n" | "next" | "" => {
self.debug_switch = DebugSwitch::NextLine;
}
"m" | "macro" => {
self.debug_switch = DebugSwitch::NextMacro;
}
"u" | "until" => {
self.debug_switch = DebugSwitch::UntilMacro;
}
"s" | "step" => {
self.debug_switch = DebugSwitch::StepMacro;
}
"h" | "help" => {
*log = RDB_HELP.to_owned();
return Ok(true);
}
"p" | "print" => {
if let Some(frag) = frag {
self.check_command_print(log, command_args, frag, logger);
} else {
return Ok(false);
}
return Ok(true);
}
_ => {
*log = format!("Invalid command : {} {}",command, &command_args);
return Ok(true);
},
}
Ok(false)
}
fn check_command_print(&self,log: &mut String, command_args: &str, frag: &MacroFragment, logger: &mut Logger) {
match command_args.to_lowercase().as_str() {
"name" | "n" => {
*log = frag.name.to_owned();
}
"line" | "l" => {
match &self.debug_switch{
DebugSwitch::StepMacro | DebugSwitch::NextMacro => {
*log = logger.get_abs_last_line().to_string();
}
_ => { *log = self.line_number.to_string(); }
}
}
"span" | "s" => {
let mut line_number = match &self.debug_switch {
&DebugSwitch::NextMacro | &DebugSwitch::StepMacro => {
logger.get_abs_line()
}
_ => self.line_number
};
let mut sums = String::new();
while let Some(line) = self.line_caches.get(&line_number) {
let mut this_line = format!("{}{}",LINE_ENDING,line);
this_line.push_str(&sums);
sums = this_line;
line_number = line_number - 1;
}
let mut this_line = format!("{}","\"Span\"");
this_line.push_str(&sums);
sums = this_line;
*log = sums;
}
"text" | "t" => {
match &self.debug_switch{
DebugSwitch::StepMacro | DebugSwitch::NextMacro => {
*log = self.line_caches.get(&logger.get_abs_last_line()).unwrap().to_owned();
}
_ => {
*log = self
.line_caches
.get(&self.line_number)
.unwrap()
.to_owned();
}
}
}
"arg" | "a" => {
*log = frag.args.to_owned();
}
_ => { *log = format!("Invalid argument \"{}\"",&command_args); }
} }
pub fn debug_wait_input(&self, log: &str, prompt: Option<&str>) -> Result<String, RadError> {
Ok(self.get_command(log, prompt)?)
}
pub fn inc_line_number(&mut self) {
self.line_number= self.line_number + 1;
}
pub fn add_line_cache(&mut self, line: &str) {
self.line_caches.insert(self.line_number, line.lines().next().unwrap().to_owned());
}
pub fn clear_line_cache(&mut self) {
self.line_caches.clear();
}
pub fn write_to_original(&mut self, content: &str) -> Result<(), RadError> {
if self.do_yield_diff {
self.diff_original.as_ref().unwrap().write_all(content.as_bytes())?;
}
Ok(())
}
pub fn write_to_processed(&mut self, content: &str) -> Result<(), RadError> {
if self.do_yield_diff {
self.diff_processed.as_ref().unwrap().write_all(content.as_bytes())?;
}
Ok(())
}
pub fn set_prompt_log(&mut self, prompt: &str) {
self.prompt_log.replace(prompt.to_owned());
}
}
pub enum DebugSwitch {
UntilMacro,
NextLine,
NextMacro,
StepMacro,
NextBreakPoint(String),
}