boilerplate-0.0.2: library/Boilerplate/Doc.hs
module Boilerplate.Doc (Doc, mkDoc, unDoc, upsert, upsertMany) where
import Data.Text (Text)
import qualified Data.Text as T
import Data.Vector (Vector)
import qualified Data.Vector as V
import HsInspect.Types (Pos(..))
newtype Doc = Doc (Vector Text)
mkDoc :: Text -> Doc
mkDoc = Doc . V.fromList . T.lines
unDoc :: Doc -> Text
unDoc (Doc txt) = T.unlines . V.toList $ txt
-- from just after Pos to just before Pos, 1 indexed rows and columns
upsert :: Doc -> Pos -> Maybe Pos -> Text -> Doc
upsert (Doc lines') (Pos sline scol) to txt =
Doc $ case splitAt' (sline - 1) lines' of
(before, Nothing) -> V.snoc before txt
(before, Just (line, after)) ->
let (before', after') = T.splitAt scol line
in case to of
Nothing -> before <> V.cons (T.concat [before', txt, after']) after
Just (Pos elin ecol) -> case splitAt' (elin - 1) lines' of
(_, Nothing) -> V.snoc before $ T.concat [before', txt]
(_, Just (line', after'')) ->
let (_, after''') = T.splitAt (ecol - 1) line'
in before <> V.cons (T.concat [before', txt, after''']) after''
splitAt' :: Int -> Vector a -> (Vector a, Maybe (a, Vector a))
splitAt' i v =
let (before, after) = V.splitAt i v
in if V.null after
then (before, Nothing)
else (before, Just (V.head after, V.tail after))
-- applies all the upserts in reverse order, if the regions do not overlap and
-- are ascending then this amounts to applying all the upserts in parallel.
upsertMany :: Doc -> [(Pos, Maybe Pos, Text)] -> Doc
upsertMany = foldr (\(from, to, txt) acc -> upsert acc from to txt)