@@ -70,6 +70,7 @@ module Text.DocLayout (
70
70
, magenta
71
71
, cyan
72
72
, white
73
+ , link
73
74
, empty
74
75
-- * Functions for concatenating documents
75
76
, (<+>)
@@ -151,6 +152,7 @@ data Doc a = Text Int a -- ^ Text with specified width.
151
152
| BlankLines Int -- ^ Ensure a number of blank lines.
152
153
| Concat (Doc a ) (Doc a ) -- ^ Two documents concatenated.
153
154
| Styled StyleReq (Doc a )
155
+ | Linked Text (Doc a ) -- ^ A hyperlink
154
156
| Empty
155
157
deriving (Show , Read , Eq , Ord , Functor , Foldable , Traversable ,
156
158
Data , Typeable , Generic )
@@ -263,6 +265,7 @@ data RenderState a = RenderState{
263
265
, column :: Int
264
266
, newlines :: Int -- ^ Number of preceding newlines
265
267
, fontStack :: [Font ]
268
+ , linkTarget :: Maybe Text -- ^ Current link target
266
269
}
267
270
268
271
peekFont :: RenderState a -> Font
@@ -276,9 +279,9 @@ newline = do
276
279
let rawpref = prefix st'
277
280
when (column st' == 0 && usePrefix st' && not (T. null rawpref)) $ do
278
281
let pref = fromString $ T. unpack $ T. dropWhileEnd isSpace rawpref
279
- modify $ \ st -> st{ output = Attr baseFont pref : output st
282
+ modify $ \ st -> st{ output = Attr Nothing baseFont pref : output st
280
283
, column = column st + realLength pref }
281
- modify $ \ st -> st { output = Attr baseFont " \n " : output st
284
+ modify $ \ st -> st { output = Attr Nothing baseFont " \n " : output st
282
285
, column = 0
283
286
, newlines = newlines st + 1
284
287
}
@@ -289,9 +292,9 @@ outp off s = do -- offset >= 0 (0 might be combining char)
289
292
let pref = if usePrefix st' then fromString $ T. unpack $ prefix st' else mempty
290
293
let font = peekFont st'
291
294
when (column st' == 0 && not (isNull pref && font == baseFont)) $
292
- modify $ \ st -> st{ output = Attr baseFont pref : output st
295
+ modify $ \ st -> st{ output = Attr Nothing baseFont pref : output st
293
296
, column = column st + realLength pref }
294
- modify $ \ st -> st{ output = Attr font s : output st
297
+ modify $ \ st -> st{ output = Attr (linkTarget st) font s : output st
295
298
, column = column st + off
296
299
, newlines = 0 }
297
300
@@ -302,22 +305,23 @@ render :: HasChars a => Maybe Int -> Doc a -> a
302
305
render = renderPlain
303
306
304
307
renderANSI :: HasChars a => Maybe Int -> Doc a -> TL. Text
305
- renderANSI n d = B. toLazyText $ snd $ go $ prerender n d where
306
- go (Attributed s) = foldl attrRender (baseFont, B. fromText " " ) s
308
+ renderANSI n d = B. toLazyText $ ( \ (_,_,o) -> o) $ go $ prerender n d where
309
+ go (Attributed s) = foldl attrRender (Nothing , baseFont, B. fromText " " ) s
307
310
308
311
renderPlain :: HasChars a => Maybe Int -> Doc a -> a
309
312
renderPlain n d = go $ prerender n d where
310
313
go (Attributed s) = foldMap attrStrip s
311
314
312
315
attrStrip :: HasChars a => Attr a -> a
313
- attrStrip (Attr _ y) | isNull y = " "
314
- | otherwise = y
316
+ attrStrip (Attr _ _ y) | isNull y = " "
317
+ | otherwise = y
315
318
316
- attrRender :: HasChars a => (Font , B. Builder ) -> Attr a -> (Font , B. Builder )
317
- attrRender (f, acc) (Attr g y)
318
- | isNull y = (f, acc)
319
- | otherwise = (g, acc <> B. fromText newFont <> build y)
319
+ attrRender :: HasChars a => (Link , Font , B. Builder ) -> Attr a -> (Link , Font , B. Builder )
320
+ attrRender (l, f, acc) (Attr m g y)
321
+ | isNull y = (l, f, acc)
322
+ | otherwise = (m, g, acc <> B. fromText newFont <> B. fromText newLink <> build y)
320
323
where
324
+ newLink = if l == m then mempty else renderOSC8 m
321
325
newFont = if f == g then mempty else renderFont g
322
326
323
327
prerender :: HasChars a => Maybe Int -> Doc a -> Attributed a
@@ -330,7 +334,8 @@ prerender linelen doc = fromList . reverse . output $
330
334
, lineLength = linelen
331
335
, column = 0
332
336
, newlines = 2
333
- , fontStack = [] }
337
+ , fontStack = []
338
+ , linkTarget = Nothing }
334
339
335
340
renderDoc :: HasChars a => Doc a -> DocState a
336
341
renderDoc = renderList . normalize . unfoldD
@@ -393,7 +398,7 @@ renderList (CookedText off s : xs) = do
393
398
let pref = if usePrefix st' then fromString $ T. unpack $ prefix st' else mempty
394
399
let elems (Attributed x) = reverse $ toList x
395
400
when (column st' == 0 && not (isNull pref)) $
396
- modify $ \ st -> st{ output = Attr baseFont pref : output st
401
+ modify $ \ st -> st{ output = Attr Nothing baseFont pref : output st
397
402
, column = column st + realLength pref }
398
403
modify $ \ st -> st{ output = elems s ++ output st
399
404
, column = column st + off
@@ -409,6 +414,18 @@ renderList (Styled style doc : xs) = do
409
414
modify $ \ s -> s{ fontStack = fontStack st, output = output s }
410
415
renderList xs
411
416
417
+ -- Nested links are nonsensical, we only handle the outermost and
418
+ -- silently ignore any attempts to have a link inside a link
419
+ renderList (Linked target doc : xs) = do
420
+ st <- get
421
+ case linkTarget st of
422
+ Nothing -> do
423
+ modify $ \ s -> s{linkTarget = Just target}
424
+ renderDoc doc
425
+ modify $ \ s -> s{linkTarget = Nothing }
426
+ _ -> renderDoc doc
427
+ renderList xs
428
+
412
429
renderList (Prefixed pref d : xs) = do
413
430
st <- get
414
431
let oldPref = prefix st
@@ -477,7 +494,7 @@ renderList (b : xs) | isBlock b = do
477
494
heightOf _ = 1
478
495
let maxheight = maximum $ map heightOf (b: bs)
479
496
let toBlockSpec (Block w ls) = (w, ls)
480
- toBlockSpec (VFill w t) = (w, map (singleton . (Attr font)) (take maxheight $ repeat t))
497
+ toBlockSpec (VFill w t) = (w, map (singleton . (Attr (linkTarget st) font)) (take maxheight $ repeat t))
481
498
toBlockSpec _ = (0 , [] )
482
499
let (_, lns') = foldl (mergeBlocks maxheight) (toBlockSpec b)
483
500
(map toBlockSpec bs)
@@ -522,6 +539,7 @@ startsBlank (BlankLines _) = True
522
539
startsBlank (Concat Empty y) = startsBlank y
523
540
startsBlank (Concat x _) = startsBlank x
524
541
startsBlank (Styled _ x) = startsBlank x
542
+ startsBlank (Linked _ x) = startsBlank x
525
543
startsBlank Empty = True
526
544
527
545
isBlock :: Doc a -> Bool
@@ -639,6 +657,7 @@ getOffset breakWhen (!l, !c) x =
639
657
CookedText n _ -> (l, c + n)
640
658
Empty -> (l, c)
641
659
Styled _ d -> getOffset breakWhen (l, c) d
660
+ Linked _ d -> getOffset breakWhen (l, c) d
642
661
CarriageReturn -> (max l c, 0 )
643
662
NewLine -> (max l c, 0 )
644
663
BlankLines _ -> (max l c, 0 )
@@ -805,6 +824,9 @@ cyan = Cyan
805
824
white :: Color
806
825
white = White
807
826
827
+ link :: HasChars a => Text -> Doc a -> Doc a
828
+ link = Linked
829
+
808
830
-- | Returns width of a character in a monospace font: 0 for a combining
809
831
-- character, 1 for a regular character, 2 for an East Asian wide character.
810
832
-- Ambiguous characters are treated as width 1.
0 commit comments