{-# LANGUAGE BangPatterns #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE NamedFieldPuns #-} -- | Simple etags formatter. See -- module GhcTags.ETag.Formatter ( withByteOffset , formatETagsFile , formatTagsFile , formatTag , BuilderWithSize (..) ) where import qualified Data.ByteString as BS import Data.ByteString.Builder (Builder) import qualified Data.ByteString.Builder as BB import Data.List (groupBy) import Data.Function (on) import Data.Foldable (foldl') import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import GhcTags.Tag -- | A product of two monoids: 'Builder' and 'Sum'. -- data BuilderWithSize = BuilderWithSize { builder :: Builder, builderSize :: !Int } instance Semigroup BuilderWithSize where BuilderWithSize b0 s0 <> BuilderWithSize b1 s1 = BuilderWithSize (b0 <> b1) (s0 + s1) instance Monoid BuilderWithSize where mempty = BuilderWithSize mempty 0 computeByteOffset :: [Int] -- ^ lengths of lines -> ETagAddress -> ETagAddress computeByteOffset ll (TagLineCol line col) = TagLineCol line byteOffset where byteOffset = foldl' (+) 0 (take (pred line) ll) + col withByteOffset :: [Int] -> ETag -> ETag withByteOffset ll tag@Tag { tagAddr } = tag { tagAddr = computeByteOffset ll tagAddr } formatTag :: ETag -> BuilderWithSize formatTag Tag {tagName, tagAddr = TagLineCol lineNr byteOffset, tagDefinition} = flip BuilderWithSize tagSize $ -- TODO: get access to the original line or pretty print original -- declaration case tagDefinition of NoTagDefinition -> BB.byteString tagNameBS TagDefinition def -> BB.byteString (Text.encodeUtf8 def) <> BB.charUtf8 '\DEL' -- or '\x7f' <> case tagDefinition of NoTagDefinition -> mempty TagDefinition _ -> BB.byteString tagNameBS <> BB.charUtf8 '\SOH' -- or '\x01' <> BB.intDec lineNr <> BB.charUtf8 ',' <> BB.intDec byteOffset <> BB.stringUtf8 endOfLine where tagNameBS = Text.encodeUtf8 . getTagName $ tagName tagNameSize = BS.length tagNameBS tagDefinitionBS = case tagDefinition of NoTagDefinition -> tagNameBS TagDefinition def -> Text.encodeUtf8 def tagDefinitionSize = BS.length tagDefinitionBS tagSize = 3 -- delimiters: '\DEL', '\SOH', ',' + tagNameSize + tagDefinitionSize + (length $ show lineNr) + (length $ show byteOffset) + (length $ endOfLine) -- | The precondition is that all the tags come frome the same file. -- formatTagsFile :: [ETag] -> Builder formatTagsFile [] = mempty formatTagsFile ts@(Tag {tagFilePath} : _) = case foldMap formatTag ts of BuilderWithSize {builder, builderSize} -> if builderSize > 0 then BB.charUtf8 '\x0c' <> BB.stringUtf8 endOfLine <> (BB.byteString . Text.encodeUtf8 . Text.pack $ tagFilePath) <> BB.charUtf8 ',' <> BB.intDec builderSize <> BB.stringUtf8 endOfLine <> builder else mempty -- | Format a list of tags as etags file. Tags from the same file must be -- groupped together. -- formatETagsFile :: [ETag] -> Builder formatETagsFile = foldMap formatTagsFile . groupBy (on (==) tagFilePath) endOfLine :: String endOfLine = "\n"