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