packages feed

snippet-0.1.0.3: src/Snippet/Pretty.hs

-- |
--
-- Module      : Snippet.Pretty
-- Copyright   : (c) 2026 Patrick Brisbin
-- License     : AGPL-3
-- Maintainer  : pbrisbin@gmail.com
-- Stability   : experimental
-- Portability : POSIX
module Snippet.Pretty
  ( Ann (..)
  , annToAnsi
  , prettySnippet
  , prettySnippetFill
  , snippetGutterWidth
  , snippetInsetWidth
  ) where

import Prelude

import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import Prettyprinter hiding (line)
import Snippet.Line
import Snippet.Pretty.Ann
import Snippet.Region
import Snippet.Snippet

-- | A 'Snippet's gutter fits the digits of its maximum (i.e. end) line number,
-- plus 1 character of whitespace.
snippetGutterWidth :: Snippet -> Int
snippetGutterWidth snippet = length (show @Int el) + 1
 where
  sl = snippet.region.startLine
  el = fromMaybe sl snippet.region.endLine

-- | A 'Snippet's inset matches how far the code is indented to support region
-- marking.
snippetInsetWidth :: Snippet -> Int
snippetInsetWidth = regionInsetWidth . (.region)

regionInsetWidth :: Region -> Int
regionInsetWidth region =
  go region + maybe 0 go region.subRegion
 where
  go r
    | maybe True (== r.startLine) r.endLine = 0
    | otherwise = 2

prettySnippet :: Snippet -> Doc Ann
prettySnippet snippet = prettySnippetFill w snippet
 where
  w = length (show @Int el) + 1
  sl = snippet.region.startLine
  el = fromMaybe sl snippet.region.endLine

-- | 'prettySnippet' but fill the left gutter to the given width
--
--
-- This is useful if multiple snippets are being rendered together and their
-- line-numbers are different numbers of digits. You can compute the maximum
-- width on the outside (see 'snippetGutterWidth') and pass it so they all
-- align.
--
-- For example:
--
-- @
-- >>> vsep [prettySnippet a, prettySnippet b]
--   ┌─ a
--   │
-- 1 │ ...
--   │
--    ┌─ b
--    │
-- 15 │ ...
--    │
-- @
--
--
-- @
-- >>> let w = max `on` snippetGutterWidth a b
-- >>> vsep [prettySnippetFill w a, prettySnippetFill w b]
--    ┌─
--    │
-- 1  │ ...
--    │
--    ┌─ b
--    │
-- 15 │ ...
--    │
-- @
prettySnippetFill :: Int -> Snippet -> Doc Ann
prettySnippetFill w snippet =
  vsep
    [ indent w
        $ annotate
          AnnDecoration
          (pretty $ "┌" <> T.replicate (snippetInsetWidth snippet) "─")
          <+> pretty (snippet.uri)
          <> ":"
          <> pretty snippet.region.startLine
          <> ":"
          <> pretty snippet.region.startColumn
    , indent w $ annotate AnnDecoration "│"
    , prettyLines w snippet.lines
    ]

prettyLines :: Int -> [Line] -> Doc Ann
prettyLines w = vsep . map (prettyLine w)

prettyLine :: Int -> Line -> Doc Ann
prettyLine w line = annotate AnnDecoration (number <> "│") <+> lineContent line
 where
  -- TODO: I'd prefer right-align
  number = fill w (maybe "" pretty $ lineNumber line)