1
1
mirror of https://github.com/github/semantic.git synced 2024-12-28 09:21:35 +03:00
semantic/src/Renderer/Patch.hs

161 lines
7.9 KiB
Haskell
Raw Normal View History

module Renderer.Patch (
2015-12-18 17:48:04 +03:00
patch,
2016-03-02 01:13:53 +03:00
hunks,
2016-04-01 22:35:09 +03:00
Hunk(..),
truncatePatch
2015-12-18 17:48:04 +03:00
) where
2015-12-18 01:22:09 +03:00
import Alignment
import Data.Bifunctor.Join
import Data.Functor.Both as Both
import Data.List (span, unzip)
import Data.String
import Data.Text (pack)
import Data.These
2015-12-18 01:22:53 +03:00
import Diff
2016-03-31 00:26:52 +03:00
import Info
2016-04-15 04:57:21 +03:00
import Patch
import Prologue hiding (fst, snd)
import Renderer
2015-12-31 01:42:18 +03:00
import Source hiding ((++), break)
import SplitDiff
2015-12-18 01:22:53 +03:00
2016-04-01 22:35:09 +03:00
-- | Render a timed out file as a truncated diff.
truncatePatch :: DiffArguments -> Both SourceBlob -> Text
2016-04-05 01:02:43 +03:00
truncatePatch _ blobs = pack $ header blobs ++ "#timed_out\nTruncating diff: timeout reached.\n"
2015-12-18 01:22:53 +03:00
2016-02-03 20:58:54 +03:00
-- | Render a diff in the traditional patch format.
2016-05-18 19:01:16 +03:00
patch :: Renderer
patch diff blobs = pack $ case getLast (foldMap (Last . Just) string) of
2016-03-17 01:56:57 +03:00
Just c | c /= '\n' -> string ++ "\n\\ No newline at end of file\n"
2016-03-17 01:41:56 +03:00
_ -> string
2016-03-31 19:30:41 +03:00
where string = header blobs ++ mconcat (showHunk blobs <$> hunks diff blobs)
2016-02-03 20:58:54 +03:00
-- | A hunk in a patch, including the offset, changes, and context.
2016-04-15 16:23:35 +03:00
data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Join These a] }
2015-12-25 02:11:38 +03:00
deriving (Eq, Show)
2015-12-18 16:25:37 +03:00
2016-02-03 20:58:54 +03:00
-- | A change in a patch hunk, along with its preceding context.
2016-04-15 16:23:35 +03:00
data Change a = Change { context :: [Join These a], contents :: [Join These a] }
deriving (Eq, Show)
2016-02-03 20:58:54 +03:00
-- | The number of lines in the hunk before and after.
hunkLength :: Hunk a -> Both (Sum Int)
2016-03-10 08:57:47 +03:00
hunkLength hunk = mconcat $ (changeLength <$> changes hunk) <> (rowIncrement <$> trailingContext hunk)
2016-02-03 20:58:54 +03:00
-- | The number of lines in change before and after.
changeLength :: Change a -> Both (Sum Int)
2016-03-10 08:57:47 +03:00
changeLength change = mconcat $ (rowIncrement <$> context change) <> (rowIncrement <$> contents change)
2015-12-30 23:56:12 +03:00
2016-03-10 08:58:22 +03:00
-- | The increment the given row implies for line numbering.
2016-04-15 16:23:35 +03:00
rowIncrement :: Join These a -> Both (Sum Int)
rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$)
2015-12-30 23:56:03 +03:00
2016-02-23 02:13:27 +03:00
-- | Given the before and after sources, render a hunk to a string.
showHunk :: Both SourceBlob -> Hunk (SplitDiff a Info) -> String
2016-03-31 19:30:41 +03:00
showHunk blobs hunk = maybeOffsetHeader ++
2016-03-17 01:23:42 +03:00
concat (showChange sources <$> changes hunk) ++
showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk)
2016-02-29 05:28:24 +03:00
where sources = source <$> blobs
2016-03-31 19:30:41 +03:00
maybeOffsetHeader = if lengthA > 0 && lengthB > 0
then offsetHeader
else mempty
offsetHeader = "@@ -" ++ offsetA ++ "," ++ show lengthA ++ " +" ++ offsetB ++ "," ++ show lengthB ++ " @@" ++ "\n"
2016-04-25 23:07:52 +03:00
(lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
2016-02-03 20:58:54 +03:00
-- | Given the before and after sources, render a change to a string.
showChange :: Both (Source Char) -> Change (SplitDiff a Info) -> String
showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) ++ deleted ++ inserted
2016-04-25 23:07:52 +03:00
where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> both '-' '+' <*> Join (unzip (fromThese Nothing Nothing . runJoin . fmap Just <$> contents change))
2015-12-30 23:27:04 +03:00
2016-02-03 20:58:54 +03:00
-- | Given a source, render a set of lines to a string with a prefix.
showLines :: Source Char -> Char -> [Maybe (SplitDiff leaf Info)] -> String
2016-02-22 23:31:52 +03:00
showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines
where prepend "" = ""
prepend source = prefix : source
2016-02-03 20:58:54 +03:00
-- | Given a source, render a line to a string.
showLine :: Source Char -> Maybe (SplitDiff leaf Info) -> Maybe String
showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line
| otherwise = Nothing
2016-02-23 02:13:27 +03:00
-- | Returns the header given two source blobs and a hunk.
2016-03-31 19:30:41 +03:00
header :: Both SourceBlob -> String
header blobs = intercalate "\n" [filepathHeader, fileModeHeader, beforeFilepath, afterFilepath] ++ "\n"
2016-03-04 19:57:37 +03:00
where filepathHeader = "diff --git a/" ++ pathA ++ " b/" ++ pathB
fileModeHeader = case (modeA, modeB) of
(Nothing, Just mode) -> intercalate "\n" [ "new file mode " ++ modeToDigits mode, blobOidHeader ]
2016-03-17 20:11:14 +03:00
(Just mode, Nothing) -> intercalate "\n" [ "deleted file mode " ++ modeToDigits mode, blobOidHeader ]
2016-03-04 19:57:37 +03:00
(Just mode, Just other) | mode == other -> "index " ++ oidA ++ ".." ++ oidB ++ " " ++ modeToDigits mode
(Just mode1, Just mode2) -> intercalate "\n" [
2016-03-17 20:51:38 +03:00
"old mode " ++ modeToDigits mode1,
"new mode " ++ modeToDigits mode2,
blobOidHeader
2016-03-04 19:57:37 +03:00
]
(Nothing, Nothing) -> ""
2016-03-04 19:57:37 +03:00
blobOidHeader = "index " ++ oidA ++ ".." ++ oidB
modeHeader :: String -> Maybe SourceKind -> String -> String
modeHeader ty maybeMode path = case maybeMode of
Just _ -> ty ++ "/" ++ path
2016-03-04 19:57:37 +03:00
Nothing -> "/dev/null"
beforeFilepath = "--- " ++ modeHeader "a" modeA pathA
afterFilepath = "+++ " ++ modeHeader "b" modeB pathB
2016-04-25 23:07:52 +03:00
(pathA, pathB) = runJoin $ path <$> blobs
(oidA, oidB) = runJoin $ oid <$> blobs
(modeA, modeB) = runJoin $ blobKind <$> blobs
2015-12-18 16:07:43 +03:00
2016-04-05 00:12:02 +03:00
-- | A hunk representing no changes.
2016-04-01 22:35:21 +03:00
emptyHunk :: Hunk (SplitDiff a Info)
emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] }
2016-02-03 20:58:54 +03:00
-- | Render a diff as a series of hunks.
hunks :: Show a => Diff a Info -> Both SourceBlob -> [Hunk (SplitDiff a Info)]
hunks _ blobs | sources <- source <$> blobs
, sourcesEqual <- runBothWith (==) sources
, sourcesNull <- runBothWith (&&) (null <$> sources)
, sourcesEqual || sourcesNull
2016-04-01 22:35:21 +03:00
= [emptyHunk]
hunks diff blobs = hunksInRows (pure 1) $ alignDiff (source <$> blobs) diff
2016-02-29 05:10:02 +03:00
-- | Given beginning line numbers, turn rows in a split diff into hunks in a
-- | patch.
2016-04-15 16:23:35 +03:00
hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)]
hunksInRows start rows = case nextHunk start rows of
2016-03-02 01:13:53 +03:00
Nothing -> []
Just (hunk, rest) -> hunk : hunksInRows (offset hunk <> hunkLength hunk) rest
-- | Given beginning line numbers, return the next hunk and the remaining rows
-- | of the split diff.
2016-04-15 16:23:35 +03:00
nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Join These (SplitDiff a Info)])
nextHunk start rows = case nextChange start rows of
Nothing -> Nothing
Just (offset, change, rest) -> let (changes, rest') = contiguousChanges rest in Just (Hunk offset (change : changes) $ take 3 rest', drop 3 rest')
2015-12-31 02:06:47 +03:00
where contiguousChanges rows = case break rowHasChanges (take 7 rows) of
(_, []) -> ([], rows)
(context, _) -> case changeIncludingContext context (drop (length context) rows) of
2015-12-31 01:54:27 +03:00
Nothing -> ([], rows)
Just (change, rest) -> let (changes, rest') = contiguousChanges rest in (change : changes, rest')
-- | Given beginning line numbers, return the number of lines to the next
-- | the next change, and the remaining rows of the split diff.
2016-04-15 16:23:35 +03:00
nextChange :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Both (Sum Int), Change (SplitDiff a Info), [Join These (SplitDiff a Info)])
2015-12-31 02:10:58 +03:00
nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of
Nothing -> Nothing
2016-03-10 08:57:47 +03:00
Just (change, afterChanges) -> Just (start <> mconcat (rowIncrement <$> skippedContext), change, afterChanges)
2015-12-31 02:10:58 +03:00
where (leadingRows, afterLeadingContext) = break rowHasChanges rows
2015-12-31 00:33:45 +03:00
(skippedContext, leadingContext) = splitAt (max (length leadingRows - 3) 0) leadingRows
2016-02-03 20:58:54 +03:00
-- | Return a Change with the given context and the rows from the begginning of
-- | the given rows that have changes, or Nothing if the first row has no
-- | changes.
2016-04-15 16:23:35 +03:00
changeIncludingContext :: [Join These (SplitDiff a Info)] -> [Join These (SplitDiff a Info)] -> Maybe (Change (SplitDiff a Info), [Join These (SplitDiff a Info)])
changeIncludingContext leadingContext rows = case changes of
[] -> Nothing
_ -> Just (Change leadingContext changes, afterChanges)
where (changes, afterChanges) = span rowHasChanges rows
2016-02-03 20:58:54 +03:00
-- | Whether a row has changes on either side.
2016-04-15 16:23:35 +03:00
rowHasChanges :: Join These (SplitDiff a Info) -> Bool
rowHasChanges row = or (hasChanges <$> row)