1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2025-11-23 21:54:45 +02:00

printer: add hyperlinks

This commit represents the initial work to get hyperlinks working and
was submitted as part of PR #2483. Subsequent commits largely retain the
functionality and structure of the hyperlink support added here, but
rejigger some things around.
This commit is contained in:
Lucas Trzesniewski
2023-07-08 00:56:50 +02:00
committed by Andrew Gallant
parent 86ef683308
commit 1a50324013
16 changed files with 1178 additions and 83 deletions

View File

@@ -580,6 +580,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
flag_glob_case_insensitive(&mut args);
flag_heading(&mut args);
flag_hidden(&mut args);
flag_hyperlink_format(&mut args);
flag_iglob(&mut args);
flag_ignore_case(&mut args);
flag_ignore_file(&mut args);
@@ -1494,6 +1495,26 @@ This flag can be disabled with --no-hidden.
args.push(arg);
}
fn flag_hyperlink_format(args: &mut Vec<RGArg>) {
const SHORT: &str = "Set the format of hyperlinks to match results.";
const LONG: &str = long!(
"\
Set the format of hyperlinks to match results. This defines a pattern which
can contain the following placeholders: {file}, {line}, {column}, and {host}.
An empty pattern or 'none' disables hyperlinks.
The {file} placeholder is required, and will be replaced with the absolute
file path with a few adjustments: The leading '/' on Unix is removed,
and '\\' is replaced with '/' on Windows.
As an example, the default pattern on Unix systems is: 'file://{host}/{file}'
"
);
let arg =
RGArg::flag("hyperlink-format", "FORMAT").help(SHORT).long_help(LONG);
args.push(arg);
}
fn flag_iglob(args: &mut Vec<RGArg>) {
const SHORT: &str = "Include or exclude files case insensitively.";
const LONG: &str = long!(

View File

@@ -5,6 +5,7 @@ use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use std::str::FromStr;
use std::sync::Arc;
use std::time::SystemTime;
@@ -17,8 +18,8 @@ use grep::pcre2::{
RegexMatcherBuilder as PCRE2RegexMatcherBuilder,
};
use grep::printer::{
default_color_specs, ColorSpecs, JSONBuilder, Standard, StandardBuilder,
Stats, Summary, SummaryBuilder, SummaryKind, JSON,
default_color_specs, ColorSpecs, HyperlinkPattern, JSONBuilder, Standard,
StandardBuilder, Stats, Summary, SummaryBuilder, SummaryKind, JSON,
};
use grep::regex::{
RegexMatcher as RustRegexMatcher,
@@ -235,6 +236,7 @@ impl Args {
let mut builder = PathPrinterBuilder::new();
builder
.color_specs(self.matches().color_specs()?)
.hyperlink_pattern(self.matches().hyperlink_pattern()?)
.separator(self.matches().path_separator()?)
.terminator(self.matches().path_terminator().unwrap_or(b'\n'));
Ok(builder.build(wtr))
@@ -772,6 +774,7 @@ impl ArgMatches {
let mut builder = StandardBuilder::new();
builder
.color_specs(self.color_specs()?)
.hyperlink_pattern(self.hyperlink_pattern()?)
.stats(self.stats())
.heading(self.heading())
.path(self.with_filename(paths))
@@ -811,6 +814,7 @@ impl ArgMatches {
builder
.kind(self.summary_kind().expect("summary format"))
.color_specs(self.color_specs()?)
.hyperlink_pattern(self.hyperlink_pattern()?)
.stats(self.stats())
.path(self.with_filename(paths))
.max_matches(self.max_count()?)
@@ -1118,6 +1122,17 @@ impl ArgMatches {
self.is_present("hidden") || self.unrestricted_count() >= 2
}
/// Returns the hyperlink pattern to use. A default pattern suitable
/// for the current system is used if the value is not set.
///
/// If an invalid pattern is provided, then an error is returned.
fn hyperlink_pattern(&self) -> Result<HyperlinkPattern> {
Ok(match self.value_of_lossy("hyperlink-format") {
Some(pattern) => HyperlinkPattern::from_str(&pattern)?,
None => HyperlinkPattern::default_file_scheme(),
})
}
/// Returns true if ignore files should be processed case insensitively.
fn ignore_file_case_insensitive(&self) -> bool {
self.is_present("ignore-file-case-insensitive")

View File

@@ -1,13 +1,16 @@
use std::io;
use std::path::Path;
use grep::printer::{ColorSpecs, PrinterPath};
use grep::printer::{
ColorSpecs, HyperlinkPattern, HyperlinkSpan, PrinterPath,
};
use termcolor::WriteColor;
/// A configuration for describing how paths should be written.
#[derive(Clone, Debug)]
struct Config {
colors: ColorSpecs,
hyperlink_pattern: HyperlinkPattern,
separator: Option<u8>,
terminator: u8,
}
@@ -16,6 +19,7 @@ impl Default for Config {
fn default() -> Config {
Config {
colors: ColorSpecs::default(),
hyperlink_pattern: HyperlinkPattern::default(),
separator: None,
terminator: b'\n',
}
@@ -37,7 +41,7 @@ impl PathPrinterBuilder {
/// Create a new path printer with the current configuration that writes
/// paths to the given writer.
pub fn build<W: WriteColor>(&self, wtr: W) -> PathPrinter<W> {
PathPrinter { config: self.config.clone(), wtr }
PathPrinter { config: self.config.clone(), wtr, buf: vec![] }
}
/// Set the color specification for this printer.
@@ -52,6 +56,17 @@ impl PathPrinterBuilder {
self
}
/// Set the hyperlink pattern to use for hyperlinks output by this printer.
///
/// Colors need to be enabled for hyperlinks to be output.
pub fn hyperlink_pattern(
&mut self,
pattern: HyperlinkPattern,
) -> &mut PathPrinterBuilder {
self.config.hyperlink_pattern = pattern;
self
}
/// A path separator.
///
/// When provided, the path's default separator will be replaced with
@@ -80,6 +95,7 @@ impl PathPrinterBuilder {
pub struct PathPrinter<W> {
config: Config,
wtr: W,
buf: Vec<u8>,
}
impl<W: WriteColor> PathPrinter<W> {
@@ -89,10 +105,30 @@ impl<W: WriteColor> PathPrinter<W> {
if !self.wtr.supports_color() {
self.wtr.write_all(ppath.as_bytes())?;
} else {
let mut hyperlink = self.start_hyperlink_span(&ppath)?;
self.wtr.set_color(self.config.colors.path())?;
self.wtr.write_all(ppath.as_bytes())?;
self.wtr.reset()?;
hyperlink.end(&mut self.wtr)?;
}
self.wtr.write_all(&[self.config.terminator])
}
/// Starts a hyperlink span when applicable.
fn start_hyperlink_span(
&mut self,
path: &PrinterPath,
) -> io::Result<HyperlinkSpan> {
if self.wtr.supports_hyperlinks() {
if let Some(spec) = path.create_hyperlink_spec(
&self.config.hyperlink_pattern,
None,
None,
&mut self.buf,
) {
return Ok(HyperlinkSpan::start(&mut self.wtr, &spec)?);
}
}
Ok(HyperlinkSpan::default())
}
}