2016-06-23 23:19:51 +03:00
|
|
|
|
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
|
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
|
|
|
|
, numberedRows
|
2016-03-22 15:20:56 +03:00
|
|
|
|
, alignDiff
|
2016-05-19 20:40:10 +03:00
|
|
|
|
, alignBranch
|
2016-04-25 21:42:38 +03:00
|
|
|
|
, applyThese
|
|
|
|
|
, modifyJoin
|
2016-03-11 20:29:17 +03:00
|
|
|
|
) where
|
2016-02-28 22:01:56 +03:00
|
|
|
|
|
2016-05-27 16:35:26 +03:00
|
|
|
|
import Control.Arrow ((***))
|
2016-03-10 23:06:03 +03:00
|
|
|
|
import Data.Align
|
2016-04-14 23:34:49 +03:00
|
|
|
|
import Data.Biapplicative
|
2016-03-18 21:30:22 +03:00
|
|
|
|
import Data.Bifunctor.Join
|
2016-04-25 18:25:32 +03:00
|
|
|
|
import Data.Function
|
2016-04-04 23:25:16 +03:00
|
|
|
|
import Data.Functor.Both as Both
|
2016-05-27 21:02:37 +03:00
|
|
|
|
import Data.Functor.Foldable (hylo)
|
2016-05-26 17:11:22 +03:00
|
|
|
|
import Data.List (partition)
|
2016-05-27 21:02:37 +03:00
|
|
|
|
import Data.Maybe (fromJust)
|
2016-04-22 19:18:40 +03:00
|
|
|
|
import qualified Data.OrderedMap as Map
|
2016-06-17 21:07:35 +03:00
|
|
|
|
import Data.Record
|
2016-04-15 05:00:00 +03:00
|
|
|
|
import Data.These
|
2016-02-28 22:01:56 +03:00
|
|
|
|
import Diff
|
2016-03-31 00:26:52 +03:00
|
|
|
|
import Info
|
2016-02-28 22:01:56 +03:00
|
|
|
|
import Patch
|
2016-05-26 22:25:45 +03:00
|
|
|
|
import Prologue hiding (fst, snd)
|
2016-05-27 20:58:50 +03:00
|
|
|
|
import qualified Prologue
|
2016-02-28 22:01:56 +03:00
|
|
|
|
import Range
|
2016-05-17 19:01:31 +03:00
|
|
|
|
import Source hiding (break, 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.
|
2016-04-15 04:16:02 +03:00
|
|
|
|
numberedRows :: [Join These a] -> [Join These (Int, a)]
|
2016-04-27 00:36:00 +03:00
|
|
|
|
numberedRows = countUp (both 1 1)
|
2016-05-27 23:17:44 +03:00
|
|
|
|
where countUp _ [] = []
|
|
|
|
|
countUp from (row : rows) = numberedLine from row : countUp (nextLineNumbers from row) rows
|
|
|
|
|
numberedLine from row = fromJust ((,) <$> modifyJoin (uncurry These) from `applyThese` row)
|
|
|
|
|
nextLineNumbers from row = modifyJoin (fromThese identity identity) (succ <$ row) <*> from
|
2016-03-02 00:29:42 +03:00
|
|
|
|
|
2016-03-02 16:05:18 +03:00
|
|
|
|
-- | Determine whether a line contains any patches.
|
2016-06-17 21:05:38 +03:00
|
|
|
|
hasChanges :: SplitDiff leaf annotation -> Bool
|
2016-04-15 04:16:02 +03:00
|
|
|
|
hasChanges = or . (True <$)
|
2016-03-09 09:36:40 +03:00
|
|
|
|
|
2016-05-27 22:48:32 +03:00
|
|
|
|
-- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side.
|
2016-06-17 21:07:35 +03:00
|
|
|
|
alignDiff :: (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Diff leaf (Record fields) -> [Join These (SplitDiff leaf (Record fields))]
|
2016-05-27 16:35:26 +03:00
|
|
|
|
alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) (free . Free) getRange sources) (alignPatch sources <$> diff)
|
2016-04-15 00:03:53 +03:00
|
|
|
|
|
2016-05-27 22:48:32 +03:00
|
|
|
|
-- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff.
|
2016-06-17 21:07:35 +03:00
|
|
|
|
alignPatch :: forall fields leaf. (Show leaf, Show (Record fields), HasField fields Range) => Both (Source Char) -> Patch (Term leaf (Record fields)) -> [Join These (SplitDiff leaf (Record fields))]
|
2016-04-15 03:27:24 +03:00
|
|
|
|
alignPatch sources patch = case patch of
|
2016-05-28 00:09:22 +03:00
|
|
|
|
Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term
|
|
|
|
|
Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term
|
2016-05-27 16:35:26 +03:00
|
|
|
|
Replace term1 term2 -> fmap (pure . SplitReplace) <$> alignWith (fmap (these identity identity const . runJoin) . Join)
|
2016-05-28 00:09:22 +03:00
|
|
|
|
(alignSyntax' this (fst sources) term1)
|
|
|
|
|
(alignSyntax' that (snd sources) term2)
|
2016-05-27 16:35:26 +03:00
|
|
|
|
where getRange = characterRange . extract
|
2016-06-17 21:07:35 +03:00
|
|
|
|
alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> Term leaf (Record fields) -> [Join These (Term leaf (Record fields))]
|
2016-05-28 00:09:22 +03:00
|
|
|
|
alignSyntax' side source term = hylo (alignSyntax side cofree getRange (Identity source)) runCofree (Identity <$> term)
|
2016-04-15 03:27:24 +03:00
|
|
|
|
this = Join . This . runIdentity
|
|
|
|
|
that = Join . That . runIdentity
|
|
|
|
|
|
2016-05-13 23:39:48 +03:00
|
|
|
|
-- | The Applicative instance f is either Identity or Both. Identity is for Terms in Patches, Both is for Diffs in unchanged portions of the diff.
|
2016-06-17 21:07:35 +03:00
|
|
|
|
alignSyntax :: (Applicative f, Show term, HasField fields Range) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) (Record fields) term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f (Record fields)) [Join These term] -> [Join These term]
|
2016-05-27 16:35:26 +03:00
|
|
|
|
alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax of
|
2016-04-05 00:18:29 +03:00
|
|
|
|
Leaf s -> catMaybes $ wrapInBranch (const (Leaf s)) . fmap (flip (,) []) <$> sequenceL lineRanges
|
2016-07-08 18:47:39 +03:00
|
|
|
|
Comment a -> catMaybes $ wrapInBranch (const (Comment a)) . fmap (flip (,) []) <$> sequenceL lineRanges
|
2016-07-08 22:19:43 +03:00
|
|
|
|
Indexed children ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join children) bothRanges
|
2016-06-10 22:10:37 +03:00
|
|
|
|
Syntax.Function id params body -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (fromMaybe [] id <> fromMaybe [] params <> body) bothRanges
|
2016-06-09 01:06:35 +03:00
|
|
|
|
-- Align FunctionCalls like Indexed nodes by appending identifier to its children.
|
2016-07-08 22:19:43 +03:00
|
|
|
|
Syntax.FunctionCall identifier children ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join (identifier : children)) bothRanges
|
2016-06-14 21:27:48 +03:00
|
|
|
|
Syntax.Assignment assignmentId value ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (assignmentId <> value) bothRanges
|
|
|
|
|
Syntax.MemberAccess memberId property ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (memberId <> property) bothRanges
|
|
|
|
|
Syntax.MethodCall targetId methodId args ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (targetId <> methodId <> args) bothRanges
|
|
|
|
|
Syntax.Args children ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join children) bothRanges
|
2016-06-17 01:14:22 +03:00
|
|
|
|
Syntax.VarDecl decl ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange decl bothRanges
|
|
|
|
|
Syntax.VarAssignment id value ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (id <> value) bothRanges
|
|
|
|
|
Switch expr cases ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (expr <> join cases) bothRanges
|
|
|
|
|
Case expr body ->
|
|
|
|
|
catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (expr <> body) bothRanges
|
2016-07-08 22:19:43 +03:00
|
|
|
|
Fixed children ->
|
|
|
|
|
catMaybes $ wrapInBranch Fixed <$> alignBranch getRange (join children) bothRanges
|
2016-05-27 23:24:18 +03:00
|
|
|
|
Keyed children -> catMaybes $ wrapInBranch (Keyed . Map.fromList) <$> alignBranch (getRange . Prologue.snd) (Map.toList children >>= pairWithKey) bothRanges
|
2016-07-08 20:16:03 +03:00
|
|
|
|
Pair a b -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (a <> b) bothRanges
|
2016-07-08 18:17:46 +03:00
|
|
|
|
Object children -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join children) bothRanges
|
2016-07-08 18:47:39 +03:00
|
|
|
|
Commented cs expr -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join cs <> join (maybeToList expr)) bothRanges
|
2016-06-29 17:51:10 +03:00
|
|
|
|
Ternary expr cases -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (expr <> join cases) bothRanges
|
|
|
|
|
Operator cases -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join cases) bothRanges
|
|
|
|
|
MathAssignment key value -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (key <> value) bothRanges
|
|
|
|
|
SubscriptAccess key value -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (key <> value) bothRanges
|
2016-05-27 23:24:18 +03:00
|
|
|
|
where bothRanges = modifyJoin (fromThese [] []) lineRanges
|
|
|
|
|
lineRanges = toJoinThese $ actualLineRanges <$> (characterRange <$> infos) <*> sources
|
2016-06-17 20:28:35 +03:00
|
|
|
|
wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (setCharacterRange info range :< constructor children)) <$> infos)
|
2016-05-27 20:58:50 +03:00
|
|
|
|
pairWithKey (key, values) = fmap ((,) key) <$> values
|
2016-03-22 15:20:56 +03:00
|
|
|
|
|
2016-05-13 23:40:46 +03:00
|
|
|
|
-- | Given a function to get the range, a list of already-aligned children, and the lists of ranges spanned by a branch, return the aligned lines.
|
2016-05-27 20:58:50 +03:00
|
|
|
|
alignBranch :: Show term => (term -> Range) -> [Join These term] -> Both [Range] -> [Join These (Range, [term])]
|
2016-05-13 23:40:46 +03:00
|
|
|
|
-- There are no more ranges, so we’re done.
|
2016-05-27 22:17:41 +03:00
|
|
|
|
alignBranch _ _ (Join ([], [])) = []
|
2016-05-13 23:40:46 +03:00
|
|
|
|
-- There are no more children, so we can just zip the remaining ranges together.
|
|
|
|
|
alignBranch _ [] ranges = runBothWith (alignWith Join) (fmap (flip (,) []) <$> ranges)
|
|
|
|
|
-- There are both children and ranges, so we need to proceed line by line
|
2016-05-13 23:46:53 +03:00
|
|
|
|
alignBranch getRange children ranges = case intersectingChildren of
|
2016-05-18 21:55:31 +03:00
|
|
|
|
-- No child intersects the current ranges on either side, so advance.
|
2016-05-13 23:46:53 +03:00
|
|
|
|
[] -> (flip (,) [] <$> headRanges) : alignBranch getRange children (drop 1 <$> ranges)
|
2016-05-18 21:55:31 +03:00
|
|
|
|
-- At least one child intersects on at least one side.
|
2016-05-27 23:51:06 +03:00
|
|
|
|
_ -> case intersectionsWithHeadRanges <$> listToMaybe symmetricalChildren of
|
2016-05-18 22:51:49 +03:00
|
|
|
|
-- At least one child intersects on both sides, so align symmetrically.
|
2016-05-27 23:51:06 +03:00
|
|
|
|
Just (True, True) -> let (line, remaining) = lineAndRemaining intersectingChildren (Just headRanges) in
|
2016-06-17 18:29:27 +03:00
|
|
|
|
line $ alignBranch getRange (remaining <> nonIntersectingChildren) (drop 1 <$> ranges)
|
2016-05-18 22:51:49 +03:00
|
|
|
|
-- A symmetrical child intersects on the right, so align asymmetrically on the left.
|
2016-05-27 23:51:06 +03:00
|
|
|
|
Just (False, True) -> alignAsymmetrically leftRange first
|
2016-05-18 22:51:49 +03:00
|
|
|
|
-- A symmetrical child intersects on the left, so align asymmetrically on the right.
|
2016-05-27 23:51:06 +03:00
|
|
|
|
Just (True, False) -> alignAsymmetrically rightRange second
|
2016-05-18 22:51:49 +03:00
|
|
|
|
-- No symmetrical child intersects, so align asymmetrically, picking the left side first to match the deletion/insertion order convention in diffs.
|
2016-05-27 20:58:50 +03:00
|
|
|
|
_ -> if any (isThis . runJoin) asymmetricalChildren
|
2016-05-27 23:51:06 +03:00
|
|
|
|
then alignAsymmetrically leftRange first
|
|
|
|
|
else alignAsymmetrically rightRange second
|
2016-05-27 20:58:50 +03:00
|
|
|
|
where (intersectingChildren, nonIntersectingChildren) = partition (or . intersects getRange headRanges) children
|
2016-05-27 23:51:06 +03:00
|
|
|
|
(symmetricalChildren, asymmetricalChildren) = partition (isThese . runJoin) intersectingChildren
|
|
|
|
|
intersectionsWithHeadRanges = fromThese True True . runJoin . intersects getRange headRanges
|
2016-05-27 18:00:43 +03:00
|
|
|
|
Just headRanges = sequenceL (listToMaybe <$> Join (runBothWith These ranges))
|
2016-05-18 22:51:49 +03:00
|
|
|
|
(leftRange, rightRange) = splitThese headRanges
|
2016-05-27 23:51:06 +03:00
|
|
|
|
alignAsymmetrically range advanceBy = let (line, remaining) = lineAndRemaining asymmetricalChildren range in
|
2016-06-17 18:29:27 +03:00
|
|
|
|
line $ alignBranch getRange (remaining <> symmetricalChildren <> nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges)
|
2016-05-27 23:51:06 +03:00
|
|
|
|
lineAndRemaining _ Nothing = (identity, [])
|
|
|
|
|
lineAndRemaining children (Just ranges) = let (intersections, remaining) = alignChildren getRange children ranges in
|
2016-05-31 02:21:06 +03:00
|
|
|
|
((:) $ (,) <$> ranges `applyToBoth` (sortBy (compare `on` getRange) <$> intersections), remaining)
|
2016-05-26 21:49:57 +03:00
|
|
|
|
|
2016-05-17 19:22:21 +03:00
|
|
|
|
-- | Given a list of aligned children, produce lists of their intersecting first lines, and a list of the remaining lines/nonintersecting first lines.
|
2016-06-02 04:37:38 +03:00
|
|
|
|
alignChildren :: (term -> Range) -> [Join These term] -> Join These Range -> (Both [term], [Join These term])
|
2016-05-18 23:19:40 +03:00
|
|
|
|
alignChildren _ [] _ = (both [] [], [])
|
2016-05-19 20:26:36 +03:00
|
|
|
|
alignChildren getRange (first:rest) headRanges
|
2016-05-27 20:58:50 +03:00
|
|
|
|
| ~(l, r) <- splitThese first
|
2016-05-27 23:56:35 +03:00
|
|
|
|
= case intersectionsWithHeadRanges first of
|
2016-05-27 17:38:20 +03:00
|
|
|
|
-- It intersects on both sides, so we can just take the first line whole.
|
2016-06-24 00:47:01 +03:00
|
|
|
|
(True, True) -> ((<>) <$> toTerms first <*> firstRemaining, restRemaining)
|
2016-05-27 17:38:20 +03:00
|
|
|
|
-- It only intersects on the left, so split it up.
|
2016-06-24 00:47:01 +03:00
|
|
|
|
(True, False) -> ((<>) <$> toTerms (fromJust l) <*> firstRemaining, maybe identity (:) r restRemaining)
|
2016-05-27 17:38:20 +03:00
|
|
|
|
-- It only intersects on the right, so split it up.
|
2016-06-24 00:47:01 +03:00
|
|
|
|
(False, True) -> ((<>) <$> toTerms (fromJust r) <*> firstRemaining, maybe identity (:) l restRemaining)
|
2016-05-27 17:38:20 +03:00
|
|
|
|
-- It doesn’t intersect at all, so skip it and move along.
|
|
|
|
|
(False, False) -> (firstRemaining, first:restRemaining)
|
2016-05-19 23:12:51 +03:00
|
|
|
|
| otherwise = alignChildren getRange rest headRanges
|
2016-05-18 23:19:40 +03:00
|
|
|
|
where (firstRemaining, restRemaining) = alignChildren getRange rest headRanges
|
2016-05-27 20:58:50 +03:00
|
|
|
|
toTerms line = modifyJoin (fromThese [] []) (pure <$> line)
|
2016-05-27 23:56:35 +03:00
|
|
|
|
intersectionsWithHeadRanges = fromThese False False . runJoin . intersects getRange headRanges
|
2016-05-17 19:22:21 +03:00
|
|
|
|
|
2016-04-15 16:35:41 +03:00
|
|
|
|
-- | Test ranges and terms for intersection on either or both sides.
|
2016-04-15 03:27:24 +03:00
|
|
|
|
intersects :: (term -> Range) -> Join These Range -> Join These term -> Join These Bool
|
2016-05-27 17:25:05 +03:00
|
|
|
|
intersects getRange ranges line = intersectsRange <$> ranges `applyToBoth` modifyJoin (fromThese (Range (-1) (-1)) (Range (-1) (-1))) (getRange <$> line)
|
2016-03-29 01:27:01 +03:00
|
|
|
|
|
2016-04-15 16:31:28 +03:00
|
|
|
|
-- | Split a These value up into independent These values representing the left and right sides, if any.
|
2016-04-15 16:33:30 +03:00
|
|
|
|
splitThese :: Join These a -> (Maybe (Join These a), Maybe (Join These a))
|
|
|
|
|
splitThese these = fromThese Nothing Nothing $ bimap (Just . Join . This) (Just . Join . That) (runJoin these)
|
2016-03-29 01:46:22 +03:00
|
|
|
|
|
|
|
|
|
infixl 4 `applyThese`
|
|
|
|
|
|
2016-04-15 16:29:23 +03:00
|
|
|
|
-- | Like `<*>`, but it returns its result in `Maybe` since the result is the intersection of the shapes of the inputs.
|
2016-03-29 01:46:22 +03:00
|
|
|
|
applyThese :: Join These (a -> b) -> Join These a -> Maybe (Join These b)
|
2016-04-15 16:29:23 +03:00
|
|
|
|
applyThese (Join fg) (Join ab) = fmap Join . uncurry maybeThese $ uncurry (***) (bimap (<*>) (<*>) (unpack fg)) (unpack ab)
|
|
|
|
|
where unpack = fromThese Nothing Nothing . bimap Just Just
|
2016-03-29 16:58:31 +03:00
|
|
|
|
|
2016-05-27 17:22:51 +03:00
|
|
|
|
infixl 4 `applyToBoth`
|
|
|
|
|
|
2016-05-27 17:22:24 +03:00
|
|
|
|
-- | Like `<*>`, but it takes a `Both` on the right to ensure that it can always return a value.
|
|
|
|
|
applyToBoth :: Join These (a -> b) -> Both a -> Join These b
|
|
|
|
|
applyToBoth (Join fg) (Join (a, b)) = Join $ these (This . ($ a)) (That . ($ b)) (\ f g -> These (f a) (g b)) fg
|
|
|
|
|
|
2016-04-15 16:30:20 +03:00
|
|
|
|
-- Map over the bifunctor inside a Join, producing another Join.
|
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
|
2016-04-15 04:47:18 +03:00
|
|
|
|
|
|
|
|
|
-- | Given a pair of Maybes, produce a These containing Just their values, or Nothing if they haven’t any.
|
|
|
|
|
maybeThese :: Maybe a -> Maybe b -> Maybe (These a b)
|
|
|
|
|
maybeThese (Just a) (Just b) = Just (These a b)
|
|
|
|
|
maybeThese (Just a) _ = Just (This a)
|
|
|
|
|
maybeThese _ (Just b) = Just (That b)
|
|
|
|
|
maybeThese _ _ = Nothing
|
|
|
|
|
|
2016-04-15 16:23:41 +03:00
|
|
|
|
|
2016-04-15 16:26:16 +03:00
|
|
|
|
-- | Instances
|
|
|
|
|
|
|
|
|
|
instance Bicrosswalk t => Crosswalk (Join t) where
|
|
|
|
|
crosswalk f = fmap Join . bicrosswalk f f . runJoin
|