From eef4da87b4a73a00a29400041b3b0e574cf6ec0c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:08:07 -0500 Subject: [PATCH 01/70] Extract the paths into a binding. --- test/CorpusSpec.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index f10c48550..8db50478b 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -72,8 +72,9 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte -- | is true, but the diff will still be calculated. testDiff :: Renderer T.Text String -> FilePath -> FilePath -> Maybe FilePath -> ((String, String) -> Expectation) -> Expectation testDiff renderer a b diff matcher = do + let paths = Join (a, b) let parser = parserForFilepath a - sources <- sequence $ readAndTranscodeFile <$> Join (a, b) + sources <- sequence $ readAndTranscodeFile <$> paths let srcs = runJoin sources let sourceBlobs = (S.SourceBlob (fst srcs) mempty a, S.SourceBlob (snd srcs) mempty b) actual <- diffFiles parser renderer sourceBlobs From 88130ee7fdd61c5dbd1dc0186a80c18dee998bd9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:08:43 -0500 Subject: [PATCH 02/70] Remove some redundant imports. --- src/Renderer/Split.hs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index b2ed501f6..50ad8661e 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -7,7 +7,6 @@ import Category import Diff import Line import Row -import Patch import Renderer import Term import SplitDiff @@ -24,10 +23,7 @@ import qualified Data.Text.Lazy as TL import Text.Blaze.Html.Renderer.Text import Data.Either import Data.Foldable -import Data.Functor.Identity import Data.Monoid -import qualified Data.OrderedMap as Map -import qualified Data.Set as Set import Source hiding ((++)) type ClassName = T.Text From 4abf8b5b6c74a53143943407d815750de2a8ec92 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:10:02 -0500 Subject: [PATCH 03/70] Spacing. --- src/Renderer/Patch.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index 08df3db4b..7a83bd43d 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -86,6 +86,7 @@ hunks diff (beforeBlob, afterBlob) = hunksInRows (1, 1) . fst $ splitDiffByLines where before = source beforeBlob after = source afterBlob + -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. hunksInRows :: (Sum Int, Sum Int) -> [Row (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)] From ea87105b4b666d573434e3cb91df31466f42d5b3 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:10:56 -0500 Subject: [PATCH 04/70] Use Join for sources everywhere. --- src/DiffOutput.hs | 5 +++-- src/Diffing.hs | 5 ++--- src/Renderer.hs | 3 ++- src/Renderer/Patch.hs | 20 +++++++++++--------- src/Renderer/Split.hs | 7 +++---- test/CorpusSpec.hs | 3 +-- test/PatchOutputSpec.hs | 3 ++- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/DiffOutput.hs b/src/DiffOutput.hs index 26e2b11c8..3cd610d2c 100644 --- a/src/DiffOutput.hs +++ b/src/DiffOutput.hs @@ -1,7 +1,8 @@ module DiffOutput where -import Diffing +import Data.Bifunctor.Join import qualified Data.ByteString.Char8 as B1 +import Diffing import Parser import Source import System.Directory @@ -17,7 +18,7 @@ data Format = Split | Patch data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath, outputPath :: FilePath } -- | Return a renderer from the command-line arguments that will print the diff. -printDiff :: Parser -> DiffArguments -> (SourceBlob, SourceBlob) -> IO () +printDiff :: Parser -> DiffArguments -> Join SourceBlob -> IO () printDiff parser arguments sources = case format arguments of Split -> put (output arguments) =<< diffFiles parser split sources where diff --git a/src/Diffing.hs b/src/Diffing.hs index f966c8acc..0032f7db3 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -13,7 +13,6 @@ import TreeSitter import Text.Parser.TreeSitter.Language import Control.Comonad.Cofree -import Control.Arrow import Data.Bifunctor.Join import qualified Data.ByteString.Char8 as B1 import Data.Foldable @@ -71,9 +70,9 @@ readAndTranscodeFile path = do -- | Given a parser and renderer, diff two sources and return the rendered -- | result. -diffFiles :: Parser -> Renderer T.Text b -> (SourceBlob, SourceBlob) -> IO b +diffFiles :: Parser -> Renderer T.Text b -> Join SourceBlob -> IO b diffFiles parser renderer sourceBlobs = do - let sources = Join $ (source *** source) sourceBlobs + let sources = source <$> sourceBlobs terms <- sequence $ parser <$> sources let replaceLeaves = breakDownLeavesByWord <$> sources return $ renderer (uncurry diffTerms $ runJoin $ replaceLeaves <*> terms) sourceBlobs diff --git a/src/Renderer.hs b/src/Renderer.hs index df4bcce0a..d9e6a78b3 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -1,7 +1,8 @@ module Renderer where +import Data.Bifunctor.Join import Diff import Source -- | A function that will render a diff, given the two source files. -type Renderer a b = Diff a Info -> (SourceBlob, SourceBlob) -> b +type Renderer a b = Diff a Info -> Join SourceBlob -> b diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index 7a83bd43d..e43533b83 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -16,6 +16,7 @@ import Control.Monad.Free import Data.Maybe import Data.Monoid import Data.Bifunctor +import Data.Bifunctor.Join import Control.Monad -- | Render a diff in the traditional patch format. @@ -48,9 +49,9 @@ lineLength EmptyLine = 0 lineLength _ = 1 -- | Given the before and after sources, render a hunk to a string. -showHunk :: (SourceBlob, SourceBlob) -> Hunk (SplitDiff a Info) -> String -showHunk blobs@(beforeBlob, afterBlob) hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd sources) ' ' (unRight <$> trailingContext hunk) - where sources = (source beforeBlob, source afterBlob) +showHunk :: Join SourceBlob -> Hunk (SplitDiff a Info) -> String +showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd sources) ' ' (unRight <$> trailingContext hunk) + where sources = runJoin $ source <$> blobs -- | Given the before and after sources, render a change to a string. showChange :: (Source Char, Source Char) -> Change (SplitDiff a Info) -> String @@ -73,19 +74,20 @@ getRange (Free (Annotated (Info range _) _)) = range getRange (Pure patch) = let Info range _ :< _ = getSplitTerm patch in range -- | Returns the header given two source blobs and a hunk. -header :: (SourceBlob, SourceBlob) -> Hunk a -> String -header blobs hunk = "diff --git a/" ++ path (fst blobs) ++ " b/" ++ path (snd blobs) ++ "\n" ++ - "index " ++ oid (fst blobs) ++ ".." ++ oid (snd blobs) ++ "\n" ++ +header :: Join SourceBlob -> Hunk a -> String +header blobs hunk = "diff --git a/" ++ pathA ++ " b/" ++ pathB ++ "\n" ++ + "index " ++ oidA ++ ".." ++ oidB ++ "\n" ++ "@@ -" ++ show offsetA ++ "," ++ show lengthA ++ " +" ++ show offsetB ++ "," ++ show lengthB ++ " @@\n" where (lengthA, lengthB) = join bimap getSum $ hunkLength hunk (offsetA, offsetB) = join bimap getSum $ offset hunk + (pathA, pathB) = runJoin $ path <$> blobs + (oidA, oidB) = runJoin $ oid <$> blobs -- | Render a diff as a series of hunks. hunks :: Renderer a [Hunk (SplitDiff a Info)] -hunks diff (beforeBlob, afterBlob) = hunksInRows (1, 1) . fst $ splitDiffByLines diff (0, 0) (before, after) +hunks diff blobs = hunksInRows (1, 1) . fst $ splitDiffByLines diff (0, 0) (before, after) where - before = source beforeBlob - after = source afterBlob + (before, after) = runJoin $ source <$> blobs -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 50ad8661e..72b498af9 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -21,7 +21,7 @@ import qualified Text.Blaze.Html5.Attributes as A import qualified Data.Text as T import qualified Data.Text.Lazy as TL import Text.Blaze.Html.Renderer.Text -import Data.Either +import Data.Bifunctor.Join import Data.Foldable import Data.Monoid import Source hiding ((++)) @@ -54,7 +54,7 @@ splitPatchToClassName patch = stringValue $ "patch " ++ case patch of -- | Render a diff as an HTML split diff. split :: Renderer leaf TL.Text -split diff (beforeBlob, afterBlob) = renderHtml +split diff blobs = renderHtml . docTypeHtml . ((head $ link ! A.rel "stylesheet" ! A.href "style.css") <>) . body @@ -62,8 +62,7 @@ split diff (beforeBlob, afterBlob) = renderHtml ((colgroup $ (col ! A.width (stringValue . show $ columnWidth)) <> col <> (col ! A.width (stringValue . show $ columnWidth)) <> col) <>) . mconcat $ numberedLinesToMarkup <$> reverse numbered where - before = Source.source beforeBlob - after = Source.source afterBlob + (before, after) = runJoin $ Source.source <$> blobs rows = fst (splitDiffByLines diff (0, 0) (before, after)) numbered = foldl' numberRows [] rows maxNumber = case numbered of diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index 8db50478b..bc1c673c4 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -75,8 +75,7 @@ testDiff renderer a b diff matcher = do let paths = Join (a, b) let parser = parserForFilepath a sources <- sequence $ readAndTranscodeFile <$> paths - let srcs = runJoin sources - let sourceBlobs = (S.SourceBlob (fst srcs) mempty a, S.SourceBlob (snd srcs) mempty b) + let sourceBlobs = Join (S.SourceBlob, S.SourceBlob) <*> sources <*> Join (mempty, mempty) <*> paths actual <- diffFiles parser renderer sourceBlobs case diff of Nothing -> actual `deepseq` matcher (actual, actual) diff --git a/test/PatchOutputSpec.hs b/test/PatchOutputSpec.hs index 56bd424f3..4ba8795ac 100644 --- a/test/PatchOutputSpec.hs +++ b/test/PatchOutputSpec.hs @@ -1,5 +1,6 @@ module PatchOutputSpec where +import Data.Bifunctor.Join import Diff import Renderer.Patch import Range @@ -12,4 +13,4 @@ spec :: Spec spec = parallel $ describe "hunks" $ it "empty diffs have no hunks" $ - hunks (Free . Annotated (Info (Range 0 0) mempty, Info (Range 0 0) mempty) $ Leaf "") (SourceBlob (fromList "") "abcde" "path2.txt", SourceBlob (fromList "") "xyz" "path2.txt") `shouldBe` [] + hunks (Free . Annotated (Info (Range 0 0) mempty, Info (Range 0 0) mempty) $ Leaf "") (Join (SourceBlob (fromList "") "abcde" "path2.txt", SourceBlob (fromList "") "xyz" "path2.txt")) `shouldBe` [] From e33014ef272bd85c85e8b5b88d3f0747783265d1 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:18:03 -0500 Subject: [PATCH 05/70] Add a Monoid instance for Join. --- src/Data/Bifunctor/Join.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Bifunctor/Join.hs b/src/Data/Bifunctor/Join.hs index 0623fad19..c6ec74967 100644 --- a/src/Data/Bifunctor/Join.hs +++ b/src/Data/Bifunctor/Join.hs @@ -6,3 +6,7 @@ newtype Join a = Join { runJoin :: (a, a) } instance Applicative Join where pure a = Join (a, a) Join (f, g) <*> Join (a, b) = Join (f a, g b) + +instance Monoid a => Monoid (Join a) where + mempty = pure mempty + mappend a b = pure mappend <*> a <*> b From b663e59c4c2fce5d5bc26b144e32ccf47adffa13 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:20:58 -0500 Subject: [PATCH 06/70] Join the sums. --- src/Renderer/Patch.hs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index e43533b83..dcee85c30 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -24,7 +24,7 @@ patch :: Renderer a String patch diff sources = mconcat $ showHunk sources <$> hunks diff sources -- | A hunk in a patch, including the offset, changes, and context. -data Hunk a = Hunk { offset :: (Sum Int, Sum Int), changes :: [Change a], trailingContext :: [Row a] } +data Hunk a = Hunk { offset :: Join (Sum Int), changes :: [Change a], trailingContext :: [Row a] } deriving (Eq, Show) -- | A change in a patch hunk, along with its preceding context. @@ -32,16 +32,16 @@ data Change a = Change { context :: [Row a], contents :: [Row a] } deriving (Eq, Show) -- | The number of lines in the hunk before and after. -hunkLength :: Hunk a -> (Sum Int, Sum Int) +hunkLength :: Hunk a -> Join (Sum Int) hunkLength hunk = mconcat $ (changeLength <$> changes hunk) <> (rowLength <$> trailingContext hunk) -- | The number of lines in change before and after. -changeLength :: Change a -> (Sum Int, Sum Int) +changeLength :: Change a -> Join (Sum Int) changeLength change = mconcat $ (rowLength <$> context change) <> (rowLength <$> contents change) -- | The number of lines in the row, each being either 0 or 1. -rowLength :: Row a -> (Sum Int, Sum Int) -rowLength (Row a b) = (lineLength a, lineLength b) +rowLength :: Row a -> Join (Sum Int) +rowLength (Row a b) = pure lineLength <*> Join (a, b) -- | The length of the line, being either 0 or 1. lineLength :: Line a -> Sum Int @@ -78,27 +78,27 @@ header :: Join SourceBlob -> Hunk a -> String header blobs hunk = "diff --git a/" ++ pathA ++ " b/" ++ pathB ++ "\n" ++ "index " ++ oidA ++ ".." ++ oidB ++ "\n" ++ "@@ -" ++ show offsetA ++ "," ++ show lengthA ++ " +" ++ show offsetB ++ "," ++ show lengthB ++ " @@\n" - where (lengthA, lengthB) = join bimap getSum $ hunkLength hunk - (offsetA, offsetB) = join bimap getSum $ offset hunk + where (lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk + (offsetA, offsetB) = runJoin . fmap getSum $ offset hunk (pathA, pathB) = runJoin $ path <$> blobs (oidA, oidB) = runJoin $ oid <$> blobs -- | Render a diff as a series of hunks. hunks :: Renderer a [Hunk (SplitDiff a Info)] -hunks diff blobs = hunksInRows (1, 1) . fst $ splitDiffByLines diff (0, 0) (before, after) +hunks diff blobs = hunksInRows (Join (1, 1)) . fst $ splitDiffByLines diff (0, 0) (before, after) where (before, after) = runJoin $ source <$> blobs -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. -hunksInRows :: (Sum Int, Sum Int) -> [Row (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)] +hunksInRows :: Join (Sum Int) -> [Row (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)] hunksInRows start rows = case nextHunk start rows of 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. -nextHunk :: (Sum Int, Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Row (SplitDiff a Info)]) +nextHunk :: Join (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Row (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') @@ -110,7 +110,7 @@ nextHunk start rows = case nextChange start rows of -- | Given beginning line numbers, return the number of lines to the next -- | the next change, and the remaining rows of the split diff. -nextChange :: (Sum Int, Sum Int) -> [Row (SplitDiff a Info)] -> Maybe ((Sum Int, Sum Int), Change (SplitDiff a Info), [Row (SplitDiff a Info)]) +nextChange :: Join (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Join (Sum Int), Change (SplitDiff a Info), [Row (SplitDiff a Info)]) nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of Nothing -> Nothing Just (change, afterChanges) -> Just (start <> mconcat (rowLength <$> skippedContext), change, afterChanges) From 2b61360697def2ea47fd3d4174e6d47b51f53dc5 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:28:24 -0500 Subject: [PATCH 07/70] Use some more Joins. --- src/Renderer/Patch.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index dcee85c30..77f9a049f 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -50,12 +50,13 @@ lineLength _ = 1 -- | Given the before and after sources, render a hunk to a string. showHunk :: Join SourceBlob -> Hunk (SplitDiff a Info) -> String -showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd sources) ' ' (unRight <$> trailingContext hunk) - where sources = runJoin $ source <$> blobs +showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd $ runJoin sources) ' ' (unRight <$> trailingContext hunk) + where sources = source <$> blobs -- | Given the before and after sources, render a change to a string. -showChange :: (Source Char, Source Char) -> Change (SplitDiff a Info) -> String -showChange sources change = showLines (snd sources) ' ' (unRight <$> context change) ++ showLines (fst sources) '-' (unLeft <$> contents change) ++ showLines (snd sources) '+' (unRight <$> contents change) +showChange :: Join (Source Char) -> Change (SplitDiff a Info) -> String +showChange sources change = showLines (snd $ runJoin sources) ' ' (unRight <$> context change) ++ deleted ++ inserted + where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> Join ('-', '+') <*> (pure fmap <*> Join (unLeft, unRight) <*> pure (contents change)) -- | Given a source, render a set of lines to a string with a prefix. showLines :: Source Char -> Char -> [Line (SplitDiff leaf Info)] -> String From 06262289178bb56bcad60895e974680594c6d963 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:29:59 -0500 Subject: [PATCH 08/70] Data.Bifunctor.Join is actually Data.Functor.Both. --- semantic-diff.cabal | 2 +- src/Data/Bifunctor/Join.hs | 12 ----------- src/Data/Functor/Both.hs | 12 +++++++++++ src/DiffOutput.hs | 4 ++-- src/Diffing.hs | 6 +++--- src/Renderer.hs | 4 ++-- src/Renderer/Patch.hs | 42 +++++++++++++++++++------------------- src/Renderer/Split.hs | 4 ++-- test/CorpusSpec.hs | 6 +++--- test/PatchOutputSpec.hs | 4 ++-- 10 files changed, 48 insertions(+), 48 deletions(-) delete mode 100644 src/Data/Bifunctor/Join.hs create mode 100644 src/Data/Functor/Both.hs diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 9c7e25b8b..75f383e8f 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -18,7 +18,7 @@ library , Category , Control.Comonad.Cofree , Control.Monad.Free - , Data.Bifunctor.Join + , Data.Functor.Both , Data.Option , Data.OrderedMap , Diff diff --git a/src/Data/Bifunctor/Join.hs b/src/Data/Bifunctor/Join.hs deleted file mode 100644 index c6ec74967..000000000 --- a/src/Data/Bifunctor/Join.hs +++ /dev/null @@ -1,12 +0,0 @@ -module Data.Bifunctor.Join where - -newtype Join a = Join { runJoin :: (a, a) } - deriving (Eq, Show, Functor, Foldable, Traversable) - -instance Applicative Join where - pure a = Join (a, a) - Join (f, g) <*> Join (a, b) = Join (f a, g b) - -instance Monoid a => Monoid (Join a) where - mempty = pure mempty - mappend a b = pure mappend <*> a <*> b diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs new file mode 100644 index 000000000..a61695e55 --- /dev/null +++ b/src/Data/Functor/Both.hs @@ -0,0 +1,12 @@ +module Data.Functor.Both where + +newtype Both a = Both { runBoth :: (a, a) } + deriving (Eq, Show, Functor, Foldable, Traversable) + +instance Applicative Both where + pure a = Both (a, a) + Both (f, g) <*> Both (a, b) = Both (f a, g b) + +instance Monoid a => Monoid (Both a) where + mempty = pure mempty + mappend a b = pure mappend <*> a <*> b diff --git a/src/DiffOutput.hs b/src/DiffOutput.hs index 3cd610d2c..90c6b9863 100644 --- a/src/DiffOutput.hs +++ b/src/DiffOutput.hs @@ -1,6 +1,6 @@ module DiffOutput where -import Data.Bifunctor.Join +import Data.Functor.Both import qualified Data.ByteString.Char8 as B1 import Diffing import Parser @@ -18,7 +18,7 @@ data Format = Split | Patch data DiffArguments = DiffArguments { format :: Format, output :: Maybe FilePath, outputPath :: FilePath } -- | Return a renderer from the command-line arguments that will print the diff. -printDiff :: Parser -> DiffArguments -> Join SourceBlob -> IO () +printDiff :: Parser -> DiffArguments -> Both SourceBlob -> IO () printDiff parser arguments sources = case format arguments of Split -> put (output arguments) =<< diffFiles parser split sources where diff --git a/src/Diffing.hs b/src/Diffing.hs index 0032f7db3..c6fb7b237 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -13,7 +13,7 @@ import TreeSitter import Text.Parser.TreeSitter.Language import Control.Comonad.Cofree -import Data.Bifunctor.Join +import Data.Functor.Both import qualified Data.ByteString.Char8 as B1 import Data.Foldable import qualified Data.Text as T @@ -70,9 +70,9 @@ readAndTranscodeFile path = do -- | Given a parser and renderer, diff two sources and return the rendered -- | result. -diffFiles :: Parser -> Renderer T.Text b -> Join SourceBlob -> IO b +diffFiles :: Parser -> Renderer T.Text b -> Both SourceBlob -> IO b diffFiles parser renderer sourceBlobs = do let sources = source <$> sourceBlobs terms <- sequence $ parser <$> sources let replaceLeaves = breakDownLeavesByWord <$> sources - return $ renderer (uncurry diffTerms $ runJoin $ replaceLeaves <*> terms) sourceBlobs + return $ renderer (uncurry diffTerms $ runBoth $ replaceLeaves <*> terms) sourceBlobs diff --git a/src/Renderer.hs b/src/Renderer.hs index d9e6a78b3..ac8dc73c1 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -1,8 +1,8 @@ module Renderer where -import Data.Bifunctor.Join +import Data.Functor.Both import Diff import Source -- | A function that will render a diff, given the two source files. -type Renderer a b = Diff a Info -> Join SourceBlob -> b +type Renderer a b = Diff a Info -> Both SourceBlob -> b diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index 77f9a049f..e93cde306 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -16,7 +16,7 @@ import Control.Monad.Free import Data.Maybe import Data.Monoid import Data.Bifunctor -import Data.Bifunctor.Join +import Data.Functor.Both import Control.Monad -- | Render a diff in the traditional patch format. @@ -24,7 +24,7 @@ patch :: Renderer a String patch diff sources = mconcat $ showHunk sources <$> hunks diff sources -- | A hunk in a patch, including the offset, changes, and context. -data Hunk a = Hunk { offset :: Join (Sum Int), changes :: [Change a], trailingContext :: [Row a] } +data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Row a] } deriving (Eq, Show) -- | A change in a patch hunk, along with its preceding context. @@ -32,16 +32,16 @@ data Change a = Change { context :: [Row a], contents :: [Row a] } deriving (Eq, Show) -- | The number of lines in the hunk before and after. -hunkLength :: Hunk a -> Join (Sum Int) +hunkLength :: Hunk a -> Both (Sum Int) hunkLength hunk = mconcat $ (changeLength <$> changes hunk) <> (rowLength <$> trailingContext hunk) -- | The number of lines in change before and after. -changeLength :: Change a -> Join (Sum Int) +changeLength :: Change a -> Both (Sum Int) changeLength change = mconcat $ (rowLength <$> context change) <> (rowLength <$> contents change) -- | The number of lines in the row, each being either 0 or 1. -rowLength :: Row a -> Join (Sum Int) -rowLength (Row a b) = pure lineLength <*> Join (a, b) +rowLength :: Row a -> Both (Sum Int) +rowLength (Row a b) = pure lineLength <*> Both (a, b) -- | The length of the line, being either 0 or 1. lineLength :: Line a -> Sum Int @@ -49,14 +49,14 @@ lineLength EmptyLine = 0 lineLength _ = 1 -- | Given the before and after sources, render a hunk to a string. -showHunk :: Join SourceBlob -> Hunk (SplitDiff a Info) -> String -showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd $ runJoin sources) ' ' (unRight <$> trailingContext hunk) +showHunk :: Both SourceBlob -> Hunk (SplitDiff a Info) -> String +showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd $ runBoth sources) ' ' (unRight <$> trailingContext hunk) where sources = source <$> blobs -- | Given the before and after sources, render a change to a string. -showChange :: Join (Source Char) -> Change (SplitDiff a Info) -> String -showChange sources change = showLines (snd $ runJoin sources) ' ' (unRight <$> context change) ++ deleted ++ inserted - where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> Join ('-', '+') <*> (pure fmap <*> Join (unLeft, unRight) <*> pure (contents change)) +showChange :: Both (Source Char) -> Change (SplitDiff a Info) -> String +showChange sources change = showLines (snd $ runBoth sources) ' ' (unRight <$> context change) ++ deleted ++ inserted + where (deleted, inserted) = runBoth $ pure showLines <*> sources <*> Both ('-', '+') <*> (pure fmap <*> Both (unLeft, unRight) <*> pure (contents change)) -- | Given a source, render a set of lines to a string with a prefix. showLines :: Source Char -> Char -> [Line (SplitDiff leaf Info)] -> String @@ -75,31 +75,31 @@ getRange (Free (Annotated (Info range _) _)) = range getRange (Pure patch) = let Info range _ :< _ = getSplitTerm patch in range -- | Returns the header given two source blobs and a hunk. -header :: Join SourceBlob -> Hunk a -> String +header :: Both SourceBlob -> Hunk a -> String header blobs hunk = "diff --git a/" ++ pathA ++ " b/" ++ pathB ++ "\n" ++ "index " ++ oidA ++ ".." ++ oidB ++ "\n" ++ "@@ -" ++ show offsetA ++ "," ++ show lengthA ++ " +" ++ show offsetB ++ "," ++ show lengthB ++ " @@\n" - where (lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk - (offsetA, offsetB) = runJoin . fmap getSum $ offset hunk - (pathA, pathB) = runJoin $ path <$> blobs - (oidA, oidB) = runJoin $ oid <$> blobs + where (lengthA, lengthB) = runBoth . fmap getSum $ hunkLength hunk + (offsetA, offsetB) = runBoth . fmap getSum $ offset hunk + (pathA, pathB) = runBoth $ path <$> blobs + (oidA, oidB) = runBoth $ oid <$> blobs -- | Render a diff as a series of hunks. hunks :: Renderer a [Hunk (SplitDiff a Info)] -hunks diff blobs = hunksInRows (Join (1, 1)) . fst $ splitDiffByLines diff (0, 0) (before, after) +hunks diff blobs = hunksInRows (Both (1, 1)) . fst $ splitDiffByLines diff (0, 0) (before, after) where - (before, after) = runJoin $ source <$> blobs + (before, after) = runBoth $ source <$> blobs -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. -hunksInRows :: Join (Sum Int) -> [Row (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)] +hunksInRows :: Both (Sum Int) -> [Row (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)] hunksInRows start rows = case nextHunk start rows of 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. -nextHunk :: Join (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Row (SplitDiff a Info)]) +nextHunk :: Both (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Row (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') @@ -111,7 +111,7 @@ nextHunk start rows = case nextChange start rows of -- | Given beginning line numbers, return the number of lines to the next -- | the next change, and the remaining rows of the split diff. -nextChange :: Join (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Join (Sum Int), Change (SplitDiff a Info), [Row (SplitDiff a Info)]) +nextChange :: Both (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Both (Sum Int), Change (SplitDiff a Info), [Row (SplitDiff a Info)]) nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of Nothing -> Nothing Just (change, afterChanges) -> Just (start <> mconcat (rowLength <$> skippedContext), change, afterChanges) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 72b498af9..85d9e8ed1 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -21,7 +21,7 @@ import qualified Text.Blaze.Html5.Attributes as A import qualified Data.Text as T import qualified Data.Text.Lazy as TL import Text.Blaze.Html.Renderer.Text -import Data.Bifunctor.Join +import Data.Functor.Both import Data.Foldable import Data.Monoid import Source hiding ((++)) @@ -62,7 +62,7 @@ split diff blobs = renderHtml ((colgroup $ (col ! A.width (stringValue . show $ columnWidth)) <> col <> (col ! A.width (stringValue . show $ columnWidth)) <> col) <>) . mconcat $ numberedLinesToMarkup <$> reverse numbered where - (before, after) = runJoin $ Source.source <$> blobs + (before, after) = runBoth $ Source.source <$> blobs rows = fst (splitDiffByLines diff (0, 0) (before, after)) numbered = foldl' numberRows [] rows maxNumber = case numbered of diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index bc1c673c4..964d793a6 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -7,7 +7,7 @@ import qualified Renderer.Split as Split import qualified Source as S import Control.DeepSeq -import Data.Bifunctor.Join +import Data.Functor.Both import qualified Data.ByteString.Char8 as B1 import Data.List as List import Data.Map as Map @@ -72,10 +72,10 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte -- | is true, but the diff will still be calculated. testDiff :: Renderer T.Text String -> FilePath -> FilePath -> Maybe FilePath -> ((String, String) -> Expectation) -> Expectation testDiff renderer a b diff matcher = do - let paths = Join (a, b) + let paths = Both (a, b) let parser = parserForFilepath a sources <- sequence $ readAndTranscodeFile <$> paths - let sourceBlobs = Join (S.SourceBlob, S.SourceBlob) <*> sources <*> Join (mempty, mempty) <*> paths + let sourceBlobs = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> Both (mempty, mempty) <*> paths actual <- diffFiles parser renderer sourceBlobs case diff of Nothing -> actual `deepseq` matcher (actual, actual) diff --git a/test/PatchOutputSpec.hs b/test/PatchOutputSpec.hs index 4ba8795ac..f8ce503a2 100644 --- a/test/PatchOutputSpec.hs +++ b/test/PatchOutputSpec.hs @@ -1,6 +1,6 @@ module PatchOutputSpec where -import Data.Bifunctor.Join +import Data.Functor.Both import Diff import Renderer.Patch import Range @@ -13,4 +13,4 @@ spec :: Spec spec = parallel $ describe "hunks" $ it "empty diffs have no hunks" $ - hunks (Free . Annotated (Info (Range 0 0) mempty, Info (Range 0 0) mempty) $ Leaf "") (Join (SourceBlob (fromList "") "abcde" "path2.txt", SourceBlob (fromList "") "xyz" "path2.txt")) `shouldBe` [] + hunks (Free . Annotated (Info (Range 0 0) mempty, Info (Range 0 0) mempty) $ Leaf "") (Both (SourceBlob (fromList "") "abcde" "path2.txt", SourceBlob (fromList "") "xyz" "path2.txt")) `shouldBe` [] From 51e916c1a8d2345d87a5472c2ecfeb99d338664f Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:43:13 -0500 Subject: [PATCH 09/70] Remove redundant imports. --- src/Renderer/Patch.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index e93cde306..203946c5c 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -15,9 +15,7 @@ import Control.Comonad.Cofree import Control.Monad.Free import Data.Maybe import Data.Monoid -import Data.Bifunctor import Data.Functor.Both -import Control.Monad -- | Render a diff in the traditional patch format. patch :: Renderer a String From 863251183fef70851326be9a2273d09af5b9035c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:43:47 -0500 Subject: [PATCH 10/70] Represent alignment sources in Both. --- src/Alignment.hs | 21 +++++++++++---------- src/Renderer/Patch.hs | 6 ++---- src/Renderer/Split.hs | 5 +++-- test/AlignmentSpec.hs | 15 ++++++++++----- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index bdbbe1ff0..cae183702 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -4,6 +4,7 @@ import Category import Control.Comonad.Cofree import Control.Monad.Free import Data.Either +import Data.Functor.Both import Data.Functor.Identity import qualified Data.OrderedMap as Map import qualified Data.Set as Set @@ -18,15 +19,15 @@ import Syntax import Term -- | Split a diff, which may span multiple lines, into rows of split diffs. -splitDiffByLines :: Diff leaf Info -> (Int, Int) -> (Source Char, Source Char) -> ([Row (SplitDiff leaf Info)], (Range, Range)) +splitDiffByLines :: Diff leaf Info -> (Int, Int) -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], (Range, Range)) splitDiffByLines diff (prevLeft, prevRight) sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) - Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd sources) in + Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd $ runBoth sources) in (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, (Range prevLeft prevLeft, range)) - Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst sources) in + Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst $ runBoth sources) in (flip Row EmptyLine . fmap (Pure . SplitDelete) <$> lines, (range, Range prevRight prevRight)) - Pure (Replace leftTerm rightTerm) -> let (leftLines, leftRange) = splitTermByLines leftTerm (fst sources) - (rightLines, rightRange) = splitTermByLines rightTerm (snd sources) in + Pure (Replace leftTerm rightTerm) -> let (leftLines, leftRange) = splitTermByLines leftTerm (fst $ runBoth sources) + (rightLines, rightRange) = splitTermByLines rightTerm (snd $ runBoth sources) in (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), (leftRange, rightRange)) where categories (Info _ left, Info _ right) = (left, right) ranges (Info left _, Info right _) = (left, right) @@ -67,19 +68,19 @@ splitTermByLines (Info range categories :< syntax) source = flip (,) range $ cas (adjoin $ lines ++ (pure . Left <$> actualLineRanges (Range previous $ start childRange) source) ++ (fmap (Right . (<$ child)) <$> childLines), end childRange) -- | Split a annotated diff into rows of split diffs. -splitAnnotatedByLines :: (Source Char, Source Char) -> (Range, Range) -> (Set.Set Category, Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] +splitAnnotatedByLines :: Both (Source Char) -> (Range, Range) -> (Set.Set Category, Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] splitAnnotatedByLines sources ranges categories syntax = case syntax of Leaf a -> wrapRowContents (Free . (`Annotated` Leaf a) . (`Info` fst categories) . unionRanges) (Free . (`Annotated` Leaf a) . (`Info` snd categories) . unionRanges) <$> contextRows ranges sources Indexed children -> adjoinChildRows (Indexed . fmap get) (Identity <$> children) Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) - where contextRows :: (Range, Range) -> (Source Char, Source Char) -> [Row Range] + where contextRows :: (Range, Range) -> Both (Source Char) -> [Row Range] contextRows ranges sources = zipWithDefaults Row EmptyLine EmptyLine - (pure <$> actualLineRanges (fst ranges) (fst sources)) - (pure <$> actualLineRanges (snd ranges) (snd sources)) + (pure <$> actualLineRanges (fst ranges) (fst $ runBoth sources)) + (pure <$> actualLineRanges (snd ranges) (snd $ runBoth sources)) adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] - adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange $ fst sources) (openDiff $ fst sources)) (openEither (openRange $ snd sources) (openDiff $ snd sources))) [] + adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange . fst $ runBoth sources) (openDiff . fst $ runBoth sources)) (openEither (openRange . snd $ runBoth sources) (openDiff . snd $ runBoth sources))) [] adjoinChildRows :: (Has f) => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], starts ranges) children in diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index 203946c5c..2a6ebb303 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -13,9 +13,9 @@ import Source hiding ((++), break) import SplitDiff import Control.Comonad.Cofree import Control.Monad.Free +import Data.Functor.Both import Data.Maybe import Data.Monoid -import Data.Functor.Both -- | Render a diff in the traditional patch format. patch :: Renderer a String @@ -84,9 +84,7 @@ header blobs hunk = "diff --git a/" ++ pathA ++ " b/" ++ pathB ++ "\n" ++ -- | Render a diff as a series of hunks. hunks :: Renderer a [Hunk (SplitDiff a Info)] -hunks diff blobs = hunksInRows (Both (1, 1)) . fst $ splitDiffByLines diff (0, 0) (before, after) - where - (before, after) = runBoth $ source <$> blobs +hunks diff blobs = hunksInRows (Both (1, 1)) . fst $ splitDiffByLines diff (0, 0) (source <$> blobs) -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 85d9e8ed1..80f8afa1a 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -62,8 +62,9 @@ split diff blobs = renderHtml ((colgroup $ (col ! A.width (stringValue . show $ columnWidth)) <> col <> (col ! A.width (stringValue . show $ columnWidth)) <> col) <>) . mconcat $ numberedLinesToMarkup <$> reverse numbered where - (before, after) = runBoth $ Source.source <$> blobs - rows = fst (splitDiffByLines diff (0, 0) (before, after)) + sources = Source.source <$> blobs + (before, after) = runBoth sources + rows = fst (splitDiffByLines diff (0, 0) sources) numbered = foldl' numberRows [] rows maxNumber = case numbered of [] -> 0 diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 39fb34271..d6dde1c07 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -8,6 +8,7 @@ import Data.Text.Arbitrary () import Alignment import Control.Comonad.Cofree import Control.Monad.Free hiding (unfold) +import Data.Functor.Both import Diff import qualified Data.Maybe as Maybe import Data.Functor.Identity @@ -18,6 +19,9 @@ import Range import Syntax import ArbitraryTerm () +instance Arbitrary a => Arbitrary (Both a) where + arbitrary = pure (curry Both) <*> arbitrary <*> arbitrary + instance Arbitrary a => Arbitrary (Row a) where arbitrary = oneof [ Row <$> arbitrary <*> arbitrary ] @@ -39,21 +43,22 @@ spec = parallel $ do describe "splitAnnotatedByLines" $ do prop "outputs one row for single-line unchanged leaves" $ forAll (arbitraryLeaf `suchThat` isOnSingleLine) $ - \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (source, source) (range, range) (categories, categories) syntax `shouldBe` [ + \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (pure source) (range, range) (categories, categories) syntax `shouldBe` [ Row (makeLine [ Free $ Annotated info $ Leaf source ]) (makeLine [ Free $ Annotated info $ Leaf source ]) ] prop "outputs one row for single-line empty unchanged indexed nodes" $ forAll (arbitrary `suchThat` (\ a -> filter (/= '\n') (toList a) == toList a)) $ - \ source -> splitAnnotatedByLines (source, source) (getTotalRange source, getTotalRange source) (mempty, mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ + \ source -> splitAnnotatedByLines (pure source) (getTotalRange source, getTotalRange source) (mempty, mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ Row (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) ] prop "preserves line counts in equal sources" $ \ source -> - length (splitAnnotatedByLines (source, source) (getTotalRange source, getTotalRange source) (mempty, mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 + length (splitAnnotatedByLines (pure source) (getTotalRange source, getTotalRange source) (mempty, mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 prop "produces the maximum line count in inequal sources" $ - \ sourceA sourceB -> - length (splitAnnotatedByLines (sourceA, sourceB) (getTotalRange sourceA, getTotalRange sourceB) (mempty, mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) + \ sources -> + let (sourceA, sourceB) = runBoth sources in + length (splitAnnotatedByLines sources (getTotalRange sourceA, getTotalRange sourceB) (mempty, mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ From 8dc4b5bb69989686eeb93aa3c13b179b8e61dfba Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 21:46:13 -0500 Subject: [PATCH 11/70] Represent alignment ranges in Both. --- src/Alignment.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index cae183702..dac65468b 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -19,16 +19,16 @@ import Syntax import Term -- | Split a diff, which may span multiple lines, into rows of split diffs. -splitDiffByLines :: Diff leaf Info -> (Int, Int) -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], (Range, Range)) +splitDiffByLines :: Diff leaf Info -> (Int, Int) -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], Both Range) splitDiffByLines diff (prevLeft, prevRight) sources = case diff of - Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) + Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, Both $ ranges annotation) Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd $ runBoth sources) in - (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, (Range prevLeft prevLeft, range)) + (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst $ runBoth sources) in - (flip Row EmptyLine . fmap (Pure . SplitDelete) <$> lines, (range, Range prevRight prevRight)) + (flip Row EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, Range prevRight prevRight)) Pure (Replace leftTerm rightTerm) -> let (leftLines, leftRange) = splitTermByLines leftTerm (fst $ runBoth sources) (rightLines, rightRange) = splitTermByLines rightTerm (snd $ runBoth sources) in - (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), (leftRange, rightRange)) + (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), Both (leftRange, rightRange)) where categories (Info _ left, Info _ right) = (left, right) ranges (Info left _, Info right _) = (left, right) @@ -97,7 +97,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of childRows :: (Has f) => ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) -> f (Diff leaf Info) -> ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) childRows (rows, previous) child = let (childRows, childRanges) = splitDiffByLines (get child) previous sources in - (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (starts childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), ends childRanges) + (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (starts $ runBoth childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), ends (runBoth childRanges)) starts (left, right) = (start left, start right) ends (left, right) = (end left, end right) From 81a1a14d4c70988f7677ed38ad26a97d7ade8760 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:01:21 -0500 Subject: [PATCH 12/70] Represent the input Ranges in Both. --- src/Alignment.hs | 22 ++++++++++------------ test/AlignmentSpec.hs | 8 ++++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index dac65468b..45ce86a39 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -21,7 +21,7 @@ import Term -- | Split a diff, which may span multiple lines, into rows of split diffs. splitDiffByLines :: Diff leaf Info -> (Int, Int) -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], Both Range) splitDiffByLines diff (prevLeft, prevRight) sources = case diff of - Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, Both $ ranges annotation) + Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd $ runBoth sources) in (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst $ runBoth sources) in @@ -30,7 +30,7 @@ splitDiffByLines diff (prevLeft, prevRight) sources = case diff of (rightLines, rightRange) = splitTermByLines rightTerm (snd $ runBoth sources) in (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), Both (leftRange, rightRange)) where categories (Info _ left, Info _ right) = (left, right) - ranges (Info left _, Info right _) = (left, right) + ranges annotations = characterRange <$> Both annotations -- | A functor that can return its content. class Functor f => Has f where @@ -68,23 +68,23 @@ splitTermByLines (Info range categories :< syntax) source = flip (,) range $ cas (adjoin $ lines ++ (pure . Left <$> actualLineRanges (Range previous $ start childRange) source) ++ (fmap (Right . (<$ child)) <$> childLines), end childRange) -- | Split a annotated diff into rows of split diffs. -splitAnnotatedByLines :: Both (Source Char) -> (Range, Range) -> (Set.Set Category, Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] +splitAnnotatedByLines :: Both (Source Char) -> Both Range -> (Set.Set Category, Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] splitAnnotatedByLines sources ranges categories syntax = case syntax of Leaf a -> wrapRowContents (Free . (`Annotated` Leaf a) . (`Info` fst categories) . unionRanges) (Free . (`Annotated` Leaf a) . (`Info` snd categories) . unionRanges) <$> contextRows ranges sources Indexed children -> adjoinChildRows (Indexed . fmap get) (Identity <$> children) Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) - where contextRows :: (Range, Range) -> Both (Source Char) -> [Row Range] + where contextRows :: Both Range -> Both (Source Char) -> [Row Range] contextRows ranges sources = zipWithDefaults Row EmptyLine EmptyLine - (pure <$> actualLineRanges (fst ranges) (fst $ runBoth sources)) - (pure <$> actualLineRanges (snd ranges) (snd $ runBoth sources)) + (pure <$> actualLineRanges (fst $ runBoth ranges) (fst $ runBoth sources)) + (pure <$> actualLineRanges (snd $ runBoth ranges) (snd $ runBoth sources)) adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange . fst $ runBoth sources) (openDiff . fst $ runBoth sources)) (openEither (openRange . snd $ runBoth sources) (openDiff . snd $ runBoth sources))) [] adjoinChildRows :: (Has f) => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] - adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], starts ranges) children in - fmap (wrapRowContents (wrap constructor (fst categories)) (wrap constructor (snd categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (ends ranges)) sources) + adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], runBoth $ start <$> ranges) children in + fmap (wrapRowContents (wrap constructor (fst categories)) (wrap constructor (snd categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) wrap :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> Set.Set Category -> [Either Range (f (SplitDiff leaf Info))] -> SplitDiff leaf Info wrap constructor categories children = Free . Annotated (Info (unionRanges $ getRange <$> children) categories) . constructor $ rights children @@ -97,11 +97,9 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of childRows :: (Has f) => ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) -> f (Diff leaf Info) -> ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) childRows (rows, previous) child = let (childRows, childRanges) = splitDiffByLines (get child) previous sources in - (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (starts $ runBoth childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), ends (runBoth childRanges)) + (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (start <$> childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), runBoth $ end <$> childRanges) - starts (left, right) = (start left, start right) - ends (left, right) = (end left, end right) - makeRanges (leftStart, rightStart) (leftEnd, rightEnd) = (Range leftStart leftEnd, Range rightStart rightEnd) + makeRanges (leftStart, rightStart) (Both (leftEnd, rightEnd)) = Both (Range leftStart leftEnd, Range rightStart rightEnd) -- | Returns a function that takes an Either, applies either the left or right -- | MaybeOpen, and returns Nothing or the original either. diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index d6dde1c07..3f18ea2c9 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -43,22 +43,22 @@ spec = parallel $ do describe "splitAnnotatedByLines" $ do prop "outputs one row for single-line unchanged leaves" $ forAll (arbitraryLeaf `suchThat` isOnSingleLine) $ - \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (pure source) (range, range) (categories, categories) syntax `shouldBe` [ + \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (pure source) (pure range) (categories, categories) syntax `shouldBe` [ Row (makeLine [ Free $ Annotated info $ Leaf source ]) (makeLine [ Free $ Annotated info $ Leaf source ]) ] prop "outputs one row for single-line empty unchanged indexed nodes" $ forAll (arbitrary `suchThat` (\ a -> filter (/= '\n') (toList a) == toList a)) $ - \ source -> splitAnnotatedByLines (pure source) (getTotalRange source, getTotalRange source) (mempty, mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ + \ source -> splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (mempty, mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ Row (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) ] prop "preserves line counts in equal sources" $ \ source -> - length (splitAnnotatedByLines (pure source) (getTotalRange source, getTotalRange source) (mempty, mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 + length (splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (mempty, mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 prop "produces the maximum line count in inequal sources" $ \ sources -> let (sourceA, sourceB) = runBoth sources in - length (splitAnnotatedByLines sources (getTotalRange sourceA, getTotalRange sourceB) (mempty, mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) + length (splitAnnotatedByLines sources (getTotalRange <$> sources) (mempty, mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ From c3039cd853401b4f42d878d8c069a3947eaee179 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:04:18 -0500 Subject: [PATCH 13/70] Run the computation over both sides of the functor. --- src/Alignment.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 45ce86a39..5e1b33e79 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -75,9 +75,8 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) where contextRows :: Both Range -> Both (Source Char) -> [Row Range] - contextRows ranges sources = zipWithDefaults Row EmptyLine EmptyLine - (pure <$> actualLineRanges (fst $ runBoth ranges) (fst $ runBoth sources)) - (pure <$> actualLineRanges (snd $ runBoth ranges) (snd $ runBoth sources)) + contextRows ranges sources = uncurry (zipWithDefaults Row EmptyLine EmptyLine) $ + runBoth (fmap pure <$> (actualLineRanges <$> ranges <*> sources)) adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange . fst $ runBoth sources) (openDiff . fst $ runBoth sources)) (openEither (openRange . snd $ runBoth sources) (openDiff . snd $ runBoth sources))) [] From 0d77576a526014cbc0a0469a842d3b7ec111a44e Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:10:02 -0500 Subject: [PATCH 14/70] Represent categories in Both. --- src/Alignment.hs | 8 ++++---- test/AlignmentSpec.hs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 5e1b33e79..e8dc9b266 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -29,7 +29,7 @@ splitDiffByLines diff (prevLeft, prevRight) sources = case diff of Pure (Replace leftTerm rightTerm) -> let (leftLines, leftRange) = splitTermByLines leftTerm (fst $ runBoth sources) (rightLines, rightRange) = splitTermByLines rightTerm (snd $ runBoth sources) in (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), Both (leftRange, rightRange)) - where categories (Info _ left, Info _ right) = (left, right) + where categories annotations = Diff.categories <$> Both annotations ranges annotations = characterRange <$> Both annotations -- | A functor that can return its content. @@ -68,9 +68,9 @@ splitTermByLines (Info range categories :< syntax) source = flip (,) range $ cas (adjoin $ lines ++ (pure . Left <$> actualLineRanges (Range previous $ start childRange) source) ++ (fmap (Right . (<$ child)) <$> childLines), end childRange) -- | Split a annotated diff into rows of split diffs. -splitAnnotatedByLines :: Both (Source Char) -> Both Range -> (Set.Set Category, Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] +splitAnnotatedByLines :: Both (Source Char) -> Both Range -> Both (Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] splitAnnotatedByLines sources ranges categories syntax = case syntax of - Leaf a -> wrapRowContents (Free . (`Annotated` Leaf a) . (`Info` fst categories) . unionRanges) (Free . (`Annotated` Leaf a) . (`Info` snd categories) . unionRanges) <$> contextRows ranges sources + Leaf a -> wrapRowContents (Free . (`Annotated` Leaf a) . (`Info` fst (runBoth categories)) . unionRanges) (Free . (`Annotated` Leaf a) . (`Info` snd (runBoth categories)) . unionRanges) <$> contextRows ranges sources Indexed children -> adjoinChildRows (Indexed . fmap get) (Identity <$> children) Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) @@ -83,7 +83,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of adjoinChildRows :: (Has f) => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], runBoth $ start <$> ranges) children in - fmap (wrapRowContents (wrap constructor (fst categories)) (wrap constructor (snd categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) + fmap (wrapRowContents (wrap constructor (fst $ runBoth categories)) (wrap constructor (snd $ runBoth categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) wrap :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> Set.Set Category -> [Either Range (f (SplitDiff leaf Info))] -> SplitDiff leaf Info wrap constructor categories children = Free . Annotated (Info (unionRanges $ getRange <$> children) categories) . constructor $ rights children diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 3f18ea2c9..862563a20 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -43,22 +43,22 @@ spec = parallel $ do describe "splitAnnotatedByLines" $ do prop "outputs one row for single-line unchanged leaves" $ forAll (arbitraryLeaf `suchThat` isOnSingleLine) $ - \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (pure source) (pure range) (categories, categories) syntax `shouldBe` [ + \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (pure source) (pure range) (pure categories) syntax `shouldBe` [ Row (makeLine [ Free $ Annotated info $ Leaf source ]) (makeLine [ Free $ Annotated info $ Leaf source ]) ] prop "outputs one row for single-line empty unchanged indexed nodes" $ forAll (arbitrary `suchThat` (\ a -> filter (/= '\n') (toList a) == toList a)) $ - \ source -> splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (mempty, mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ + \ source -> splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (pure mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ Row (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) ] prop "preserves line counts in equal sources" $ \ source -> - length (splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (mempty, mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 + length (splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (pure mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 prop "produces the maximum line count in inequal sources" $ \ sources -> let (sourceA, sourceB) = runBoth sources in - length (splitAnnotatedByLines sources (getTotalRange <$> sources) (mempty, mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) + length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ From 3cf56fb6359ea53e0a4bb7fb787eaf931aa2350b Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:13:32 -0500 Subject: [PATCH 15/70] Map the corpus tests over Both input files. --- test/CorpusSpec.hs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index 964d793a6..873741ef8 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -35,13 +35,13 @@ spec = parallel $ do runTestsIn directory matcher = do paths <- runIO $ examples directory let tests = correctTests =<< paths - mapM_ (\ (formatName, renderer, a, b, output) -> it (normalizeName a ++ " (" ++ formatName ++ ")") $ testDiff renderer a b output matcher) tests + mapM_ (\ (formatName, renderer, paths, output) -> it (normalizeName (fst $ runBoth paths) ++ " (" ++ formatName ++ ")") $ testDiff renderer paths output matcher) tests - correctTests :: (FilePath, FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer a String, FilePath, FilePath, Maybe FilePath)] - correctTests paths@(_, _, Nothing, Nothing) = testsForPaths paths - correctTests paths = List.filter (\(_, _, _, _, output) -> isJust output) $ testsForPaths paths - testsForPaths :: (FilePath, FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer a String, FilePath, FilePath, Maybe FilePath)] - testsForPaths (a, b, patch, split) = [ ("patch", P.patch, a, b, patch), ("split", testSplit, a, b, split) ] + correctTests :: (Both FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer a String, Both FilePath, Maybe FilePath)] + correctTests paths@(_, Nothing, Nothing) = testsForPaths paths + correctTests paths = List.filter (\(_, _, _, output) -> isJust output) $ testsForPaths paths + testsForPaths :: (Both FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer a String, Both FilePath, Maybe FilePath)] + testsForPaths (paths, patch, split) = [ ("patch", P.patch, paths, patch), ("split", testSplit, paths, split) ] testSplit :: Renderer a String testSplit diff sources = TL.unpack $ Split.split diff sources @@ -49,14 +49,14 @@ spec = parallel $ do -- | Return all the examples from the given directory. Examples are expected to -- | have the form "foo.A.js", "foo.B.js", "foo.patch.js". Diffs are not -- | required as the test may be verifying that the inputs don't crash. -examples :: FilePath -> IO [(FilePath, FilePath, Maybe FilePath, Maybe FilePath)] +examples :: FilePath -> IO [(Both FilePath, Maybe FilePath, Maybe FilePath)] examples directory = do as <- toDict <$> globFor "*.A.*" bs <- toDict <$> globFor "*.B.*" patches <- toDict <$> globFor "*.patch.*" splits <- toDict <$> globFor "*.split.*" let keys = Set.unions $ keysSet <$> [as, bs] - return $ (\name -> (as ! name, bs ! name, Map.lookup name patches, Map.lookup name splits)) <$> sort (Set.toList keys) + return $ (\name -> (Both (as ! name, bs ! name), Map.lookup name patches, Map.lookup name splits)) <$> sort (Set.toList keys) where globFor :: String -> IO [FilePath] @@ -70,10 +70,9 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte -- | Given file paths for A, B, and, optionally, a diff, return whether diffing -- | the files will produce the diff. If no diff is provided, then the result -- | is true, but the diff will still be calculated. -testDiff :: Renderer T.Text String -> FilePath -> FilePath -> Maybe FilePath -> ((String, String) -> Expectation) -> Expectation -testDiff renderer a b diff matcher = do - let paths = Both (a, b) - let parser = parserForFilepath a +testDiff :: Renderer T.Text String -> Both FilePath -> Maybe FilePath -> ((String, String) -> Expectation) -> Expectation +testDiff renderer paths diff matcher = do + let parser = parserForFilepath (fst $ runBoth paths) sources <- sequence $ readAndTranscodeFile <$> paths let sourceBlobs = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> Both (mempty, mempty) <*> paths actual <- diffFiles parser renderer sourceBlobs From a6c1ec1ddf9f9be829a7b89a418207e2a42d6278 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:18:44 -0500 Subject: [PATCH 16/70] Curry. --- test/CorpusSpec.hs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index 873741ef8..e6ce6e712 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -22,16 +22,16 @@ import Test.Hspec spec :: Spec spec = parallel $ do -- describe "crashers crash" $ runTestsIn "test/crashers-todo/" ((`shouldThrow` anyException) . return) - describe "crashers should not crash" $ runTestsIn "test/crashers/" (uncurry shouldBe) - describe "todos are incorrect" $ runTestsIn "test/diffs-todo/" (uncurry shouldNotBe) - describe "should produce the correct diff" $ runTestsIn "test/diffs/" (uncurry shouldBe) + describe "crashers should not crash" $ runTestsIn "test/crashers/" shouldBe + describe "todos are incorrect" $ runTestsIn "test/diffs-todo/" shouldNotBe + describe "should produce the correct diff" $ runTestsIn "test/diffs/" shouldBe it "lists example fixtures" $ do examples "test/crashers/" `shouldNotReturn` [] examples "test/diffs/" `shouldNotReturn` [] where - runTestsIn :: String -> ((String, String) -> Expectation) -> SpecWith () + runTestsIn :: String -> (String -> String -> Expectation) -> SpecWith () runTestsIn directory matcher = do paths <- runIO $ examples directory let tests = correctTests =<< paths @@ -70,14 +70,14 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte -- | Given file paths for A, B, and, optionally, a diff, return whether diffing -- | the files will produce the diff. If no diff is provided, then the result -- | is true, but the diff will still be calculated. -testDiff :: Renderer T.Text String -> Both FilePath -> Maybe FilePath -> ((String, String) -> Expectation) -> Expectation +testDiff :: Renderer T.Text String -> Both FilePath -> Maybe FilePath -> (String -> String -> Expectation) -> Expectation testDiff renderer paths diff matcher = do let parser = parserForFilepath (fst $ runBoth paths) sources <- sequence $ readAndTranscodeFile <$> paths let sourceBlobs = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> Both (mempty, mempty) <*> paths actual <- diffFiles parser renderer sourceBlobs case diff of - Nothing -> actual `deepseq` matcher (actual, actual) + Nothing -> actual `deepseq` matcher actual actual Just file -> do expected <- readFile file - matcher (actual, expected) + matcher actual expected From 9f09b39b9825aa3cace2361570d7e53f96731d5c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:21:17 -0500 Subject: [PATCH 17/70] 100% undiluted pure mempty. --- test/CorpusSpec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index e6ce6e712..891b46bcb 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -74,7 +74,7 @@ testDiff :: Renderer T.Text String -> Both FilePath -> Maybe FilePath -> (String testDiff renderer paths diff matcher = do let parser = parserForFilepath (fst $ runBoth paths) sources <- sequence $ readAndTranscodeFile <$> paths - let sourceBlobs = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> Both (mempty, mempty) <*> paths + let sourceBlobs = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> pure mempty <*> paths actual <- diffFiles parser renderer sourceBlobs case diff of Nothing -> actual `deepseq` matcher actual actual From fdd3234c44e4399c05b6bee10450902025584df0 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:29:22 -0500 Subject: [PATCH 18/70] Remove some parens. --- src/Alignment.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index e8dc9b266..a1e314b7d 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -81,7 +81,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange . fst $ runBoth sources) (openDiff . fst $ runBoth sources)) (openEither (openRange . snd $ runBoth sources) (openDiff . snd $ runBoth sources))) [] - adjoinChildRows :: (Has f) => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] + adjoinChildRows :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], runBoth $ start <$> ranges) children in fmap (wrapRowContents (wrap constructor (fst $ runBoth categories)) (wrap constructor (snd $ runBoth categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) From c8166041ef671156ed2d8a3395181c2f13fb0ca6 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:32:27 -0500 Subject: [PATCH 19/70] Remove some more parens. --- src/Alignment.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index a1e314b7d..29180b9ed 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -94,7 +94,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of (Free (Annotated (Info range _) _)) -> range getRange (Left range) = range - childRows :: (Has f) => ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) -> f (Diff leaf Info) -> ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) + childRows :: Has f => ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) -> f (Diff leaf Info) -> ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) childRows (rows, previous) child = let (childRows, childRanges) = splitDiffByLines (get child) previous sources in (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (start <$> childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), runBoth $ end <$> childRanges) From d2f6ea8385115c2fbe59d29324647955943797c6 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Sun, 28 Feb 2016 22:43:48 -0500 Subject: [PATCH 20/70] Represent the previous integers in Both. --- src/Alignment.hs | 13 +++++++------ src/Renderer/Patch.hs | 2 +- src/Renderer/Split.hs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 29180b9ed..f17afa7fd 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -19,8 +19,8 @@ import Syntax import Term -- | Split a diff, which may span multiple lines, into rows of split diffs. -splitDiffByLines :: Diff leaf Info -> (Int, Int) -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], Both Range) -splitDiffByLines diff (prevLeft, prevRight) sources = case diff of +splitDiffByLines :: Diff leaf Info -> Both Int -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], Both Range) +splitDiffByLines diff previous sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd $ runBoth sources) in (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) @@ -31,6 +31,7 @@ splitDiffByLines diff (prevLeft, prevRight) sources = case diff of (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), Both (leftRange, rightRange)) where categories annotations = Diff.categories <$> Both annotations ranges annotations = characterRange <$> Both annotations + (prevLeft, prevRight) = runBoth previous -- | A functor that can return its content. class Functor f => Has f where @@ -82,7 +83,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange . fst $ runBoth sources) (openDiff . fst $ runBoth sources)) (openEither (openRange . snd $ runBoth sources) (openDiff . snd $ runBoth sources))) [] adjoinChildRows :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] - adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], runBoth $ start <$> ranges) children in + adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], start <$> ranges) children in fmap (wrapRowContents (wrap constructor (fst $ runBoth categories)) (wrap constructor (snd $ runBoth categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) wrap :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> Set.Set Category -> [Either Range (f (SplitDiff leaf Info))] -> SplitDiff leaf Info @@ -94,11 +95,11 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of (Free (Annotated (Info range _) _)) -> range getRange (Left range) = range - childRows :: Has f => ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) -> f (Diff leaf Info) -> ([Row (Either Range (f (SplitDiff leaf Info)))], (Int, Int)) + childRows :: Has f => ([Row (Either Range (f (SplitDiff leaf Info)))], Both Int) -> f (Diff leaf Info) -> ([Row (Either Range (f (SplitDiff leaf Info)))], Both Int) childRows (rows, previous) child = let (childRows, childRanges) = splitDiffByLines (get child) previous sources in - (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (start <$> childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), runBoth $ end <$> childRanges) + (adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (start <$> childRanges)) sources) ++ (fmap (Right . (<$ child)) <$> childRows), end <$> childRanges) - makeRanges (leftStart, rightStart) (Both (leftEnd, rightEnd)) = Both (Range leftStart leftEnd, Range rightStart rightEnd) + makeRanges (Both (leftStart, rightStart)) (Both (leftEnd, rightEnd)) = Both (Range leftStart leftEnd, Range rightStart rightEnd) -- | Returns a function that takes an Either, applies either the left or right -- | MaybeOpen, and returns Nothing or the original either. diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index 2a6ebb303..e4b8f84d5 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -84,7 +84,7 @@ header blobs hunk = "diff --git a/" ++ pathA ++ " b/" ++ pathB ++ "\n" ++ -- | Render a diff as a series of hunks. hunks :: Renderer a [Hunk (SplitDiff a Info)] -hunks diff blobs = hunksInRows (Both (1, 1)) . fst $ splitDiffByLines diff (0, 0) (source <$> blobs) +hunks diff blobs = hunksInRows (Both (1, 1)) . fst $ splitDiffByLines diff (pure 0) (source <$> blobs) -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 80f8afa1a..586580d07 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -64,7 +64,7 @@ split diff blobs = renderHtml where sources = Source.source <$> blobs (before, after) = runBoth sources - rows = fst (splitDiffByLines diff (0, 0) sources) + rows = fst (splitDiffByLines diff (pure 0) sources) numbered = foldl' numberRows [] rows maxNumber = case numbered of [] -> 0 From 834b521e2360df845d2a819c7bfc9bcd17517530 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 00:34:18 -0500 Subject: [PATCH 21/70] Distribute mapping replacements over Both terms. --- src/Alignment.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index f17afa7fd..d256ec4db 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -26,9 +26,9 @@ splitDiffByLines diff previous sources = case diff of (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst $ runBoth sources) in (flip Row EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, Range prevRight prevRight)) - Pure (Replace leftTerm rightTerm) -> let (leftLines, leftRange) = splitTermByLines leftTerm (fst $ runBoth sources) - (rightLines, rightRange) = splitTermByLines rightTerm (snd $ runBoth sources) in - (zipWithDefaults Row EmptyLine EmptyLine (fmap (Pure . SplitReplace) <$> leftLines) (fmap (Pure . SplitReplace) <$> rightLines), Both (leftRange, rightRange)) + Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources + (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in + (uncurry (zipWithDefaults Row EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) where categories annotations = Diff.categories <$> Both annotations ranges annotations = characterRange <$> Both annotations (prevLeft, prevRight) = runBoth previous From e606700b5710ae6949b50a59e0dc09a095a614e1 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 00:42:48 -0500 Subject: [PATCH 22/70] unRow returns Both. --- src/Row.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Row.hs b/src/Row.hs index c4ac66895..fd7718062 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -1,6 +1,7 @@ module Row where import Control.Arrow +import Data.Functor.Both import Line -- | A row in a split diff, composed of a before line and an after line. @@ -8,8 +9,8 @@ data Row a = Row { unLeft :: !(Line a), unRight :: !(Line a) } deriving (Eq, Functor) -- | Return a tuple of lines from the row. -unRow :: Row a -> (Line a, Line a) -unRow (Row a b) = (a, b) +unRow :: Row a -> Both (Line a) +unRow (Row a b) = Both (a, b) -- | Map over both sides of a row with the given functions. wrapRowContents :: ([a] -> b) -> ([a] -> b) -> Row a -> Row b @@ -21,19 +22,19 @@ adjoinRowsBy :: MaybeOpen a -> MaybeOpen a -> [Row a] -> Row a -> [Row a] adjoinRowsBy _ _ [] row = [row] adjoinRowsBy f g rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows, Just _ <- openLineBy g $ unRight <$> rows = zipWith Row (lefts left') (rights right') - where (lefts, rights) = adjoinLinesBy f *** adjoinLinesBy g $ unzip $ unRow <$> rows + where (lefts, rights) = adjoinLinesBy f *** adjoinLinesBy g $ unzip $ runBoth . unRow <$> rows adjoinRowsBy f _ rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of EmptyLine -> rest _ -> Row EmptyLine right' : rest where rest = zipWith Row (lefts left') rights - (lefts, rights) = first (adjoinLinesBy f) $ unzip $ unRow <$> rows + (lefts, rights) = first (adjoinLinesBy f) $ unzip $ runBoth . unRow <$> rows adjoinRowsBy _ g rows (Row left' right') | Just _ <- openLineBy g $ unRight <$> rows = case left' of EmptyLine -> rest _ -> Row left' EmptyLine : rest where rest = zipWith Row lefts (rights right') - (lefts, rights) = second (adjoinLinesBy g) $ unzip $ unRow <$> rows + (lefts, rights) = second (adjoinLinesBy g) $ unzip $ runBoth . unRow <$> rows adjoinRowsBy _ _ rows row = row : rows From e23da3feb9853f132a84237aee890f68e4a0d0ba Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:18:09 -0500 Subject: [PATCH 23/70] Perform the adjoining through Both. --- src/Row.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Row.hs b/src/Row.hs index fd7718062..e1e4ca297 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -22,7 +22,7 @@ adjoinRowsBy :: MaybeOpen a -> MaybeOpen a -> [Row a] -> Row a -> [Row a] adjoinRowsBy _ _ [] row = [row] adjoinRowsBy f g rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows, Just _ <- openLineBy g $ unRight <$> rows = zipWith Row (lefts left') (rights right') - where (lefts, rights) = adjoinLinesBy f *** adjoinLinesBy g $ unzip $ runBoth . unRow <$> rows + where (lefts, rights) = runBoth $ adjoinLinesBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) adjoinRowsBy f _ rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of EmptyLine -> rest From 6170622468750f7821908704f2e6ee1bdc036496 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:22:18 -0500 Subject: [PATCH 24/70] Zip through Both. --- src/Row.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Row.hs b/src/Row.hs index e1e4ca297..024da525c 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -21,8 +21,8 @@ wrapRowContents transformLeft transformRight (Row left right) = Row (wrapLineCon adjoinRowsBy :: MaybeOpen a -> MaybeOpen a -> [Row a] -> Row a -> [Row a] adjoinRowsBy _ _ [] row = [row] -adjoinRowsBy f g rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows, Just _ <- openLineBy g $ unRight <$> rows = zipWith Row (lefts left') (rights right') - where (lefts, rights) = runBoth $ adjoinLinesBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) +adjoinRowsBy f g rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows, Just _ <- openLineBy g $ unRight <$> rows = uncurry (zipWith Row) . runBoth $ both <*> Both (left', right') + where both = adjoinLinesBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) adjoinRowsBy f _ rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of EmptyLine -> rest From 7f73085dd50d4ed7535fed54cc396d97e4b55f7d Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:25:50 -0500 Subject: [PATCH 25/70] Compute openLineBy across Both sides. --- src/Row.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Row.hs b/src/Row.hs index 024da525c..3ef131a6d 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -21,7 +21,7 @@ wrapRowContents transformLeft transformRight (Row left right) = Row (wrapLineCon adjoinRowsBy :: MaybeOpen a -> MaybeOpen a -> [Row a] -> Row a -> [Row a] adjoinRowsBy _ _ [] row = [row] -adjoinRowsBy f g rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows, Just _ <- openLineBy g $ unRight <$> rows = uncurry (zipWith Row) . runBoth $ both <*> Both (left', right') +adjoinRowsBy f g rows (Row left' right') | Both (Just _, Just _) <- openLineBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) = uncurry (zipWith Row) . runBoth $ both <*> Both (left', right') where both = adjoinLinesBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) adjoinRowsBy f _ rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of From 391d102dcf456b4517c8f0526150a7ba6d03a1ee Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:31:04 -0500 Subject: [PATCH 26/70] Add a `both` function. --- src/Data/Functor/Both.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index a61695e55..6d1362fab 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -3,6 +3,9 @@ module Data.Functor.Both where newtype Both a = Both { runBoth :: (a, a) } deriving (Eq, Show, Functor, Foldable, Traversable) +both :: a -> a -> Both a +both = curry Both + instance Applicative Both where pure a = Both (a, a) Both (f, g) <*> Both (a, b) = Both (f a, g b) From b6ffeeba2a871321b548fdc1cc4bfd4bcc36bd58 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:31:45 -0500 Subject: [PATCH 27/70] Document `both`. --- src/Data/Functor/Both.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 6d1362fab..9ad592356 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -3,6 +3,7 @@ module Data.Functor.Both where newtype Both a = Both { runBoth :: (a, a) } deriving (Eq, Show, Functor, Foldable, Traversable) +-- | Given two operands returns a functor operating on `Both`. This is a curried synonym for Both. both :: a -> a -> Both a both = curry Both From ecdb3c3e9be9a81d4ee9791293563ab0d73629cc Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:46:43 -0500 Subject: [PATCH 28/70] Implement unzip over Both. --- src/Data/Functor/Both.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 9ad592356..1f09d7b97 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -7,6 +7,10 @@ newtype Both a = Both { runBoth :: (a, a) } both :: a -> a -> Both a both = curry Both +unzip :: [Both a] -> Both [a] +unzip = foldr pair (pure []) + where pair (Both (a, b)) (Both (as, bs)) = Both (a : as, b : bs) + instance Applicative Both where pure a = Both (a, a) Both (f, g) <*> Both (a, b) = Both (f a, g b) From 3296276f900c6aac8f26f52a1f2cc0617a513164 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:51:37 -0500 Subject: [PATCH 29/70] Implement zip over Both. --- src/Data/Functor/Both.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 1f09d7b97..037f3843d 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -7,6 +7,11 @@ newtype Both a = Both { runBoth :: (a, a) } both :: a -> a -> Both a both = curry Both +zip :: Both [a] -> [Both a] +zip (Both ([], _)) = [] +zip (Both (_, [])) = [] +zip (Both (a : as, b : bs)) = both a b : Data.Functor.Both.zip (both as bs) + unzip :: [Both a] -> Both [a] unzip = foldr pair (pure []) where pair (Both (a, b)) (Both (as, bs)) = Both (a : as, b : bs) From cff25113057bc7a17216c8cd2a796c9ad3964820 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 01:54:35 -0500 Subject: [PATCH 30/70] Implement zipWith over Both. --- src/Data/Functor/Both.hs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 037f3843d..e3ea3febc 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -1,5 +1,7 @@ module Data.Functor.Both where +import Prelude hiding (zipWith) + newtype Both a = Both { runBoth :: (a, a) } deriving (Eq, Show, Functor, Foldable, Traversable) @@ -8,9 +10,12 @@ both :: a -> a -> Both a both = curry Both zip :: Both [a] -> [Both a] -zip (Both ([], _)) = [] -zip (Both (_, [])) = [] -zip (Both (a : as, b : bs)) = both a b : Data.Functor.Both.zip (both as bs) +zip = zipWith both + +zipWith :: (a -> a -> b) -> Both [a] -> [b] +zipWith f (Both ([], _)) = [] +zipWith f (Both (_, [])) = [] +zipWith f (Both (a : as, b : bs)) = f a b : zipWith f (both as bs) unzip :: [Both a] -> Both [a] unzip = foldr pair (pure []) From a8e89b8facee54933a159f2a1bb23738d190a4d3 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 07:43:43 -0500 Subject: [PATCH 31/70] =?UTF-8?q?Clean=20up=20a=20bit=20using=20Both?= =?UTF-8?q?=E2=80=99s=20zipWith/unzip.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Alignment.hs | 2 +- src/Row.hs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index d256ec4db..7d840d13b 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -128,4 +128,4 @@ openDiff source diff = const diff <$> case get diff of -- | Zip two lists by applying a function, using the default values to extend -- | the shorter list. zipWithDefaults :: (a -> b -> c) -> a -> b -> [a] -> [b] -> [c] -zipWithDefaults f da db a b = take (max (length a) (length b)) $ zipWith f (a ++ repeat da) (b ++ repeat db) +zipWithDefaults f da db a b = take (max (length a) (length b)) $ Prelude.zipWith f (a ++ repeat da) (b ++ repeat db) diff --git a/src/Row.hs b/src/Row.hs index 3ef131a6d..b122ee5a9 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -1,7 +1,7 @@ module Row where import Control.Arrow -import Data.Functor.Both +import Data.Functor.Both as Both import Line -- | A row in a split diff, composed of a before line and an after line. @@ -21,20 +21,20 @@ wrapRowContents transformLeft transformRight (Row left right) = Row (wrapLineCon adjoinRowsBy :: MaybeOpen a -> MaybeOpen a -> [Row a] -> Row a -> [Row a] adjoinRowsBy _ _ [] row = [row] -adjoinRowsBy f g rows (Row left' right') | Both (Just _, Just _) <- openLineBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) = uncurry (zipWith Row) . runBoth $ both <*> Both (left', right') - where both = adjoinLinesBy <$> Both (f, g) <*> Both (unzip $ runBoth . unRow <$> rows) +adjoinRowsBy f g rows (Row left' right') | Both (Just _, Just _) <- openLineBy <$> Both (f, g) <*> (Both.unzip $ unRow <$> rows) = Both.zipWith Row $ both <*> Both (left', right') + where both = adjoinLinesBy <$> Both (f, g) <*> (Both.unzip $ unRow <$> rows) adjoinRowsBy f _ rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of EmptyLine -> rest _ -> Row EmptyLine right' : rest - where rest = zipWith Row (lefts left') rights - (lefts, rights) = first (adjoinLinesBy f) $ unzip $ runBoth . unRow <$> rows + where rest = Prelude.zipWith Row (lefts left') rights + (lefts, rights) = first (adjoinLinesBy f) . runBoth $ Both.unzip $ unRow <$> rows adjoinRowsBy _ g rows (Row left' right') | Just _ <- openLineBy g $ unRight <$> rows = case left' of EmptyLine -> rest _ -> Row left' EmptyLine : rest - where rest = zipWith Row lefts (rights right') - (lefts, rights) = second (adjoinLinesBy g) $ unzip $ runBoth . unRow <$> rows + where rest = Prelude.zipWith Row lefts (rights right') + (lefts, rights) = second (adjoinLinesBy g) . runBoth $ Both.unzip $ unRow <$> rows adjoinRowsBy _ _ rows row = row : rows From 0eeda4b047e54b9c1c82be08ce3c051c7b191065 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 07:44:15 -0500 Subject: [PATCH 32/70] Remove a redundant import. --- src/DiffOutput.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DiffOutput.hs b/src/DiffOutput.hs index 90c6b9863..c71640543 100644 --- a/src/DiffOutput.hs +++ b/src/DiffOutput.hs @@ -1,7 +1,6 @@ module DiffOutput where import Data.Functor.Both -import qualified Data.ByteString.Char8 as B1 import Diffing import Parser import Source From 2c4acfb14fd181806a2991d3d48434f0f8294261 Mon Sep 17 00:00:00 2001 From: joshvera Date: Fri, 26 Feb 2016 19:28:49 -0700 Subject: [PATCH 33/70] Disambiguate the tests. --- test/AlignmentSpec.hs | 2 +- test/CorpusSpec.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 862563a20..fbfa3084a 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -58,7 +58,7 @@ spec = parallel $ do prop "produces the maximum line count in inequal sources" $ \ sources -> let (sourceA, sourceB) = runBoth sources in - length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) + length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ Prelude.zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index 891b46bcb..940291a1c 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -61,7 +61,7 @@ examples directory = do where globFor :: String -> IO [FilePath] globFor p = globDir1 (compile p) directory - toDict list = Map.fromList ((normalizeName <$> list) `zip` list) + toDict list = Map.fromList ((normalizeName <$> list) `Prelude.zip` list) -- | Given a test name like "foo.A.js", return "foo.js". normalizeName :: FilePath -> FilePath From 8303fd08ad6091ce02cad00eb7f950e7ee4ca763 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 08:38:58 -0500 Subject: [PATCH 34/70] wrapRowContents applies Both functions to Row contents. --- src/Alignment.hs | 4 ++-- src/Row.hs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 7d840d13b..f482309d6 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -71,7 +71,7 @@ splitTermByLines (Info range categories :< syntax) source = flip (,) range $ cas -- | Split a annotated diff into rows of split diffs. splitAnnotatedByLines :: Both (Source Char) -> Both Range -> Both (Set.Set Category) -> Syntax leaf (Diff leaf Info) -> [Row (SplitDiff leaf Info)] splitAnnotatedByLines sources ranges categories syntax = case syntax of - Leaf a -> wrapRowContents (Free . (`Annotated` Leaf a) . (`Info` fst (runBoth categories)) . unionRanges) (Free . (`Annotated` Leaf a) . (`Info` snd (runBoth categories)) . unionRanges) <$> contextRows ranges sources + Leaf a -> wrapRowContents (((Free . (`Annotated` Leaf a)) .) <$> ((. unionRanges) . flip Info <$> categories)) <$> contextRows ranges sources Indexed children -> adjoinChildRows (Indexed . fmap get) (Identity <$> children) Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) @@ -84,7 +84,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of adjoinChildRows :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], start <$> ranges) children in - fmap (wrapRowContents (wrap constructor (fst $ runBoth categories)) (wrap constructor (snd $ runBoth categories))) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) + fmap (wrapRowContents (wrap constructor <$> categories)) . adjoin $ rows ++ (fmap Left <$> contextRows (makeRanges previous (end <$> ranges)) sources) wrap :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> Set.Set Category -> [Either Range (f (SplitDiff leaf Info))] -> SplitDiff leaf Info wrap constructor categories children = Free . Annotated (Info (unionRanges $ getRange <$> children) categories) . constructor $ rights children diff --git a/src/Row.hs b/src/Row.hs index b122ee5a9..fc8dac4a3 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -13,8 +13,8 @@ unRow :: Row a -> Both (Line a) unRow (Row a b) = Both (a, b) -- | Map over both sides of a row with the given functions. -wrapRowContents :: ([a] -> b) -> ([a] -> b) -> Row a -> Row b -wrapRowContents transformLeft transformRight (Row left right) = Row (wrapLineContents transformLeft left) (wrapLineContents transformRight right) +wrapRowContents :: Both ([a] -> b) -> Row a -> Row b +wrapRowContents transform row = uncurry Row . runBoth $ wrapLineContents <$> transform <*> unRow row -- | Given functions that determine whether an item is open, add a row to a -- | first open, non-empty item in a list of rows, or add it as a new row. From 6fe2060e45de8e9cc3e62ff10976ee1368464c48 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 08:44:36 -0500 Subject: [PATCH 35/70] Define an Applicative instance for Row. --- src/Row.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Row.hs b/src/Row.hs index fc8dac4a3..e7ac17fe9 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -41,3 +41,7 @@ adjoinRowsBy _ _ rows row = row : rows instance Show a => Show (Row a) where show (Row left right) = "\n" ++ show left ++ " | " ++ show right + +instance Applicative Row where + pure a = let a' = pure a in Row a' a' + Row f g <*> Row a b = Row (f <*> a) (g <*> b) From 1abab2bbce3dbeea3fb0a86efad6a2c43c0ef278 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 08:55:30 -0500 Subject: [PATCH 36/70] adjoinRowsBy applies Both MaybeOpen tests. --- src/Alignment.hs | 2 +- src/Row.hs | 14 +++++++------- test/AlignmentSpec.hs | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index f482309d6..0ccc66bfa 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -80,7 +80,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of runBoth (fmap pure <$> (actualLineRanges <$> ranges <*> sources)) adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] - adjoin = reverse . foldl (adjoinRowsBy (openEither (openRange . fst $ runBoth sources) (openDiff . fst $ runBoth sources)) (openEither (openRange . snd $ runBoth sources) (openDiff . snd $ runBoth sources))) [] + adjoin = reverse . foldl (adjoinRowsBy (openEither <$> (openRange <$> sources) <*> (openDiff <$> sources))) [] adjoinChildRows :: Has f => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f (Diff leaf Info)] -> [Row (SplitDiff leaf Info)] adjoinChildRows constructor children = let (rows, previous) = foldl childRows ([], start <$> ranges) children in diff --git a/src/Row.hs b/src/Row.hs index e7ac17fe9..1ab152171 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -18,25 +18,25 @@ wrapRowContents transform row = uncurry Row . runBoth $ wrapLineContents <$> tra -- | Given functions that determine whether an item is open, add a row to a -- | first open, non-empty item in a list of rows, or add it as a new row. -adjoinRowsBy :: MaybeOpen a -> MaybeOpen a -> [Row a] -> Row a -> [Row a] -adjoinRowsBy _ _ [] row = [row] +adjoinRowsBy :: Both (MaybeOpen a) -> [Row a] -> Row a -> [Row a] +adjoinRowsBy _ [] row = [row] -adjoinRowsBy f g rows (Row left' right') | Both (Just _, Just _) <- openLineBy <$> Both (f, g) <*> (Both.unzip $ unRow <$> rows) = Both.zipWith Row $ both <*> Both (left', right') - where both = adjoinLinesBy <$> Both (f, g) <*> (Both.unzip $ unRow <$> rows) +adjoinRowsBy f rows (Row left' right') | Both (Just _, Just _) <- openLineBy <$> f <*> (Both.unzip $ unRow <$> rows) = Both.zipWith Row $ both <*> Both (left', right') + where both = adjoinLinesBy <$> f <*> (Both.unzip $ unRow <$> rows) -adjoinRowsBy f _ rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of +adjoinRowsBy (Both (f, _)) rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of EmptyLine -> rest _ -> Row EmptyLine right' : rest where rest = Prelude.zipWith Row (lefts left') rights (lefts, rights) = first (adjoinLinesBy f) . runBoth $ Both.unzip $ unRow <$> rows -adjoinRowsBy _ g rows (Row left' right') | Just _ <- openLineBy g $ unRight <$> rows = case left' of +adjoinRowsBy (Both (_, g)) rows (Row left' right') | Just _ <- openLineBy g $ unRight <$> rows = case left' of EmptyLine -> rest _ -> Row left' EmptyLine : rest where rest = Prelude.zipWith Row lefts (rights right') (lefts, rights) = second (adjoinLinesBy g) . runBoth $ Both.unzip $ unRow <$> rows -adjoinRowsBy _ _ rows row = row : rows +adjoinRowsBy _ rows row = row : rows instance Show a => Show (Row a) where diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index fbfa3084a..ffb719ddf 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -62,24 +62,24 @@ spec = parallel $ do describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ - \ a -> adjoinRowsBy openMaybe openMaybe [] a == [ a ] + \ a -> adjoinRowsBy (pure openMaybe) [] a == [ a ] prop "appends onto open rows" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ \ (a@(Row a1 b1), b@(Row a2 b2)) -> - adjoinRowsBy openMaybe openMaybe [ a ] b `shouldBe` [ Row (makeLine $ unLine a1 ++ unLine a2) (makeLine $ unLine b1 ++ unLine b2) ] + adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row (makeLine $ unLine a1 ++ unLine a2) (makeLine $ unLine b1 ++ unLine b2) ] prop "does not append onto closed rows" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ - \ (a, b) -> adjoinRowsBy openMaybe openMaybe [ a ] b `shouldBe` [ b, a ] + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ b, a ] prop "does not promote elements through empty lines onto closed lines" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ - \ (a, b) -> adjoinRowsBy openMaybe openMaybe [ Row EmptyLine EmptyLine, a ] b `shouldBe` [ b, Row EmptyLine EmptyLine, a ] + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ Row EmptyLine EmptyLine, a ] b `shouldBe` [ b, Row EmptyLine EmptyLine, a ] prop "promotes elements through empty lines onto open lines" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ - \ (a, b) -> adjoinRowsBy openMaybe openMaybe [ Row EmptyLine EmptyLine, a ] b `shouldBe` Row EmptyLine EmptyLine : adjoinRowsBy openMaybe openMaybe [ a ] b + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ Row EmptyLine EmptyLine, a ] b `shouldBe` Row EmptyLine EmptyLine : adjoinRowsBy (pure openMaybe) [ a ] b describe "splitTermByLines" $ do prop "preserves line count" $ From e26b4bacd5c13f3b2ae67be614105f934c7c8940 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:15:26 -0500 Subject: [PATCH 37/70] Document Both. --- src/Data/Functor/Both.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index e3ea3febc..50ef1dffe 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -2,6 +2,7 @@ module Data.Functor.Both where import Prelude hiding (zipWith) +-- | A computation over both sides of a pair. newtype Both a = Both { runBoth :: (a, a) } deriving (Eq, Show, Functor, Foldable, Traversable) From a8efcb539a99d8bb7eea70d313a68c116a4d2b73 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:22:52 -0500 Subject: [PATCH 38/70] Row wraps a Both. --- src/Alignment.hs | 8 ++++---- src/Renderer/Patch.hs | 4 ++-- src/Renderer/Split.hs | 2 +- src/Row.hs | 35 ++++++++++++++++++++--------------- test/AlignmentSpec.hs | 21 ++++++++++----------- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 0ccc66bfa..d8b9781a9 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -23,12 +23,12 @@ splitDiffByLines :: Diff leaf Info -> Both Int -> Both (Source Char) -> ([Row (S splitDiffByLines diff previous sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd $ runBoth sources) in - (Row EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) + (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst $ runBoth sources) in - (flip Row EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, Range prevRight prevRight)) + (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, Range prevRight prevRight)) Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in - (uncurry (zipWithDefaults Row EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) + (uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) where categories annotations = Diff.categories <$> Both annotations ranges annotations = characterRange <$> Both annotations (prevLeft, prevRight) = runBoth previous @@ -76,7 +76,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) where contextRows :: Both Range -> Both (Source Char) -> [Row Range] - contextRows ranges sources = uncurry (zipWithDefaults Row EmptyLine EmptyLine) $ + contextRows ranges sources = uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) $ runBoth (fmap pure <$> (actualLineRanges <$> ranges <*> sources)) adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index e4b8f84d5..6a957ded2 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -39,7 +39,7 @@ changeLength change = mconcat $ (rowLength <$> context change) <> (rowLength <$> -- | The number of lines in the row, each being either 0 or 1. rowLength :: Row a -> Both (Sum Int) -rowLength (Row a b) = pure lineLength <*> Both (a, b) +rowLength = fmap lineLength . unRow -- | The length of the line, being either 0 or 1. lineLength :: Line a -> Sum Int @@ -125,7 +125,7 @@ changeIncludingContext leadingContext rows = case changes of -- | Whether a row has changes on either side. rowHasChanges :: Row (SplitDiff a Info) -> Bool -rowHasChanges (Row left right) = lineHasChanges left || lineHasChanges right +rowHasChanges (Row lines) = or (lineHasChanges <$> lines) -- | Whether a line has changes. lineHasChanges :: Line (SplitDiff a Info) -> Bool diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 586580d07..b3a1db9b4 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -88,7 +88,7 @@ split diff blobs = renderHtml -- | Add a row to list of tuples of ints and lines, where the ints denote -- | how many non-empty lines exist on that side up to that point. numberRows :: [(Int, Line a, Int, Line a)] -> Row a -> [(Int, Line a, Int, Line a)] - numberRows rows (Row left right) = (leftCount rows + valueOf left, left, rightCount rows + valueOf right, right) : rows + numberRows rows (Row (Both (left, right))) = (leftCount rows + valueOf left, left, rightCount rows + valueOf right, right) : rows where leftCount [] = 0 leftCount ((x, _, _, _):_) = x diff --git a/src/Row.hs b/src/Row.hs index 1ab152171..23002c0fc 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -5,43 +5,48 @@ import Data.Functor.Both as Both import Line -- | A row in a split diff, composed of a before line and an after line. -data Row a = Row { unLeft :: !(Line a), unRight :: !(Line a) } +newtype Row a = Row { unRow :: Both (Line a) } deriving (Eq, Functor) --- | Return a tuple of lines from the row. -unRow :: Row a -> Both (Line a) -unRow (Row a b) = Both (a, b) +makeRow :: Line a -> Line a -> Row a +makeRow a = Row . both a + +unLeft :: Row a -> Line a +unLeft = fst . runBoth . unRow + +unRight :: Row a -> Line a +unRight = snd . runBoth . unRow -- | Map over both sides of a row with the given functions. wrapRowContents :: Both ([a] -> b) -> Row a -> Row b -wrapRowContents transform row = uncurry Row . runBoth $ wrapLineContents <$> transform <*> unRow row +wrapRowContents transform row = Row $ wrapLineContents <$> transform <*> unRow row -- | Given functions that determine whether an item is open, add a row to a -- | first open, non-empty item in a list of rows, or add it as a new row. adjoinRowsBy :: Both (MaybeOpen a) -> [Row a] -> Row a -> [Row a] adjoinRowsBy _ [] row = [row] -adjoinRowsBy f rows (Row left' right') | Both (Just _, Just _) <- openLineBy <$> f <*> (Both.unzip $ unRow <$> rows) = Both.zipWith Row $ both <*> Both (left', right') +adjoinRowsBy f rows (Row bothLines) | Both (Just _, Just _) <- openLineBy <$> f <*> (Both.unzip $ unRow <$> rows) = Both.zipWith makeRow $ both <*> bothLines where both = adjoinLinesBy <$> f <*> (Both.unzip $ unRow <$> rows) -adjoinRowsBy (Both (f, _)) rows (Row left' right') | Just _ <- openLineBy f $ unLeft <$> rows = case right' of +adjoinRowsBy (Both (f, _)) rows (Row (Both (left', right'))) | Just _ <- openLineBy f $ unLeft <$> rows = case right' of EmptyLine -> rest - _ -> Row EmptyLine right' : rest - where rest = Prelude.zipWith Row (lefts left') rights + _ -> makeRow EmptyLine right' : rest + where rest = Prelude.zipWith makeRow (lefts left') rights (lefts, rights) = first (adjoinLinesBy f) . runBoth $ Both.unzip $ unRow <$> rows -adjoinRowsBy (Both (_, g)) rows (Row left' right') | Just _ <- openLineBy g $ unRight <$> rows = case left' of +adjoinRowsBy (Both (_, g)) rows (Row (Both (left', right'))) | Just _ <- openLineBy g $ unRight <$> rows = case left' of EmptyLine -> rest - _ -> Row left' EmptyLine : rest - where rest = Prelude.zipWith Row lefts (rights right') + _ -> makeRow left' EmptyLine : rest + where rest = Prelude.zipWith makeRow lefts (rights right') (lefts, rights) = second (adjoinLinesBy g) . runBoth $ Both.unzip $ unRow <$> rows adjoinRowsBy _ rows row = row : rows instance Show a => Show (Row a) where - show (Row left right) = "\n" ++ show left ++ " | " ++ show right + show (Row (Both (left, right))) = "\n" ++ show left ++ " | " ++ show right instance Applicative Row where - pure a = let a' = pure a in Row a' a' - Row f g <*> Row a b = Row (f <*> a) (g <*> b) + pure = Row . pure . pure + Row (Both (f, g)) <*> Row (Both (a, b)) = Row $ both (f <*> a) (g <*> b) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index ffb719ddf..7366983f9 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -23,8 +23,7 @@ instance Arbitrary a => Arbitrary (Both a) where arbitrary = pure (curry Both) <*> arbitrary <*> arbitrary instance Arbitrary a => Arbitrary (Row a) where - arbitrary = oneof [ - Row <$> arbitrary <*> arbitrary ] + arbitrary = Row <$> arbitrary instance Arbitrary a => Arbitrary (Line a) where arbitrary = oneof [ @@ -44,12 +43,12 @@ spec = parallel $ do prop "outputs one row for single-line unchanged leaves" $ forAll (arbitraryLeaf `suchThat` isOnSingleLine) $ \ (source, info@(Info range categories), syntax) -> splitAnnotatedByLines (pure source) (pure range) (pure categories) syntax `shouldBe` [ - Row (makeLine [ Free $ Annotated info $ Leaf source ]) (makeLine [ Free $ Annotated info $ Leaf source ]) ] + makeRow (makeLine [ Free $ Annotated info $ Leaf source ]) (makeLine [ Free $ Annotated info $ Leaf source ]) ] prop "outputs one row for single-line empty unchanged indexed nodes" $ forAll (arbitrary `suchThat` (\ a -> filter (/= '\n') (toList a) == toList a)) $ \ source -> splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (pure mempty) (Indexed [] :: Syntax String (Diff String Info)) `shouldBe` [ - Row (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) ] + makeRow (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) (makeLine [ Free $ Annotated (Info (getTotalRange source) mempty) $ Indexed [] ]) ] prop "preserves line counts in equal sources" $ \ source -> @@ -66,8 +65,8 @@ spec = parallel $ do prop "appends onto open rows" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ - \ (a@(Row a1 b1), b@(Row a2 b2)) -> - adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row (makeLine $ unLine a1 ++ unLine a2) (makeLine $ unLine b1 ++ unLine b2) ] + \ (a@(Row (Both (a1, b1))), b@(Row (Both (a2, b2)))) -> + adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ makeRow (makeLine $ unLine a1 ++ unLine a2) (makeLine $ unLine b1 ++ unLine b2) ] prop "does not append onto closed rows" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ @@ -75,11 +74,11 @@ spec = parallel $ do prop "does not promote elements through empty lines onto closed lines" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ - \ (a, b) -> adjoinRowsBy (pure openMaybe) [ Row EmptyLine EmptyLine, a ] b `shouldBe` [ b, Row EmptyLine EmptyLine, a ] + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ makeRow EmptyLine EmptyLine, a ] b `shouldBe` [ b, makeRow EmptyLine EmptyLine, a ] prop "promotes elements through empty lines onto open lines" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ - \ (a, b) -> adjoinRowsBy (pure openMaybe) [ Row EmptyLine EmptyLine, a ] b `shouldBe` Row EmptyLine EmptyLine : adjoinRowsBy (pure openMaybe) [ a ] b + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ makeRow EmptyLine EmptyLine, a ] b `shouldBe` makeRow EmptyLine EmptyLine : adjoinRowsBy (pure openMaybe) [ a ] b describe "splitTermByLines" $ do prop "preserves line count" $ @@ -106,9 +105,9 @@ spec = parallel $ do openTerm (fromList " \n") (Identity $ Info (Range 0 2) mempty :< Leaf "") `shouldBe` Nothing where - isOpenBy f (Row a b) = Maybe.isJust (openLineBy f [ a ]) && Maybe.isJust (openLineBy f [ b ]) - isClosedBy f (Row a@(Line _) b@(Line _)) = Maybe.isNothing (openLineBy f [ a ]) && Maybe.isNothing (openLineBy f [ b ]) - isClosedBy _ (Row _ _) = False + isOpenBy f (Row lines) = and (Maybe.isJust . openLineBy f . pure <$> lines) + isClosedBy f (Row lines@(Both (Line _, Line _))) = and (Maybe.isNothing . openLineBy f . pure <$> lines) + isClosedBy _ _ = False isOnSingleLine (a, _, _) = filter (/= '\n') (toList a) == toList a From 0628793ab4412249561ad54e306249222feadf61 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:29:50 -0500 Subject: [PATCH 39/70] Use the Applicative instance in a test. --- test/AlignmentSpec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 7366983f9..3424ed495 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -66,7 +66,7 @@ spec = parallel $ do prop "appends onto open rows" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ \ (a@(Row (Both (a1, b1))), b@(Row (Both (a2, b2)))) -> - adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ makeRow (makeLine $ unLine a1 ++ unLine a2) (makeLine $ unLine b1 ++ unLine b2) ] + adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row $ makeLine <$> ((++) <$> (unLine <$> unRow a) <*> (unLine <$> unRow b)) ] prop "does not append onto closed rows" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ From acd19b450228c6d83ce3f61992d5598c1d94b2a7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:33:02 -0500 Subject: [PATCH 40/70] =?UTF-8?q?Don=E2=80=99t=20bind=20variables=20we=20d?= =?UTF-8?q?on=E2=80=99t=20use.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Data/Functor/Both.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 50ef1dffe..4593dfcae 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -14,8 +14,8 @@ zip :: Both [a] -> [Both a] zip = zipWith both zipWith :: (a -> a -> b) -> Both [a] -> [b] -zipWith f (Both ([], _)) = [] -zipWith f (Both (_, [])) = [] +zipWith _ (Both ([], _)) = [] +zipWith _ (Both (_, [])) = [] zipWith f (Both (a : as, b : bs)) = f a b : zipWith f (both as bs) unzip :: [Both a] -> Both [a] From acca5d89177125967ab19fc33dcc9f423f0e13ce Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:33:38 -0500 Subject: [PATCH 41/70] Add runLeft/runRight conveniences. --- src/Data/Functor/Both.hs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 4593dfcae..a71eb2d13 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -10,6 +10,14 @@ newtype Both a = Both { runBoth :: (a, a) } both :: a -> a -> Both a both = curry Both +-- | Runs the left side of a `Both`. +runLeft :: Both a -> a +runLeft = fst . runBoth + +-- | Runs the right side of a `Both`. +runRight :: Both a -> a +runRight = snd . runBoth + zip :: Both [a] -> [Both a] zip = zipWith both From fe193b89d4bc6b36e47035900c4045fe8918911c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:34:27 -0500 Subject: [PATCH 42/70] Use runLeft/runRight in unLeft/unRight. --- src/Row.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Row.hs b/src/Row.hs index 23002c0fc..5655a9799 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -12,10 +12,10 @@ makeRow :: Line a -> Line a -> Row a makeRow a = Row . both a unLeft :: Row a -> Line a -unLeft = fst . runBoth . unRow +unLeft = runLeft . unRow unRight :: Row a -> Line a -unRight = snd . runBoth . unRow +unRight = runRight . unRow -- | Map over both sides of a row with the given functions. wrapRowContents :: Both ([a] -> b) -> Row a -> Row b From 5bbca4cf8db416702e125d0c4b67bf389eeaa7fe Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 09:59:54 -0500 Subject: [PATCH 43/70] Use runLeft/runRight when aligning insertions/deletions. --- src/Alignment.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index d8b9781a9..bdbe35e75 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -22,9 +22,9 @@ import Term splitDiffByLines :: Diff leaf Info -> Both Int -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], Both Range) splitDiffByLines diff previous sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) - Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd $ runBoth sources) in + Pure (Insert term) -> let (lines, range) = splitTermByLines term (runRight sources) in (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) - Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst $ runBoth sources) in + Pure (Delete term) -> let (lines, range) = splitTermByLines term (runLeft sources) in (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, Range prevRight prevRight)) Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in From 9108b56e4f8d3ca8ddc3455631088117248dc651 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:00:02 -0500 Subject: [PATCH 44/70] Add a rangeAt function to construct a Range at an index. --- src/Range.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Range.hs b/src/Range.hs index 8b5408a84..77f58569f 100644 --- a/src/Range.hs +++ b/src/Range.hs @@ -11,6 +11,10 @@ import Data.Option data Range = Range { start :: !Int, end :: !Int } deriving (Eq, Show) +-- | Make a range at a given index. +rangeAt :: Int -> Range +rangeAt a = Range a a + -- | Return the length of the range. rangeLength :: Range -> Int rangeLength range = end range - start range From 768e3fedbafecdb56dc9a8ec611ef8674c153904 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:00:48 -0500 Subject: [PATCH 45/70] Construct insertion/deletion Ranges with rangeAt. --- src/Alignment.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index bdbe35e75..7042e3f1b 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -23,9 +23,9 @@ splitDiffByLines :: Diff leaf Info -> Both Int -> Both (Source Char) -> ([Row (S splitDiffByLines diff previous sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) Pure (Insert term) -> let (lines, range) = splitTermByLines term (runRight sources) in - (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (Range prevLeft prevLeft, range)) + (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (rangeAt prevLeft, range)) Pure (Delete term) -> let (lines, range) = splitTermByLines term (runLeft sources) in - (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, Range prevRight prevRight)) + (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, rangeAt prevRight)) Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in (uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) From e678c863b35a23f073839be847dca0ce31780dc9 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:02:53 -0500 Subject: [PATCH 46/70] runLeft/runRight to construct insertion/deletion ranges. --- src/Alignment.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 7042e3f1b..c9af0164c 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -23,15 +23,14 @@ splitDiffByLines :: Diff leaf Info -> Both Int -> Both (Source Char) -> ([Row (S splitDiffByLines diff previous sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) Pure (Insert term) -> let (lines, range) = splitTermByLines term (runRight sources) in - (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (rangeAt prevLeft, range)) + (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (rangeAt $ runLeft previous, range)) Pure (Delete term) -> let (lines, range) = splitTermByLines term (runLeft sources) in - (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, rangeAt prevRight)) + (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, rangeAt $ runRight previous)) Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in (uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) where categories annotations = Diff.categories <$> Both annotations ranges annotations = characterRange <$> Both annotations - (prevLeft, prevRight) = runBoth previous -- | A functor that can return its content. class Functor f => Has f where From 93e44a3dc59480c8f1330674e0113eb21da6b258 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:04:59 -0500 Subject: [PATCH 47/70] Sort some imports. --- src/Diff.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Diff.hs b/src/Diff.hs index 9d09ac954..34ec40094 100644 --- a/src/Diff.hs +++ b/src/Diff.hs @@ -1,12 +1,12 @@ module Diff where -import Syntax -import Data.Set -import Control.Monad.Free -import Patch -import Term -import Range import Category +import Control.Monad.Free +import Data.Set +import Patch +import Range +import Syntax +import Term -- | An annotated syntax in a diff tree. data Annotated a annotation f = Annotated !annotation !(Syntax a f) From 065c21c01345bdbb1f64e77e5f703b3ba6a227fe Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:07:44 -0500 Subject: [PATCH 48/70] Sort more imports. --- src/Term.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Term.hs b/src/Term.hs index 64b63a2b2..4ca9ab16e 100644 --- a/src/Term.hs +++ b/src/Term.hs @@ -1,8 +1,8 @@ module Term where -import Data.OrderedMap hiding (size) -import Data.Maybe import Control.Comonad.Cofree +import Data.Maybe +import Data.OrderedMap hiding (size) import Syntax -- | An annotated node (Syntax) in an abstract syntax tree. From c889174aeed0941c46fe350abd76e2febcdd1abe Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:12:34 -0500 Subject: [PATCH 49/70] Use Both to represent Diff annotations. --- src/Alignment.hs | 4 ++-- src/Diff.hs | 3 ++- src/Interpreter.hs | 11 ++++++----- src/Term.hs | 9 +++++---- test/AlignmentSpec.hs | 4 ++-- test/PatchOutputSpec.hs | 2 +- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index c9af0164c..eec981e1a 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -29,8 +29,8 @@ splitDiffByLines diff previous sources = case diff of Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in (uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) - where categories annotations = Diff.categories <$> Both annotations - ranges annotations = characterRange <$> Both annotations + where categories annotations = Diff.categories <$> annotations + ranges annotations = characterRange <$> annotations -- | A functor that can return its content. class Functor f => Has f where diff --git a/src/Diff.hs b/src/Diff.hs index 34ec40094..adf123928 100644 --- a/src/Diff.hs +++ b/src/Diff.hs @@ -2,6 +2,7 @@ module Diff where import Category import Control.Monad.Free +import Data.Functor.Both import Data.Set import Patch import Range @@ -21,7 +22,7 @@ instance Categorizable Info where categories = Diff.categories -- | An annotated series of patches of terms. -type Diff a annotation = Free (Annotated a (annotation, annotation)) (Patch (Term a annotation)) +type Diff a annotation = Free (Annotated a (Both annotation)) (Patch (Term a annotation)) -- | Sum the result of a transform applied to all the patches in the diff. diffSum :: (Patch (Term a annotation) -> Integer) -> Diff a annotation -> Integer diff --git a/src/Interpreter.hs b/src/Interpreter.hs index 306a7adc2..04053ba91 100644 --- a/src/Interpreter.hs +++ b/src/Interpreter.hs @@ -11,6 +11,7 @@ import Term import Category import Control.Monad.Free import Control.Comonad.Cofree hiding (unwrap) +import Data.Functor.Both import qualified Data.OrderedMap as Map import Data.OrderedMap ((!)) import qualified Data.List as List @@ -39,7 +40,7 @@ constructAndRun :: (Eq a, Eq annotation) => Comparable a annotation -> Term a an constructAndRun _ a b | a == b = hylo introduce eliminate <$> zipTerms a b where eliminate :: Cofree f a -> (a, f (Cofree f a)) eliminate (extract :< unwrap) = (extract, unwrap) - introduce :: (annotation, annotation) -> Syntax a (Diff a annotation) -> Diff a annotation + introduce :: Both annotation -> Syntax a (Diff a annotation) -> Diff a annotation introduce ann syntax = Free $ Annotated ann syntax constructAndRun comparable a b | not $ comparable a b = Nothing constructAndRun comparable (annotation1 :< a) (annotation2 :< b) = @@ -48,15 +49,15 @@ constructAndRun comparable (annotation1 :< a) (annotation2 :< b) = algorithm (Keyed a') (Keyed b') = Free $ ByKey a' b' (annotate . Keyed) algorithm (Leaf a') (Leaf b') | a' == b' = annotate $ Leaf b' algorithm a' b' = Free $ Recursive (annotation1 :< a') (annotation2 :< b') Pure - annotate = Pure . Free . Annotated (annotation1, annotation2) + annotate = Pure . Free . Annotated (Both (annotation1, annotation2)) -- | Runs the diff algorithm run :: (Eq a, Eq annotation) => Comparable a annotation -> Algorithm a annotation (Diff a annotation) -> Maybe (Diff a annotation) run _ (Pure diff) = Just diff run comparable (Free (Recursive (annotation1 :< a) (annotation2 :< b) f)) = run comparable . f $ recur a b where - recur (Indexed a') (Indexed b') | length a' == length b' = annotate . Indexed $ zipWith (interpret comparable) a' b' - recur (Fixed a') (Fixed b') | length a' == length b' = annotate . Fixed $ zipWith (interpret comparable) a' b' + recur (Indexed a') (Indexed b') | length a' == length b' = annotate . Indexed $ Prelude.zipWith (interpret comparable) a' b' + recur (Fixed a') (Fixed b') | length a' == length b' = annotate . Fixed $ Prelude.zipWith (interpret comparable) a' b' recur (Keyed a') (Keyed b') | Map.keys a' == bKeys = annotate . Keyed . Map.fromList . fmap repack $ bKeys where bKeys = Map.keys b' @@ -64,7 +65,7 @@ run comparable (Free (Recursive (annotation1 :< a) (annotation2 :< b) f)) = run interpretInBoth key x y = interpret comparable (x ! key) (y ! key) recur _ _ = Pure $ Replace (annotation1 :< a) (annotation2 :< b) - annotate = Free . Annotated (annotation1, annotation2) + annotate = Free . Annotated (Both (annotation1, annotation2)) run comparable (Free (ByKey a b f)) = run comparable $ f byKey where byKey = Map.fromList $ toKeyValue <$> List.union aKeys bKeys diff --git a/src/Term.hs b/src/Term.hs index 4ca9ab16e..71810560b 100644 --- a/src/Term.hs +++ b/src/Term.hs @@ -1,6 +1,7 @@ module Term where import Control.Comonad.Cofree +import Data.Functor.Both import Data.Maybe import Data.OrderedMap hiding (size) import Syntax @@ -10,13 +11,13 @@ type Term a annotation = Cofree (Syntax a) annotation -- | Zip two terms by combining their annotations into a pair of annotations. -- | If the structure of the two terms don't match, then Nothing will be returned. -zipTerms :: Term a annotation -> Term a annotation -> Maybe (Term a (annotation, annotation)) +zipTerms :: Term a annotation -> Term a annotation -> Maybe (Term a (Both annotation)) zipTerms (annotation1 :< a) (annotation2 :< b) = annotate $ zipUnwrap a b where - annotate = fmap ((annotation1, annotation2) :<) + annotate = fmap (Both (annotation1, annotation2) :<) zipUnwrap (Leaf _) (Leaf b') = Just $ Leaf b' - zipUnwrap (Indexed a') (Indexed b') = Just . Indexed . catMaybes $ zipWith zipTerms a' b' - zipUnwrap (Fixed a') (Fixed b') = Just . Fixed . catMaybes $ zipWith zipTerms a' b' + zipUnwrap (Indexed a') (Indexed b') = Just . Indexed . catMaybes $ Prelude.zipWith zipTerms a' b' + zipUnwrap (Fixed a') (Fixed b') = Just . Fixed . catMaybes $ Prelude.zipWith zipTerms a' b' zipUnwrap (Keyed a') (Keyed b') | keys a' == keys b' = Just . Keyed . fromList . catMaybes $ zipUnwrapMaps a' b' <$> keys a' zipUnwrap _ _ = Nothing zipUnwrapMaps a' b' key = (,) key <$> zipTerms (a' ! key) (b' ! key) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 3424ed495..7f8232156 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -113,9 +113,9 @@ spec = parallel $ do getTotalRange (Source vector) = Range 0 $ length vector - combineIntoLeaves (leaves, start) char = (leaves ++ [ Free $ Annotated (Info (Range start $ start + 1) mempty, Info (Range start $ start + 1) mempty) (Leaf [ char ]) ], start + 1) + combineIntoLeaves (leaves, start) char = (leaves ++ [ Free $ Annotated (Both (Info (Range start $ start + 1) mempty, Info (Range start $ start + 1) mempty)) (Leaf [ char ]) ], start + 1) - leafWithRangesInSources sourceA sourceB rangeA rangeB = Free $ Annotated (Info rangeA mempty, Info rangeB mempty) (Leaf $ toList sourceA ++ toList sourceB) + leafWithRangesInSources sourceA sourceB rangeA rangeB = Free $ Annotated (Both (Info rangeA mempty, Info rangeB mempty)) (Leaf $ toList sourceA ++ toList sourceB) openMaybe :: Maybe Bool -> Maybe (Maybe Bool) openMaybe (Just a) = Just (Just a) diff --git a/test/PatchOutputSpec.hs b/test/PatchOutputSpec.hs index f8ce503a2..c368b4431 100644 --- a/test/PatchOutputSpec.hs +++ b/test/PatchOutputSpec.hs @@ -13,4 +13,4 @@ spec :: Spec spec = parallel $ describe "hunks" $ it "empty diffs have no hunks" $ - hunks (Free . Annotated (Info (Range 0 0) mempty, Info (Range 0 0) mempty) $ Leaf "") (Both (SourceBlob (fromList "") "abcde" "path2.txt", SourceBlob (fromList "") "xyz" "path2.txt")) `shouldBe` [] + hunks (Free . Annotated (pure (Info (Range 0 0) mempty)) $ Leaf "") (Both (SourceBlob (fromList "") "abcde" "path2.txt", SourceBlob (fromList "") "xyz" "path2.txt")) `shouldBe` [] From 7048a61660aa711ab06074dabe2780892d2f40e7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:30:33 -0500 Subject: [PATCH 50/70] Define leafWIthRangesInSources over Both. --- test/AlignmentSpec.hs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 7f8232156..e3d436b71 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -8,7 +8,7 @@ import Data.Text.Arbitrary () import Alignment import Control.Comonad.Cofree import Control.Monad.Free hiding (unfold) -import Data.Functor.Both +import Data.Functor.Both as Both import Diff import qualified Data.Maybe as Maybe import Data.Functor.Identity @@ -56,8 +56,7 @@ spec = parallel $ do prop "produces the maximum line count in inequal sources" $ \ sources -> - let (sourceA, sourceB) = runBoth sources in - length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ Prelude.zipWith (leafWithRangesInSources sourceA sourceB) (actualLineRanges (getTotalRange sourceA) sourceA) (actualLineRanges (getTotalRange sourceB) sourceB))) `shouldBe` max (length (filter (== '\n') $ toList sourceA) + 1) (length (filter (== '\n') $ toList sourceB) + 1) + length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ leafWithRangesInSources sources <$> Both.zip (actualLineRanges <$> (getTotalRange <$> sources) <*> sources))) `shouldBe` uncurry max (runBoth ((+ 1) . length . filter (== '\n') . toList <$> sources)) describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ @@ -115,7 +114,7 @@ spec = parallel $ do combineIntoLeaves (leaves, start) char = (leaves ++ [ Free $ Annotated (Both (Info (Range start $ start + 1) mempty, Info (Range start $ start + 1) mempty)) (Leaf [ char ]) ], start + 1) - leafWithRangesInSources sourceA sourceB rangeA rangeB = Free $ Annotated (Both (Info rangeA mempty, Info rangeB mempty)) (Leaf $ toList sourceA ++ toList sourceB) + leafWithRangesInSources sources ranges = Free $ Annotated (Info <$> ranges <*> pure mempty) (Leaf $ toList (runLeft sources) ++ toList (runRight sources)) openMaybe :: Maybe Bool -> Maybe (Maybe Bool) openMaybe (Just a) = Just (Just a) From df2b684229594b4b58c6bb21574839d59c61e747 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:31:53 -0500 Subject: [PATCH 51/70] Simplify how we compute combineIntoLeaves. --- test/AlignmentSpec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index e3d436b71..0765a5dc0 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -112,7 +112,7 @@ spec = parallel $ do getTotalRange (Source vector) = Range 0 $ length vector - combineIntoLeaves (leaves, start) char = (leaves ++ [ Free $ Annotated (Both (Info (Range start $ start + 1) mempty, Info (Range start $ start + 1) mempty)) (Leaf [ char ]) ], start + 1) + combineIntoLeaves (leaves, start) char = (leaves ++ [ Free $ Annotated (Info <$> (pure (Range start $ start + 1)) <*> mempty) (Leaf [ char ]) ], start + 1) leafWithRangesInSources sources ranges = Free $ Annotated (Info <$> ranges <*> pure mempty) (Leaf $ toList (runLeft sources) ++ toList (runRight sources)) From 665ffa4133b921b6c85f2b10cea0a84507a9c0d4 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:33:29 -0500 Subject: [PATCH 52/70] =?UTF-8?q?We=20don=E2=80=99t=20need=20to=20destruct?= =?UTF-8?q?ure=20the=20Rows=20in=20the=20append=20test.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/AlignmentSpec.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 0765a5dc0..f8fc60630 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -64,8 +64,7 @@ spec = parallel $ do prop "appends onto open rows" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ - \ (a@(Row (Both (a1, b1))), b@(Row (Both (a2, b2)))) -> - adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row $ makeLine <$> ((++) <$> (unLine <$> unRow a) <*> (unLine <$> unRow b)) ] + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row $ makeLine <$> ((++) <$> (unLine <$> unRow a) <*> (unLine <$> unRow b)) ] prop "does not append onto closed rows" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ From ac39e7369178a4bf4634171cccfbf39ad4853254 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:38:39 -0500 Subject: [PATCH 53/70] =?UTF-8?q?Line=E2=80=99s=20Monoid=20instance=20will?= =?UTF-8?q?=20do=20just=20fine=20here.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/AlignmentSpec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index f8fc60630..cf5a4b806 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -64,7 +64,7 @@ spec = parallel $ do prop "appends onto open rows" $ forAll ((arbitrary `suchThat` isOpenBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isOpenBy openMaybe)) $ - \ (a, b) -> adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row $ makeLine <$> ((++) <$> (unLine <$> unRow a) <*> (unLine <$> unRow b)) ] + \ (a, b) -> adjoinRowsBy (pure openMaybe) [ a ] b `shouldBe` [ Row (mappend <$> unRow a <*> unRow b) ] prop "does not append onto closed rows" $ forAll ((arbitrary `suchThat` isClosedBy openMaybe) >>= \ a -> (,) a <$> (arbitrary `suchThat` isClosedBy openMaybe)) $ From 5b5f22711a25581a4fa577e4c226af3eb0dbb048 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 10:57:01 -0500 Subject: [PATCH 54/70] Define zipWithDefaults over Both. --- src/Alignment.hs | 10 ++-------- src/Data/Functor/Both.hs | 5 +++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index eec981e1a..217021f47 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -28,7 +28,7 @@ splitDiffByLines diff previous sources = case diff of (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, rangeAt $ runRight previous)) Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in - (uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) . runBoth $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) + (zipWithDefaults makeRow (pure mempty) $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) where categories annotations = Diff.categories <$> annotations ranges annotations = characterRange <$> annotations @@ -75,8 +75,7 @@ splitAnnotatedByLines sources ranges categories syntax = case syntax of Fixed children -> adjoinChildRows (Fixed . fmap get) (Identity <$> children) Keyed children -> adjoinChildRows (Keyed . Map.fromList) (Map.toList children) where contextRows :: Both Range -> Both (Source Char) -> [Row Range] - contextRows ranges sources = uncurry (zipWithDefaults makeRow EmptyLine EmptyLine) $ - runBoth (fmap pure <$> (actualLineRanges <$> ranges <*> sources)) + contextRows ranges sources = zipWithDefaults makeRow (pure mempty) (fmap pure <$> (actualLineRanges <$> ranges <*> sources)) adjoin :: Has f => [Row (Either Range (f (SplitDiff leaf Info)))] -> [Row (Either Range (f (SplitDiff leaf Info)))] adjoin = reverse . foldl (adjoinRowsBy (openEither <$> (openRange <$> sources) <*> (openDiff <$> sources))) [] @@ -123,8 +122,3 @@ openDiff :: Has f => Source Char -> MaybeOpen (f (SplitDiff leaf Info)) openDiff source diff = const diff <$> case get diff of (Free (Annotated (Info range _) _)) -> openRange source range (Pure patch) -> let Info range _ :< _ = getSplitTerm patch in openRange source range - --- | Zip two lists by applying a function, using the default values to extend --- | the shorter list. -zipWithDefaults :: (a -> b -> c) -> a -> b -> [a] -> [b] -> [c] -zipWithDefaults f da db a b = take (max (length a) (length b)) $ Prelude.zipWith f (a ++ repeat da) (b ++ repeat db) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index a71eb2d13..c8ce89e75 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -21,6 +21,11 @@ runRight = snd . runBoth zip :: Both [a] -> [Both a] zip = zipWith both +-- | Zip two lists by applying a function, using the default values to extend +-- | the shorter list. +zipWithDefaults :: (a -> a -> b) -> Both a -> Both [a] -> [b] +zipWithDefaults f ds as = take (uncurry max $ runBoth (length <$> as)) $ zipWith f ((++) <$> as <*> (repeat <$> ds)) + zipWith :: (a -> a -> b) -> Both [a] -> [b] zipWith _ (Both ([], _)) = [] zipWith _ (Both (_, [])) = [] From ed48c7f427116082850ec14a29f5a7bd3ddb3988 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 11:30:32 -0500 Subject: [PATCH 55/70] Add a renderLine convenience. --- src/Renderer/Split.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index b3a1db9b4..989e164c1 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -81,6 +81,9 @@ split diff blobs = renderHtml numberedLinesToMarkup :: (Int, Line (SplitDiff a Info), Int, Line (SplitDiff a Info)) -> Markup numberedLinesToMarkup (m, left, n, right) = tr $ toMarkup (or $ hasChanges <$> left, m, renderable before left) <> toMarkup (or $ hasChanges <$> right, n, renderable after right) <> string "\n" + renderLine :: (Int, Line (SplitDiff leaf Info)) -> Source Char -> Markup + renderLine (number, line) source = toMarkup (or $ hasChanges <$> line, number, renderable source line) + renderable source = fmap (Renderable . (,) source) hasChanges diff = or $ const True <$> diff From 48abfb6dac7fe0e3fca3f5fdd78ebc8435339cd7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:22:28 -0500 Subject: [PATCH 56/70] Number rows through Both. --- src/Renderer/Split.hs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 989e164c1..0be4f5923 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -68,7 +68,7 @@ split diff blobs = renderHtml numbered = foldl' numberRows [] rows maxNumber = case numbered of [] -> 0 - ((x, _, y, _) : _) -> max x y + (row : _) -> uncurry max . runBoth $ fst <$> row -- | The number of digits in a number (e.g. 342 has 3 digits). digits :: Int -> Int @@ -78,8 +78,8 @@ split diff blobs = renderHtml columnWidth = max (20 + digits maxNumber * 8) 40 -- | Render a line with numbers as an HTML row. - numberedLinesToMarkup :: (Int, Line (SplitDiff a Info), Int, Line (SplitDiff a Info)) -> Markup - numberedLinesToMarkup (m, left, n, right) = tr $ toMarkup (or $ hasChanges <$> left, m, renderable before left) <> toMarkup (or $ hasChanges <$> right, n, renderable after right) <> string "\n" + numberedLinesToMarkup :: Both (Int, Line (SplitDiff a Info)) -> Markup + numberedLinesToMarkup numberedLines = tr $ uncurry (<>) (runBoth (renderLine <$> numberedLines <*> sources)) <> string "\n" renderLine :: (Int, Line (SplitDiff leaf Info)) -> Source Char -> Markup renderLine (number, line) source = toMarkup (or $ hasChanges <$> line, number, renderable source line) @@ -90,15 +90,11 @@ split diff blobs = renderHtml -- | Add a row to list of tuples of ints and lines, where the ints denote -- | how many non-empty lines exist on that side up to that point. - numberRows :: [(Int, Line a, Int, Line a)] -> Row a -> [(Int, Line a, Int, Line a)] - numberRows rows (Row (Both (left, right))) = (leftCount rows + valueOf left, left, rightCount rows + valueOf right, right) : rows - where - leftCount [] = 0 - leftCount ((x, _, _, _):_) = x - rightCount [] = 0 - rightCount ((_, _, x, _):_) = x - valueOf EmptyLine = 0 - valueOf _ = 1 + numberRows :: [Both (Int, Line a)] -> Row a -> [Both (Int, Line a)] + numberRows rows row = ((,) <$> ((+) <$> count rows <*> (valueOf <$> unRow row)) <*> unRow row) : rows + where count = maybe (pure 0) (fmap fst) . maybeFirst + valueOf EmptyLine = 0 + valueOf _ = 1 -- | Something that can be rendered as markup. newtype Renderable a = Renderable (Source Char, a) From 6563b4e653767bf8f96591a9cdb9a827c2bda4c2 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:22:59 -0500 Subject: [PATCH 57/70] =?UTF-8?q?Don=E2=80=99t=20bother=20destructuring=20?= =?UTF-8?q?the=20sources.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Renderer/Split.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 0be4f5923..7b7e84e2e 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -63,7 +63,6 @@ split diff blobs = renderHtml . mconcat $ numberedLinesToMarkup <$> reverse numbered where sources = Source.source <$> blobs - (before, after) = runBoth sources rows = fst (splitDiffByLines diff (pure 0) sources) numbered = foldl' numberRows [] rows maxNumber = case numbered of From 9677c59bcf6d15caa87e0fa6c8e433318dd9d7af Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:30:34 -0500 Subject: [PATCH 58/70] Move the ToMarkup instance for Line to the Renderer.Split module. --- src/Line.hs | 8 -------- src/Renderer/Split.hs | 7 +++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Line.hs b/src/Line.hs index 05d2336d9..81b4727e6 100644 --- a/src/Line.hs +++ b/src/Line.hs @@ -4,8 +4,6 @@ module Line where import qualified Data.Foldable as Foldable import Data.Monoid import qualified Data.Vector as Vector -import Text.Blaze.Html5 hiding (map) -import qualified Text.Blaze.Html5.Attributes as A -- | A line of items or an empty line. data Line a = @@ -79,9 +77,3 @@ instance Monoid (Line a) where mappend EmptyLine line = line mappend line EmptyLine = line mappend (Line xs) (Line ys) = Line (xs <> ys) - -instance ToMarkup a => ToMarkup (Bool, Int, Line a) where - toMarkup (_, _, EmptyLine) = td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell") <> td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell") <> string "\n" - toMarkup (hasChanges, num, Line contents) - = td (string $ show num) ! A.class_ (stringValue $ if hasChanges then "blob-num blob-num-replacement" else "blob-num") - <> td (mconcat . Vector.toList $ toMarkup <$> contents) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code") <> string "\n" diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 7b7e84e2e..53eb4cedf 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -124,3 +124,10 @@ instance ToMarkup (Renderable (SplitDiff a Info)) where where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) toMarkupAndRange patch = let term@(Info range _ :< _) = getSplitTerm patch in ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue . show $ termSize term)) . toMarkup $ Renderable (source, term), range) + + +instance ToMarkup a => ToMarkup (Bool, Int, Line a) where + toMarkup (_, _, EmptyLine) = td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell") <> td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell") <> string "\n" + toMarkup (hasChanges, num, line) + = td (string $ show num) ! A.class_ (stringValue $ if hasChanges then "blob-num blob-num-replacement" else "blob-num") + <> td (mconcat $ toMarkup <$> unLine line) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code") <> string "\n" From 52b23a8e5cfacd8c42df0faaa9f88c27b41047c5 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:38:14 -0500 Subject: [PATCH 59/70] =?UTF-8?q?Don=E2=80=99t=20warn=20about=20orphans.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is for the orphan ToMarkup instance in Renderer.Split. --- semantic-diff.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 75f383e8f..e56690815 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -55,7 +55,7 @@ library , vector default-language: Haskell2010 default-extensions: DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric, OverloadedStrings - ghc-options: -Wall -fno-warn-name-shadowing -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" -j + ghc-options: -Wall -fno-warn-name-shadowing -fno-warn-orphans -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" -j test-suite semantic-diff-test type: exitcode-stdio-1.0 From 90b58cc1ae979a8a5627ba21e992bfcf0837fc04 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:40:12 -0500 Subject: [PATCH 60/70] Map into Renderable directly. --- src/Renderer/Split.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 53eb4cedf..482103bdc 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -81,9 +81,7 @@ split diff blobs = renderHtml numberedLinesToMarkup numberedLines = tr $ uncurry (<>) (runBoth (renderLine <$> numberedLines <*> sources)) <> string "\n" renderLine :: (Int, Line (SplitDiff leaf Info)) -> Source Char -> Markup - renderLine (number, line) source = toMarkup (or $ hasChanges <$> line, number, renderable source line) - - renderable source = fmap (Renderable . (,) source) + renderLine (number, line) source = toMarkup (or $ hasChanges <$> line, number, Renderable . (,) source <$> line) hasChanges diff = or $ const True <$> diff From fe9f2113368c8d27e73f4a784d374542f1fcd464 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:43:23 -0500 Subject: [PATCH 61/70] Generalize Renderable. --- src/Renderer/Split.hs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 482103bdc..0c2f8083a 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -94,10 +94,10 @@ split diff blobs = renderHtml valueOf _ = 1 -- | Something that can be rendered as markup. -newtype Renderable a = Renderable (Source Char, a) +newtype Renderable a = Renderable a -instance ToMarkup f => ToMarkup (Renderable (Info, Syntax a (f, Range))) where - toMarkup (Renderable (source, (Info range categories, syntax))) = classifyMarkup categories $ case syntax of +instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where + toMarkup (Renderable (source, Info range categories, syntax)) = classifyMarkup categories $ case syntax of Leaf _ -> span . string . toString $ slice range source Indexed children -> ul . mconcat $ wrapIn li <$> contentElements children Fixed children -> ul . mconcat $ wrapIn li <$> contentElements children @@ -114,11 +114,11 @@ instance ToMarkup f => ToMarkup (Renderable (Info, Syntax a (f, Range))) where contentElements children = let (elements, previous) = foldl' markupForSeparatorAndChild ([], start range) children in elements ++ [ string . toString $ slice (Range previous $ end range) source ] -instance ToMarkup (Renderable (Term a Info)) where - toMarkup (Renderable (source, term)) = fst $ cata (\ info@(Info range _) syntax -> (toMarkup $ Renderable (source, (info, syntax)), range)) term +instance ToMarkup (Renderable (Source Char, Term a Info)) where + toMarkup (Renderable (source, term)) = fst $ cata (\ info@(Info range _) syntax -> (toMarkup $ Renderable (source, info, syntax), range)) term -instance ToMarkup (Renderable (SplitDiff a Info)) where - toMarkup (Renderable (source, diff)) = fst $ iter (\ (Annotated info@(Info range _) syntax) -> (toMarkup $ Renderable (source, (info, syntax)), range)) $ toMarkupAndRange <$> diff +instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where + toMarkup (Renderable (source, diff)) = fst $ iter (\ (Annotated info@(Info range _) syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) $ toMarkupAndRange <$> diff where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) toMarkupAndRange patch = let term@(Info range _ :< _) = getSplitTerm patch in ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue . show $ termSize term)) . toMarkup $ Renderable (source, term), range) From 75b7f9d58c365e49e61eb792ba3031d275f98f5d Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:43:38 -0500 Subject: [PATCH 62/70] =?UTF-8?q?Revert=20"Don=E2=80=99t=20warn=20about=20?= =?UTF-8?q?orphans."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 18205b45d7cce5985265248f6c69f9c1932b057d. --- semantic-diff.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index e56690815..75f383e8f 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -55,7 +55,7 @@ library , vector default-language: Haskell2010 default-extensions: DeriveFunctor, DeriveFoldable, DeriveTraversable, DeriveGeneric, OverloadedStrings - ghc-options: -Wall -fno-warn-name-shadowing -fno-warn-orphans -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" -j + ghc-options: -Wall -fno-warn-name-shadowing -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" -j test-suite semantic-diff-test type: exitcode-stdio-1.0 From 8dddf49c109074e3e8c005ed24c491cb4130131c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:46:26 -0500 Subject: [PATCH 63/70] Embed the Bool/Int/Line ToMarkup instance in Renderable. --- src/Renderer/Split.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 0c2f8083a..8e190ff7b 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -81,7 +81,7 @@ split diff blobs = renderHtml numberedLinesToMarkup numberedLines = tr $ uncurry (<>) (runBoth (renderLine <$> numberedLines <*> sources)) <> string "\n" renderLine :: (Int, Line (SplitDiff leaf Info)) -> Source Char -> Markup - renderLine (number, line) source = toMarkup (or $ hasChanges <$> line, number, Renderable . (,) source <$> line) + renderLine (number, line) source = toMarkup $ Renderable (or $ hasChanges <$> line, number, Renderable . (,) source <$> line) hasChanges diff = or $ const True <$> diff @@ -124,8 +124,8 @@ instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue . show $ termSize term)) . toMarkup $ Renderable (source, term), range) -instance ToMarkup a => ToMarkup (Bool, Int, Line a) where - toMarkup (_, _, EmptyLine) = td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell") <> td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell") <> string "\n" - toMarkup (hasChanges, num, line) +instance ToMarkup a => ToMarkup (Renderable (Bool, Int, Line a)) where + toMarkup (Renderable (_, _, EmptyLine)) = td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell") <> td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell") <> string "\n" + toMarkup (Renderable (hasChanges, num, line)) = td (string $ show num) ! A.class_ (stringValue $ if hasChanges then "blob-num blob-num-replacement" else "blob-num") <> td (mconcat $ toMarkup <$> unLine line) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code") <> string "\n" From 3e172365a2aacb9c4791e3bdcd588fa4b0d14c75 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 13:50:51 -0500 Subject: [PATCH 64/70] Map directly over the applicatives. --- src/Data/Functor/Both.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index c8ce89e75..c89d0b40f 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -41,4 +41,4 @@ instance Applicative Both where instance Monoid a => Monoid (Both a) where mempty = pure mempty - mappend a b = pure mappend <*> a <*> b + mappend a b = mappend <$> a <*> b From ace761689963979701919a625fb844e1949185e7 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 19:38:22 -0500 Subject: [PATCH 65/70] The Foldable instance for OrderedMap takes care of this for us. --- src/Term.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Term.hs b/src/Term.hs index 71810560b..9519f22f5 100644 --- a/src/Term.hs +++ b/src/Term.hs @@ -32,4 +32,4 @@ termSize = cata size where size _ (Leaf _) = 1 size _ (Indexed i) = sum i size _ (Fixed f) = sum f - size _ (Keyed k) = sum $ snd <$> toList k + size _ (Keyed k) = sum k From 7548e148dcbc1dad927baaddcb121f74084a53e0 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 19:39:04 -0500 Subject: [PATCH 66/70] Rename runLeft/runRight to fst/snd. --- src/Alignment.hs | 12 +++++++----- src/Data/Functor/Both.hs | 11 ++++++----- src/Renderer/Patch.hs | 8 +++++--- src/Renderer/Split.hs | 13 +++++++------ src/Row.hs | 5 +++-- test/AlignmentSpec.hs | 10 ++++++---- test/CorpusSpec.hs | 8 +++++--- 7 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Alignment.hs b/src/Alignment.hs index 217021f47..bf73c2929 100644 --- a/src/Alignment.hs +++ b/src/Alignment.hs @@ -11,6 +11,8 @@ import qualified Data.Set as Set import Diff import Line import Patch +import Prelude hiding (fst, snd) +import qualified Prelude import Range import Row import Source hiding ((++)) @@ -22,10 +24,10 @@ import Term splitDiffByLines :: Diff leaf Info -> Both Int -> Both (Source Char) -> ([Row (SplitDiff leaf Info)], Both Range) splitDiffByLines diff previous sources = case diff of Free (Annotated annotation syntax) -> (splitAnnotatedByLines sources (ranges annotation) (categories annotation) syntax, ranges annotation) - Pure (Insert term) -> let (lines, range) = splitTermByLines term (runRight sources) in - (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (rangeAt $ runLeft previous, range)) - Pure (Delete term) -> let (lines, range) = splitTermByLines term (runLeft sources) in - (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, rangeAt $ runRight previous)) + Pure (Insert term) -> let (lines, range) = splitTermByLines term (snd sources) in + (makeRow EmptyLine . fmap (Pure . SplitInsert) <$> lines, Both (rangeAt $ fst previous, range)) + Pure (Delete term) -> let (lines, range) = splitTermByLines term (fst sources) in + (flip makeRow EmptyLine . fmap (Pure . SplitDelete) <$> lines, Both (range, rangeAt $ snd previous)) Pure (Replace leftTerm rightTerm) -> let Both ((leftLines, leftRange), (rightLines, rightRange)) = splitTermByLines <$> Both (leftTerm, rightTerm) <*> sources (lines, ranges) = (Both (leftLines, rightLines), Both (leftRange, rightRange)) in (zipWithDefaults makeRow (pure mempty) $ fmap (fmap (Pure . SplitReplace)) <$> lines, ranges) @@ -40,7 +42,7 @@ instance Has Identity where get = runIdentity instance Has ((,) a) where - get = snd + get = Prelude.snd -- | Takes a term and a source and returns a list of lines and their range within source. splitTermByLines :: Term leaf Info -> Source Char -> ([Line (Term leaf Info)], Range) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index c89d0b40f..c9584e214 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -1,6 +1,7 @@ module Data.Functor.Both where -import Prelude hiding (zipWith) +import Prelude hiding (zipWith, fst, snd) +import qualified Prelude -- | A computation over both sides of a pair. newtype Both a = Both { runBoth :: (a, a) } @@ -11,12 +12,12 @@ both :: a -> a -> Both a both = curry Both -- | Runs the left side of a `Both`. -runLeft :: Both a -> a -runLeft = fst . runBoth +fst :: Both a -> a +fst = Prelude.fst . runBoth -- | Runs the right side of a `Both`. -runRight :: Both a -> a -runRight = snd . runBoth +snd :: Both a -> a +snd = Prelude.snd . runBoth zip :: Both [a] -> [Both a] zip = zipWith both diff --git a/src/Renderer/Patch.hs b/src/Renderer/Patch.hs index 6a957ded2..e834a07bd 100644 --- a/src/Renderer/Patch.hs +++ b/src/Renderer/Patch.hs @@ -6,6 +6,8 @@ module Renderer.Patch ( import Alignment import Diff import Line +import Prelude hiding (fst, snd) +import qualified Prelude import Range import Renderer import Row @@ -48,12 +50,12 @@ lineLength _ = 1 -- | Given the before and after sources, render a hunk to a string. showHunk :: Both SourceBlob -> Hunk (SplitDiff a Info) -> String -showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd $ runBoth sources) ' ' (unRight <$> trailingContext hunk) +showHunk blobs hunk = header blobs hunk ++ concat (showChange sources <$> changes hunk) ++ showLines (snd sources) ' ' (unRight <$> trailingContext hunk) where sources = source <$> blobs -- | 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 $ runBoth sources) ' ' (unRight <$> context change) ++ deleted ++ inserted +showChange sources change = showLines (snd sources) ' ' (unRight <$> context change) ++ deleted ++ inserted where (deleted, inserted) = runBoth $ pure showLines <*> sources <*> Both ('-', '+') <*> (pure fmap <*> Both (unLeft, unRight) <*> pure (contents change)) -- | Given a source, render a set of lines to a string with a prefix. @@ -84,7 +86,7 @@ header blobs hunk = "diff --git a/" ++ pathA ++ " b/" ++ pathB ++ "\n" ++ -- | Render a diff as a series of hunks. hunks :: Renderer a [Hunk (SplitDiff a Info)] -hunks diff blobs = hunksInRows (Both (1, 1)) . fst $ splitDiffByLines diff (pure 0) (source <$> blobs) +hunks diff blobs = hunksInRows (Both (1, 1)) . Prelude.fst $ splitDiffByLines diff (pure 0) (source <$> blobs) -- | Given beginning line numbers, turn rows in a split diff into hunks in a -- | patch. diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index 8e190ff7b..d6c39f8ab 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -2,10 +2,11 @@ module Renderer.Split where import Alignment -import Prelude hiding (div, head, span) import Category import Diff import Line +import Prelude hiding (div, head, span, fst, snd) +import qualified Prelude import Row import Renderer import Term @@ -63,11 +64,11 @@ split diff blobs = renderHtml . mconcat $ numberedLinesToMarkup <$> reverse numbered where sources = Source.source <$> blobs - rows = fst (splitDiffByLines diff (pure 0) sources) + rows = Prelude.fst (splitDiffByLines diff (pure 0) sources) numbered = foldl' numberRows [] rows maxNumber = case numbered of [] -> 0 - (row : _) -> uncurry max . runBoth $ fst <$> row + (row : _) -> uncurry max . runBoth $ Prelude.fst <$> row -- | The number of digits in a number (e.g. 342 has 3 digits). digits :: Int -> Int @@ -89,7 +90,7 @@ split diff blobs = renderHtml -- | how many non-empty lines exist on that side up to that point. numberRows :: [Both (Int, Line a)] -> Row a -> [Both (Int, Line a)] numberRows rows row = ((,) <$> ((+) <$> count rows <*> (valueOf <$> unRow row)) <*> unRow row) : rows - where count = maybe (pure 0) (fmap fst) . maybeFirst + where count = maybe (pure 0) (fmap Prelude.fst) . maybeFirst valueOf EmptyLine = 0 valueOf _ = 1 @@ -115,10 +116,10 @@ instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Ran elements ++ [ string . toString $ slice (Range previous $ end range) source ] instance ToMarkup (Renderable (Source Char, Term a Info)) where - toMarkup (Renderable (source, term)) = fst $ cata (\ info@(Info range _) syntax -> (toMarkup $ Renderable (source, info, syntax), range)) term + toMarkup (Renderable (source, term)) = Prelude.fst $ cata (\ info@(Info range _) syntax -> (toMarkup $ Renderable (source, info, syntax), range)) term instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where - toMarkup (Renderable (source, diff)) = fst $ iter (\ (Annotated info@(Info range _) syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) $ toMarkupAndRange <$> diff + toMarkup (Renderable (source, diff)) = Prelude.fst $ iter (\ (Annotated info@(Info range _) syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) $ toMarkupAndRange <$> diff where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range) toMarkupAndRange patch = let term@(Info range _ :< _) = getSplitTerm patch in ((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue . show $ termSize term)) . toMarkup $ Renderable (source, term), range) diff --git a/src/Row.hs b/src/Row.hs index 5655a9799..0098fd55e 100644 --- a/src/Row.hs +++ b/src/Row.hs @@ -3,6 +3,7 @@ module Row where import Control.Arrow import Data.Functor.Both as Both import Line +import Prelude hiding (fst, snd) -- | A row in a split diff, composed of a before line and an after line. newtype Row a = Row { unRow :: Both (Line a) } @@ -12,10 +13,10 @@ makeRow :: Line a -> Line a -> Row a makeRow a = Row . both a unLeft :: Row a -> Line a -unLeft = runLeft . unRow +unLeft = fst . unRow unRight :: Row a -> Line a -unRight = runRight . unRow +unRight = snd . unRow -- | Map over both sides of a row with the given functions. wrapRowContents :: Both ([a] -> b) -> Row a -> Row b diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index cf5a4b806..1ec08f461 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -6,18 +6,20 @@ import Test.QuickCheck hiding (Fixed) import Data.Text.Arbitrary () import Alignment +import ArbitraryTerm () import Control.Comonad.Cofree import Control.Monad.Free hiding (unfold) import Data.Functor.Both as Both import Diff import qualified Data.Maybe as Maybe import Data.Functor.Identity -import Source hiding ((++)) import Line +import Prelude hiding (fst, snd) +import qualified Prelude import Row import Range +import Source hiding ((++)) import Syntax -import ArbitraryTerm () instance Arbitrary a => Arbitrary (Both a) where arbitrary = pure (curry Both) <*> arbitrary <*> arbitrary @@ -52,7 +54,7 @@ spec = parallel $ do prop "preserves line counts in equal sources" $ \ source -> - length (splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (pure mempty) (Indexed . fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 + length (splitAnnotatedByLines (pure source) (pure (getTotalRange source)) (pure mempty) (Indexed . Prelude.fst $ foldl combineIntoLeaves ([], 0) source)) `shouldBe` length (filter (== '\n') $ toList source) + 1 prop "produces the maximum line count in inequal sources" $ \ sources -> @@ -113,7 +115,7 @@ spec = parallel $ do combineIntoLeaves (leaves, start) char = (leaves ++ [ Free $ Annotated (Info <$> (pure (Range start $ start + 1)) <*> mempty) (Leaf [ char ]) ], start + 1) - leafWithRangesInSources sources ranges = Free $ Annotated (Info <$> ranges <*> pure mempty) (Leaf $ toList (runLeft sources) ++ toList (runRight sources)) + leafWithRangesInSources sources ranges = Free $ Annotated (Info <$> ranges <*> pure mempty) (Leaf $ toList (fst sources) ++ toList (snd sources)) openMaybe :: Maybe Bool -> Maybe (Maybe Bool) openMaybe (Just a) = Just (Just a) diff --git a/test/CorpusSpec.hs b/test/CorpusSpec.hs index 940291a1c..89d5452a2 100644 --- a/test/CorpusSpec.hs +++ b/test/CorpusSpec.hs @@ -5,7 +5,6 @@ import Renderer import qualified Renderer.Patch as P import qualified Renderer.Split as Split -import qualified Source as S import Control.DeepSeq import Data.Functor.Both import qualified Data.ByteString.Char8 as B1 @@ -15,6 +14,9 @@ import Data.Maybe import Data.Set as Set import qualified Data.Text as T import qualified Data.Text.Lazy as TL +import Prelude hiding (fst, snd) +import qualified Prelude +import qualified Source as S import System.FilePath import System.FilePath.Glob import Test.Hspec @@ -35,7 +37,7 @@ spec = parallel $ do runTestsIn directory matcher = do paths <- runIO $ examples directory let tests = correctTests =<< paths - mapM_ (\ (formatName, renderer, paths, output) -> it (normalizeName (fst $ runBoth paths) ++ " (" ++ formatName ++ ")") $ testDiff renderer paths output matcher) tests + mapM_ (\ (formatName, renderer, paths, output) -> it (normalizeName (fst paths) ++ " (" ++ formatName ++ ")") $ testDiff renderer paths output matcher) tests correctTests :: (Both FilePath, Maybe FilePath, Maybe FilePath) -> [(String, Renderer a String, Both FilePath, Maybe FilePath)] correctTests paths@(_, Nothing, Nothing) = testsForPaths paths @@ -72,7 +74,7 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte -- | is true, but the diff will still be calculated. testDiff :: Renderer T.Text String -> Both FilePath -> Maybe FilePath -> (String -> String -> Expectation) -> Expectation testDiff renderer paths diff matcher = do - let parser = parserForFilepath (fst $ runBoth paths) + let parser = parserForFilepath (fst paths) sources <- sequence $ readAndTranscodeFile <$> paths let sourceBlobs = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> pure mempty <*> paths actual <- diffFiles parser renderer sourceBlobs From 51ad3ba0497d54709fa40736df7f424c23ab48f0 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 19:41:05 -0500 Subject: [PATCH 67/70] Add runBothWith. --- src/Data/Functor/Both.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index c9584e214..6435e39a8 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -11,6 +11,10 @@ newtype Both a = Both { runBoth :: (a, a) } both :: a -> a -> Both a both = curry Both +-- | Apply a function to `Both` sides of a computation. +runBothWith :: (a -> a -> b) -> Both a -> b +runBothWith f = uncurry f . runBoth + -- | Runs the left side of a `Both`. fst :: Both a -> a fst = Prelude.fst . runBoth From ae149c94ffc893083684086f6fcec80e812dde65 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Mon, 29 Feb 2016 19:43:57 -0500 Subject: [PATCH 68/70] Use runBothWith to tidy up a few computations. --- src/Data/Functor/Both.hs | 2 +- src/Diffing.hs | 2 +- src/Renderer/Split.hs | 4 ++-- test/AlignmentSpec.hs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Data/Functor/Both.hs b/src/Data/Functor/Both.hs index 6435e39a8..68a880d9a 100644 --- a/src/Data/Functor/Both.hs +++ b/src/Data/Functor/Both.hs @@ -29,7 +29,7 @@ zip = zipWith both -- | Zip two lists by applying a function, using the default values to extend -- | the shorter list. zipWithDefaults :: (a -> a -> b) -> Both a -> Both [a] -> [b] -zipWithDefaults f ds as = take (uncurry max $ runBoth (length <$> as)) $ zipWith f ((++) <$> as <*> (repeat <$> ds)) +zipWithDefaults f ds as = take (runBothWith max (length <$> as)) $ zipWith f ((++) <$> as <*> (repeat <$> ds)) zipWith :: (a -> a -> b) -> Both [a] -> [b] zipWith _ (Both ([], _)) = [] diff --git a/src/Diffing.hs b/src/Diffing.hs index c6fb7b237..342ec50db 100644 --- a/src/Diffing.hs +++ b/src/Diffing.hs @@ -75,4 +75,4 @@ diffFiles parser renderer sourceBlobs = do let sources = source <$> sourceBlobs terms <- sequence $ parser <$> sources let replaceLeaves = breakDownLeavesByWord <$> sources - return $ renderer (uncurry diffTerms $ runBoth $ replaceLeaves <*> terms) sourceBlobs + return $ renderer (runBothWith diffTerms $ replaceLeaves <*> terms) sourceBlobs diff --git a/src/Renderer/Split.hs b/src/Renderer/Split.hs index d6c39f8ab..6855cfc24 100644 --- a/src/Renderer/Split.hs +++ b/src/Renderer/Split.hs @@ -68,7 +68,7 @@ split diff blobs = renderHtml numbered = foldl' numberRows [] rows maxNumber = case numbered of [] -> 0 - (row : _) -> uncurry max . runBoth $ Prelude.fst <$> row + (row : _) -> runBothWith max $ Prelude.fst <$> row -- | The number of digits in a number (e.g. 342 has 3 digits). digits :: Int -> Int @@ -79,7 +79,7 @@ split diff blobs = renderHtml -- | Render a line with numbers as an HTML row. numberedLinesToMarkup :: Both (Int, Line (SplitDiff a Info)) -> Markup - numberedLinesToMarkup numberedLines = tr $ uncurry (<>) (runBoth (renderLine <$> numberedLines <*> sources)) <> string "\n" + numberedLinesToMarkup numberedLines = tr $ (runBothWith (<>) (renderLine <$> numberedLines <*> sources)) <> string "\n" renderLine :: (Int, Line (SplitDiff leaf Info)) -> Source Char -> Markup renderLine (number, line) source = toMarkup $ Renderable (or $ hasChanges <$> line, number, Renderable . (,) source <$> line) diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index 1ec08f461..0c94181f4 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -58,7 +58,7 @@ spec = parallel $ do prop "produces the maximum line count in inequal sources" $ \ sources -> - length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ leafWithRangesInSources sources <$> Both.zip (actualLineRanges <$> (getTotalRange <$> sources) <*> sources))) `shouldBe` uncurry max (runBoth ((+ 1) . length . filter (== '\n') . toList <$> sources)) + length (splitAnnotatedByLines sources (getTotalRange <$> sources) (pure mempty) (Indexed $ leafWithRangesInSources sources <$> Both.zip (actualLineRanges <$> (getTotalRange <$> sources) <*> sources))) `shouldBe` runBothWith max ((+ 1) . length . filter (== '\n') . toList <$> sources) describe "adjoinRowsBy" $ do prop "is identity on top of no rows" $ From 05b6e3a5f7ffc68eddd025a3ad7c736d527f148c Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Tue, 1 Mar 2016 08:20:24 -0500 Subject: [PATCH 69/70] =?UTF-8?q?We=20don=E2=80=99t=20need=20to=20use=20g+?= =?UTF-8?q?+=20on=20Linux=20for=20the=20tests.=20I=20think.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- semantic-diff.cabal | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 9c7e25b8b..b7c3eb1f2 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -79,10 +79,7 @@ test-suite semantic-diff-test , quickcheck-text , semantic-diff , text >= 1.2.1.3 - if os(darwin) - ghc-options: -threaded -rtsopts -with-rtsopts=-N -j - else - ghc-options: -threaded -rtsopts -with-rtsopts=-N -j -pgml=script/g++ + ghc-options: -threaded -rtsopts -with-rtsopts=-N -j default-language: Haskell2010 default-extensions: DeriveGeneric, OverloadedStrings if os(darwin) From 7c179d55c45a730ee796dcc661b428956c29ab76 Mon Sep 17 00:00:00 2001 From: Rob Rix Date: Tue, 1 Mar 2016 08:23:42 -0500 Subject: [PATCH 70/70] =?UTF-8?q?Revert=20"We=20don=E2=80=99t=20need=20to?= =?UTF-8?q?=20use=20g++=20on=20Linux=20for=20the=20tests.=20I=20think."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 6ac237bb0b4e301db0b49bd35d894a7923bbf9e1. --- semantic-diff.cabal | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index b7c3eb1f2..9c7e25b8b 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -79,7 +79,10 @@ test-suite semantic-diff-test , quickcheck-text , semantic-diff , text >= 1.2.1.3 - ghc-options: -threaded -rtsopts -with-rtsopts=-N -j + if os(darwin) + ghc-options: -threaded -rtsopts -with-rtsopts=-N -j + else + ghc-options: -threaded -rtsopts -with-rtsopts=-N -j -pgml=script/g++ default-language: Haskell2010 default-extensions: DeriveGeneric, OverloadedStrings if os(darwin)