content: 
  -- | This module deals with updating spans of characters in values of type Text.
  --
  -- It defines some helper types and functions to apply these "updates".
  
  module Update.Span
    ( SpanUpdate(..)
    , SrcSpan(..)
    , SourcePos(..)
    , updateSpan
    , updateSpans
    , linearizeSourcePos
    , prettyPrintSourcePos
    , split
    ) where
  
  import           Control.Exception              ( assert )
  import           Data.Data                      ( Data )
  import           Data.Int                       ( Int64 )
  import           Data.List                      ( genericTake
                                                  , sortOn
                                                  )
  import           Data.Text                      ( Text
                                                  , length
                                                  , lines
                                                  , splitAt
                                                  )
  import           Nix.Expr.Types.Annotated
  import           Prelude                 hiding ( length
                                                  , lines
                                                  , splitAt
                                                  )
  
  -- | A span and some text to replace it with.
  -- They don't have to be the same length.
  data SpanUpdate = SpanUpdate
    { spanUpdateSpan     :: SrcSpan
    , spanUpdateContents :: Text
    }
    deriving (Show, Data)
  
  -- | Update many spans in a file. They must be non-overlapping.
  updateSpans :: [SpanUpdate] -> Text -> Text
  updateSpans us t =
    let sortedSpans = sortOn (spanBegin . spanUpdateSpan) us
        anyOverlap =
          any (uncurry overlaps) (zip <*> tail $ spanUpdateSpan <$> sortedSpans)
    in  assert (not anyOverlap) (foldr updateSpan t sortedSpans)
  
  -- | Update a single span of characters inside a text value. If you're updating
  -- multiples spans it's best to use 'updateSpans'.
  updateSpan :: SpanUpdate -> Text -> Text
  updateSpan (SpanUpdate (SrcSpan b e) r) t =
    let (before, _  ) = split b t
        (_     , end) = split e t
    in  before <> r <> end
  
  -- | Do two spans overlap
  overlaps :: SrcSpan -> SrcSpan -> Bool
  overlaps (SrcSpan b1 e1) (SrcSpan b2 e2) =
    b2 >= b1 && b2 < e1 || e2 >= b1 && e2 < e1
  
  -- | Split some text at a particular 'SourcePos'
  split :: SourcePos -> Text -> (Text, Text)
  split (SourcePos _ row col) t = splitAt
    (fromIntegral
      (linearizeSourcePos t
                          (fromIntegral (unPos row - 1))
                          (fromIntegral (unPos col - 1))
      )
    )
    t
  
  -- | Go from a line and column representation to a single character offset from
  -- the beginning of the text.
  --
  -- This probably fails on crazy texts with multi character line breaks.
  linearizeSourcePos
    :: Text -- ^ The string to linearize in
    -> Int64 -- ^ The line offset
    -> Int64 -- ^ The column offset
    -> Int64 -- ^ The character offset
  linearizeSourcePos t l c = fromIntegral lineCharOffset + c
   where
    lineCharOffset = sum . fmap ((+ 1) . length) . genericTake l . lines $ t
  
  prettyPrintSourcePos :: SourcePos -> String
  prettyPrintSourcePos (SourcePos _ row column) =
    show (unPos row) <> ":" <> show (unPos column)
  