@@ -32,7 +32,7 @@ pub(crate) struct AnsiBytesIter<'s> {
3232}
3333
3434impl Iterator for AnsiBytesIter < ' _ > {
35- type Item = ( anstyle :: Style , String ) ;
35+ type Item = Element ;
3636
3737 #[ inline]
3838 fn next ( & mut self ) -> Option < Self :: Item > {
@@ -45,7 +45,7 @@ fn next_bytes(
4545 bytes : & mut & [ u8 ] ,
4646 parser : & mut anstyle_parse:: Parser ,
4747 capture : & mut AnsiCapture ,
48- ) -> Option < ( anstyle :: Style , String ) > {
48+ ) -> Option < Element > {
4949 capture. reset ( ) ;
5050 while capture. ready . is_none ( ) {
5151 let byte = if let Some ( ( byte, remainder) ) = ( * bytes) . split_first ( ) {
@@ -59,16 +59,20 @@ fn next_bytes(
5959 if capture. printable . is_empty ( ) {
6060 return None ;
6161 }
62-
63- let style = capture. ready . unwrap_or ( capture. style ) ;
64- Some ( ( style, std:: mem:: take ( & mut capture. printable ) ) )
62+ let ( style, url) = capture. ready . clone ( ) . unwrap_or ( ( capture. style , None ) ) ;
63+ Some ( Element {
64+ text : std:: mem:: take ( & mut capture. printable ) ,
65+ style,
66+ url,
67+ } )
6568}
6669
6770#[ derive( Default , Clone , Debug , PartialEq , Eq ) ]
6871struct AnsiCapture {
6972 style : anstyle:: Style ,
7073 printable : String ,
71- ready : Option < anstyle:: Style > ,
74+ hyperlink : String ,
75+ ready : Option < ( anstyle:: Style , Option < String > ) > ,
7276}
7377
7478impl AnsiCapture {
@@ -262,10 +266,57 @@ impl anstyle_parse::Perform for AnsiCapture {
262266 }
263267
264268 if style != self . style && !self . printable . is_empty ( ) {
265- self . ready = Some ( self . style ) ;
269+ if self . hyperlink . is_empty ( ) {
270+ self . ready = Some ( ( self . style , None ) ) ;
271+ } else {
272+ self . ready = Some ( ( self . style , Some ( self . hyperlink . clone ( ) ) ) ) ;
273+ }
266274 }
267275 self . style = style;
268276 }
277+
278+ fn osc_dispatch ( & mut self , params : & [ & [ u8 ] ] , _bell_terminated : bool ) {
279+ let mut state = OscState :: Normal ;
280+ for value in params {
281+ match ( state, value) {
282+ ( OscState :: Normal , & [ b'8' ] ) => {
283+ state = OscState :: HyperlinkParams ;
284+ }
285+ ( OscState :: HyperlinkParams , _) => {
286+ state = OscState :: HyperlinkUri ;
287+ }
288+ ( OscState :: HyperlinkUri , & [ ] ) => {
289+ if !self . hyperlink . is_empty ( ) {
290+ self . ready = Some ( ( self . style , Some ( std:: mem:: take ( & mut self . hyperlink ) ) ) ) ;
291+ }
292+ break ;
293+ }
294+ ( OscState :: HyperlinkUri , uri) => {
295+ for b in uri. iter ( ) {
296+ self . hyperlink . push ( * b as char ) ;
297+ }
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+ }
313+ }
314+
315+ #[ derive( Clone , Debug , PartialEq , Eq ) ]
316+ pub ( crate ) struct Element {
317+ pub ( crate ) text : String ,
318+ pub ( crate ) style : anstyle:: Style ,
319+ pub ( crate ) url : Option < String > ,
269320}
270321
271322#[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
@@ -277,6 +328,13 @@ enum CsiState {
277328 Underline ,
278329}
279330
331+ #[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
332+ enum OscState {
333+ Normal ,
334+ HyperlinkParams ,
335+ HyperlinkUri ,
336+ }
337+
280338#[ derive( Copy , Clone , PartialEq , Eq , Debug ) ]
281339enum ColorTarget {
282340 Fg ,
@@ -306,11 +364,8 @@ mod test {
306364 const URL : & str = "https://example.com" ;
307365
308366 #[ track_caller]
309- fn verify ( input : & str , expected : Vec < ( anstyle:: Style , & str ) > ) {
310- let expected = expected
311- . into_iter ( )
312- . map ( |( style, value) | ( style, value. to_owned ( ) ) )
313- . collect :: < Vec < _ > > ( ) ;
367+ fn verify ( input : & str , expected : Vec < Element > ) {
368+ let expected = expected. into_iter ( ) . collect :: < Vec < _ > > ( ) ;
314369 let mut state = AnsiBytes :: new ( ) ;
315370 let actual = state. extract_next ( input. as_bytes ( ) ) . collect :: < Vec < _ > > ( ) ;
316371 assert_eq ! ( expected, actual, "{input:?}" ) ;
@@ -325,8 +380,16 @@ mod test {
325380 let green_on_red = anstyle:: AnsiColor :: Green . on ( anstyle:: AnsiColor :: Red ) ;
326381 let input = format ! ( "{green_on_red}Hello{green_on_red:#} world!" ) ;
327382 let expected = vec ! [
328- ( green_on_red, "Hello" ) ,
329- ( anstyle:: Style :: default ( ) , " world!" ) ,
383+ Element {
384+ text: "Hello" . to_string( ) ,
385+ style: green_on_red,
386+ url: None ,
387+ } ,
388+ Element {
389+ text: " world!" . to_string( ) ,
390+ style: anstyle:: Style :: default ( ) ,
391+ url: None ,
392+ } ,
330393 ] ;
331394 verify ( & input, expected) ;
332395 }
@@ -336,9 +399,21 @@ mod test {
336399 let green_on_red = anstyle:: AnsiColor :: Green . on ( anstyle:: AnsiColor :: Red ) ;
337400 let input = format ! ( "Hello {green_on_red}world{green_on_red:#}!" ) ;
338401 let expected = vec ! [
339- ( anstyle:: Style :: default ( ) , "Hello " ) ,
340- ( green_on_red, "world" ) ,
341- ( anstyle:: Style :: default ( ) , "!" ) ,
402+ Element {
403+ text: "Hello " . to_string( ) ,
404+ style: anstyle:: Style :: default ( ) ,
405+ url: None ,
406+ } ,
407+ Element {
408+ text: "world" . to_string( ) ,
409+ style: green_on_red,
410+ url: None ,
411+ } ,
412+ Element {
413+ text: "!" . to_string( ) ,
414+ style: anstyle:: Style :: default ( ) ,
415+ url: None ,
416+ } ,
342417 ] ;
343418 verify ( & input, expected) ;
344419 }
@@ -348,8 +423,16 @@ mod test {
348423 let green_on_red = anstyle:: AnsiColor :: Green . on ( anstyle:: AnsiColor :: Red ) ;
349424 let input = format ! ( "Hello {green_on_red}world!{green_on_red:#}" ) ;
350425 let expected = vec ! [
351- ( anstyle:: Style :: default ( ) , "Hello " ) ,
352- ( green_on_red, "world!" ) ,
426+ Element {
427+ text: "Hello " . to_string( ) ,
428+ style: anstyle:: Style :: default ( ) ,
429+ url: None ,
430+ } ,
431+ Element {
432+ text: "world!" . to_string( ) ,
433+ style: green_on_red,
434+ url: None ,
435+ } ,
353436 ] ;
354437 verify ( & input, expected) ;
355438 }
@@ -360,9 +443,21 @@ mod test {
360443 // termcolor only supports "brights" via these
361444 let input = format ! ( "Hello {ansi_11}world{ansi_11:#}!" ) ;
362445 let expected = vec ! [
363- ( anstyle:: Style :: default ( ) , "Hello " ) ,
364- ( ansi_11, "world" ) ,
365- ( anstyle:: Style :: default ( ) , "!" ) ,
446+ Element {
447+ text: "Hello " . to_string( ) ,
448+ style: anstyle:: Style :: default ( ) ,
449+ url: None ,
450+ } ,
451+ Element {
452+ text: "world" . to_string( ) ,
453+ style: ansi_11,
454+ url: None ,
455+ } ,
456+ Element {
457+ text: "!" . to_string( ) ,
458+ style: anstyle:: Style :: default ( ) ,
459+ url: None ,
460+ } ,
366461 ] ;
367462 verify ( & input, expected) ;
368463 }
@@ -375,8 +470,16 @@ mod test {
375470 hyperlink( "Hello" , URL )
376471 ) ;
377472 let expected = vec ! [
378- ( green_on_red, "Hello" ) ,
379- ( anstyle:: Style :: default ( ) , " world!" ) ,
473+ Element {
474+ text: "Hello" . to_string( ) ,
475+ style: green_on_red,
476+ url: Some ( URL . to_owned( ) ) ,
477+ } ,
478+ Element {
479+ text: " world!" . to_string( ) ,
480+ style: anstyle:: Style :: default ( ) ,
481+ url: None ,
482+ } ,
380483 ] ;
381484 verify ( & input, expected) ;
382485 }
@@ -389,9 +492,21 @@ mod test {
389492 hyperlink( "world" , URL )
390493 ) ;
391494 let expected = vec ! [
392- ( anstyle:: Style :: default ( ) , "Hello " ) ,
393- ( green_on_red, "world" ) ,
394- ( anstyle:: Style :: default ( ) , "!" ) ,
495+ Element {
496+ text: "Hello " . to_string( ) ,
497+ style: anstyle:: Style :: default ( ) ,
498+ url: None ,
499+ } ,
500+ Element {
501+ text: "world" . to_string( ) ,
502+ style: green_on_red,
503+ url: Some ( URL . to_owned( ) ) ,
504+ } ,
505+ Element {
506+ text: "!" . to_string( ) ,
507+ style: anstyle:: Style :: default ( ) ,
508+ url: None ,
509+ } ,
395510 ] ;
396511 verify ( & input, expected) ;
397512 }
@@ -404,8 +519,16 @@ mod test {
404519 hyperlink( "world!" , URL )
405520 ) ;
406521 let expected = vec ! [
407- ( anstyle:: Style :: default ( ) , "Hello " ) ,
408- ( green_on_red, "world!" ) ,
522+ Element {
523+ text: "Hello " . to_string( ) ,
524+ style: anstyle:: Style :: default ( ) ,
525+ url: None ,
526+ } ,
527+ Element {
528+ text: "world!" . to_string( ) ,
529+ style: green_on_red,
530+ url: Some ( URL . to_owned( ) ) ,
531+ } ,
409532 ] ;
410533 verify ( & input, expected) ;
411534 }
@@ -416,9 +539,21 @@ mod test {
416539 // termcolor only supports "brights" via these
417540 let input = format ! ( "Hello {ansi_11}{}{ansi_11:#}!" , hyperlink( "world" , URL ) ) ;
418541 let expected = vec ! [
419- ( anstyle:: Style :: default ( ) , "Hello " ) ,
420- ( ansi_11, "world" ) ,
421- ( anstyle:: Style :: default ( ) , "!" ) ,
542+ Element {
543+ text: "Hello " . to_string( ) ,
544+ style: anstyle:: Style :: default ( ) ,
545+ url: None ,
546+ } ,
547+ Element {
548+ text: "world" . to_string( ) ,
549+ style: ansi_11,
550+ url: Some ( URL . to_owned( ) ) ,
551+ } ,
552+ Element {
553+ text: "!" . to_string( ) ,
554+ style: anstyle:: Style :: default ( ) ,
555+ url: None ,
556+ } ,
422557 ] ;
423558 verify ( & input, expected) ;
424559 }
@@ -429,9 +564,21 @@ mod test {
429564 let styled_str = format ! ( "Hello {green_on_red}world{green_on_red:#}!" ) ;
430565 let input = hyperlink ( & styled_str, URL ) ;
431566 let expected = vec ! [
432- ( anstyle:: Style :: default ( ) , "Hello " ) ,
433- ( green_on_red, "world" ) ,
434- ( anstyle:: Style :: default ( ) , "!" ) ,
567+ Element {
568+ text: "Hello " . to_string( ) ,
569+ style: anstyle:: Style :: default ( ) ,
570+ url: Some ( URL . to_owned( ) ) ,
571+ } ,
572+ Element {
573+ text: "world" . to_string( ) ,
574+ style: green_on_red,
575+ url: Some ( URL . to_owned( ) ) ,
576+ } ,
577+ Element {
578+ text: "!" . to_string( ) ,
579+ style: anstyle:: Style :: default ( ) ,
580+ url: Some ( URL . to_owned( ) ) ,
581+ } ,
435582 ] ;
436583 verify ( & input, expected) ;
437584 }
@@ -444,8 +591,16 @@ mod test {
444591 hyperlink( "Hello" , "" )
445592 ) ;
446593 let expected = vec ! [
447- ( green_on_red, "Hello" ) ,
448- ( anstyle:: Style :: default ( ) , " world!" ) ,
594+ Element {
595+ text: "Hello" . to_string( ) ,
596+ style: green_on_red,
597+ url: None ,
598+ } ,
599+ Element {
600+ text: " world!" . to_string( ) ,
601+ style: anstyle:: Style :: default ( ) ,
602+ url: None ,
603+ } ,
449604 ] ;
450605 verify ( & input, expected) ;
451606 }
@@ -457,7 +612,11 @@ mod test {
457612 let expected = if s. is_empty( ) {
458613 vec![ ]
459614 } else {
460- vec![ ( anstyle:: Style :: default ( ) , s. clone( ) ) ]
615+ vec![ Element {
616+ text: s. clone( ) ,
617+ style: anstyle:: Style :: default ( ) ,
618+ url: None ,
619+ } ]
461620 } ;
462621 let mut state = AnsiBytes :: new( ) ;
463622 let actual = state. extract_next( s. as_bytes( ) ) . collect:: <Vec <_>>( ) ;
0 commit comments