2016-03-09 13:02:54 +03:00
{- # LANGUAGE RankNTypes # -}
2016-03-11 20:29:17 +03:00
module Alignment
( adjoinRows
, alignRows
, hasChanges
, linesInRangeOfSource
, numberedRows
, splitAbstractedTerm
, splitDiffByLines
, Row
) where
2016-02-28 22:01:56 +03:00
import Category
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-10 23:06:03 +03:00
import Data.Align
import Data.Bifunctor.These
2016-03-03 07:11:24 +03:00
import Data.Copointed
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
import qualified Data.Set as Set
import Diff
import Line
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-11 23:29:12 +03:00
import Source
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-10 23:20:08 +03:00
splitDiffByLines sources = iter ( \ ( Annotated infos syntax ) -> splitAbstractedTerm alignRows ( ( 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-05 03:07:54 +03:00
splitPatchByLines :: Both ( Source Char ) -> Patch ( Term leaf Info ) -> [ Row ( SplitDiff leaf Info , Range ) ]
2016-03-10 23:20:08 +03:00
splitPatchByLines sources patch = alignRows $ fmap ( fmap ( first ( Pure . constructor patch ) ) . runIdentity ) <$> lines
2016-03-09 13:02:54 +03:00
where lines = maybe [] . cata . splitAbstractedTerm sequenceA ( :< ) <$> ( Identity <$> sources ) <*> ( fmap ( fmap Identity ) <$> unPatch 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-11 19:25:15 +03:00
splitAbstractedTerm :: ( Applicative f , Foldable f ) => AlignFunction f -> ( Info -> Syntax leaf outTerm -> outTerm ) -> f ( Source Char ) -> f Info -> Syntax leaf [ f ( Line ( outTerm , Range ) ) ] -> [ f ( Line ( outTerm , Range ) ) ]
2016-03-09 13:02:54 +03:00
splitAbstractedTerm align makeTerm sources infos syntax = case syntax of
2016-03-10 17:02:21 +03:00
Leaf a -> align $ fmap <$> ( ( \ categories -> fmap ( \ range -> ( makeTerm ( Info range categories ) ( Leaf a ) , range ) ) ) <$> ( Diff . categories <$> infos ) ) <*> ( linesInRangeOfSource <$> ( characterRange <$> infos ) <*> sources )
2016-03-09 13:02:54 +03:00
Indexed children -> adjoinChildren sources infos align ( constructor ( Indexed . fmap runIdentity ) ) ( Identity <$> children )
Fixed children -> adjoinChildren sources infos align ( constructor ( Fixed . fmap runIdentity ) ) ( Identity <$> children )
Keyed children -> adjoinChildren sources infos align ( 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-11 19:27:49 +03:00
adjoinChildren :: ( Copointed c , Functor c , Applicative f , Foldable f ) => f ( Source Char ) -> f Info -> AlignFunction f -> ( Info -> [ c a ] -> outTerm ) -> [ c [ f ( Line ( a , Range ) ) ] ] -> [ f ( Line ( outTerm , Range ) ) ]
2016-03-09 12:17:39 +03:00
adjoinChildren sources infos align constructor children =
2016-03-10 17:19:47 +03:00
fmap wrap . foldr ( adjoinRows align ) [] $
2016-03-11 23:29:12 +03:00
align leadingContext <> lines
2016-03-09 12:02:00 +03:00
where ( lines , next ) = foldr ( childLines sources align ) ( [] , end <$> ranges ) children
ranges = characterRange <$> infos
categories = Diff . categories <$> infos
2016-03-10 16:53:40 +03:00
leadingContext = fmap ( fmap ( ( , ) Nothing ) ) <$> ( linesInRangeOfSource <$> ( Range <$> ( start <$> ranges ) <*> next ) <*> sources )
2016-03-09 12:17:39 +03:00
wrap = ( wrapLineContents <$> ( makeBranchTerm constructor <$> categories <*> next ) <*> )
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-11 19:27:49 +03:00
childLines :: ( Copointed c , Functor c , Applicative f , Foldable f ) => f ( Source Char ) -> AlignFunction f -> c [ f ( Line ( a , Range ) ) ] -> ( [ f ( Line ( Maybe ( c a ) , Range ) ) ] , f Int ) -> ( [ 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-11 19:46:36 +03:00
childLines sources align child ( followingLines , next ) | or $ ( > ) . end <$> childRanges <*> next = ( followingLines , next )
| otherwise =
2016-03-11 04:06:16 +03:00
( ( placeChildAndRangeInContainer <$> copoint child )
2016-03-11 23:29:12 +03:00
<> align ( pairWithNothing <$> trailingContextLines )
<> followingLines , start <$> childRanges )
2016-03-11 04:06:16 +03:00
where pairWithNothing = fmap ( fmap ( ( , ) Nothing ) )
placeChildAndRangeInContainer = fmap ( fmap ( first ( Just . ( <$ child ) ) ) )
trailingContextLines = linesInRangeOfSource <$> rangeOfContextToNext <*> sources
2016-03-11 19:46:36 +03:00
rangeOfContextToNext = ( Range <$> ( end <$> childRanges ) <*> next )
childRanges = unionLineRangesFrom <$> ( rangeAt <$> next ) <*> sequenceA ( copoint child )
2016-02-28 22:01:56 +03:00
2016-03-10 17:08:49 +03:00
-- | Produce open/closed lines for the portion of the source spanned by a range.
2016-03-10 16:53:24 +03:00
linesInRangeOfSource :: Range -> Source Char -> [ Line Range ]
2016-03-10 17:19:07 +03:00
linesInRangeOfSource range source = pureBy ( openRange source ) <$> actualLineRanges range source
2016-03-10 16:53:24 +03:00
2016-03-04 16:54:59 +03:00
-- | Wrap a list of child terms in a branch.
2016-03-05 01:17:17 +03:00
makeBranchTerm :: ( Info -> [ inTerm ] -> outTerm ) -> Set . Set Category -> Int -> [ ( Maybe inTerm , Range ) ] -> ( outTerm , Range )
2016-03-07 23:52:12 +03:00
makeBranchTerm constructor categories next children = ( constructor ( Info range categories ) . catMaybes $ Prelude . fst <$> children , range )
where range = unionRangesFrom ( rangeAt next ) $ Prelude . snd <$> children
2016-03-04 16:39:40 +03:00
2016-03-04 18:00:35 +03:00
-- | Compute the union of the ranges in a list of ranged lines.
2016-03-04 21:07:34 +03:00
unionLineRangesFrom :: Range -> [ Line ( a , Range ) ] -> Range
unionLineRangesFrom start lines = unionRangesFrom start ( lines >>= ( fmap Prelude . snd . unLine ) )
2016-03-04 17:41:57 +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-11 19:29:36 +03:00
-- | A function to align a context of lists into a list of contexts, possibly padding out the shorter list with default values.
2016-03-11 19:27:01 +03:00
type AlignFunction f = forall b list . ( Align list , Applicative list ) => f ( list ( Line b ) ) -> list ( f ( Line b ) )
2016-03-11 19:25:15 +03:00
2016-03-09 09:36:40 +03:00
-- | Merge open lines and prepend closed lines (as determined by a pair of functions) onto a list of rows.
2016-03-11 22:27:56 +03:00
adjoinRows :: Applicative f => AlignFunction f -> f ( Line a ) -> [ f ( Line a ) ] -> [ f ( Line a ) ]
2016-03-10 17:19:47 +03:00
adjoinRows _ row [] = [ row ]
2016-03-11 23:29:12 +03:00
adjoinRows align row ( nextRow : rows ) = align ( coalesceLines <$> row <*> nextRow ) <> rows
2016-03-10 23:06:03 +03:00
2016-03-10 23:07:49 +03:00
-- | Align Both containers of lines into a container of Both lines, filling any gaps with empty rows which are either open or closed to match the opposite side.
2016-03-10 23:06:03 +03:00
alignRows :: Align f => Both ( f ( Line a ) ) -> f ( Both ( Line a ) )
alignRows = runBothWith ( alignWith combine )
2016-03-11 04:14:02 +03:00
where combine = these ( Both . ( flip ( , ) ( Line [] ) ) ) ( Both . ( ( , ) ( Line [] ) ) ) both