use crate::config::{FormatterBackend, ReprexFormatter, ReprexMode};
use crate::external::formatter;
use crossterm::{
ExecutableCommand, cursor,
terminal::{self, ClearType},
};
use std::io::{self, Write};
#[derive(Debug, Clone)]
pub struct ReprexRuntime {
pub mode: ReprexMode,
pub comment: String,
pub formatter: Option<FormatterBackend>,
pub formatter_selector: ReprexFormatter,
}
impl ReprexRuntime {
#[cfg(test)]
pub fn new(mode: ReprexMode, comment: impl Into<String>, formatter: FormatterBackend) -> Self {
Self {
mode,
comment: comment.into(),
formatter: Some(formatter),
formatter_selector: match formatter {
FormatterBackend::Air => ReprexFormatter::Air,
FormatterBackend::Arity => ReprexFormatter::Arity,
},
}
}
pub fn from_resolved(
mode: ReprexMode,
comment: impl Into<String>,
formatter_selector: ReprexFormatter,
formatter: Option<FormatterBackend>,
) -> Self {
Self {
mode,
comment: comment.into(),
formatter,
formatter_selector,
}
}
pub fn is_enabled(&self) -> bool {
self.mode != ReprexMode::Off
}
pub fn set_mode(&mut self, mode: ReprexMode) {
self.mode = mode;
arf_libr::set_reprex_mode(mode != ReprexMode::Off, &self.comment);
}
pub fn maybe_format_code(&self, code: &str) -> Result<String, formatter::FormatterError> {
if self.mode == ReprexMode::Format {
let backend = self
.formatter
.ok_or(formatter::FormatterError::Unavailable {
selector: self.formatter_selector,
})?;
formatter::format_code(backend, code)
} else {
Ok(code.to_string())
}
}
}
pub fn strip_reprex_output(input: &str) -> String {
input
.lines()
.filter(|line| !line.starts_with("#>"))
.collect::<Vec<_>>()
.join("\n")
}
pub fn clear_input_lines(original: &str, display: &str) {
let mut stdout = io::stdout();
let line_count = original.lines().count().max(1);
let _ = stdout.execute(cursor::MoveUp(line_count as u16));
for _ in 0..line_count {
let _ = stdout.execute(terminal::Clear(ClearType::CurrentLine));
let _ = stdout.execute(cursor::MoveDown(1));
}
let _ = stdout.execute(cursor::MoveUp(line_count as u16));
println!("{}", display);
let _ = stdout.flush();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_single_output_line() {
let input = "x <- 1 + 1\nx\n#> [1] 2";
let result = strip_reprex_output(input);
assert_eq!(result, "x <- 1 + 1\nx");
}
#[test]
fn test_strip_multiple_output_lines() {
let input = "1:3\n#> [1] 1 2 3\nmean(1:3)\n#> [1] 2";
let result = strip_reprex_output(input);
assert_eq!(result, "1:3\nmean(1:3)");
}
#[test]
fn test_no_output_lines() {
let input = "x <- 1\ny <- 2\nz <- x + y";
let result = strip_reprex_output(input);
assert_eq!(result, input);
}
#[test]
fn test_only_output_lines() {
let input = "#> [1] 1\n#> [1] 2";
let result = strip_reprex_output(input);
assert_eq!(result, "");
}
#[test]
fn test_empty_input() {
let result = strip_reprex_output("");
assert_eq!(result, "");
}
#[test]
fn test_preserves_regular_comments() {
let input = "# This is a comment\nx <- 1\n#> [1] 1";
let result = strip_reprex_output(input);
assert_eq!(result, "# This is a comment\nx <- 1");
}
#[test]
fn test_output_with_spaces_after_prefix() {
let input = "x\n#> [1] 1";
let result = strip_reprex_output(input);
assert_eq!(result, "x");
}
#[test]
fn test_multiline_output() {
let input = "1:10\n#> [1] 1 2 3 4 5 6 7 8 9 10";
let result = strip_reprex_output(input);
assert_eq!(result, "1:10");
}
#[test]
fn test_preserves_hash_in_strings() {
let input = "\"#> not output\"\n#> [1] \"#> not output\"";
let result = strip_reprex_output(input);
assert_eq!(result, "\"#> not output\"");
}
}