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
) where
2016-02-28 22:01:56 +03:00
2016-03-03 16:47:37 +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 19:01:59 +03:00
import Data.Aligned
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-03-05 04:18:49 +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-18 20:55:41 +03:00
import qualified Data.Set as Set
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-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-14 17:35:31 +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-18 18:59:19 +03:00
where splitAndFoldTerm ( This deleted ) = tsequenceL mempty $ both ( runIdentity <$> Term . cata ( splitAbstractedTerm ( :< ) ( Identity $ fst sources ) ) ( Identity <$> deleted ) ) nil
splitAndFoldTerm ( That inserted ) = tsequenceL mempty $ both nil ( runIdentity <$> Term . cata ( splitAbstractedTerm ( :< ) ( Identity $ snd sources ) ) ( Identity <$> inserted ) )
splitAndFoldTerm ( These deleted inserted ) = tsequenceL mempty $ both ( runIdentity <$> Term . cata ( splitAbstractedTerm ( :< ) ( Identity $ fst sources ) ) ( Identity <$> deleted ) ) ( runIdentity <$> Term . 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-15 17:58:54 +03:00
Leaf a -> tsequenceL ( pure mempty ) $ fmap <$> ( ( \ categories -> fmap ( \ range -> ( makeTerm ( Info range categories ) ( Leaf a ) , range ) ) ) <$> ( Diff . 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
categories = Diff . 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-15 16:22:39 +03:00
childRanges = unionRangesFrom <$> ( rangeAt <$> next ) <*> ( concat . fmap ( 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
-- | A fixpoint over a functor.
newtype Fix f = Fix { unFix :: f ( Fix f ) }
2016-03-18 18:59:48 +03:00
2016-03-18 20:55:41 +03:00
type AlignedDiff leaf = Cofree ( Aligned ( Syntax leaf ) ) Info
2016-03-18 19:02:07 +03:00
2016-03-18 20:46:56 +03:00
alignPatch :: Patch ( Term leaf Info ) -> AlignedDiff leaf
2016-03-18 20:55:41 +03:00
alignPatch ( Insert term ) = hylo ( alignTermBy AlignThis ) unCofree term
alignPatch ( Delete term ) = hylo ( alignTermBy AlignThat ) unCofree term
alignPatch ( Replace term1 term2 ) = let Info r1 c1 :< AlignThis a = hylo ( alignTermBy AlignThis ) unCofree term1
Info r2 c2 :< AlignThat b = hylo ( alignTermBy AlignThat ) unCofree term2 in
Info ( r1 ` unionRange ` r2 ) ( Set . union c1 c2 ) :< AlignThese a b
alignTermBy :: ( forall r . [ Syntax leaf r ] -> Aligned ( Syntax leaf ) r ) -> Info -> Syntax leaf ( AlignedDiff leaf ) -> AlignedDiff leaf
alignTermBy constructor info syntax = info :< constructor [ syntax ]