packages feed

snippet-0.0.0.0: src/Snippet/Lines.hs

-- |
--
-- Module      : Snippet.Lines
-- Copyright   : (c) 2026 Patrick Brisbin
-- License     : AGPL-3
-- Maintainer  : pbrisbin@gmail.com
-- Stability   : experimental
-- Portability : POSIX
module Snippet.Lines
  ( makeLines
  , makeLines1
  , makeLinesN
  ) where

import Prelude

import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NE
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Snippet.Line
import Snippet.Region

makeLines :: Region -> NonEmpty Text -> NonEmpty Line
makeLines region input = case NE.length input of
  1 -> makeLines1 region input
  _ -> makeLinesN region input

-- | Make 'Line's for the one-line case:
--
-- @
-- 85 │       , ((annotate AnnIcon "=" \<+\>) . pretty) \<$\> r.fullDescription
--    │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- @
makeLines1 :: Region -> NonEmpty Text -> NonEmpty Line
makeLines1 region input = headLine :| [underbarLine]
 where
  headLine =
    Line
      { number = Just sl
      , pre
      , focused
      , post
      }

  underbarLine =
    Line
      { number = Nothing
      , pre = T.replicate (sc - 1) " "
      , focused = T.replicate (ec - sc) "^"
      , post = ""
      }

  (pre, focused, post) = focusLine sc (Just ec) $ NE.head input

  sl = region.startLine
  sc = fromMaybe 1 region.startColumn
  ec = fromMaybe (defaultEndColumn input) region.endColumn

-- | Make 'Line's for multi-line case
--
-- If on column 1:
--
-- @
-- 15 │ ╭ data ArtifactLocation = ArtifactLocation
-- 16 │ │   { uri :: Text
-- 17 │ │   }
-- 18 │ │   deriving stock (Eq)
--    │ ╰─────────────────────^
-- @
--
-- If not on column 1:
--
-- @
-- 15 │ data ArtifactLocation = ArtifactLocation
--    │ ╭────^
-- 16 │ │   { uri :: Text
-- 17 │ │   }
-- 18 │ │   deriving stock (Eq)
--    │ ╰─────────────────────^
-- @
makeLinesN :: Region -> NonEmpty Text -> NonEmpty Line
makeLinesN region input
  | sc == 1 =
      Line
        { number = Just sl
        , pre = ""
        , focused = "╭ " <> NE.head input
        , post = ""
        }
        :| tailLines
  | otherwise =
      let (pre, focused, post) = focusLine sc Nothing $ NE.head input
      in  Line
            { number = Just sl
            , pre = "  " <> pre
            , focused
            , post
            }
            :| Line
              { number = Nothing
              , pre = ""
              , focused = "╭" <> T.replicate sc "─" <> "^"
              , post = ""
              }
            : tailLines
 where
  tailLines = zipWith tailLine [sl + 1 ..] (NE.tail input) <> [underbarLine]

  tailLine n x =
    Line
      { number = Just n
      , pre = ""
      , focused = "│ " <> x
      , post = ""
      }

  underbarLine =
    Line
      { number = Nothing
      , pre = ""
      , focused = "╰─" <> T.replicate (ec - 2) "─" <> "^"
      , post = ""
      }

  sl = region.startLine
  sc = fromMaybe 1 region.startColumn
  ec = fromMaybe (defaultEndColumn input) region.endColumn

focusLine :: Int -> Maybe Int -> Text -> (Text, Text, Text)
focusLine sc mec t = (pre, marked, post)
 where
  (pre, rest) = T.splitAt (sc - 1) t
  (marked, post) = case mec of
    Nothing -> (rest, "")
    Just ec -> T.splitAt (ec - sc) rest

defaultEndColumn :: NonEmpty Text -> Int
defaultEndColumn = (+ 1) . T.length . NE.last