{-# LANGUAGE NoImplicitPrelude, BangPatterns, DerivingVia, DeriveGeneric, OverloadedRecordDot #-} module Resolution ( Result(..) , NewContent(..) , Untabify(..) , resolveContent , fullySuccessful ) where import Conflict (Conflict(..), Sides(..)) import qualified Conflict import Data.Foldable (Foldable(..)) import Generic.Data (Generic, Generically(..)) import Prelude.Compat data Resolution = NoResolution | Resolution String | PartialResolution String resolveGen :: Eq a => Sides a -> Maybe a resolveGen (Sides a base b) | a == base = Just b | b == base = Just a | a == b = Just a | otherwise = Nothing resolveConflict :: Conflict -> Resolution resolveConflict c = case resolveGen c.bodies of Just r -> Resolution $ unlines r Nothing | matchTop > 0 || matchBottom > 0 -> PartialResolution $ unlines $ take matchTop c.bodies.sideA ++ Conflict.prettyLines (Conflict.setEachBody unmatched c) ++ takeEnd matchBottom c.bodies.sideA | otherwise -> NoResolution where match base a b | null base = lengthOfCommonPrefix a b | otherwise = minimum $ map (lengthOfCommonPrefix base) [a, b] matchTop = match c.bodies.sideBase c.bodies.sideA c.bodies.sideB revBottom = reverse . drop matchTop matchBottom = match (revBottom c.bodies.sideBase) (revBottom c.bodies.sideA) (revBottom c.bodies.sideB) dropEnd count xs = take (length xs - count) xs takeEnd count xs = drop (length xs - count) xs unmatched xs = drop matchTop $ dropEnd matchBottom xs lengthOfCommonPrefix :: Eq a => [a] -> [a] -> Int lengthOfCommonPrefix x y = length $ takeWhile id $ zipWith (==) x y data Result = Result { resolvedSuccessfully :: !Int , reducedConflicts :: !Int , failedToResolve :: !Int } fullySuccessful :: Result -> Bool fullySuccessful (Result _ reduced failed) = reduced == 0 && failed == 0 instance Semigroup Result where Result x0 y0 z0 <> Result x1 y1 z1 = Result (x0+x1) (y0+y1) (z0+z1) instance Monoid Result where mempty = Result 0 0 0 data NewContent = NewContent { result :: !Result , newContent :: !String } deriving Generic deriving (Semigroup, Monoid) via Generically NewContent newtype Untabify = Untabify { mUntabifySize :: Maybe Int } untabifyStr :: Int -> String -> String untabifyStr size = go 0 where cyclicInc col | col >= size - 1 = 0 | otherwise = col + 1 go !col ('\t':rest) = replicate (size - col) ' ' ++ go 0 rest go !col (x:rest) = x : go (cyclicInc col) rest go _ [] = [] untabify :: Int -> Conflict -> Conflict untabify = Conflict.setStrings . untabifyStr data LineEnding = LF | CRLF | Mixed deriving (Eq, Ord) lineEnding :: String -> LineEnding lineEnding x@(_:_) | last x == '\r' = CRLF lineEnding _ = LF lineEndings :: [String] -> LineEnding lineEndings [] = Mixed lineEndings xs = foldl1 f (map lineEnding xs) where f Mixed _ = Mixed f x c | x == c = x | otherwise = Mixed allSame :: Eq a => [a] -> Bool allSame (x:y:rest) = x == y && allSame (y:rest) allSame _ = True lineBreakFix :: Conflict -> Conflict lineBreakFix c | any null (toList c.bodies) || allSame (toList endings) = c | otherwise = case resolveGen endings of Just LF -> Conflict.setStrings removeCr c Just CRLF -> Conflict.setStrings makeCr c _ -> c where endings = fmap lineEndings c.bodies removeCr x@(_:_) | last x == '\r' = init x removeCr x = x makeCr x@(_:_) | last x == '\r' = x makeCr x = x <> "\r" resolveContent :: Untabify -> [Either String Conflict] -> NewContent resolveContent (Untabify mTabSize) = foldMap go where untabified = maybe id untabify mTabSize go (Left line) = NewContent mempty (unlines [line]) go (Right conflict) = case (resolveConflict . lineBreakFix . untabified) conflict of NoResolution -> NewContent (Result 0 0 1) (Conflict.pretty (untabified conflict)) Resolution trivialLines -> NewContent (Result 1 0 0) trivialLines PartialResolution newLines -> NewContent (Result 0 1 0) newLines