@@ -60,18 +60,20 @@ fn next_bytes(
6060 return None ;
6161 }
6262
63- let style = capture. ready . unwrap_or ( capture. style ) ;
63+ let ( style, url ) = capture. ready . clone ( ) . unwrap_or ( ( capture. style , None ) ) ;
6464 Some ( Element {
6565 text : std:: mem:: take ( & mut capture. printable ) ,
6666 style,
67+ url,
6768 } )
6869}
6970
7071#[ derive( Default , Clone , Debug , PartialEq , Eq ) ]
7172struct AnsiCapture {
7273 style : anstyle:: Style ,
7374 printable : String ,
74- ready : Option < anstyle:: Style > ,
75+ hyperlink : Option < String > ,
76+ ready : Option < ( anstyle:: Style , Option < String > ) > ,
7577}
7678
7779impl AnsiCapture {
@@ -265,16 +267,56 @@ impl anstyle_parse::Perform for AnsiCapture {
265267 }
266268
267269 if style != self . style && !self . printable . is_empty ( ) {
268- self . ready = Some ( self . style ) ;
270+ if self . hyperlink . is_some ( ) {
271+ self . ready = Some ( ( self . style , self . hyperlink . clone ( ) ) ) ;
272+ } else {
273+ self . ready = Some ( ( self . style , None ) ) ;
274+ }
269275 }
270276 self . style = style;
271277 }
278+
279+ fn osc_dispatch ( & mut self , params : & [ & [ u8 ] ] , _bell_terminated : bool ) {
280+ let mut state = OscState :: Normal ;
281+ for value in params {
282+ match ( state, value) {
283+ ( OscState :: Normal , & [ b'8' ] ) => {
284+ state = OscState :: HyperlinkParams ;
285+ }
286+ ( OscState :: HyperlinkParams , _) => {
287+ state = OscState :: HyperlinkUri ;
288+ }
289+ ( OscState :: HyperlinkUri , & [ ] ) => {
290+ if self . hyperlink . is_some ( ) {
291+ self . ready = Some ( ( self . style , std:: mem:: take ( & mut self . hyperlink ) ) ) ;
292+ }
293+ break ;
294+ }
295+ ( OscState :: HyperlinkUri , uri) => {
296+ let hyperlink = uri. iter ( ) . map ( |b| * b as char ) . collect :: < String > ( ) ;
297+ self . hyperlink = Some ( hyperlink) ;
298+
299+ // Any current text in `self.printable` needs to be
300+ // rendered, so it doesn't get confused with Hyperlink text
301+ if !self . printable . is_empty ( ) {
302+ self . ready = Some ( ( self . style , None ) ) ;
303+ }
304+ break ;
305+ }
306+
307+ _ => {
308+ break ;
309+ }
310+ }
311+ }
312+ }
272313}
273314
274315#[ derive( Clone , Debug , PartialEq , Eq ) ]
275316pub ( crate ) struct Element {
276317 pub ( crate ) text : String ,
277318 pub ( crate ) style : anstyle:: Style ,
319+ pub ( crate ) url : Option < String > ,
278320}
279321
280322#[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
@@ -286,6 +328,13 @@ enum CsiState {
286328 Underline ,
287329}
288330
331+ #[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
332+ enum OscState {
333+ Normal ,
334+ HyperlinkParams ,
335+ HyperlinkUri ,
336+ }
337+
289338#[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
290339enum ColorTarget {
291340 Fg ,
@@ -334,10 +383,12 @@ mod test {
334383 Element {
335384 text: "Hello" . to_owned( ) ,
336385 style: green_on_red,
386+ url: None ,
337387 } ,
338388 Element {
339389 text: " world!" . to_owned( ) ,
340390 style: anstyle:: Style :: default ( ) ,
391+ url: None ,
341392 } ,
342393 ] ;
343394 verify ( & input, expected) ;
@@ -351,14 +402,17 @@ mod test {
351402 Element {
352403 text: "Hello " . to_owned( ) ,
353404 style: anstyle:: Style :: default ( ) ,
405+ url: None ,
354406 } ,
355407 Element {
356408 text: "world" . to_owned( ) ,
357409 style: green_on_red,
410+ url: None ,
358411 } ,
359412 Element {
360413 text: "!" . to_owned( ) ,
361414 style: anstyle:: Style :: default ( ) ,
415+ url: None ,
362416 } ,
363417 ] ;
364418 verify ( & input, expected) ;
@@ -372,10 +426,12 @@ mod test {
372426 Element {
373427 text: "Hello " . to_owned( ) ,
374428 style: anstyle:: Style :: default ( ) ,
429+ url: None ,
375430 } ,
376431 Element {
377432 text: "world!" . to_owned( ) ,
378433 style: green_on_red,
434+ url: None ,
379435 } ,
380436 ] ;
381437 verify ( & input, expected) ;
@@ -390,14 +446,17 @@ mod test {
390446 Element {
391447 text: "Hello " . to_owned( ) ,
392448 style: anstyle:: Style :: default ( ) ,
449+ url: None ,
393450 } ,
394451 Element {
395452 text: "world" . to_owned( ) ,
396453 style: ansi_11,
454+ url: None ,
397455 } ,
398456 Element {
399457 text: "!" . to_owned( ) ,
400458 style: anstyle:: Style :: default ( ) ,
459+ url: None ,
401460 } ,
402461 ] ;
403462 verify ( & input, expected) ;
@@ -414,10 +473,12 @@ mod test {
414473 Element {
415474 text: "Hello" . to_owned( ) ,
416475 style: green_on_red,
476+ url: Some ( URL . to_owned( ) ) ,
417477 } ,
418478 Element {
419479 text: " world!" . to_owned( ) ,
420480 style: anstyle:: Style :: default ( ) ,
481+ url: None ,
421482 } ,
422483 ] ;
423484 verify ( & input, expected) ;
@@ -434,14 +495,17 @@ mod test {
434495 Element {
435496 text: "Hello " . to_owned( ) ,
436497 style: anstyle:: Style :: default ( ) ,
498+ url: None ,
437499 } ,
438500 Element {
439501 text: "world" . to_owned( ) ,
440502 style: green_on_red,
503+ url: Some ( URL . to_owned( ) ) ,
441504 } ,
442505 Element {
443506 text: "!" . to_owned( ) ,
444507 style: anstyle:: Style :: default ( ) ,
508+ url: None ,
445509 } ,
446510 ] ;
447511 verify ( & input, expected) ;
@@ -458,10 +522,12 @@ mod test {
458522 Element {
459523 text: "Hello " . to_owned( ) ,
460524 style: anstyle:: Style :: default ( ) ,
525+ url: None ,
461526 } ,
462527 Element {
463528 text: "world!" . to_owned( ) ,
464529 style: green_on_red,
530+ url: Some ( URL . to_owned( ) ) ,
465531 } ,
466532 ] ;
467533 verify ( & input, expected) ;
@@ -476,14 +542,17 @@ mod test {
476542 Element {
477543 text: "Hello " . to_owned( ) ,
478544 style: anstyle:: Style :: default ( ) ,
545+ url: None ,
479546 } ,
480547 Element {
481548 text: "world" . to_owned( ) ,
482549 style: ansi_11,
550+ url: Some ( URL . to_owned( ) ) ,
483551 } ,
484552 Element {
485553 text: "!" . to_owned( ) ,
486554 style: anstyle:: Style :: default ( ) ,
555+ url: None ,
487556 } ,
488557 ] ;
489558 verify ( & input, expected) ;
@@ -498,14 +567,17 @@ mod test {
498567 Element {
499568 text: "Hello " . to_owned( ) ,
500569 style: anstyle:: Style :: default ( ) ,
570+ url: Some ( URL . to_owned( ) ) ,
501571 } ,
502572 Element {
503573 text: "world" . to_owned( ) ,
504574 style: green_on_red,
575+ url: Some ( URL . to_owned( ) ) ,
505576 } ,
506577 Element {
507578 text: "!" . to_owned( ) ,
508579 style: anstyle:: Style :: default ( ) ,
580+ url: Some ( URL . to_owned( ) ) ,
509581 } ,
510582 ] ;
511583 verify ( & input, expected) ;
@@ -522,10 +594,12 @@ mod test {
522594 Element {
523595 text: "Hello" . to_owned( ) ,
524596 style: green_on_red,
597+ url: None ,
525598 } ,
526599 Element {
527600 text: " world!" . to_owned( ) ,
528601 style: anstyle:: Style :: default ( ) ,
602+ url: None ,
529603 } ,
530604 ] ;
531605 verify ( & input, expected) ;
@@ -541,6 +615,7 @@ mod test {
541615 vec![ Element {
542616 text: s. clone( ) ,
543617 style: anstyle:: Style :: default ( ) ,
618+ url: None ,
544619 } ]
545620 } ;
546621 let mut state = AnsiBytes :: new( ) ;
0 commit comments