2016-03-14 21:13:06 +03:00
{- # LANGUAGE RankNTypes # -}
2016-03-11 20:29:17 +03:00
module Alignment
2016-03-14 21:13:06 +03:00
( hasChanges
2016-03-11 20:29:17 +03:00
, linesInRangeOfSource
, numberedRows
, splitAbstractedTerm
, splitDiffByLines
, Row
2016-03-22 15:20:56 +03:00
, AlignedDiff
, alignDiff
2016-03-24 20:53:49 +03:00
, groupChildrenByLine
2016-03-11 20:29:17 +03:00
) where
2016-02-28 22:01:56 +03:00
2016-04-04 22:54:38 +03:00
import Control.Arrow ( ( &&& ) )
2016-02-28 22:01:56 +03:00
import Control.Comonad.Cofree
2016-03-05 04:18:49 +03:00
import Control.Monad
2016-02-28 22:01:56 +03:00
import Control.Monad.Free
2016-03-12 01:59:58 +03:00
import Data.Adjoined
2016-03-10 23:06:03 +03:00
import Data.Align
2016-03-18 21:53:52 +03:00
import Data.Bifunctor
2016-03-18 21:30:22 +03:00
import Data.Bifunctor.Join
2016-03-10 23:06:03 +03:00
import Data.Bifunctor.These
2016-03-14 17:35:31 +03:00
import Data.Coalescent
2016-03-03 07:11:24 +03:00
import Data.Copointed
2016-03-12 01:59:58 +03:00
import Data.Foldable
2016-04-04 23:25:16 +03:00
import Data.Functor.Both as Both
2016-02-28 22:01:56 +03:00
import Data.Functor.Identity
2016-03-03 15:53:23 +03:00
import Data.Maybe
2016-03-11 23:29:12 +03:00
import Data.Monoid
2016-02-28 22:01:56 +03:00
import qualified Data.OrderedMap as Map
2016-03-14 17:35:31 +03:00
import qualified Data.Text as T
2016-02-28 22:01:56 +03:00
import Diff
2016-03-31 00:26:52 +03:00
import Info
2016-03-15 03:43:45 +03:00
import Line
2016-02-28 22:01:56 +03:00
import Patch
2016-03-01 03:39:04 +03:00
import Prelude hiding ( fst , snd )
import qualified Prelude
2016-02-28 22:01:56 +03:00
import Range
2016-03-29 00:55:01 +03:00
import Source hiding ( fromList , uncons , ( ++ ) )
2016-02-28 22:01:56 +03:00
import SplitDiff
import Syntax
import Term
2016-03-02 00:29:42 +03:00
-- | Assign line numbers to the lines on each side of a list of rows.
numberedRows :: [ Row a ] -> [ Both ( Int , Line a ) ]
2016-03-08 01:13:51 +03:00
numberedRows = countUp ( pure 1 )
2016-03-10 08:56:31 +03:00
where countUp from ( row : rows ) = ( ( , ) <$> from <*> row ) : countUp ( ( + ) <$> from <*> ( lineIncrement <$> row ) ) rows
2016-03-08 01:13:51 +03:00
countUp _ [] = []
2016-03-02 00:29:42 +03:00
2016-03-02 16:05:18 +03:00
-- | Determine whether a line contains any patches.
hasChanges :: Line ( SplitDiff leaf Info ) -> Bool
hasChanges = or . fmap ( or . ( True <$ ) )
2016-03-07 20:05:21 +03:00
-- | Split a diff, which may span multiple lines, into rows of split diffs paired with the Range of characters spanned by that Row on each side of the diff.
2016-03-05 03:29:57 +03:00
splitDiffByLines :: Both ( Source Char ) -> Diff leaf Info -> [ Row ( SplitDiff leaf Info , Range ) ]
2016-03-15 03:43:45 +03:00
splitDiffByLines sources = toList . iter ( \ ( Annotated infos syntax ) -> splitAbstractedTerm ( ( Free . ) . Annotated ) sources infos syntax ) . fmap ( splitPatchByLines sources )
2016-03-03 00:05:34 +03:00
2016-03-03 00:05:59 +03:00
-- | Split a patch, which may span multiple lines, into rows of split diffs.
2016-03-15 03:43:45 +03:00
splitPatchByLines :: Both ( Source Char ) -> Patch ( Term leaf Info ) -> Adjoined ( Both ( Line ( SplitDiff leaf Info , Range ) ) )
splitPatchByLines sources patch = wrapTermInPatch <$> splitAndFoldTerm ( unPatch patch )
2016-03-29 16:22:54 +03:00
where splitAndFoldTerm ( This deleted ) = tsequenceL mempty $ both ( runIdentity <$> cata ( splitAbstractedTerm ( :< ) ( Identity $ fst sources ) ) ( Identity <$> deleted ) ) nil
splitAndFoldTerm ( That inserted ) = tsequenceL mempty $ both nil ( runIdentity <$> cata ( splitAbstractedTerm ( :< ) ( Identity $ snd sources ) ) ( Identity <$> inserted ) )
splitAndFoldTerm ( These deleted inserted ) = tsequenceL mempty $ both ( runIdentity <$> cata ( splitAbstractedTerm ( :< ) ( Identity $ fst sources ) ) ( Identity <$> deleted ) ) ( runIdentity <$> cata ( splitAbstractedTerm ( :< ) ( Identity $ snd sources ) ) ( Identity <$> inserted ) )
2016-03-14 21:13:06 +03:00
wrapTermInPatch = fmap ( fmap ( first ( Pure . constructor patch ) ) )
2016-03-03 20:30:23 +03:00
constructor ( Replace _ _ ) = SplitReplace
constructor ( Insert _ ) = SplitInsert
constructor ( Delete _ ) = SplitDelete
2016-02-28 22:01:56 +03:00
2016-03-09 17:36:12 +03:00
-- | Split a term comprised of an Info & Syntax up into one `outTerm` (abstracted by an alignment function & constructor) per line in `Source`.
2016-03-15 03:43:45 +03:00
splitAbstractedTerm :: ( Applicative f , Coalescent ( f ( Line ( Maybe ( Identity outTerm ) , Range ) ) ) , Coalescent ( f ( Line ( Maybe ( T . Text , outTerm ) , Range ) ) ) , Foldable f , TotalCrosswalk f ) => ( Info -> Syntax leaf outTerm -> outTerm ) -> f ( Source Char ) -> f Info -> Syntax leaf ( Adjoined ( f ( Line ( outTerm , Range ) ) ) ) -> Adjoined ( f ( Line ( outTerm , Range ) ) )
2016-03-14 21:50:54 +03:00
splitAbstractedTerm makeTerm sources infos syntax = case syntax of
2016-03-31 00:26:52 +03:00
Leaf a -> tsequenceL ( pure mempty ) $ fmap <$> ( ( \ categories -> fmap ( \ range -> ( makeTerm ( Info range categories ) ( Leaf a ) , range ) ) ) <$> ( Info . categories <$> infos ) ) <*> ( linesInRangeOfSource <$> ( characterRange <$> infos ) <*> sources )
2016-03-14 21:52:34 +03:00
Indexed children -> adjoinChildren sources infos ( constructor ( Indexed . fmap runIdentity ) ) ( Identity <$> children )
Fixed children -> adjoinChildren sources infos ( constructor ( Fixed . fmap runIdentity ) ) ( Identity <$> children )
Keyed children -> adjoinChildren sources infos ( constructor ( Keyed . Map . fromList ) ) ( Map . toList children )
2016-03-09 12:45:30 +03:00
where constructor with info = makeTerm info . with
2016-03-03 21:41:56 +03:00
2016-03-11 19:54:39 +03:00
-- | Adjoin a branch term’ s lines, wrapping children & context in branch nodes using a constructor.
2016-03-15 03:43:45 +03:00
adjoinChildren :: ( Copointed c , Functor c , Applicative f , Coalescent ( f ( Line ( Maybe ( c a ) , Range ) ) ) , Foldable f , TotalCrosswalk f ) => f ( Source Char ) -> f Info -> ( Info -> [ c a ] -> outTerm ) -> [ c ( Adjoined ( f ( Line ( a , Range ) ) ) ) ] -> Adjoined ( f ( Line ( outTerm , Range ) ) )
2016-03-14 21:57:39 +03:00
adjoinChildren sources infos constructor children = wrap <$> leadingContext <> lines
2016-03-14 21:56:32 +03:00
where ( lines , next ) = foldr ( childLines sources ) ( mempty , end <$> ranges ) children
2016-03-09 12:02:00 +03:00
ranges = characterRange <$> infos
2016-03-31 00:26:52 +03:00
categories = Info . categories <$> infos
2016-03-15 17:58:54 +03:00
leadingContext = tsequenceL ( pure mempty ) $ makeContextLines <$> ( linesInRangeOfSource <$> ( Range <$> ( start <$> ranges ) <*> next ) <*> sources )
2016-03-09 12:17:39 +03:00
wrap = ( wrapLineContents <$> ( makeBranchTerm constructor <$> categories <*> next ) <*> )
2016-03-14 21:32:48 +03:00
makeBranchTerm constructor categories next children = let range = unionRangesFrom ( rangeAt next ) $ Prelude . snd <$> children in
( constructor ( Info range categories ) . catMaybes . toList $ Prelude . fst <$> children , range )
2016-03-09 12:02:00 +03:00
2016-03-11 20:00:06 +03:00
-- | Accumulate the lines of and between a branch term’ s children.
2016-03-15 03:43:45 +03:00
childLines :: ( Copointed c , Functor c , Applicative f , Coalescent ( f ( Line ( Maybe ( c a ) , Range ) ) ) , Foldable f , TotalCrosswalk f ) => f ( Source Char ) -> c ( Adjoined ( f ( Line ( a , Range ) ) ) ) -> ( Adjoined ( f ( Line ( Maybe ( c a ) , Range ) ) ) , f Int ) -> ( Adjoined ( f ( Line ( Maybe ( c a ) , Range ) ) ) , f Int )
2016-03-09 10:50:31 +03:00
-- We depend on source ranges increasing monotonically. If a child invalidates that, e.g. if it’ s a move in a Keyed node, we don’ t output rows for it in this iteration. (It will still show up in the diff as context rows.) This works around https://github.com/github/semantic-diff/issues/488.
2016-03-15 15:56:18 +03:00
childLines sources child ( nextLines , next ) | or ( ( > ) . end <$> childRanges <*> next ) = ( nextLines , next )
2016-03-15 16:23:14 +03:00
| otherwise = ( ( makeChildLines <$> copoint child )
2016-03-15 17:58:54 +03:00
<> tsequenceL ( pure mempty ) ( makeContextLines <$> trailingContextLines )
2016-03-15 15:56:18 +03:00
<> nextLines , start <$> childRanges )
2016-03-15 16:23:14 +03:00
where makeChildLines = fmap ( fmap ( first ( Just . ( <$ child ) ) ) )
2016-03-15 17:36:18 +03:00
trailingContextLines = linesInRangeOfSource <$> ( Range <$> ( end <$> childRanges ) <*> next ) <*> sources
2016-03-28 11:39:32 +03:00
childRanges = unionRangesFrom <$> ( rangeAt <$> next ) <*> ( concatMap ( fmap Prelude . snd . unLine ) <$> sequenceA ( copoint child ) )
2016-02-28 22:01:56 +03:00
2016-03-15 17:58:54 +03:00
makeContextLines :: Adjoined ( Line Range ) -> Adjoined ( Line ( Maybe a , Range ) )
2016-03-15 15:54:35 +03:00
makeContextLines = fmap ( fmap ( ( , ) Nothing ) )
2016-03-10 17:08:49 +03:00
-- | Produce open/closed lines for the portion of the source spanned by a range.
2016-03-15 17:58:54 +03:00
linesInRangeOfSource :: Range -> Source Char -> Adjoined ( Line Range )
linesInRangeOfSource range source = fromList $ pureBy ( openRange source ) <$> actualLineRanges range source
2016-03-10 16:53:24 +03:00
2016-03-09 06:07:38 +03:00
-- | Does this Range in this Source end with a newline?
openRange :: Source Char -> Range -> Bool
2016-03-09 06:13:31 +03:00
openRange source range = ( at source <$> maybeLastIndex range ) /= Just '\ n'
2016-03-09 09:36:40 +03:00
2016-03-09 09:38:59 +03:00
-- | A row in a split diff, composed of a before line and an after line.
type Row a = Both ( Line a )
2016-03-18 18:59:37 +03:00
2016-03-22 15:20:56 +03:00
type AlignedDiff leaf = [ Join These ( SplitDiff leaf Info ) ]
2016-03-18 19:02:07 +03:00
2016-03-18 21:15:49 +03:00
alignPatch :: Both ( Source Char ) -> Patch ( Term leaf Info ) -> AlignedDiff leaf
2016-04-05 00:26:40 +03:00
alignPatch sources ( Delete term ) = hylo ( alignSyntax ( Join . This . runIdentity ) ( Identity ( fst sources ) ) ) unCofree ( Identity <$> term )
alignPatch sources ( Insert term ) = hylo ( alignSyntax ( Join . That . runIdentity ) ( Identity ( snd sources ) ) ) unCofree ( Identity <$> term )
alignPatch sources ( Replace term1 term2 ) = alignWith ( fmap ( these id id const . runJoin ) . Join )
( hylo ( alignSyntax ( Join . This . runIdentity ) ( Identity ( fst sources ) ) ) unCofree ( Identity <$> term1 ) )
( hylo ( alignSyntax ( Join . That . runIdentity ) ( Identity ( snd sources ) ) ) unCofree ( Identity <$> term2 ) )
2016-03-21 19:53:41 +03:00
2016-03-21 20:26:38 +03:00
alignDiff :: Both ( Source Char ) -> Diff leaf Info -> AlignedDiff leaf
2016-04-05 00:18:29 +03:00
alignDiff sources diff = iter ( uncurry ( alignSyntax ( runBothWith ( ( Join . ) . These ) ) sources ) . ( annotation &&& syntax ) ) ( alignPatch sources <$> diff )
2016-04-04 22:49:22 +03:00
2016-04-05 00:18:29 +03:00
alignSyntax :: Applicative f => ( forall a . f a -> Join These a ) -> f ( Source Char ) -> f Info -> Syntax leaf ( AlignedDiff leaf ) -> AlignedDiff leaf
alignSyntax toJoinThese sources infos syntax = case syntax of
Leaf s -> catMaybes $ wrapInBranch ( const ( Leaf s ) ) . fmap ( flip ( , ) [] ) <$> sequenceL lineRanges
Indexed children -> catMaybes $ wrapInBranch Indexed <$> groupChildrenByLine lineRanges children
Fixed children -> catMaybes $ wrapInBranch Fixed <$> groupChildrenByLine lineRanges children
2016-04-04 22:49:22 +03:00
_ -> []
2016-04-05 00:18:29 +03:00
where lineRanges = toJoinThese $ actualLineRanges <$> ( characterRange <$> infos ) <*> sources
wrapInBranch constructor = applyThese $ toJoinThese ( ( \ info ( range , children ) -> Free ( Annotated ( setCharacterRange info range ) ( constructor children ) ) ) <$> infos )
2016-03-22 15:20:56 +03:00
groupChildrenByLine :: Join These [ Range ] -> [ AlignedDiff leaf ] -> [ Join These ( Range , [ SplitDiff leaf Info ] ) ]
2016-04-05 00:56:53 +03:00
groupChildrenByLine ranges children | not ( and $ null <$> ranges )
, ( nextRanges , nextChildren , lines ) <- group2 ranges children
2016-03-31 23:07:11 +03:00
= lines ++ groupChildrenByLine nextRanges nextChildren
2016-04-05 00:56:53 +03:00
| otherwise = []
2016-03-29 01:27:01 +03:00
2016-03-31 23:07:11 +03:00
group2 :: Join These [ Range ] -> [ AlignedDiff leaf ] -> ( Join These [ Range ] , [ AlignedDiff leaf ] , [ Join These ( Range , [ SplitDiff leaf Info ] ) ] )
2016-04-11 19:04:34 +03:00
group2 ranges children | Just ( headRanges , tailRanges ) <- unconsThese ranges
2016-04-11 19:06:02 +03:00
, ( ( firstLine : rest ) : restOfChildren ) <- children
, ~ ( l , r ) <- split firstLine
= case fromThese False False . runJoin $ intersects headRanges firstLine of
( True , True ) -> let ( moreRanges , moreChildren , remainingLines ) = group2 tailRanges ( rest : restOfChildren ) in
( moreRanges , moreChildren , pairRangesWithLine headRanges ( pure <$> firstLine ) : remainingLines )
( True , False ) -> let ( moreRanges , moreChildren , remainingLines ) = group2 ( modifyJoin ( bimap ( drop 1 ) ( if null r then id else drop 1 ) ) ranges ) ( ( r ++ rest ) : restOfChildren ) in
( moreRanges , moreChildren , pairRangesWithLine headRanges ( mask firstLine $ modifyJoin ( uncurry These . fromThese [] [] ) $ pure <$> head l ) : remainingLines )
( False , True ) -> let ( moreRanges , moreChildren , remainingLines ) = group2 ( modifyJoin ( bimap ( if null l then id else drop 1 ) ( drop 1 ) ) ranges ) ( ( l ++ rest ) : restOfChildren ) in
( moreRanges , moreChildren , pairRangesWithLine headRanges ( mask firstLine $ modifyJoin ( uncurry These . fromThese [] [] ) $ pure <$> head r ) : remainingLines )
_ -> ( tailRanges , children , [ flip ( , ) [] <$> headRanges ] )
2016-04-01 00:10:25 +03:00
| ( [] : rest ) <- children = group2 ranges rest
2016-04-05 00:56:53 +03:00
| otherwise = ( [] <$ ranges , children , fmap ( flip ( , ) [] ) <$> sequenceL ranges )
2016-03-31 21:45:19 +03:00
2016-04-08 21:53:52 +03:00
-- | Partitions and splits a list of children into a tuple consisting of:
2016-04-08 19:06:19 +03:00
-- | - elements which matched; if an element matches only partially this field will contain only the matching side
2016-04-08 21:53:52 +03:00
-- | - the left sides of elements which matched only on the right side
-- | - the right sides of elements which matched only on the left side
2016-04-08 19:06:19 +03:00
-- | - elements which do not intersect.
2016-04-08 21:53:52 +03:00
spanThese :: ( Join These a -> Join These Bool ) -> [ [ Join These a ] ] -> ( [ [ Join These a ] ] , [ [ Join These a ] ] , [ [ Join These a ] ] , [ [ Join These a ] ] )
2016-04-08 19:04:01 +03:00
spanThese pred children | ( child : rest ) <- children
, not ( null child )
2016-04-08 21:53:52 +03:00
, ~ ( moreChildren , moreL , moreR , moreLines ) <- spanThese pred rest
2016-04-08 19:04:01 +03:00
, ~ ( l , r ) <- split ( head child )
= case fromThese False False ( runJoin ( pred ( head child ) ) ) of
2016-04-08 21:53:52 +03:00
( True , True ) -> ( child : moreChildren , moreL , moreR , moreLines )
( True , False ) -> ( l : moreChildren , moreL , r : moreR , moreLines )
( False , True ) -> ( r : moreChildren , l : moreL , moreR , moreLines )
_ -> ( [] , [] , [] , children )
2016-04-08 19:04:01 +03:00
| ( [] : rest ) <- children = spanThese pred rest
2016-04-08 21:53:52 +03:00
| otherwise = ( [] , [] , [] , children )
2016-04-08 19:04:01 +03:00
2016-04-01 17:56:44 +03:00
pairRangesWithLine :: Monoid b => Join These a -> Join These b -> Join These ( a , b )
pairRangesWithLine headRanges childLine = fromMaybe ( flip ( , ) mempty <$> headRanges ) $ ( , ) <$> headRanges ` applyThese ` childLine
2016-04-01 17:55:25 +03:00
2016-04-04 19:43:32 +03:00
mask :: Join These a -> Join These b -> Join These b
mask ( Join ( This _ ) ) ( Join ( This b1 ) ) = Join $ This b1
mask ( Join ( This _ ) ) ( Join ( These b1 _ ) ) = Join $ This b1
mask ( Join ( That _ ) ) ( Join ( That b2 ) ) = Join $ That b2
mask ( Join ( That _ ) ) ( Join ( These _ b2 ) ) = Join $ That b2
mask ( Join ( These _ _ ) ) ( Join ( This b1 ) ) = Join $ This b1
mask ( Join ( These _ _ ) ) ( Join ( That b2 ) ) = Join $ That b2
mask ( Join ( These _ _ ) ) ( Join ( These b1 b2 ) ) = Join $ These b1 b2
mask _ b = b
2016-03-29 02:17:38 +03:00
unconsThese :: Join These [ a ] -> Maybe ( Join These a , Join These [ a ] )
unconsThese ( Join ( This ( a : as ) ) ) = Just ( Join ( This a ) , Join ( This as ) )
unconsThese ( Join ( That ( b : bs ) ) ) = Just ( Join ( That b ) , Join ( That bs ) )
unconsThese ( Join ( These ( a : as ) ( b : bs ) ) ) = Just ( Join ( These a b ) , Join ( These as bs ) )
unconsThese ( Join ( These ( a : as ) _ ) ) = Just ( Join ( This a ) , Join ( This as ) )
unconsThese ( Join ( These _ ( b : bs ) ) ) = Just ( Join ( That b ) , Join ( That bs ) )
unconsThese _ = Nothing
2016-03-29 01:27:01 +03:00
getRange :: SplitDiff leaf Info -> Range
getRange ( Free ( Annotated ( Info range _ ) _ ) ) = range
getRange ( Pure patch ) | Info range _ :< _ <- getSplitTerm patch = range
2016-04-05 01:05:46 +03:00
intersects :: Join These Range -> Join These ( SplitDiff leaf Info ) -> Join These Bool
intersects ranges line = fromMaybe ( False <$ line ) $ intersectsChild <$> ranges ` applyThese ` line
2016-03-29 01:27:01 +03:00
intersectsChild :: Range -> SplitDiff leaf Info -> Bool
intersectsChild range child = end ( getRange child ) <= end range
split :: Join These a -> ( [ Join These a ] , [ Join These a ] )
split these = fromThese [] [] $ bimap ( pure . Join . This ) ( pure . Join . That ) ( runJoin these )
2016-03-29 01:46:22 +03:00
infixl 4 ` applyThese `
applyThese :: Join These ( a -> b ) -> Join These a -> Maybe ( Join These b )
applyThese ( Join ( This f ) ) ( Join ( This a ) ) = Just ( Join ( This ( f a ) ) )
applyThese ( Join ( That g ) ) ( Join ( That b ) ) = Just ( Join ( That ( g b ) ) )
applyThese ( Join ( These f g ) ) ( Join ( These a b ) ) = Just ( Join ( These ( f a ) ( g b ) ) )
applyThese ( Join ( These f _ ) ) ( Join ( This a ) ) = Just ( Join ( This ( f a ) ) )
applyThese ( Join ( These _ g ) ) ( Join ( That b ) ) = Just ( Join ( That ( g b ) ) )
applyThese ( Join ( This f ) ) ( Join ( These a _ ) ) = Just ( Join ( This ( f a ) ) )
applyThese ( Join ( That g ) ) ( Join ( These _ b ) ) = Just ( Join ( That ( g b ) ) )
applyThese _ _ = Nothing
2016-03-29 16:58:31 +03:00
2016-03-24 21:18:53 +03:00
modifyJoin :: ( p a a -> q b b ) -> Join p a -> Join q b
2016-03-24 21:26:25 +03:00
modifyJoin f = Join . f . runJoin