@@ -6,6 +6,7 @@ use anstream::AutoStream;
6
6
use anstyle:: Style ;
7
7
8
8
use crate :: util:: errors:: CargoResult ;
9
+ use crate :: util:: hostname;
9
10
use crate :: util:: style:: * ;
10
11
11
12
pub enum TtyWidth {
@@ -57,6 +58,7 @@ pub struct Shell {
57
58
/// Flag that indicates the current line needs to be cleared before
58
59
/// printing. Used when a progress bar is currently displayed.
59
60
needs_clear : bool ,
61
+ hostname : Option < String > ,
60
62
}
61
63
62
64
impl fmt:: Debug for Shell {
@@ -85,6 +87,7 @@ enum ShellOut {
85
87
stderr : AutoStream < std:: io:: Stderr > ,
86
88
stderr_tty : bool ,
87
89
color_choice : ColorChoice ,
90
+ hyperlinks : bool ,
88
91
} ,
89
92
}
90
93
@@ -111,10 +114,12 @@ impl Shell {
111
114
stdout : AutoStream :: new ( std:: io:: stdout ( ) , stdout_choice) ,
112
115
stderr : AutoStream :: new ( std:: io:: stderr ( ) , stderr_choice) ,
113
116
color_choice : auto_clr,
117
+ hyperlinks : supports_hyperlinks:: supports_hyperlinks ( ) ,
114
118
stderr_tty : std:: io:: stderr ( ) . is_terminal ( ) ,
115
119
} ,
116
120
verbosity : Verbosity :: Verbose ,
117
121
needs_clear : false ,
122
+ hostname : None ,
118
123
}
119
124
}
120
125
@@ -124,6 +129,7 @@ impl Shell {
124
129
output : ShellOut :: Write ( AutoStream :: never ( out) ) , // strip all formatting on write
125
130
verbosity : Verbosity :: Verbose ,
126
131
needs_clear : false ,
132
+ hostname : None ,
127
133
}
128
134
}
129
135
@@ -314,6 +320,16 @@ impl Shell {
314
320
Ok ( ( ) )
315
321
}
316
322
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
+
317
333
/// Gets the current color choice.
318
334
///
319
335
/// If we are not using a color stream, this will always return `Never`, even if the color
@@ -340,6 +356,63 @@ impl Shell {
340
356
}
341
357
}
342
358
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
+
343
416
/// Prints a message to stderr and translates ANSI escape code into console colors.
344
417
pub fn print_ansi_stderr ( & mut self , message : & [ u8 ] ) -> CargoResult < ( ) > {
345
418
if self . needs_clear {
@@ -439,6 +512,34 @@ fn supports_color(choice: anstream::ColorChoice) -> bool {
439
512
}
440
513
}
441
514
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
+
442
543
#[ cfg( unix) ]
443
544
mod imp {
444
545
use super :: { Shell , TtyWidth } ;
0 commit comments