1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Experimental support for hyperlinking.
//!
//! # Usage
//!
//! Enable the `hyperlink` crate feature. Enabling `hyperlink` implicitly
//! enables `std`.
//!
//! Import the `HyperlinkExt` extension trait and use the [`link()`] builder
//! method.
//!
//! [`link()`]: HyperlinkExt::link()
//!
//! ```rust
//! use yansi::Paint;
//! use yansi::hyperlink::HyperlinkExt;
//!
//! println!("Go to {}.", "our docs".link("https://docs.rs/yansi").green());
//! ```
//!
//! `>` Go to <a href="https://docs.rs/yansi"><span style="color: green;">our docs</span></a>.
//!
//! The `link()` method returns a [`PaintedLink`] structure which implements all
//! of the unverisal chainable methods available across the library.
//! Furthermore, [`Painted`] is extended with a [`link()`](Painted::link())
//! method. The net effect is that you can use `link()` as if it were any other
//! styling method:
//!
//! ```rust
//! use yansi::Paint;
//! use yansi::hyperlink::HyperlinkExt;
//!
//! println!("Go to {}.", "our docs".green().link("https://docs.rs/yansi").on_black().invert());
//! ```
//!
//! `>` Go to <a href="https://docs.rs/yansi">
//! <span style="background: green; color: black;">our docs</span>
//! </a>.
//!
//! # Caveats
//!
//! 1. You can only create a link when there is a target value to print, that
//!    is, when the receiver is something "printable". In other words, you
//!    _cannot_ apply `link()` to a bare `Style`. This means the following will
//!    not work:
//!
//!    ```rust,compile_fail
//!    use yansi::{Paint, Style, Color::*};
//!    use yansi::hyperlink::HyperlinkExt;
//!
//!    static LINKED: Style = Green.link("https://docs.rs/yansi");
//!    ```
//!    <br/>
//!
//! 2. While some modern terminals support hyperlinking, many do not. Those that
//!    do not _should_ gracefully ignore the target URL and print the original
//!    value. That is, instead of `>` <a href="https://docs.rs/yansi">our
//!    docs</a>, such terminals would print `>` our docs.
use core::fmt;

use crate::*;

/// A [`Painted`] with an associated target URL to hyperlink.
pub struct PaintedLink<T> {
    painted: Painted<T>,
    link: String,
}

/// Extension trait to apply hyperlinks to any value, implemented for all types.
///
/// See the [module level docs](hyperlink) for usage details.
pub trait HyperlinkExt {
    /// Create a painted hyperlink with a target URL of `url`.
    ///
    /// See [`hyperlink`] for details.
    ///
    /// # Example
    ///
    /// ```rust
    /// use yansi::hyperlink::HyperlinkExt;
    ///
    /// println!("See {}.", "our docs".link("https://docs.rs/yansi"));
    /// ```
    fn link(&self, url: impl ToString) -> PaintedLink<&Self>;
}

impl<T> PaintedLink<T> {
    fn fmt_args(
        &self,
        fmt: &dyn Fn(&Painted<T>, &mut fmt::Formatter) -> fmt::Result,
        f: &mut fmt::Formatter,
        _args: fmt::Arguments<'_>,
    ) -> fmt::Result {
        if !self.painted.enabled() {
            return fmt(&self.painted, f);
        }

        write!(f, "\x1B]8;;{}\x1B\\", self.link)?;
        fmt(&self.painted, f)?;
        write!(f, "\x1B]8;;\x1B\\")
    }
}

impl_fmt_traits!(<T> PaintedLink<T> => self.painted (Painted<T>));

impl<T> HyperlinkExt for T {
    fn link(&self, url: impl ToString) -> PaintedLink<&Self> {
        PaintedLink { painted: Painted::new(self), link: url.to_string() }
    }
}

/// Experimental support for hyperlinking.
impl<T> Painted<T> {
    /// Create a painted hyperlink with a target URL of `url`.
    ///
    /// See [`hyperlink`] for details.
    ///
    /// # Example
    ///
    /// ```rust
    /// use yansi::Paint;
    /// use yansi::hyperlink::HyperlinkExt;
    ///
    /// println!("See {}.", "our docs".green().link("https://docs.rs/yansi"));
    /// ```
    pub fn link(&self, url: impl ToString) -> PaintedLink<&Self> {
        PaintedLink { painted: Painted::new(self), link: url.to_string() }
    }
}

impl<T> PaintedLink<T> {
    #[inline(always)]
    const fn apply(mut self, a: crate::style::Application) -> Self {
        self.painted.style = self.painted.style.apply(a);
        self
    }

    properties!([pub const] constructor(Self) -> Self);
}