use crate::cli::{Cli, Encoding};
use crate::highlight::{Highlighter, resolve_theme};
use crate::input::{InputKind, LineRange, decode};
use crate::printer::{PrinterConfig, StyleFlags, print};
use crate::syntax;
use anyhow::Result;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::thread::sleep;
use std::time::Duration;
fn read_decoded(path: &Path, encoding: Encoding) -> Result<String> {
let bytes = fs::read(path)?;
decode(&bytes, encoding)
}
const POLL_INTERVAL: Duration = Duration::from_millis(200);
pub fn run(
args: &Cli,
syntax_set: &syntect::parsing::SyntaxSet,
theme_set: &syntect::highlighting::ThemeSet,
) -> Result<()> {
if args.files.len() > 1 {
anyhow::bail!("follow mode supports a single file");
}
let path: PathBuf = match args.files.first() {
Some(p) if p.as_os_str() != "-" => p.clone(),
_ => anyhow::bail!("follow mode requires a file path"),
};
let input = InputKind::File(path.clone());
let theme = resolve_theme(theme_set, args.theme.as_deref());
let use_color = match args.color {
crate::cli::ColorWhen::Always => true,
crate::cli::ColorWhen::Never => false,
crate::cli::ColorWhen::Auto => {
use std::io::IsTerminal;
std::io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none()
}
};
let force_plain = args.plain
|| matches!(args.decorations, crate::cli::DecorationsWhen::Never);
let style = StyleFlags::parse(&args.style, force_plain, args.number, args.diff);
let highlight_lines: std::collections::HashSet<usize> = args.highlight_line.iter().copied().collect();
let cursor: Option<usize> = args.highlight_line.first().copied();
let width = crate::term_width();
let mut stdout = std::io::stdout().lock();
let contents = read_decoded(&path, args.encoding)?;
let total = contents.lines().count();
let first_line = contents.lines().next();
let syntax = syntax::detect_syntax(syntax_set, Some(&path), args.language.as_deref(), first_line);
let start = total.saturating_sub(args.tail_lines).saturating_add(1).max(1);
let initial_range = LineRange { start, end: usize::MAX };
{
let mut hl = Highlighter::new(syntax, theme, syntax_set);
let cfg = PrinterConfig {
style,
line_range: Some(initial_range),
highlight_lines: highlight_lines.clone(),
tabs: args.tabs,
wrap: args.wrap,
show_all: args.show_all,
use_color,
width,
language_name: &syntax.name,
cursor,
line_numbers: args.line_numbers,
markdown: false, };
print(&mut stdout, &input, &contents, &mut hl, &cfg)?;
stdout.flush()?;
}
let mut last_lineno = total;
let mut last_size = fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
loop {
sleep(POLL_INTERVAL);
let meta = match fs::metadata(&path) {
Ok(m) => m,
Err(_) => continue, };
let size = meta.len();
if size == last_size {
continue;
}
if size < last_size {
writeln!(stdout, "--- file truncated, restarting ---")?;
stdout.flush()?;
let contents = read_decoded(&path, args.encoding).unwrap_or_default();
let total = contents.lines().count();
let start = total.saturating_sub(args.tail_lines).saturating_add(1).max(1);
let mut hl = Highlighter::new(syntax, theme, syntax_set);
let cfg = PrinterConfig {
style: StyleFlags { header: false, ..style },
line_range: Some(LineRange { start, end: usize::MAX }),
highlight_lines: highlight_lines.clone(),
tabs: args.tabs,
wrap: args.wrap,
show_all: args.show_all,
use_color,
width,
language_name: &syntax.name,
cursor,
line_numbers: args.line_numbers,
markdown: false,
};
print(&mut stdout, &input, &contents, &mut hl, &cfg)?;
stdout.flush()?;
last_lineno = total;
last_size = size;
continue;
}
let contents = match read_decoded(&path, args.encoding) {
Ok(c) => c,
Err(_) => continue,
};
let total = contents.lines().count();
if total > last_lineno {
let mut hl = Highlighter::new(syntax, theme, syntax_set);
let cfg = PrinterConfig {
style: StyleFlags { header: false, ..style },
line_range: Some(LineRange { start: last_lineno + 1, end: usize::MAX }),
highlight_lines: highlight_lines.clone(),
tabs: args.tabs,
wrap: args.wrap,
show_all: args.show_all,
use_color,
width,
language_name: &syntax.name,
cursor,
line_numbers: args.line_numbers,
markdown: false,
};
print(&mut stdout, &input, &contents, &mut hl, &cfg)?;
stdout.flush()?;
last_lineno = total;
}
last_size = size;
}
}