opengrep 1.1.0

Advanced AST-aware code search tool with tree-sitter parsing and AI integration capabilities
Documentation
//! Text output formatter.

use crate::config::OutputConfig;
use crate::search::SearchResult;
use super::OutputFormatter;
use anyhow::Result;
use std::io::Write;
use termcolor::{Ansi, Color, ColorSpec, NoColor, WriteColor};

/// Formats search results as plain text.
pub struct TextFormatter {
    use_color: bool,
}

impl Default for TextFormatter {
    fn default() -> Self {
        Self::new()
    }
}

impl TextFormatter {
    /// Creates a new `TextFormatter`.
    pub fn new() -> Self {
        let use_color = atty::is(atty::Stream::Stdout);
        Self { use_color }
    }
}

impl OutputFormatter for TextFormatter {
    fn format<W: Write>(
        &self,
        results: &[SearchResult],
        writer: &mut W,
        config: &OutputConfig,
    ) -> Result<()> {
        if self.use_color {
            let mut color_writer = Ansi::new(writer);
            self.format_colored(&mut color_writer, results, config)?;
        } else {
            let mut nocolor_writer = NoColor::new(writer);
            self.format_plain(&mut nocolor_writer, results, config)?;
        }
        Ok(())
    }
}

impl TextFormatter {
    fn format_colored<W: Write + WriteColor>(
        &self,
        writer: &mut W,
        results: &[SearchResult],
        config: &OutputConfig,
    ) -> Result<()> {
        for result in results.iter().filter(|r| !r.matches.is_empty()) {
            writer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
            writeln!(writer, "{}", result.path.display())?;
            writer.reset()?;

            for m in &result.matches {
                writer.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
                write!(writer, "{:>6}: ", m.line_number)?;
                writer.reset()?;

                let line = &m.line_text;
                let range = &m.column_range;
                write!(writer, "{}", &line[..range.start])?;
                writer.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
                write!(writer, "{}", &line[range.start..range.end])?;
                writer.reset()?;
                writeln!(writer, "{}", &line[range.end..].trim_end())?;

                if config.show_ast_context {
                    if let Some(ast_context) = &m.ast_context {
                        writer.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
                        writeln!(
                            writer,
                            "      └─ AST Context: {}",
                            ast_context.summary
                        )?;
                        writer.reset()?;
                    }
                }
            }
            writeln!(writer)?;
        }
        writer.reset()?;
        Ok(())
    }

    fn format_plain<W: Write>(
        &self,
        writer: &mut W,
        results: &[SearchResult],
        config: &OutputConfig,
    ) -> Result<()> {
        for result in results.iter().filter(|r| !r.matches.is_empty()) {
            writeln!(writer, "{}", result.path.display())?;

            for m in &result.matches {
                writeln!(writer, "{}:{}", m.line_number, m.line_text.trim_end())?;

                if config.show_ast_context {
                    if let Some(ast_context) = &m.ast_context {
                        writeln!(
                            writer,
                            "  └─ AST Context: {}",
                            ast_context.summary
                        )?;
                    }
                }
            }
            writeln!(writer)?;
        }
        Ok(())
    }
}