@@ -6,6 +6,7 @@ use anstream::AutoStream;
66use anstyle:: Style ;
77
88use crate :: util:: errors:: CargoResult ;
9+ use crate :: util:: hostname;
910use crate :: util:: style:: * ;
1011
1112pub enum TtyWidth {
@@ -57,6 +58,7 @@ pub struct Shell {
5758 /// Flag that indicates the current line needs to be cleared before
5859 /// printing. Used when a progress bar is currently displayed.
5960 needs_clear : bool ,
61+ hostname : Option < String > ,
6062}
6163
6264impl fmt:: Debug for Shell {
@@ -85,6 +87,7 @@ enum ShellOut {
8587 stderr : AutoStream < std:: io:: Stderr > ,
8688 stderr_tty : bool ,
8789 color_choice : ColorChoice ,
90+ hyperlinks : bool ,
8891 } ,
8992}
9093
@@ -111,10 +114,12 @@ impl Shell {
111114 stdout : AutoStream :: new ( std:: io:: stdout ( ) , stdout_choice) ,
112115 stderr : AutoStream :: new ( std:: io:: stderr ( ) , stderr_choice) ,
113116 color_choice : auto_clr,
117+ hyperlinks : supports_hyperlinks:: supports_hyperlinks ( ) ,
114118 stderr_tty : std:: io:: stderr ( ) . is_terminal ( ) ,
115119 } ,
116120 verbosity : Verbosity :: Verbose ,
117121 needs_clear : false ,
122+ hostname : None ,
118123 }
119124 }
120125
@@ -124,6 +129,7 @@ impl Shell {
124129 output : ShellOut :: Write ( AutoStream :: never ( out) ) , // strip all formatting on write
125130 verbosity : Verbosity :: Verbose ,
126131 needs_clear : false ,
132+ hostname : None ,
127133 }
128134 }
129135
@@ -314,6 +320,16 @@ impl Shell {
314320 Ok ( ( ) )
315321 }
316322
323+ pub fn set_hyperlinks ( & mut self , yes : bool ) -> CargoResult < ( ) > {
324+ if let ShellOut :: Stream {
325+ ref mut hyperlinks, ..
326+ } = self . output
327+ {
328+ * hyperlinks = yes;
329+ }
330+ Ok ( ( ) )
331+ }
332+
317333 /// Gets the current color choice.
318334 ///
319335 /// If we are not using a color stream, this will always return `Never`, even if the color
@@ -340,6 +356,63 @@ impl Shell {
340356 }
341357 }
342358
359+ pub fn out_hyperlink < D : fmt:: Display > ( & self , url : D ) -> Hyperlink < D > {
360+ let supports_hyperlinks = match & self . output {
361+ ShellOut :: Write ( _) => false ,
362+ ShellOut :: Stream {
363+ stdout, hyperlinks, ..
364+ } => stdout. current_choice ( ) == anstream:: ColorChoice :: AlwaysAnsi && * hyperlinks,
365+ } ;
366+ if supports_hyperlinks {
367+ Hyperlink { url : Some ( url) }
368+ } else {
369+ Hyperlink { url : None }
370+ }
371+ }
372+
373+ pub fn err_hyperlink < D : fmt:: Display > ( & self , url : D ) -> Hyperlink < D > {
374+ let supports_hyperlinks = match & self . output {
375+ ShellOut :: Write ( _) => false ,
376+ ShellOut :: Stream {
377+ stderr, hyperlinks, ..
378+ } => stderr. current_choice ( ) == anstream:: ColorChoice :: AlwaysAnsi && * hyperlinks,
379+ } ;
380+ if supports_hyperlinks {
381+ Hyperlink { url : Some ( url) }
382+ } else {
383+ Hyperlink { url : None }
384+ }
385+ }
386+
387+ pub fn out_file_hyperlink ( & mut self , path : & std:: path:: Path ) -> Hyperlink < url:: Url > {
388+ let url = self . file_hyperlink ( path) ;
389+ url. map ( |u| self . out_hyperlink ( u) ) . unwrap_or_default ( )
390+ }
391+
392+ pub fn err_file_hyperlink ( & mut self , path : & std:: path:: Path ) -> Hyperlink < url:: Url > {
393+ let url = self . file_hyperlink ( path) ;
394+ url. map ( |u| self . err_hyperlink ( u) ) . unwrap_or_default ( )
395+ }
396+
397+ fn file_hyperlink ( & mut self , path : & std:: path:: Path ) -> Option < url:: Url > {
398+ let mut url = url:: Url :: from_file_path ( path) . ok ( ) ?;
399+ // Do a best-effort of setting the host in the URL to avoid issues with opening a link
400+ // scoped to the computer you've SSHed into
401+ let hostname = if cfg ! ( windows) {
402+ // Not supported correctly on windows
403+ None
404+ } else {
405+ if let Some ( hostname) = self . hostname . as_deref ( ) {
406+ Some ( hostname)
407+ } else {
408+ self . hostname = hostname ( ) . ok ( ) . and_then ( |h| h. into_string ( ) . ok ( ) ) ;
409+ self . hostname . as_deref ( )
410+ }
411+ } ;
412+ let _ = url. set_host ( hostname) ;
413+ Some ( url)
414+ }
415+
343416 /// Prints a message to stderr and translates ANSI escape code into console colors.
344417 pub fn print_ansi_stderr ( & mut self , message : & [ u8 ] ) -> CargoResult < ( ) > {
345418 if self . needs_clear {
@@ -439,6 +512,34 @@ fn supports_color(choice: anstream::ColorChoice) -> bool {
439512 }
440513}
441514
515+ pub struct Hyperlink < D : fmt:: Display > {
516+ url : Option < D > ,
517+ }
518+
519+ impl < D : fmt:: Display > Default for Hyperlink < D > {
520+ fn default ( ) -> Self {
521+ Self { url : None }
522+ }
523+ }
524+
525+ impl < D : fmt:: Display > Hyperlink < D > {
526+ pub fn open ( & self ) -> impl fmt:: Display {
527+ if let Some ( url) = self . url . as_ref ( ) {
528+ itertools:: Either :: Left ( format ! ( "\x1B ]8;;{url}\x1B \\ " ) )
529+ } else {
530+ itertools:: Either :: Right ( "" )
531+ }
532+ }
533+
534+ pub fn close ( & self ) -> impl fmt:: Display {
535+ if self . url . is_some ( ) {
536+ itertools:: Either :: Left ( "\x1B ]8;;\x1B \\ " )
537+ } else {
538+ itertools:: Either :: Right ( "" )
539+ }
540+ }
541+ }
542+
442543#[ cfg( unix) ]
443544mod imp {
444545 use super :: { Shell , TtyWidth } ;
0 commit comments