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

116 lines
7.1 KiB
Haskell
Raw Normal View History

2016-02-28 21:55:19 +03:00
module Alignment 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
2016-03-03 07:11:24 +03:00
import Data.Copointed
import Data.Foldable (foldl')
2016-03-05 04:18:49 +03:00
import Data.Functor.Both as Both
import Data.Functor.Identity
2016-03-02 22:57:42 +03:00
import qualified Data.List as List
import Data.Maybe
2016-03-05 04:18:49 +03:00
import Data.Option
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 Row
import Source hiding ((++))
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)]
numberedRows = foldl' numberRows []
where numberRows rows row = ((,) <$> ((+) <$> count rows <*> (valueOf <$> unRow row)) <*> unRow row) : rows
count = maybe (pure 0) (fmap Prelude.fst) . maybeFirst
valueOf EmptyLine = 0
valueOf _ = 1
-- | 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-07 21:14:34 +03:00
splitDiffByLines sources = iter (\ (Annotated info syntax) -> splitAnnotatedByLines sources info 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)]
splitPatchByLines sources patch = zipWithDefaults makeRow (pure mempty) $ fmap (fmap (first (Pure . constructor patch))) <$> lines
2016-03-07 21:14:34 +03:00
where lines = maybe [] . cata . splitAbstractedTerm (:<) <$> sources <*> unPatch patch
constructor (Replace _ _) = SplitReplace
constructor (Insert _) = SplitInsert
constructor (Delete _) = SplitDelete
2016-03-05 05:12:49 +03:00
-- | Split a term comprised of an Info & Syntax up into one `outTerm` (abstracted by a constructor) per line in `Source`.
splitAbstractedTerm :: (Info -> Syntax leaf outTerm -> outTerm) -> Source Char -> Info -> Syntax leaf [Line (outTerm, Range)] -> [Line (outTerm, Range)]
splitAbstractedTerm makeTerm source (Info range categories) syntax = case syntax of
Leaf a -> pure . ((`makeTerm` Leaf a) . (`Info` categories) &&& id) <$> actualLineRanges range source
Indexed children -> adjoinChildLines (Indexed . fmap (Prelude.fst . copoint)) (Identity <$> children)
Fixed children -> adjoinChildLines (Fixed . fmap (Prelude.fst . copoint)) (Identity <$> children)
Keyed children -> adjoinChildLines (Keyed . fmap Prelude.fst . Map.fromList) (Map.toList children)
2016-03-07 23:52:12 +03:00
where adjoinChildLines constructor children = let (lines, next) = foldr childLines ([], end range) children in
2016-03-08 00:09:45 +03:00
fmap (wrapLineContents (makeBranchTerm (\ info -> makeTerm info . constructor) categories next)) . foldr (adjoinLinesByR (openRangePair source)) []
2016-03-07 23:54:46 +03:00
$ (pure . (,) Nothing <$> actualLineRanges (Range (start range) next) source) ++ lines
2016-03-07 23:52:12 +03:00
childLines child (lines, next) = let childRange = unionLineRangesFrom (rangeAt next) (copoint child) in
((fmap (flip (,) childRange . Just . (<$ child)) <$> copoint child)
++ (pure . (,) Nothing <$> actualLineRanges (Range (end childRange) next) source)
++ lines, start childRange)
2016-03-05 05:09:00 +03:00
-- | Split an annotated diff into rows of split diffs.
splitAnnotatedByLines :: Both (Source Char) -> Both Info -> Syntax leaf [Row (SplitDiff leaf Info, Range)] -> [Row (SplitDiff leaf Info, Range)]
splitAnnotatedByLines sources infos syntax = case syntax of
2016-03-04 20:26:40 +03:00
Leaf a -> zipWithDefaults makeRow (pure mempty) $ fmap <$> ((\ categories range -> pure (Free (Annotated (Info range categories) (Leaf a)), range)) <$> categories) <*> (actualLineRanges <$> ranges <*> sources)
2016-03-03 07:23:34 +03:00
Indexed children -> adjoinChildRows (Indexed . fmap copoint) (Identity <$> children)
Fixed children -> adjoinChildRows (Fixed . fmap copoint) (Identity <$> children)
2016-03-05 04:18:57 +03:00
Keyed children -> adjoinChildRows (Keyed . Map.fromList) (List.sortOn (rowRanges . Prelude.snd) $ Map.toList children)
where ranges = characterRange <$> infos
categories = Diff.categories <$> infos
adjoinChildRows :: (Copointed f, Functor f) => ([f (SplitDiff leaf Info)] -> Syntax leaf (SplitDiff leaf Info)) -> [f [Row (SplitDiff leaf Info, Range)]] -> [Row (SplitDiff leaf Info, Range)]
2016-03-08 00:05:40 +03:00
adjoinChildRows constructor children = let (rows, next) = foldr childRows ([], end <$> ranges) children in
2016-03-08 00:26:49 +03:00
fmap (Row . (wrapLineContents <$> (makeBranchTerm (\ info -> Free . Annotated info . constructor) <$> categories <*> next) <*>) . unRow) . foldr (adjoinRowsByR (openRangePair <$> sources)) []
2016-03-08 00:05:40 +03:00
$ zipWithDefaults makeRow (pure mempty) (fmap (pure . (,) Nothing) <$> (actualLineRanges <$> (Range <$> (start <$> ranges) <*> next) <*> sources)) ++ rows
2016-03-08 00:05:40 +03:00
childRows :: (Copointed f, Functor f) => f [Row (SplitDiff leaf Info, Range)] -> ([Row (Maybe (f (SplitDiff leaf Info)), Range)], Both Int) -> ([Row (Maybe (f (SplitDiff leaf Info)), Range)], Both Int)
childRows child (rows, next) = let childRanges = unionLineRangesFrom <$> (rangeAt <$> next) <*> sequenceA (unRow <$> copoint child) in
-- 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-08 00:05:40 +03:00
if or $ (>) . end <$> childRanges <*> next
then (rows, next)
else ((fmap (first (Just . (<$ child))) <$> copoint child)
++ zipWithDefaults makeRow (pure mempty) (fmap (pure . (,) Nothing) <$> (actualLineRanges <$> (Range <$> (end <$> childRanges) <*> next) <*> sources))
++ rows, start <$> childRanges)
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))
2016-03-05 04:18:49 +03:00
-- | Returns the ranges of a list of Rows.
rowRanges :: [Row (a, Range)] -> Both (Maybe Range)
2016-03-07 21:14:34 +03:00
rowRanges rows = maybeConcat . join <$> Both.unzip (fmap (fmap Prelude.snd . unLine) . unRow <$> rows)
2016-03-02 22:15:20 +03:00
2016-03-03 15:59:03 +03:00
-- | MaybeOpen test for (Range, a) pairs.
openRangePair :: Source Char -> MaybeOpen (a, Range)
openRangePair source pair = pair <$ openRange source (Prelude.snd pair)
-- | Given a source and a range, returns nothing if it ends with a `\n`;
-- | otherwise returns the range.
openRange :: Source Char -> MaybeOpen Range
openRange source range = case (source `at`) <$> maybeLastIndex range of
Just '\n' -> Nothing
_ -> Just range