use std::{cell::RefCell, io, path::Path, sync::Arc};
use {
bstr::ByteSlice,
termcolor::{HyperlinkSpec, WriteColor},
};
use crate::{hyperlink_aliases, util::DecimalFormatter};
/// Hyperlink configuration.
///
/// This configuration specifies both the [hyperlink format](HyperlinkFormat)
/// and an [environment](HyperlinkConfig) for interpolating a subset of
/// variables. The specific subset includes variables that are intended to
/// be invariant throughout the lifetime of a process, such as a machine's
/// hostname.
///
/// A hyperlink configuration can be provided to printer builders such as
/// [`StandardBuilder::hyperlink`](crate::StandardBuilder::hyperlink).
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct HyperlinkConfig(Arc<HyperlinkConfigInner>);
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct HyperlinkConfigInner {
env: HyperlinkEnvironment,
format: HyperlinkFormat,
}
impl HyperlinkConfig {
/// Create a new configuration from an environment and a format.
pub fn new(
env: HyperlinkEnvironment,
format: HyperlinkFormat,
) -> HyperlinkConfig {
HyperlinkConfig(Arc::new(HyperlinkConfigInner { env, format }))
}
/// Returns the hyperlink environment in this configuration.
pub(crate) fn environment(&self) -> &HyperlinkEnvironment {
&self.0.env
}
/// Returns the hyperlink format in this configuration.
pub(crate) fn format(&self) -> &HyperlinkFormat {
&self.0.format
}
}
/// A hyperlink format with variables.
///
/// This can be created by parsing a string using `HyperlinkFormat::from_str`.
///
/// The default format is empty. An empty format is valid and effectively
/// disables hyperlinks.
///
/// # Example
///
/// ```
/// use grep_printer::HyperlinkFormat;
///
/// let fmt = "vscode".parse::<HyperlinkFormat>()?;
/// assert_eq!(fmt.to_string(), "vscode://file{path}:{line}:{column}");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct HyperlinkFormat {
parts: Vec<Part>,
is_line_dependent: bool,
}
impl HyperlinkFormat {
/// Creates an empty hyperlink format.
pub fn empty() -> HyperlinkFormat {
HyperlinkFormat::default()
}
/// Returns true if this format is empty.
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
/// Creates a [`HyperlinkConfig`] from this format and the environment
/// given.
pub fn into_config(self, env: HyperlinkEnvironment) -> HyperlinkConfig {
HyperlinkConfig::new(env, self)
}
/// Returns true if the format can produce line-dependent hyperlinks.
pub(crate) fn is_line_dependent(&self) -> bool {
self.is_line_dependent
}
}
impl std::str::FromStr for HyperlinkFormat {
type Err = HyperlinkFormatError;
fn from_str(s: &str) -> Result<HyperlinkFormat, HyperlinkFormatError> {
use self::HyperlinkFormatErrorKind::*;
#[derive(Debug)]
enum State {
Verbatim,
VerbatimCloseVariable,
OpenVariable,
InVariable,
}
let mut builder = FormatBuilder::new();
let input = match hyperlink_aliases::find(s) {
Some(format) => format,
None => s,
};
let mut name = String::new();
let mut state = State::Verbatim;
let err = |kind| HyperlinkFormatError { kind };
for ch in input.chars() {
state = match state {
State::Verbatim => {
if ch == '{' {
State::OpenVariable
} else if ch == '}' {
State::VerbatimCloseVariable
} else {
builder.append_char(ch);
State::Verbatim
}
}
State::VerbatimCloseVariable => {
if ch == '}' {
builder.append_char('}');
State::Verbatim
} else {
return Err(err(InvalidCloseVariable));
}
}
State::OpenVariable => {
if ch == '{' {
builder.append_char('{');
State::Verbatim
} else {
name.clear();
if ch == '}' {
builder.append_var(&name)?;
State::Verbatim
} else {
name.push(ch);
State::InVariable
}
}
}
State::InVariable => {
if ch == '}' {
builder.append_var(&name)?;
State::Verbatim
} else