@@ -5,17 +5,22 @@
55-- | Diffing GHC ASTs modulo span positions.
66
77module Ormolu.Diff
8− ( diff
9− , Diff(..)
8+ ( Diff(..)
9+ , diffParseResult
10+ , diffText
1011 )
1112where
1213
1314import BasicTypes (SourceText)
1415import Data.ByteString (ByteString)
1516import Data.Generics
17+import Data.Text (Text)
1618import GHC
1719import Ormolu.Imports (sortImports)
1820import Ormolu.Parser.Result
21+import qualified Data.Text as T
22+import qualified FastString as GHC
23+import qualified SrcLoc as GHC
1924
2025-- | Result of comparing two 'ParseResult's.
2126
@@ -31,16 +36,16 @@ instance Semigroup Diff where
3136instance Monoid Diff where
3237 mempty = Same
3338
34−-- | Return 'False' if two annotated ASTs are the same modulo span
35−-- positions.
39+-- | Return 'Diff' of two 'ParseResult's.
3640
37−diff :: ParseResult -> ParseResult -> Diff
38−diff ParseResult { prCommentStream = cstream0
39− , prParsedSource = ps0
40− }
41− ParseResult { prCommentStream = cstream1
42− , prParsedSource = ps1
43− } =
41+diffParseResult :: ParseResult -> ParseResult -> Diff
42+diffParseResult
43+ ParseResult { prCommentStream = cstream0
44+ , prParsedSource = ps0
45+ }
46+ ParseResult { prCommentStream = cstream1
47+ , prParsedSource = ps1
48+ } =
4449 matchIgnoringSrcSpans cstream0 cstream1 <>
4550 matchIgnoringSrcSpans ps0 ps1
4651
@@ -96,3 +101,36 @@ matchIgnoringSrcSpans = genericQuery
96101 fresh = not $ any (flip isSubspanOf s) ss
97102 helpful = isGoodSrcSpan s
98103 appendSpan _ d = d
104+
105+-- | Diff two texts and return the location they start to differ, alongside
106+-- with excerpts around that location.
107+
108+diffText
109+ :: Text -- ^ Text before
110+ -> Text -- ^ Text after
111+ -> FilePath -- ^ Path to use to construct 'GHC.RealSrcLoc'
112+ -> Maybe (GHC.RealSrcLoc, Text, Text)
113+diffText left right fp =
114+ case go (0, 0, 0) left right of
115+ Nothing -> Nothing
116+ Just (row, col, loc) -> Just (
117+ GHC.mkRealSrcLoc (GHC.mkFastString fp) row col,
118+ getSpan loc left,
119+ getSpan loc right
120+ )
121+ where
122+ go (row, col, loc) t1 t2 =
123+ case (T.uncons t1, T.uncons t2) of
124+ -- both text empty, all good
125+ (Nothing, Nothing) ->
126+ Nothing
127+ -- first chars are the same, adjust position and recurse
128+ (Just (c1, r1), Just (c2, r2)) | c1 == c2 ->
129+ let (row', col', loc') = if c1 == '\n'
130+ then (row + 1, 0, loc + 1)
131+ else (row, col + 1, loc + 1)
132+ in go (row', col', loc') r1 r2
133+ -- something is different, return the position
134+ _ ->
135+ Just (row, col, loc)
136+ getSpan loc = T.take 20 . T.drop (loc - 10)