1
1
mirror of https://github.com/github/semantic.git synced 2024-12-28 17:32:05 +03:00
semantic/src/Alignment.hs

126 lines
7.2 KiB
Haskell
Raw Normal View History

{-# LANGUAGE RankNTypes #-}
2016-03-11 20:29:17 +03:00
module Alignment
( adjoinRows
, alignRows
, hasChanges
, linesInRangeOfSource
, numberedRows
, splitAbstractedTerm
, splitDiffByLines
, Row
) where
import Category
2016-03-03 16:47:37 +03:00
import Control.Arrow
import Control.Comonad.Cofree
2016-03-05 04:18:49 +03:00
import Control.Monad
import Control.Monad.Free
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
import Data.Functor.Identity
import Data.Maybe
import Data.Monoid
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
import Range
import Source
import SplitDiff
import Syntax
import Term
-- | 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 _ [] = []
-- | Determine whether a line contains any patches.
hasChanges :: Line (SplitDiff leaf Info) -> Bool
hasChanges = or . fmap (or . (True <$))
-- | 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.
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:59 +03:00
-- | Split a patch, which may span multiple lines, into rows of split diffs.
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
where lines = maybe [] . cata . splitAbstractedTerm sequenceA (:<) <$> (Identity <$> sources) <*> (fmap (fmap Identity) <$> unPatch patch)
constructor (Replace _ _) = SplitReplace
constructor (Insert _) = SplitInsert
constructor (Delete _) = SplitDelete
-- | Split a term comprised of an Info & Syntax up into one `outTerm` (abstracted by an alignment function & constructor) per line in `Source`.
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))]
splitAbstractedTerm align makeTerm sources infos syntax = case syntax of
Leaf a -> align $ fmap <$> ((\ categories -> fmap (\ range -> (makeTerm (Info range categories) (Leaf a), range))) <$> (Diff.categories <$> infos)) <*> (linesInRangeOfSource <$> (characterRange <$> infos) <*> sources)
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)
where constructor with info = makeTerm info . with
2016-03-11 19:54:39 +03:00
-- | Adjoin a branch terms lines, wrapping children & context in branch nodes using a constructor.
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 =
fmap wrap . foldr (adjoinRows align) [] $
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
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 terms children.
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)
-- We depend on source ranges increasing monotonically. If a child invalidates that, e.g. if its a move in a Keyed node, we dont 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)
<> 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-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.
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 18:00:35 +03:00
-- | Compute the union of the ranges in a list of ranged lines.
unionLineRangesFrom :: Range -> [Line (a, Range)] -> Range
unionLineRangesFrom start lines = unionRangesFrom start (lines >>= (fmap Prelude.snd . unLine))
-- | Does this Range in this Source end with a newline?
openRange :: Source Char -> Range -> Bool
openRange source range = (at source <$> maybeLastIndex range) /= Just '\n'
2016-03-09 09:36:40 +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.
type AlignFunction f = forall b list. (Align list, Applicative list) => f (list (Line b)) -> list (f (Line b))
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)]
adjoinRows _ row [] = [ row ]
adjoinRows align row (nextRow : rows) = align (coalesceLines <$> row <*> nextRow) <> rows
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.
alignRows :: Align f => Both (f (Line a)) -> f (Both (Line a))
alignRows = runBothWith (alignWith combine)
where combine = these (Both . (flip (,) (Line []))) (Both . ((,) (Line []))) both