-- | 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 Prelude hiding (length, lines, splitAt) import Nix.Expr.Types.Annotated -- | 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) = "line " <> show row <> " column " <> show column