mirror of
https://github.com/github/semantic.git
synced 2024-12-23 06:41:45 +03:00
Merge remote-tracking branch 'origin/master' into ghc8
This commit is contained in:
commit
fbf368b659
1
UI/.gitignore
vendored
Normal file
1
UI/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.html
|
@ -16,13 +16,7 @@ library
|
||||
exposed-modules: Algorithm
|
||||
, Alignment
|
||||
, Category
|
||||
, Data.Adjoined
|
||||
, Data.Align
|
||||
, Data.Bifunctor.These
|
||||
, Data.Coalescent
|
||||
, Data.Copointed
|
||||
, Data.Functor.Both
|
||||
, Data.Option
|
||||
, Data.OrderedMap
|
||||
, Diff
|
||||
, Diffing
|
||||
@ -30,7 +24,6 @@ library
|
||||
, Info
|
||||
, Interpreter
|
||||
, Language
|
||||
, Line
|
||||
, Operation
|
||||
, Parser
|
||||
, Patch
|
||||
@ -50,6 +43,7 @@ library
|
||||
, Prologue
|
||||
build-depends: aeson
|
||||
, base >= 4.8 && < 5
|
||||
, bifunctors
|
||||
, blaze-html
|
||||
, blaze-markup
|
||||
, bytestring
|
||||
@ -57,8 +51,11 @@ library
|
||||
, directory
|
||||
, filepath
|
||||
, mtl
|
||||
, pointed
|
||||
, semigroups
|
||||
, text >= 1.2.1.3
|
||||
, text-icu
|
||||
, these
|
||||
, tree-sitter-parsers
|
||||
, vector
|
||||
, recursion-schemes
|
||||
@ -76,24 +73,25 @@ test-suite semantic-diff-test
|
||||
other-modules: AlignmentSpec
|
||||
, ArbitraryTerm
|
||||
, CorpusSpec
|
||||
, Data.Adjoined.Spec
|
||||
, Data.Functor.Both.Spec
|
||||
, InterpreterSpec
|
||||
, OrderedMapSpec
|
||||
, PatchOutputSpec
|
||||
, TermSpec
|
||||
, DiffSummarySpec
|
||||
build-depends: base
|
||||
, bifunctors
|
||||
, bytestring
|
||||
, containers
|
||||
, deepseq
|
||||
, filepath
|
||||
, Glob
|
||||
, hspec >= 2.1.10
|
||||
, mtl
|
||||
, QuickCheck >= 2.8.1
|
||||
, quickcheck-text
|
||||
, semantic-diff
|
||||
, text >= 1.2.1.3
|
||||
, these
|
||||
, free
|
||||
, recursion-schemes >= 4.1
|
||||
if os(darwin)
|
||||
|
214
src/Alignment.hs
214
src/Alignment.hs
@ -1,108 +1,164 @@
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
|
||||
module Alignment
|
||||
( hasChanges
|
||||
, linesInRangeOfSource
|
||||
, numberedRows
|
||||
, splitAbstractedTerm
|
||||
, splitDiffByLines
|
||||
, Row
|
||||
, alignDiff
|
||||
, alignBranch
|
||||
, applyThese
|
||||
, modifyJoin
|
||||
) where
|
||||
|
||||
import Control.Arrow
|
||||
import Data.Functor.Foldable
|
||||
import Data.Adjoined
|
||||
import Control.Arrow ((***))
|
||||
import Data.Align
|
||||
import Data.Bifunctor.These
|
||||
import Data.Coalescent
|
||||
import Data.Copointed
|
||||
import Data.Biapplicative
|
||||
import Data.Bifunctor.Join
|
||||
import Data.Function
|
||||
import Data.Functor.Both as Both
|
||||
import Data.Functor.Foldable (hylo)
|
||||
import Data.List (partition)
|
||||
import Data.Maybe (fromJust)
|
||||
import qualified Data.OrderedMap as Map
|
||||
import qualified Data.Text as T
|
||||
import Data.These
|
||||
import Diff
|
||||
import Info
|
||||
import Line
|
||||
import Patch
|
||||
import Prologue as Prologue hiding (first)
|
||||
import Prologue hiding (fst, snd)
|
||||
import qualified Prologue
|
||||
import Range
|
||||
import Source hiding (fromList, uncons)
|
||||
import Source hiding (break, fromList, uncons, (++))
|
||||
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 = countUp (pure 1)
|
||||
where countUp from (row : rows) = ((,) <$> from <*> row) : countUp ((+) <$> from <*> (lineIncrement <$> row)) rows
|
||||
countUp _ [] = []
|
||||
numberedRows :: [Join These a] -> [Join These (Int, a)]
|
||||
numberedRows = countUp (both 1 1)
|
||||
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
|
||||
|
||||
-- | Determine whether a line contains any patches.
|
||||
hasChanges :: Line (SplitDiff leaf Info) -> Bool
|
||||
hasChanges = or . fmap (or . (True <$))
|
||||
hasChanges :: SplitDiff leaf Info -> Bool
|
||||
hasChanges = 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)]
|
||||
splitDiffByLines sources = toList . iter (\ (infos :< syntax) -> splitAbstractedTerm ((free .) . (Free .) . (:<)) sources (infos :< syntax)) . fmap (splitPatchByLines sources)
|
||||
-- | Align a Diff into a list of Join These SplitDiffs representing the (possibly blank) lines on either side.
|
||||
alignDiff :: Show leaf => Both (Source Char) -> Diff leaf Info -> [Join These (SplitDiff leaf Info)]
|
||||
alignDiff sources diff = iter (alignSyntax (runBothWith ((Join .) . These)) (free . Free) getRange sources) (alignPatch sources <$> diff)
|
||||
|
||||
-- | Split a patch, which may span multiple lines, into rows of split diffs.
|
||||
splitPatchByLines :: Both (Source Char) -> Patch (Term leaf Info) -> Adjoined (Both (Line (SplitDiff leaf Info, Range)))
|
||||
splitPatchByLines sources patch = wrapTermInPatch <$> splitAndFoldTerm (unPatch patch)
|
||||
where
|
||||
splitAndFoldTerm :: These (Term leaf Info) (Term leaf Info) -> Adjoined (Both (Line (Term leaf Info, Range)))
|
||||
splitAndFoldTerm (This deleted) = tsequenceL mempty $ both (runIdentity <$> cata (splitAbstractedTerm ((cofree.) . (:<)) (Identity $ Both.fst sources)) (hylo (cofree . annotationMap Identity) runCofree deleted)) nil
|
||||
splitAndFoldTerm (That inserted) = tsequenceL mempty $ both nil (runIdentity <$> cata (splitAbstractedTerm ((cofree .) . (:<)) (Identity $ Both.snd sources)) (hylo (cofree . annotationMap Identity) runCofree inserted))
|
||||
splitAndFoldTerm (These deleted inserted) = tsequenceL mempty $ both (runIdentity <$> cata (splitAbstractedTerm ((cofree .) . (:<)) (Identity $ Both.fst sources)) (hylo (cofree . annotationMap Identity) runCofree deleted)) (runIdentity <$> cata (splitAbstractedTerm ((cofree .) . (:<)) (Identity $ Both.snd sources)) (hylo (cofree . annotationMap Identity) runCofree inserted))
|
||||
wrapTermInPatch = fmap (fmap (first (free . Pure . constructor patch)))
|
||||
constructor (Replace _ _) = SplitReplace
|
||||
constructor (Insert _) = SplitInsert
|
||||
constructor (Delete _) = SplitDelete
|
||||
-- | Align the contents of a patch into a list of lines on the corresponding side(s) of the diff.
|
||||
alignPatch :: forall leaf. Show leaf => Both (Source Char) -> Patch (Term leaf Info) -> [Join These (SplitDiff leaf Info)]
|
||||
alignPatch sources patch = case patch of
|
||||
Delete term -> fmap (pure . SplitDelete) <$> alignSyntax' this (fst sources) term
|
||||
Insert term -> fmap (pure . SplitInsert) <$> alignSyntax' that (snd sources) term
|
||||
Replace term1 term2 -> fmap (pure . SplitReplace) <$> alignWith (fmap (these identity identity const . runJoin) . Join)
|
||||
(alignSyntax' this (fst sources) term1)
|
||||
(alignSyntax' that (snd sources) term2)
|
||||
where getRange = characterRange . extract
|
||||
alignSyntax' :: (forall a. Identity a -> Join These a) -> Source Char -> Term leaf Info -> [Join These (Term leaf Info)]
|
||||
alignSyntax' side source term = hylo (alignSyntax side cofree getRange (Identity source)) runCofree (Identity <$> term)
|
||||
this = Join . This . runIdentity
|
||||
that = Join . That . runIdentity
|
||||
|
||||
annotationMap :: (a -> b) -> TermF leaf a f -> TermF leaf b f
|
||||
annotationMap f (a :< r) = f a :< r
|
||||
-- | 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.
|
||||
alignSyntax :: (Applicative f, Show term) => (forall a. f a -> Join These a) -> (CofreeF (Syntax leaf) Info term -> term) -> (term -> Range) -> f (Source Char) -> CofreeF (Syntax leaf) (f Info) [Join These term] -> [Join These term]
|
||||
alignSyntax toJoinThese toNode getRange sources (infos :< syntax) = case syntax of
|
||||
Leaf s -> catMaybes $ wrapInBranch (const (Leaf s)) . fmap (flip (,) []) <$> sequenceL lineRanges
|
||||
Indexed children -> catMaybes $ wrapInBranch Indexed <$> alignBranch getRange (join children) bothRanges
|
||||
Fixed children -> catMaybes $ wrapInBranch Fixed <$> alignBranch getRange (join children) bothRanges
|
||||
Keyed children -> catMaybes $ wrapInBranch (Keyed . Map.fromList) <$> alignBranch (getRange . Prologue.snd) (Map.toList children >>= pairWithKey) bothRanges
|
||||
where bothRanges = modifyJoin (fromThese [] []) lineRanges
|
||||
lineRanges = toJoinThese $ actualLineRanges <$> (characterRange <$> infos) <*> sources
|
||||
wrapInBranch constructor = applyThese $ toJoinThese ((\ info (range, children) -> toNode (info { characterRange = range } :< constructor children)) <$> infos)
|
||||
pairWithKey (key, values) = fmap ((,) key) <$> values
|
||||
|
||||
-- | 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, 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) -> CofreeF (Syntax leaf) (f Info) (Adjoined (f (Line (outTerm, Range)))) -> Adjoined (f (Line (outTerm, Range)))
|
||||
splitAbstractedTerm makeTerm sources (infos :< syntax) = case syntax of
|
||||
Leaf a -> let lineRanges = linesInRangeOfSource <$> (characterRange <$> infos) <*> sources in
|
||||
tsequenceL (pure mempty)
|
||||
$ fmap <$> ((\ info -> fmap (\ range -> (makeTerm info { characterRange = range } (Leaf a), range))) <$> infos) <*> lineRanges
|
||||
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)
|
||||
where constructor with info = makeTerm info . with
|
||||
-- | 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.
|
||||
alignBranch :: Show term => (term -> Range) -> [Join These term] -> Both [Range] -> [Join These (Range, [term])]
|
||||
-- There are no more ranges, so we’re done.
|
||||
alignBranch _ _ (Join ([], [])) = []
|
||||
-- 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
|
||||
alignBranch getRange children ranges = case intersectingChildren of
|
||||
-- No child intersects the current ranges on either side, so advance.
|
||||
[] -> (flip (,) [] <$> headRanges) : alignBranch getRange children (drop 1 <$> ranges)
|
||||
-- At least one child intersects on at least one side.
|
||||
_ -> case intersectionsWithHeadRanges <$> listToMaybe symmetricalChildren of
|
||||
-- At least one child intersects on both sides, so align symmetrically.
|
||||
Just (True, True) -> let (line, remaining) = lineAndRemaining intersectingChildren (Just headRanges) in
|
||||
line $ alignBranch getRange (remaining ++ nonIntersectingChildren) (drop 1 <$> ranges)
|
||||
-- A symmetrical child intersects on the right, so align asymmetrically on the left.
|
||||
Just (False, True) -> alignAsymmetrically leftRange first
|
||||
-- A symmetrical child intersects on the left, so align asymmetrically on the right.
|
||||
Just (True, False) -> alignAsymmetrically rightRange second
|
||||
-- No symmetrical child intersects, so align asymmetrically, picking the left side first to match the deletion/insertion order convention in diffs.
|
||||
_ -> if any (isThis . runJoin) asymmetricalChildren
|
||||
then alignAsymmetrically leftRange first
|
||||
else alignAsymmetrically rightRange second
|
||||
where (intersectingChildren, nonIntersectingChildren) = partition (or . intersects getRange headRanges) children
|
||||
(symmetricalChildren, asymmetricalChildren) = partition (isThese . runJoin) intersectingChildren
|
||||
intersectionsWithHeadRanges = fromThese True True . runJoin . intersects getRange headRanges
|
||||
Just headRanges = sequenceL (listToMaybe <$> Join (runBothWith These ranges))
|
||||
(leftRange, rightRange) = splitThese headRanges
|
||||
alignAsymmetrically range advanceBy = let (line, remaining) = lineAndRemaining asymmetricalChildren range in
|
||||
line $ alignBranch getRange (remaining ++ symmetricalChildren ++ nonIntersectingChildren) (modifyJoin (advanceBy (drop 1)) ranges)
|
||||
lineAndRemaining _ Nothing = (identity, [])
|
||||
lineAndRemaining children (Just ranges) = let (intersections, remaining) = alignChildren getRange children ranges in
|
||||
((:) $ (,) <$> ranges `applyToBoth` (sortBy (compare `on` getRange) <$> intersections), remaining)
|
||||
|
||||
-- | Adjoin a branch term’s lines, wrapping children & context in branch nodes using a constructor.
|
||||
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)))
|
||||
adjoinChildren sources infos constructor children = wrap <$> leadingContext <> lines
|
||||
where (lines, next) = foldr (childLines sources) (mempty, end <$> ranges) children
|
||||
ranges = characterRange <$> infos
|
||||
categories = Info.categories <$> infos
|
||||
sizes = size <$> infos
|
||||
leadingContext = tsequenceL (pure mempty) $ makeContextLines <$> (linesInRangeOfSource <$> (Range <$> (start <$> ranges) <*> next) <*> sources)
|
||||
wrap = (wrapLineContents <$> (makeBranchTerm constructor <$> categories <*> sizes <*> next) <*>)
|
||||
makeBranchTerm constructor categories size next children = let range = unionRangesFrom (rangeAt next) $ Prologue.snd <$> children in
|
||||
(constructor (Info range categories size) . catMaybes . toList $ Prologue.fst <$> children, range)
|
||||
-- | Given a list of aligned children, produce lists of their intersecting first lines, and a list of the remaining lines/nonintersecting first lines.
|
||||
alignChildren :: (term -> Range) -> [Join These (term)] -> Join These Range -> (Both [term], [Join These term])
|
||||
alignChildren _ [] _ = (both [] [], [])
|
||||
alignChildren getRange (first:rest) headRanges
|
||||
| ~(l, r) <- splitThese first
|
||||
= case intersectionsWithHeadRanges first of
|
||||
-- It intersects on both sides, so we can just take the first line whole.
|
||||
(True, True) -> ((++) <$> toTerms first <*> firstRemaining, restRemaining)
|
||||
-- It only intersects on the left, so split it up.
|
||||
(True, False) -> ((++) <$> toTerms (fromJust l) <*> firstRemaining, maybe identity (:) r restRemaining)
|
||||
-- It only intersects on the right, so split it up.
|
||||
(False, True) -> ((++) <$> toTerms (fromJust r) <*> firstRemaining, maybe identity (:) l restRemaining)
|
||||
-- It doesn’t intersect at all, so skip it and move along.
|
||||
(False, False) -> (firstRemaining, first:restRemaining)
|
||||
| otherwise = alignChildren getRange rest headRanges
|
||||
where (firstRemaining, restRemaining) = alignChildren getRange rest headRanges
|
||||
toTerms line = modifyJoin (fromThese [] []) (pure <$> line)
|
||||
intersectionsWithHeadRanges = fromThese False False . runJoin . intersects getRange headRanges
|
||||
|
||||
-- | Accumulate the lines of and between a branch term’s children.
|
||||
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)
|
||||
-- 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.
|
||||
childLines sources child (nextLines, next) | or ((>) . end <$> childRanges <*> next) = (nextLines, next)
|
||||
| otherwise = ((makeChildLines <$> copoint child)
|
||||
<> tsequenceL (pure mempty) (makeContextLines <$> trailingContextLines)
|
||||
<> nextLines, start <$> childRanges)
|
||||
where makeChildLines = fmap (fmap (first (Just . (<$ child))))
|
||||
trailingContextLines = linesInRangeOfSource <$> (Range <$> (end <$> childRanges) <*> next) <*> sources
|
||||
childRanges = unionRangesFrom <$> (rangeAt <$> next) <*> (concat . fmap (fmap Prologue.snd . unLine) <$> sequenceA (copoint child))
|
||||
-- | Test ranges and terms for intersection on either or both sides.
|
||||
intersects :: (term -> Range) -> Join These Range -> Join These term -> Join These Bool
|
||||
intersects getRange ranges line = intersectsRange <$> ranges `applyToBoth` modifyJoin (fromThese (Range (-1) (-1)) (Range (-1) (-1))) (getRange <$> line)
|
||||
|
||||
makeContextLines :: Adjoined (Line Range) -> Adjoined (Line (Maybe a, Range))
|
||||
makeContextLines = fmap (fmap ((,) Nothing))
|
||||
-- | Split a These value up into independent These values representing the left and right sides, if any.
|
||||
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)
|
||||
|
||||
-- | Produce open/closed lines for the portion of the source spanned by a range.
|
||||
linesInRangeOfSource :: Range -> Source Char -> Adjoined (Line Range)
|
||||
linesInRangeOfSource range source = fromList $ pureBy (openRange source) <$> actualLineRanges range source
|
||||
infixl 4 `applyThese`
|
||||
|
||||
-- | Does this Range in this Source end with a newline?
|
||||
openRange :: Source Char -> Range -> Bool
|
||||
openRange source range = (at source <$> maybeLastIndex range) /= Just '\n'
|
||||
-- | Like `<*>`, but it returns its result in `Maybe` since the result is the intersection of the shapes of the inputs.
|
||||
applyThese :: Join These (a -> b) -> Join These a -> Maybe (Join These b)
|
||||
applyThese (Join fg) (Join ab) = fmap Join . uncurry maybeThese $ uncurry (***) (bimap (<*>) (<*>) (unpack fg)) (unpack ab)
|
||||
where unpack = fromThese Nothing Nothing . bimap Just Just
|
||||
|
||||
-- | A row in a split diff, composed of a before line and an after line.
|
||||
type Row a = Both (Line a)
|
||||
infixl 4 `applyToBoth`
|
||||
|
||||
-- | 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
|
||||
|
||||
-- Map over the bifunctor inside a Join, producing another Join.
|
||||
modifyJoin :: (p a a -> q b b) -> Join p a -> Join q b
|
||||
modifyJoin f = Join . f . runJoin
|
||||
|
||||
-- | 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
|
||||
|
||||
|
||||
-- | Instances
|
||||
|
||||
instance Bicrosswalk t => Crosswalk (Join t) where
|
||||
crosswalk f = fmap Join . bicrosswalk f f . runJoin
|
||||
|
@ -1,70 +0,0 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
module Data.Adjoined where
|
||||
|
||||
import Prologue hiding (uncons, (:<))
|
||||
import Data.Sequence as Seq hiding (null)
|
||||
import Data.Align
|
||||
import Data.Bifunctor.These
|
||||
import Data.Coalescent
|
||||
|
||||
-- | A collection of elements which can be adjoined onto other such collections associatively. There are two big wins with Data.Adjoined:
|
||||
-- |
|
||||
-- | 1. Efficient adjoining of lines and concatenation, thanks to its use of Data.Sequence’s `Seq` type.
|
||||
-- | 2. The Monoid instance guarantees that adjoining cannot touch any lines other than the outermost.
|
||||
-- |
|
||||
-- | Since aligning diffs proceeds through the diff tree depth-first, adjoining child nodes and context from right to left, the former is crucial for efficiency, and the latter is crucial for correctness. Prior to using Data.Adjoined, repeatedly adjoining the last line in a node into its parent, and then its grandparent, and so forth, would sometimes cause blank lines to “travel” downwards, ultimately shifting blank lines at the end of nodes down proportionately to the depth in the tree at which they were introduced.
|
||||
newtype Adjoined a = Adjoined { unAdjoined :: Seq a }
|
||||
deriving (Eq, Foldable, Functor, Show, Traversable)
|
||||
|
||||
-- | Construct an Adjoined from a list.
|
||||
fromList :: [a] -> Adjoined a
|
||||
fromList = Adjoined . Seq.fromList
|
||||
|
||||
-- | Construct Adjoined by adding an element at the left.
|
||||
cons :: a -> Adjoined a -> Adjoined a
|
||||
cons a (Adjoined as) = Adjoined (a <| as)
|
||||
|
||||
-- | Destructure a non-empty Adjoined into Just the leftmost element and the rightward remainder of the Adjoined, or Nothing otherwise.
|
||||
uncons :: Adjoined a -> Maybe (a, Adjoined a)
|
||||
uncons (Adjoined v) | a :< as <- viewl v = Just (a, Adjoined as)
|
||||
| otherwise = Nothing
|
||||
|
||||
-- | Construct Adjoined by adding an element at the right.
|
||||
snoc :: Adjoined a -> a -> Adjoined a
|
||||
snoc (Adjoined as) a = Adjoined (as |> a)
|
||||
|
||||
-- | Destructure a non-empty Adjoined into Just the rightmost element and the leftward remainder of the Adjoined, or Nothing otherwise.
|
||||
unsnoc :: Adjoined a -> Maybe (Adjoined a, a)
|
||||
unsnoc (Adjoined v) | as :> a <- viewr v = Just (Adjoined as, a)
|
||||
| otherwise = Nothing
|
||||
|
||||
instance Applicative Adjoined where
|
||||
pure = return
|
||||
(<*>) = ap
|
||||
|
||||
instance Alternative Adjoined where
|
||||
empty = Adjoined Seq.empty
|
||||
Adjoined a <|> Adjoined b = Adjoined (a >< b)
|
||||
|
||||
instance Monad Adjoined where
|
||||
return = Adjoined . pure
|
||||
a >>= f | Just (a, as) <- uncons a = f a <|> (as >>= f)
|
||||
| otherwise = Adjoined Seq.empty
|
||||
|
||||
instance Coalescent a => Monoid (Adjoined a) where
|
||||
mempty = Adjoined Seq.empty
|
||||
a `mappend` b = a <> b
|
||||
|
||||
instance Coalescent a => Semigroup (Adjoined a) where
|
||||
a <> b | Just (as, a) <- unsnoc a,
|
||||
Just (b, bs) <- uncons b
|
||||
= as <|> coalesce a b <|> bs
|
||||
| otherwise = Adjoined (unAdjoined a >< unAdjoined b)
|
||||
|
||||
instance Align Adjoined where
|
||||
nil = Adjoined Seq.empty
|
||||
align as bs | Just (as, a) <- unsnoc as,
|
||||
Just (bs, b) <- unsnoc bs = align as bs `snoc` These a b
|
||||
| null bs = This <$> as
|
||||
| null as = That <$> bs
|
||||
| otherwise = nil
|
@ -1,37 +0,0 @@
|
||||
module Data.Align where
|
||||
|
||||
import Prologue
|
||||
import Data.Bifunctor.These
|
||||
|
||||
-- | A functor which can be aligned, essentially the union of (potentially) asymmetrical values.
|
||||
-- |
|
||||
-- | For example, this allows a zip over lists which pads out the shorter side with a default value.
|
||||
class Functor f => Align f where
|
||||
-- | The empty value. The identity value for `align` (modulo the `This` or `That` constructor wrapping the results).
|
||||
nil :: f a
|
||||
-- | Combine two structures into a structure of `These` holding pairs of values in `These` where they overlap, and individual values in `This` and `That` elsewhere.
|
||||
-- |
|
||||
-- | Analogous with `zip`.
|
||||
align :: f a -> f b -> f (These a b)
|
||||
align = alignWith identity
|
||||
-- | Combine two structures into a structure by applying a function to pairs of values in `These` where they overlap, and individual values in `This` and `That` elsewhere.
|
||||
-- |
|
||||
-- | Analogous with `zipWith`.
|
||||
alignWith :: (These a b -> c) -> f a -> f b -> f c
|
||||
alignWith f a b = f <$> align a b
|
||||
|
||||
|
||||
-- | A functor which can be traversed through an `Align`able functor, inverting the nesting of one in the other, given some default value.
|
||||
-- |
|
||||
-- | Analogous with `zip`, in that it can e.g. turn a tuple of lists into a list of tuples.
|
||||
class Functor t => TotalCrosswalk t where
|
||||
-- | Given some default value, embed a structure into an `Align`able functor by mapping its elements into that functor and convoluting (inverting the embedding).
|
||||
tcrosswalk :: Align f => t b -> (a -> f b) -> t a -> f (t b)
|
||||
tcrosswalk d f = tsequenceL d . fmap f
|
||||
|
||||
-- | Given some default value, convolute (invert the embedding of) a structure over an `Align`able functor.
|
||||
tsequenceL :: Align f => t a -> t (f a) -> f (t a)
|
||||
tsequenceL d = tcrosswalk d identity
|
||||
|
||||
instance TotalCrosswalk Identity where
|
||||
tcrosswalk _ f = fmap Identity . f . runIdentity
|
@ -1,28 +0,0 @@
|
||||
module Data.Bifunctor.These where
|
||||
|
||||
import Prologue
|
||||
|
||||
data These a b = This a | That b | These a b
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | Eliminate These by case analysis.
|
||||
these :: (a -> c) -> (b -> c) -> (a -> b -> c) -> These a b -> c
|
||||
these f _ _ (This this) = f this
|
||||
these _ f _ (That that) = f that
|
||||
these _ _ f (These this that) = f this that
|
||||
|
||||
-- | Return Just the value in This, or the first value in These, if any.
|
||||
maybeFirst :: These a b -> Maybe a
|
||||
maybeFirst = these Just (const Nothing) ((Just .) . const)
|
||||
|
||||
-- | Return Just the value in That, or the second value in These, if any.
|
||||
maybeSecond :: These a b -> Maybe b
|
||||
maybeSecond = these (const Nothing) Just ((Just .) . flip const)
|
||||
|
||||
|
||||
-- Instances
|
||||
|
||||
instance Bifunctor These where
|
||||
bimap f _ (This a) = This (f a)
|
||||
bimap _ g (That b) = That (g b)
|
||||
bimap f g (These a b) = These (f a) (g b)
|
@ -1,12 +0,0 @@
|
||||
module Data.Coalescent where
|
||||
|
||||
import Prologue
|
||||
import Data.Align
|
||||
|
||||
-- | The class of types which can optionally be coalesced together.
|
||||
class Coalescent a where
|
||||
-- | Returns the result of coalescing the operands together in an Alternative context. If they cannot be coalesced, they should each be produced individually.
|
||||
coalesce :: (Align f, Alternative f) => a -> a -> f a
|
||||
|
||||
instance Coalescent a => Coalescent (Identity a) where
|
||||
a `coalesce` b = sequenceA (coalesce <$> a <*> b)
|
@ -1,13 +0,0 @@
|
||||
module Data.Copointed where
|
||||
|
||||
import Prologue
|
||||
|
||||
-- | A value that can return its content.
|
||||
class Copointed c where
|
||||
copoint :: c a -> a
|
||||
|
||||
instance Copointed ((,) a) where
|
||||
copoint = snd
|
||||
|
||||
instance Copointed Identity where
|
||||
copoint = runIdentity
|
@ -1,55 +1,33 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
module Data.Functor.Both where
|
||||
|
||||
import Data.Align
|
||||
import Data.Bifunctor
|
||||
import Data.Bifunctor.These
|
||||
import Data.Maybe
|
||||
import Data.Bifunctor.Join
|
||||
import Prologue hiding (zipWith, fst, snd)
|
||||
import qualified Prologue
|
||||
|
||||
-- | A computation over both sides of a pair.
|
||||
newtype Both a = Both { runBoth :: (a, a) }
|
||||
deriving (Eq, Foldable, Functor, Ord, Show, Traversable)
|
||||
type Both a = Join (,) a
|
||||
|
||||
-- | Given two operands returns a functor operating on `Both`. This is a curried synonym for Both.
|
||||
both :: a -> a -> Both a
|
||||
both = curry Both
|
||||
|
||||
-- | Construct Both with These values & defaults.
|
||||
bothOfThese :: Both a -> These a a -> Both a
|
||||
bothOfThese a = these (`both` snd a) (both (fst a)) both
|
||||
|
||||
-- | Construct Both (Maybe) with These values, defaulting to Nothing.
|
||||
maybeBothOfThese :: These a a -> Both (Maybe a)
|
||||
maybeBothOfThese = bothOfThese (pure Nothing) . bimap Just Just
|
||||
both = curry Join
|
||||
|
||||
-- | Apply a function to `Both` sides of a computation.
|
||||
runBothWith :: (a -> a -> b) -> Both a -> b
|
||||
runBothWith f = uncurry f . runBoth
|
||||
runBothWith f = uncurry f . runJoin
|
||||
|
||||
-- | Runs the left side of a `Both`.
|
||||
fst :: Both a -> a
|
||||
fst = Prologue.fst . runBoth
|
||||
fst = Prologue.fst . runJoin
|
||||
|
||||
-- | Runs the right side of a `Both`.
|
||||
snd :: Both a -> a
|
||||
snd = Prologue.snd . runBoth
|
||||
snd = Prologue.snd . runJoin
|
||||
|
||||
unzip :: [Both a] -> Both [a]
|
||||
unzip = foldr pair (pure [])
|
||||
where pair (Both (a, b)) (Both (as, bs)) = Both (a : as, b : bs)
|
||||
|
||||
instance Applicative Both where
|
||||
pure a = Both (a, a)
|
||||
Both (f, g) <*> Both (a, b) = Both (f a, g b)
|
||||
|
||||
instance (Semigroup a, Monoid a) => Monoid (Both a) where
|
||||
instance (Semigroup a, Monoid a) => Monoid (Join (,) a) where
|
||||
mempty = pure mempty
|
||||
mappend = (<>)
|
||||
|
||||
instance Semigroup a => Semigroup (Both a) where
|
||||
a <> b = (<>) <$> a <*> b
|
||||
|
||||
instance TotalCrosswalk Both where
|
||||
tsequenceL d = runBothWith (alignWith (\ these -> fromMaybe <$> d <*> maybeBothOfThese these))
|
||||
instance (Semigroup a) => Semigroup (Join (,) a) where
|
||||
a <> b = Join $ runJoin a <> runJoin b
|
||||
|
@ -1,13 +0,0 @@
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
module Data.Option where
|
||||
|
||||
import Prologue
|
||||
|
||||
newtype Option a = Option { getOption :: Maybe a }
|
||||
|
||||
option :: b -> (a -> b) -> Option a -> b
|
||||
option b f = maybe b f . getOption
|
||||
|
||||
-- | Return Just the concatenation of any elements in a Foldable, or Nothing if it is empty.
|
||||
maybeConcat :: (Foldable f, Monoid (Option a)) => f a -> Maybe a
|
||||
maybeConcat = getOption . foldMap (Option. Just)
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
module Data.OrderedMap (
|
||||
OrderedMap
|
||||
, fromList
|
||||
@ -16,13 +17,9 @@ module Data.OrderedMap (
|
||||
import Prologue hiding (toList, empty)
|
||||
|
||||
-- | An ordered map of keys and values.
|
||||
data OrderedMap key value = OrderedMap { toList :: [(key, value)] }
|
||||
newtype OrderedMap key value = OrderedMap { toList :: [(key, value)] }
|
||||
deriving (Show, Eq, Functor, Foldable, Traversable)
|
||||
|
||||
instance Eq key => Monoid (OrderedMap key value) where
|
||||
mempty = fromList []
|
||||
mappend = union
|
||||
|
||||
-- | Construct an ordered map from a list of pairs of keys and values.
|
||||
fromList :: [(key, value)] -> OrderedMap key value
|
||||
fromList = OrderedMap
|
||||
@ -66,3 +63,10 @@ intersectionWith combine (OrderedMap a) (OrderedMap b) = OrderedMap $ a >>= (\ (
|
||||
difference :: Eq key => OrderedMap key a -> OrderedMap key b -> OrderedMap key a
|
||||
difference (OrderedMap a) (OrderedMap b) = OrderedMap $ filter ((`notElem` extant) . fst) a
|
||||
where extant = fst <$> b
|
||||
|
||||
|
||||
-- Instances
|
||||
|
||||
instance Eq key => Monoid (OrderedMap key value) where
|
||||
mempty = fromList []
|
||||
mappend = union
|
||||
|
@ -1,6 +1,13 @@
|
||||
module Diffing where
|
||||
|
||||
import Prologue
|
||||
import Data.Bifunctor.Join
|
||||
import qualified Data.ByteString.Char8 as B1
|
||||
import Data.Functor.Both
|
||||
import Data.Functor.Foldable
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.ICU.Detect as Detect
|
||||
import qualified Data.Text.ICU.Convert as Convert
|
||||
import Diff
|
||||
import Info
|
||||
import Interpreter
|
||||
@ -10,18 +17,11 @@ import Range
|
||||
import Renderer
|
||||
import Source hiding ((++))
|
||||
import Syntax
|
||||
import System.FilePath
|
||||
import Term
|
||||
import TreeSitter
|
||||
import Text.Parser.TreeSitter.Language
|
||||
|
||||
import Data.Functor.Both
|
||||
import Data.Functor.Foldable
|
||||
import qualified Data.ByteString.Char8 as B1
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.ICU.Detect as Detect
|
||||
import qualified Data.Text.ICU.Convert as Convert
|
||||
import System.FilePath
|
||||
|
||||
-- | Return a parser based on the file extension (including the ".").
|
||||
parserForType :: T.Text -> Parser
|
||||
parserForType mediaType = case languageForType mediaType of
|
||||
@ -56,8 +56,8 @@ breakDownLeavesByWord source = cata replaceIn
|
||||
| ranges <- rangesAndWordsInSource range
|
||||
, length ranges > 1
|
||||
= cofree $ Info range categories (1 + fromIntegral (length ranges)) :< Indexed (makeLeaf categories <$> ranges)
|
||||
replaceIn (Info range categories _ :< syntax)
|
||||
= cofree $ Info range categories (1 + sum (size . extract <$> syntax)) :< syntax
|
||||
replaceIn (info :< syntax)
|
||||
= cofree $ info { size = 1 + sum (size . extract <$> syntax) } :< syntax
|
||||
rangesAndWordsInSource range = rangesAndWordsFrom (start range) (toString $ slice range source)
|
||||
makeLeaf categories (range, substring) = cofree $ Info range categories 1 :< Leaf (T.pack substring)
|
||||
|
||||
@ -83,14 +83,8 @@ diffFiles parser renderer sourceBlobs = do
|
||||
let sources = source <$> sourceBlobs
|
||||
terms <- sequence $ parser <$> sources
|
||||
let replaceLeaves = breakDownLeavesByWord <$> sources
|
||||
pure $! renderer (runBothWith (diffTerms diffCostWithAbsoluteDifferenceOfCachedDiffSizes) $ replaceLeaves <*> terms) sourceBlobs
|
||||
pure $! renderer (runBothWith (diffTerms diffCostWithCachedTermSizes) $ replaceLeaves <*> terms) sourceBlobs
|
||||
|
||||
-- | The sum of the node count of the diff’s patches.
|
||||
diffCostWithCachedTermSizes :: Diff a Info -> Integer
|
||||
diffCostWithCachedTermSizes = diffSum (getSum . foldMap (Sum . size . extract))
|
||||
|
||||
-- | The absolute difference between the node counts of a diff.
|
||||
diffCostWithAbsoluteDifferenceOfCachedDiffSizes :: Diff a Info -> Integer
|
||||
diffCostWithAbsoluteDifferenceOfCachedDiffSizes term = case runFree term of
|
||||
(Free (Both (before, after) :< _)) -> abs $ size before - size after
|
||||
(Pure patch) -> sum $ size . extract <$> patch
|
||||
|
@ -29,7 +29,7 @@ interpret comparable cost a b = fromMaybe (pure $ Replace a b) $ constructAndRun
|
||||
|
||||
-- | Constructs an algorithm and runs it
|
||||
constructAndRun :: (Eq a, Eq annotation) => Comparable a annotation -> Cost a annotation -> Term a annotation -> Term a annotation -> Maybe (Diff a annotation)
|
||||
constructAndRun _ _ a b | a == b = hylo (\termF -> free . Free $ headF termF :< tailF termF) runCofree <$> zipTerms a b
|
||||
constructAndRun _ _ a b | a == b = hylo (free . Free) runCofree <$> zipTerms a b
|
||||
|
||||
constructAndRun comparable _ a b | not $ comparable a b = Nothing
|
||||
|
||||
@ -40,15 +40,15 @@ constructAndRun comparable cost t1 t2 =
|
||||
algorithm (Leaf a') (Leaf b') | a' == b' = annotate $ Leaf b'
|
||||
algorithm a' b' = free . Free $ Recursive (cofree (annotation1 :< a')) (cofree (annotation2 :< b')) pure
|
||||
(annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2)
|
||||
annotate = pure . free . Free . (:<) (both annotation1 annotation2)
|
||||
annotate = pure . free . Free . (both annotation1 annotation2 :<)
|
||||
|
||||
-- | Runs the diff algorithm
|
||||
run :: (Eq a, Eq annotation) => Comparable a annotation -> Cost a annotation -> Algorithm a annotation (Diff a annotation) -> Maybe (Diff a annotation)
|
||||
run comparable cost algorithm = case runFree algorithm of
|
||||
(Pure diff) -> Just diff
|
||||
(Free (Recursive t1 t2 f)) -> run comparable cost . f $ recur a b where
|
||||
Pure diff -> Just diff
|
||||
Free (Recursive t1 t2 f) -> run comparable cost . f $ recur a b where
|
||||
(annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2)
|
||||
annotate = free . Free . (Both (annotation1, annotation2) :<)
|
||||
annotate = free . Free . (both annotation1 annotation2 :<)
|
||||
|
||||
recur (Indexed a') (Indexed b') | length a' == length b' = annotate . Indexed $ zipWith (interpret comparable cost) a' b'
|
||||
recur (Fixed a') (Fixed b') | length a' == length b' = annotate . Fixed $ zipWith (interpret comparable cost) a' b'
|
||||
@ -57,6 +57,7 @@ run comparable cost algorithm = case runFree algorithm of
|
||||
repack key = (key, interpretInBoth key a' b')
|
||||
interpretInBoth key x y = interpret comparable cost (x ! key) (y ! key)
|
||||
recur _ _ = pure $ Replace (cofree (annotation1 :< a)) (cofree (annotation2 :< b))
|
||||
|
||||
Free (ByKey a b f) -> run comparable cost $ f byKey where
|
||||
byKey = Map.fromList $ toKeyValue <$> List.union aKeys bKeys
|
||||
toKeyValue key | key `List.elem` deleted = (key, pure . Delete $ a ! key)
|
||||
@ -66,5 +67,5 @@ run comparable cost algorithm = case runFree algorithm of
|
||||
bKeys = Map.keys b
|
||||
deleted = aKeys \\ bKeys
|
||||
inserted = bKeys \\ aKeys
|
||||
Free (ByIndex a b f) -> run comparable cost . f $ ses (constructAndRun comparable cost) cost a b
|
||||
|
||||
Free (ByIndex a b f) -> run comparable cost . f $ ses (constructAndRun comparable cost) cost a b
|
||||
|
68
src/Line.hs
68
src/Line.hs
@ -1,68 +0,0 @@
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
module Line where
|
||||
|
||||
import Prologue
|
||||
import Data.Align
|
||||
import Data.Coalescent
|
||||
import Data.Functor.Both
|
||||
|
||||
-- | A line of items or an empty line.
|
||||
data Line a = Line [a] | Closed [a]
|
||||
deriving (Eq, Foldable, Functor, Show, Traversable)
|
||||
|
||||
-- | Construct a single-element Line with a predicate determining whether the line is open.
|
||||
pureBy :: (a -> Bool) -> a -> Line a
|
||||
pureBy predicate a | predicate a = Line [ a ]
|
||||
| otherwise = Closed [ a ]
|
||||
|
||||
unLine :: Line a -> [a]
|
||||
unLine (Line as) = as
|
||||
unLine (Closed as) = as
|
||||
|
||||
-- | Is the given line empty?
|
||||
isEmpty :: Line a -> Bool
|
||||
isEmpty = null . unLine
|
||||
|
||||
-- | Is the given line open?
|
||||
isOpen :: Line a -> Bool
|
||||
isOpen (Line _) = True
|
||||
isOpen _ = False
|
||||
|
||||
-- | The increment the given line implies for line numbering.
|
||||
lineIncrement :: Num n => Line a -> n
|
||||
lineIncrement line | isEmpty line = 0
|
||||
| otherwise = 1
|
||||
|
||||
-- | Transform the line by applying a function to a list of all the items in the
|
||||
-- | line.
|
||||
wrapLineContents :: ([a] -> b) -> Line a -> Line b
|
||||
wrapLineContents transform line = lineMap (if isEmpty line then const [] else pure . transform) line
|
||||
|
||||
-- | Map the elements of a line, preserving closed lines.
|
||||
lineMap :: ([a] -> [b]) -> Line a -> Line b
|
||||
lineMap f (Line ls) = Line (f ls)
|
||||
lineMap f (Closed cs) = Closed (f cs)
|
||||
|
||||
-- | Return the first item in the Foldable, or Nothing if it's empty.
|
||||
maybeFirst :: Foldable f => f a -> Maybe a
|
||||
maybeFirst = foldr (const . Just) Nothing
|
||||
|
||||
instance Applicative Line where
|
||||
pure = Line . pure
|
||||
as <*> bs | isOpen as && isOpen bs = Line (unLine as <*> unLine bs)
|
||||
| otherwise = Closed (unLine as <*> unLine bs)
|
||||
|
||||
|
||||
instance Semigroup (Line a) where
|
||||
xs <> ys = lineMap ((<>) (unLine xs)) ys
|
||||
|
||||
instance Monoid (Line a) where
|
||||
mempty = Line []
|
||||
mappend = (<>)
|
||||
|
||||
instance Coalescent (Line a) where
|
||||
coalesce a b | isOpen a = pure (a `mappend` b)
|
||||
| otherwise = pure a <|> pure b
|
||||
|
||||
instance Coalescent (Both (Line a)) where
|
||||
coalesce as bs = tsequenceL (pure (Line [])) (coalesce <$> as <*> bs)
|
24
src/Patch.hs
24
src/Patch.hs
@ -1,7 +1,15 @@
|
||||
module Patch (Patch(..), after, before, unPatch, patchSum) where
|
||||
module Patch
|
||||
( Patch(..)
|
||||
, after
|
||||
, before
|
||||
, unPatch
|
||||
, patchSum
|
||||
, maybeFst
|
||||
, maybeSnd
|
||||
) where
|
||||
|
||||
import Data.These
|
||||
import Prologue
|
||||
import Data.Bifunctor.These
|
||||
|
||||
-- | An operation to replace, insert, or delete an item.
|
||||
data Patch a =
|
||||
@ -12,11 +20,11 @@ data Patch a =
|
||||
|
||||
-- | Return the item from the after side of the patch.
|
||||
after :: Patch a -> Maybe a
|
||||
after = maybeFirst . unPatch
|
||||
after = maybeFst . unPatch
|
||||
|
||||
-- | Return the item from the before side of the patch.
|
||||
before :: Patch a -> Maybe a
|
||||
before = maybeSecond . unPatch
|
||||
before = maybeSnd . unPatch
|
||||
|
||||
-- | Return both sides of a patch.
|
||||
unPatch :: Patch a -> These a a
|
||||
@ -27,3 +35,11 @@ unPatch (Delete a) = This a
|
||||
-- | Calculate the cost of the patch given a function to compute the cost of a item.
|
||||
patchSum :: (a -> Integer) -> Patch a -> Integer
|
||||
patchSum termCost patch = maybe 0 termCost (before patch) + maybe 0 termCost (after patch)
|
||||
|
||||
-- | Return Just the value in This, or the first value in These, if any.
|
||||
maybeFst :: These a b -> Maybe a
|
||||
maybeFst = these Just (const Nothing) ((Just .) . const)
|
||||
|
||||
-- | Return Just the value in That, or the second value in These, if any.
|
||||
maybeSnd :: These a b -> Maybe b
|
||||
maybeSnd = these (const Nothing) Just ((Just .) . flip const)
|
||||
|
@ -1,8 +1,21 @@
|
||||
module Prologue (module X, lookup, FilePath) where
|
||||
module Prologue
|
||||
( module X
|
||||
, lookup
|
||||
, traceShowId
|
||||
, FilePath
|
||||
) where
|
||||
|
||||
import Protolude as X
|
||||
import Data.List (lookup)
|
||||
import System.IO (FilePath)
|
||||
|
||||
import Control.Comonad.Trans.Cofree as X
|
||||
import Control.Monad.Trans.Free as X
|
||||
import Control.Comonad as X
|
||||
|
||||
import qualified GHC.Show as P
|
||||
import qualified Debug.Trace as T
|
||||
|
||||
{-# WARNING traceShowId "'traceShowId' remains in code" #-}
|
||||
traceShowId :: P.Show a => a -> a
|
||||
traceShowId a = T.trace (P.show a) a
|
||||
|
29
src/Range.hs
29
src/Range.hs
@ -1,11 +1,11 @@
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
module Range where
|
||||
|
||||
import Prologue
|
||||
import Data.String
|
||||
import Data.Char as Char
|
||||
import qualified Data.Char as Char
|
||||
import Data.List (span)
|
||||
import Data.Option
|
||||
import Data.Semigroup
|
||||
import Data.String
|
||||
import Prologue
|
||||
|
||||
-- | A half-open interval of integers, defined by start & end indices.
|
||||
data Range = Range { start :: !Int, end :: !Int }
|
||||
@ -48,6 +48,15 @@ maybeLastIndex :: Range -> Maybe Int
|
||||
maybeLastIndex (Range start end) | start == end = Nothing
|
||||
maybeLastIndex (Range _ end) = Just $ end - 1
|
||||
|
||||
-- | Test two ranges for intersection.
|
||||
intersectsRange :: Range -> Range -> Bool
|
||||
intersectsRange range1 range2 = isWellFormedAndNonEmpty $ intersectionRange range1 range2
|
||||
where isWellFormedAndNonEmpty range = start range < end range
|
||||
|
||||
-- Return the (possibly empty, possibly ill-formed) intersection of two ranges.
|
||||
intersectionRange :: Range -> Range -> Range
|
||||
intersectionRange range1 range2 = Range (max (start range1) (start range2)) (min (end range1) (end range2))
|
||||
|
||||
-- | Return a range that contains both the given ranges.
|
||||
unionRange :: Range -> Range -> Range
|
||||
unionRange (Range start1 end1) (Range start2 end2) = Range (min start1 start2) (max end1 end2)
|
||||
@ -56,16 +65,16 @@ unionRange (Range start1 end1) (Range start2 end2) = Range (min start1 start2) (
|
||||
unionRanges :: Foldable f => f Range -> Range
|
||||
unionRanges = unionRangesFrom (Range 0 0)
|
||||
|
||||
-- | Return Just the concatenation of any elements in a Foldable, or Nothing if it is empty.
|
||||
maybeConcat :: (Foldable f, Semigroup a) => f a -> Maybe a
|
||||
maybeConcat = getOption . foldMap (Option . Just)
|
||||
|
||||
-- | Return a range that contains all the ranges in a Foldable, or the passed Range if the Foldable is empty.
|
||||
unionRangesFrom :: Foldable f => Range -> f Range -> Range
|
||||
unionRangesFrom range = fromMaybe range . maybeConcat
|
||||
|
||||
instance Monoid (Option Range) where
|
||||
mempty = Option Nothing
|
||||
mappend (Option (Just a)) (Option (Just b)) = Option (Just (unionRange a b))
|
||||
mappend a@(Option (Just _)) _ = a
|
||||
mappend _ b@(Option (Just _)) = b
|
||||
mappend _ _ = mempty
|
||||
instance Semigroup Range where
|
||||
a <> b = unionRange a b
|
||||
|
||||
instance Ord Range where
|
||||
a <= b = start a <= start b
|
||||
|
@ -8,15 +8,13 @@ import Prologue hiding (toList)
|
||||
import Alignment
|
||||
import Category
|
||||
import Data.Aeson hiding (json)
|
||||
import Data.Aeson.Encode
|
||||
import Data.Functor.Both
|
||||
import Data.Bifunctor.Join
|
||||
import Data.ByteString.Builder
|
||||
import Data.OrderedMap hiding (fromList)
|
||||
import Data.Text.Lazy (toStrict)
|
||||
import Data.Text.Lazy.Builder (toLazyText)
|
||||
import qualified Data.Text as T
|
||||
import Data.These
|
||||
import Data.Vector hiding (toList)
|
||||
import Info
|
||||
import Line
|
||||
import Range
|
||||
import Renderer
|
||||
import Source hiding (fromList)
|
||||
@ -26,22 +24,25 @@ import Term
|
||||
|
||||
-- | Render a diff to a string representing its JSON.
|
||||
json :: Renderer
|
||||
json diff sources = toStrict . toLazyText . encodeToTextBuilder $ object ["rows" .= annotateRows (splitDiffByLines (source <$> sources) diff), "oids" .= (oid <$> sources), "paths" .= (path <$> sources)]
|
||||
json diff sources = toS . toLazyByteString . fromEncoding . pairs $ "rows" .= annotateRows (alignDiff (source <$> sources) diff) <> "oids" .= (oid <$> sources) <> "paths" .= (path <$> sources)
|
||||
where annotateRows = fmap (fmap NumberedLine) . numberedRows
|
||||
|
||||
newtype NumberedLine a = NumberedLine (Int, Line a)
|
||||
newtype NumberedLine a = NumberedLine (Int, a)
|
||||
|
||||
instance ToJSON (NumberedLine (SplitDiff leaf Info, Range)) where
|
||||
toJSON (NumberedLine (n, a)) = object (lineFields n a)
|
||||
toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a)
|
||||
instance ToJSON (NumberedLine (SplitDiff leaf Info)) where
|
||||
toJSON (NumberedLine (n, a)) = object (lineFields n a (getRange a))
|
||||
toEncoding (NumberedLine (n, a)) = pairs $ mconcat (lineFields n a (getRange a))
|
||||
instance ToJSON Category where
|
||||
toJSON (Other s) = String $ T.pack s
|
||||
toJSON s = String . T.pack $ show s
|
||||
instance ToJSON Range where
|
||||
toJSON (Range start end) = Array . fromList $ toJSON <$> [ start, end ]
|
||||
toEncoding (Range start end) = foldable [ start, end ]
|
||||
instance ToJSON a => ToJSON (Both a) where
|
||||
toJSON (Both (a, b)) = Array . fromList $ toJSON <$> [ a, b ]
|
||||
instance ToJSON a => ToJSON (Join These a) where
|
||||
toJSON (Join vs) = Array . fromList $ toJSON <$> these pure pure (\ a b -> [ a, b ]) vs
|
||||
toEncoding = foldable
|
||||
instance ToJSON a => ToJSON (Join (,) a) where
|
||||
toJSON (Join (a, b)) = Array . fromList $ toJSON <$> [ a, b ]
|
||||
toEncoding = foldable
|
||||
instance ToJSON (SplitDiff leaf Info) where
|
||||
toJSON splitDiff = case runFree splitDiff of
|
||||
@ -51,19 +52,18 @@ instance ToJSON (SplitDiff leaf Info) where
|
||||
(Free (info :< syntax)) -> pairs $ mconcat (termFields info syntax)
|
||||
(Pure patch) -> pairs $ mconcat (patchFields patch)
|
||||
instance ToJSON value => ToJSON (OrderedMap T.Text value) where
|
||||
toJSON map = object $ uncurry (.=) <$> toList map
|
||||
toEncoding map = pairs . mconcat $ uncurry (.=) <$> toList map
|
||||
toJSON kv = object $ uncurry (.=) <$> toList kv
|
||||
toEncoding kv = pairs . mconcat $ uncurry (.=) <$> toList kv
|
||||
instance ToJSON (Term leaf Info) where
|
||||
toJSON term | (info :< syntax) <- runCofree term = object (termFields info syntax)
|
||||
toEncoding term | (info :< syntax) <- runCofree term = pairs $ mconcat (termFields info syntax)
|
||||
|
||||
lineFields :: KeyValue kv => Int -> Line (SplitDiff leaf Info, Range) -> [kv]
|
||||
lineFields n line | isEmpty line = []
|
||||
| otherwise = [ "number" .= n
|
||||
, "terms" .= unLine (Prologue.fst <$> line)
|
||||
, "range" .= unionRanges (Prologue.snd <$> line)
|
||||
, "hasChanges" .= hasChanges (Prologue.fst <$> line)
|
||||
]
|
||||
lineFields :: KeyValue kv => Int -> SplitDiff leaf Info -> Range -> [kv]
|
||||
lineFields n term range = [ "number" .= n
|
||||
, "terms" .= [ term ]
|
||||
, "range" .= range
|
||||
, "hasChanges" .= hasChanges term
|
||||
]
|
||||
|
||||
termFields :: (ToJSON recur, KeyValue kv) => Info -> Syntax leaf recur -> [kv]
|
||||
termFields (Info range categories _) syntax = "range" .= range : "categories" .= categories : case syntax of
|
||||
|
@ -5,19 +5,20 @@ module Renderer.Patch (
|
||||
truncatePatch
|
||||
) where
|
||||
|
||||
import Data.String
|
||||
import Alignment
|
||||
import Data.Bifunctor.Join
|
||||
import Data.Functor.Both as Both
|
||||
import Data.List (span, unzip)
|
||||
import Data.String
|
||||
import Data.Text (pack)
|
||||
import Data.These
|
||||
import Diff
|
||||
import Info
|
||||
import Line
|
||||
import Prologue hiding (snd)
|
||||
import Data.List (span)
|
||||
import Range
|
||||
import Patch
|
||||
import Prologue hiding (fst, snd)
|
||||
import Renderer
|
||||
import Source hiding ((++), break)
|
||||
import SplitDiff
|
||||
import Data.Functor.Both as Both
|
||||
import Data.Text (pack)
|
||||
|
||||
-- | Render a timed out file as a truncated diff.
|
||||
truncatePatch :: DiffArguments -> Both SourceBlob -> Text
|
||||
@ -31,11 +32,11 @@ patch diff blobs = pack $ case getLast (foldMap (Last . Just) string) of
|
||||
where string = header blobs ++ mconcat (showHunk blobs <$> hunks diff blobs)
|
||||
|
||||
-- | A hunk in a patch, including the offset, changes, and context.
|
||||
data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Row a] }
|
||||
data Hunk a = Hunk { offset :: Both (Sum Int), changes :: [Change a], trailingContext :: [Join These a] }
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | A change in a patch hunk, along with its preceding context.
|
||||
data Change a = Change { context :: [Row a], contents :: [Row a] }
|
||||
data Change a = Change { context :: [Join These a], contents :: [Join These a] }
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | The number of lines in the hunk before and after.
|
||||
@ -47,43 +48,37 @@ changeLength :: Change a -> Both (Sum Int)
|
||||
changeLength change = mconcat $ (rowIncrement <$> context change) <> (rowIncrement <$> contents change)
|
||||
|
||||
-- | The increment the given row implies for line numbering.
|
||||
rowIncrement :: Row a -> Both (Sum Int)
|
||||
rowIncrement = fmap lineIncrement
|
||||
rowIncrement :: Join These a -> Both (Sum Int)
|
||||
rowIncrement = Join . fromThese (Sum 0) (Sum 0) . runJoin . (Sum 1 <$)
|
||||
|
||||
-- | Given the before and after sources, render a hunk to a string.
|
||||
showHunk :: Both SourceBlob -> Hunk (SplitDiff a Info) -> String
|
||||
showHunk blobs hunk = maybeOffsetHeader ++
|
||||
concat (showChange sources <$> changes hunk) ++
|
||||
showLines (snd sources) ' ' (snd <$> trailingContext hunk)
|
||||
showLines (snd sources) ' ' (maybeSnd . runJoin <$> trailingContext hunk)
|
||||
where sources = source <$> blobs
|
||||
maybeOffsetHeader = if lengthA > 0 && lengthB > 0
|
||||
then offsetHeader
|
||||
else mempty
|
||||
offsetHeader = "@@ -" ++ offsetA ++ "," ++ show lengthA ++ " +" ++ offsetB ++ "," ++ show lengthB ++ " @@" ++ "\n"
|
||||
(lengthA, lengthB) = runBoth . fmap getSum $ hunkLength hunk
|
||||
(offsetA, offsetB) = runBoth . fmap (show . getSum) $ offset hunk
|
||||
(lengthA, lengthB) = runJoin . fmap getSum $ hunkLength hunk
|
||||
(offsetA, offsetB) = runJoin . fmap (show . getSum) $ offset hunk
|
||||
|
||||
-- | Given the before and after sources, render a change to a string.
|
||||
showChange :: Both (Source Char) -> Change (SplitDiff a Info) -> String
|
||||
showChange sources change = showLines (snd sources) ' ' (snd <$> context change) ++ deleted ++ inserted
|
||||
where (deleted, inserted) = runBoth $ pure showLines <*> sources <*> Both ('-', '+') <*> Both.unzip (contents change)
|
||||
showChange sources change = showLines (snd sources) ' ' (maybeSnd . runJoin <$> context change) ++ deleted ++ inserted
|
||||
where (deleted, inserted) = runJoin $ pure showLines <*> sources <*> both '-' '+' <*> Join (unzip (fromThese Nothing Nothing . runJoin . fmap Just <$> contents change))
|
||||
|
||||
-- | Given a source, render a set of lines to a string with a prefix.
|
||||
showLines :: Source Char -> Char -> [Line (SplitDiff leaf Info)] -> String
|
||||
showLines :: Source Char -> Char -> [Maybe (SplitDiff leaf Info)] -> String
|
||||
showLines source prefix lines = fromMaybe "" . mconcat $ fmap prepend . showLine source <$> lines
|
||||
where prepend "" = ""
|
||||
prepend source = prefix : source
|
||||
|
||||
-- | Given a source, render a line to a string.
|
||||
showLine :: Source Char -> Line (SplitDiff leaf Info) -> Maybe String
|
||||
showLine source line | isEmpty line = Nothing
|
||||
| otherwise = Just . toString . (`slice` source) . unionRanges $ getRange <$> unLine line
|
||||
|
||||
-- | Return the range from a split diff.
|
||||
getRange :: SplitDiff leaf Info -> Range
|
||||
getRange splitDiff = case runFree splitDiff of
|
||||
(Free (Info range _ _ :< _)) -> range
|
||||
(Pure patch) -> range where (Info range _ _ :< _) = runCofree $ getSplitTerm patch
|
||||
showLine :: Source Char -> Maybe (SplitDiff leaf Info) -> Maybe String
|
||||
showLine source line | Just line <- line = Just . toString . (`slice` source) $ getRange line
|
||||
| otherwise = Nothing
|
||||
|
||||
-- | Returns the header given two source blobs and a hunk.
|
||||
header :: Both SourceBlob -> String
|
||||
@ -106,33 +101,33 @@ header blobs = intercalate "\n" [filepathHeader, fileModeHeader, beforeFilepath,
|
||||
Nothing -> "/dev/null"
|
||||
beforeFilepath = "--- " ++ modeHeader "a" modeA pathA
|
||||
afterFilepath = "+++ " ++ modeHeader "b" modeB pathB
|
||||
(pathA, pathB) = runBoth $ path <$> blobs
|
||||
(oidA, oidB) = runBoth $ oid <$> blobs
|
||||
(modeA, modeB) = runBoth $ blobKind <$> blobs
|
||||
(pathA, pathB) = runJoin $ path <$> blobs
|
||||
(oidA, oidB) = runJoin $ oid <$> blobs
|
||||
(modeA, modeB) = runJoin $ blobKind <$> blobs
|
||||
|
||||
-- | A hunk representing no changes.
|
||||
emptyHunk :: Hunk (SplitDiff a Info)
|
||||
emptyHunk = Hunk { offset = mempty, changes = [], trailingContext = [] }
|
||||
|
||||
-- | Render a diff as a series of hunks.
|
||||
hunks :: Diff a Info -> Both SourceBlob -> [Hunk (SplitDiff a Info)]
|
||||
hunks :: Show a => Diff a Info -> Both SourceBlob -> [Hunk (SplitDiff a Info)]
|
||||
hunks _ blobs | sources <- source <$> blobs
|
||||
, sourcesEqual <- runBothWith (==) sources
|
||||
, sourcesNull <- runBothWith (&&) (null <$> sources)
|
||||
, sourcesEqual || sourcesNull
|
||||
= [emptyHunk]
|
||||
hunks diff blobs = hunksInRows (Both (1, 1)) $ fmap (fmap Prologue.fst) <$> splitDiffByLines (source <$> blobs) diff
|
||||
hunks diff blobs = hunksInRows (pure 1) $ alignDiff (source <$> blobs) diff
|
||||
|
||||
-- | Given beginning line numbers, turn rows in a split diff into hunks in a
|
||||
-- | patch.
|
||||
hunksInRows :: Both (Sum Int) -> [Row (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)]
|
||||
hunksInRows :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> [Hunk (SplitDiff a Info)]
|
||||
hunksInRows start rows = case nextHunk start rows of
|
||||
Nothing -> []
|
||||
Just (hunk, rest) -> hunk : hunksInRows (offset hunk <> hunkLength hunk) rest
|
||||
|
||||
-- | Given beginning line numbers, return the next hunk and the remaining rows
|
||||
-- | of the split diff.
|
||||
nextHunk :: Both (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Row (SplitDiff a Info)])
|
||||
nextHunk :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Hunk (SplitDiff a Info), [Join These (SplitDiff a Info)])
|
||||
nextHunk start rows = case nextChange start rows of
|
||||
Nothing -> Nothing
|
||||
Just (offset, change, rest) -> let (changes, rest') = contiguousChanges rest in Just (Hunk offset (change : changes) $ take 3 rest', drop 3 rest')
|
||||
@ -144,7 +139,7 @@ nextHunk start rows = case nextChange start rows of
|
||||
|
||||
-- | Given beginning line numbers, return the number of lines to the next
|
||||
-- | the next change, and the remaining rows of the split diff.
|
||||
nextChange :: Both (Sum Int) -> [Row (SplitDiff a Info)] -> Maybe (Both (Sum Int), Change (SplitDiff a Info), [Row (SplitDiff a Info)])
|
||||
nextChange :: Both (Sum Int) -> [Join These (SplitDiff a Info)] -> Maybe (Both (Sum Int), Change (SplitDiff a Info), [Join These (SplitDiff a Info)])
|
||||
nextChange start rows = case changeIncludingContext leadingContext afterLeadingContext of
|
||||
Nothing -> Nothing
|
||||
Just (change, afterChanges) -> Just (start <> mconcat (rowIncrement <$> skippedContext), change, afterChanges)
|
||||
@ -154,20 +149,12 @@ nextChange start rows = case changeIncludingContext leadingContext afterLeadingC
|
||||
-- | Return a Change with the given context and the rows from the begginning of
|
||||
-- | the given rows that have changes, or Nothing if the first row has no
|
||||
-- | changes.
|
||||
changeIncludingContext :: [Row (SplitDiff a Info)] -> [Row (SplitDiff a Info)] -> Maybe (Change (SplitDiff a Info), [Row (SplitDiff a Info)])
|
||||
changeIncludingContext :: [Join These (SplitDiff a Info)] -> [Join These (SplitDiff a Info)] -> Maybe (Change (SplitDiff a Info), [Join These (SplitDiff a Info)])
|
||||
changeIncludingContext leadingContext rows = case changes of
|
||||
[] -> Nothing
|
||||
_ -> Just (Change leadingContext changes, afterChanges)
|
||||
where (changes, afterChanges) = span rowHasChanges rows
|
||||
|
||||
-- | Whether a row has changes on either side.
|
||||
rowHasChanges :: Row (SplitDiff a Info) -> Bool
|
||||
rowHasChanges lines = or (lineHasChanges <$> lines)
|
||||
|
||||
-- | Whether a line has changes.
|
||||
lineHasChanges :: Line (SplitDiff a Info) -> Bool
|
||||
lineHasChanges = or . fmap diffHasChanges
|
||||
|
||||
-- | Whether a split diff has changes.
|
||||
diffHasChanges :: SplitDiff a Info -> Bool
|
||||
diffHasChanges = or . fmap (const True)
|
||||
rowHasChanges :: Join These (SplitDiff a Info) -> Bool
|
||||
rowHasChanges row = or (hasChanges <$> row)
|
||||
|
@ -4,12 +4,15 @@ module Renderer.Split where
|
||||
import Data.String
|
||||
import Alignment
|
||||
import Category
|
||||
import Data.Bifunctor.Join
|
||||
import Data.Foldable
|
||||
import Data.Functor.Both
|
||||
import Data.Functor.Foldable
|
||||
import Data.Functor.Foldable (cata)
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import Data.These
|
||||
import Info
|
||||
import Line
|
||||
import Prologue hiding (div, head, snd, link)
|
||||
import Prologue hiding (div, head, fst, snd, link)
|
||||
import qualified Prologue
|
||||
import Range
|
||||
import Renderer
|
||||
import Source hiding ((++))
|
||||
@ -22,6 +25,9 @@ import Text.Blaze.Html5 hiding (map)
|
||||
import qualified Text.Blaze.Html5.Attributes as A
|
||||
import qualified Text.Blaze.Internal as Blaze
|
||||
|
||||
-- | Return the first item in the Foldable, or Nothing if it's empty.
|
||||
maybeFirst :: Foldable f => f a -> Maybe a
|
||||
maybeFirst = foldr (const . Just) Nothing
|
||||
|
||||
-- | Add the first category from a Foldable of categories as a class name as a
|
||||
-- | class name on the markup, prefixed by `category-`.
|
||||
@ -59,10 +65,10 @@ split diff blobs = TL.toStrict . renderHtml
|
||||
. mconcat $ numberedLinesToMarkup <$> numbered
|
||||
where
|
||||
sources = Source.source <$> blobs
|
||||
numbered = numberedRows (fmap (fmap Prologue.fst) <$> splitDiffByLines sources diff)
|
||||
numbered = numberedRows (alignDiff sources diff)
|
||||
maxNumber = case numbered of
|
||||
[] -> 0
|
||||
(row : _) -> runBothWith max $ Prologue.fst <$> row
|
||||
(row : _) -> mergeThese max . runJoin $ Prologue.fst <$> row
|
||||
|
||||
-- | The number of digits in a number (e.g. 342 has 3 digits).
|
||||
digits :: Int -> Int
|
||||
@ -72,11 +78,15 @@ split diff blobs = TL.toStrict . renderHtml
|
||||
columnWidth = max (20 + digits maxNumber * 8) 40
|
||||
|
||||
-- | Render a line with numbers as an HTML row.
|
||||
numberedLinesToMarkup :: Both (Int, Line (SplitDiff a Info)) -> Markup
|
||||
numberedLinesToMarkup numberedLines = tr $ runBothWith mappend (renderLine <$> numberedLines <*> sources) `mappend` string "\n"
|
||||
numberedLinesToMarkup :: Join These (Int, SplitDiff a Info) -> Markup
|
||||
numberedLinesToMarkup numberedLines = tr $ runBothWith mappend (renderLine <$> Join (fromThese Nothing Nothing (runJoin (Just <$> numberedLines))) <*> sources) `mappend` string "\n"
|
||||
|
||||
renderLine :: (Int, Line (SplitDiff leaf Info)) -> Source Char -> Markup
|
||||
renderLine (number, line) source = toMarkup $ Renderable (hasChanges line, number, Renderable . (,) source <$> line)
|
||||
renderLine :: Maybe (Int, SplitDiff leaf Info) -> Source Char -> Markup
|
||||
renderLine (Just (number, line)) source = toMarkup $ Renderable (hasChanges line, number, Renderable (source, line))
|
||||
renderLine _ _ =
|
||||
td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell")
|
||||
`mappend` td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell")
|
||||
`mappend` string "\n"
|
||||
|
||||
-- | Something that can be rendered as markup.
|
||||
newtype Renderable a = Renderable a
|
||||
@ -84,20 +94,23 @@ newtype Renderable a = Renderable a
|
||||
instance ToMarkup f => ToMarkup (Renderable (Source Char, Info, Syntax a (f, Range))) where
|
||||
toMarkup (Renderable (source, Info range categories size, syntax)) = (! A.data_ (stringValue (show size))) . classifyMarkup categories $ case syntax of
|
||||
Leaf _ -> span . string . toString $ slice range source
|
||||
Indexed children -> ul . mconcat $ wrapIn li <$> contentElements children
|
||||
Fixed children -> ul . mconcat $ wrapIn li <$> contentElements children
|
||||
Keyed children -> dl . mconcat $ wrapIn dd <$> contentElements children
|
||||
where markupForSeparatorAndChild :: ToMarkup f => ([Markup], Int) -> (f, Range) -> ([Markup], Int)
|
||||
markupForSeparatorAndChild (rows, previous) (child, range) = (rows ++ [ string (toString $ slice (Range previous $ start range) source), toMarkup child ], end range)
|
||||
Indexed children -> ul . mconcat $ wrapIn li <$> contentElements source range children
|
||||
Fixed children -> ul . mconcat $ wrapIn li <$> contentElements source range children
|
||||
Keyed children -> dl . mconcat $ wrapIn dd <$> contentElements source range children
|
||||
|
||||
wrapIn _ l@Blaze.Leaf{} = l
|
||||
wrapIn _ l@Blaze.CustomLeaf{} = l
|
||||
wrapIn _ l@Blaze.Content{} = l
|
||||
wrapIn _ l@Blaze.Comment{} = l
|
||||
wrapIn f p = f p
|
||||
contentElements :: (Foldable t, ToMarkup f) => Source Char -> Range -> t (f, Range) -> [Markup]
|
||||
contentElements source range children = let (elements, next) = foldr' (markupForContextAndChild source) ([], end range) children in
|
||||
string (toString (slice (Range (start range) (max next (start range))) source)) : elements
|
||||
|
||||
contentElements children = let (elements, previous) = foldl' markupForSeparatorAndChild ([], start range) children in
|
||||
elements ++ [ string . toString $ slice (Range previous $ end range) source ]
|
||||
markupForContextAndChild :: ToMarkup f => Source Char -> (f, Range) -> ([Markup], Int) -> ([Markup], Int)
|
||||
markupForContextAndChild source (child, range) (rows, next) = (toMarkup child : string (toString (slice (Range (end range) next) source)) : rows, start range)
|
||||
|
||||
wrapIn :: (Markup -> Markup) -> Markup -> Markup
|
||||
wrapIn _ l@Blaze.Leaf{} = l
|
||||
wrapIn _ l@Blaze.CustomLeaf{} = l
|
||||
wrapIn _ l@Blaze.Content{} = l
|
||||
wrapIn _ l@Blaze.Comment{} = l
|
||||
wrapIn f p = f p
|
||||
|
||||
instance ToMarkup (Renderable (Source Char, Term a Info)) where
|
||||
toMarkup (Renderable (source, term)) = Prologue.fst $ cata (\ (info@(Info range _ _) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) term
|
||||
@ -105,16 +118,11 @@ instance ToMarkup (Renderable (Source Char, Term a Info)) where
|
||||
instance ToMarkup (Renderable (Source Char, SplitDiff a Info)) where
|
||||
toMarkup (Renderable (source, diff)) = Prologue.fst $ iter (\ (info@(Info range _ _) :< syntax) -> (toMarkup $ Renderable (source, info, syntax), range)) $ toMarkupAndRange <$> diff
|
||||
where toMarkupAndRange :: SplitPatch (Term a Info) -> (Markup, Range)
|
||||
toMarkupAndRange patch = let term@(Info range _ _ :< _) = runCofree $ getSplitTerm patch in
|
||||
((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue . show . termSize $ cofree term)) . toMarkup $ Renderable (source, cofree term), range)
|
||||
toMarkupAndRange patch = let term@(Info range _ size :< _) = runCofree $ getSplitTerm patch in
|
||||
((div ! A.class_ (splitPatchToClassName patch) ! A.data_ (stringValue (show size))) . toMarkup $ Renderable (source, cofree term), range)
|
||||
|
||||
|
||||
instance ToMarkup a => ToMarkup (Renderable (Bool, Int, Line a)) where
|
||||
toMarkup (Renderable (_, _, line)) | isEmpty line =
|
||||
td mempty ! A.class_ (stringValue "blob-num blob-num-empty empty-cell")
|
||||
`mappend` td mempty ! A.class_ (stringValue "blob-code blob-code-empty empty-cell")
|
||||
`mappend` string "\n"
|
||||
instance ToMarkup a => ToMarkup (Renderable (Bool, Int, a)) where
|
||||
toMarkup (Renderable (hasChanges, num, line)) =
|
||||
td (string $ show num) ! A.class_ (stringValue $ if hasChanges then "blob-num blob-num-replacement" else "blob-num")
|
||||
`mappend` td (mconcat $ toMarkup <$> unLine line) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code")
|
||||
`mappend` td (toMarkup line) ! A.class_ (stringValue $ if hasChanges then "blob-code blob-code-replacement" else "blob-code")
|
||||
`mappend` string "\n"
|
||||
|
@ -81,3 +81,8 @@ actualLines source = case Source.break (== '\n') source of
|
||||
actualLineRanges :: Range -> Source Char -> [Range]
|
||||
actualLineRanges range = drop 1 . scanl toRange (Range (start range) (start range)) . actualLines . slice range
|
||||
where toRange previous string = Range (end previous) $ end previous + length string
|
||||
|
||||
|
||||
instance Monoid (Source a) where
|
||||
mempty = fromList []
|
||||
mappend = (Source.++)
|
||||
|
@ -1,5 +1,7 @@
|
||||
module SplitDiff where
|
||||
|
||||
import Info
|
||||
import Range
|
||||
import Prologue
|
||||
import Syntax
|
||||
import Term (Term)
|
||||
@ -14,5 +16,11 @@ getSplitTerm (SplitInsert a) = a
|
||||
getSplitTerm (SplitDelete a) = a
|
||||
getSplitTerm (SplitReplace a) = a
|
||||
|
||||
-- | Get the range of a SplitDiff.
|
||||
getRange :: SplitDiff leaf Info -> Range
|
||||
getRange diff = characterRange $ case runFree diff of
|
||||
Free annotated -> headF annotated
|
||||
Pure patch -> extract (getSplitTerm patch)
|
||||
|
||||
-- | A diff with only one side’s annotations.
|
||||
type SplitDiff leaf annotation = Free (CofreeF (Syntax leaf) annotation) (SplitPatch (Term leaf annotation))
|
||||
|
@ -21,7 +21,7 @@ zipTerms :: Term a annotation -> Term a annotation -> Maybe (Term a (Both annota
|
||||
zipTerms t1 t2 = annotate (zipUnwrap a b)
|
||||
where
|
||||
(annotation1 :< a, annotation2 :< b) = (runCofree t1, runCofree t2)
|
||||
annotate = fmap (cofree . (Both (annotation1, annotation2) :<))
|
||||
annotate = fmap (cofree . (both annotation1 annotation2 :<))
|
||||
zipUnwrap (Leaf _) (Leaf b') = Just $ Leaf b'
|
||||
zipUnwrap (Indexed a') (Indexed b') = Just . Indexed . catMaybes $ zipWith zipTerms a' b'
|
||||
zipUnwrap (Fixed a') (Fixed b') = Just . Fixed . catMaybes $ zipWith zipTerms a' b'
|
||||
|
@ -1,63 +1,306 @@
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
module AlignmentSpec where
|
||||
|
||||
import Test.Hspec
|
||||
import Test.Hspec.QuickCheck
|
||||
import Test.QuickCheck hiding (Fixed)
|
||||
import Data.Text.Arbitrary ()
|
||||
|
||||
import Alignment
|
||||
import ArbitraryTerm (arbitraryLeaf)
|
||||
import Control.Arrow
|
||||
import Data.Adjoined
|
||||
import ArbitraryTerm ()
|
||||
import Control.Arrow ((&&&))
|
||||
import Control.Monad.State
|
||||
import Data.Align hiding (align)
|
||||
import Data.Bifunctor
|
||||
import Data.Bifunctor.Join
|
||||
import Data.Functor.Both as Both
|
||||
import Diff
|
||||
import Data.List (nub)
|
||||
import Data.Monoid
|
||||
import Data.String
|
||||
import Data.Text.Arbitrary ()
|
||||
import Data.These
|
||||
import Info
|
||||
import Line
|
||||
import Patch
|
||||
import Prologue hiding (fst, snd)
|
||||
import qualified Prologue
|
||||
import Range
|
||||
import Source hiding ((++), fromList)
|
||||
import qualified Source
|
||||
import SplitDiff
|
||||
import Syntax
|
||||
import Term
|
||||
import Test.Hspec
|
||||
import Test.Hspec.QuickCheck
|
||||
import Test.QuickCheck
|
||||
|
||||
spec :: Spec
|
||||
spec = parallel $ do
|
||||
describe "splitDiffByLines" $ do
|
||||
prop "preserves line counts in equal sources" $
|
||||
\ source ->
|
||||
length (splitDiffByLines (pure source) (free . Free $ (pure $ Info (totalRange source) mempty 1) :< (Indexed . Prologue.fst $ foldl combineIntoLeaves ([], 0) source))) `shouldBe` length (filter (== '\n') $ toString source) + 1
|
||||
describe "alignBranch" $ do
|
||||
it "produces symmetrical context" $
|
||||
alignBranch getRange ([] :: [Join These (SplitDiff String Info)]) (both [Range 0 2, Range 2 4] [Range 0 2, Range 2 4]) `shouldBe`
|
||||
[ Join (These (Range 0 2, [])
|
||||
(Range 0 2, []))
|
||||
, Join (These (Range 2 4, [])
|
||||
(Range 2 4, []))
|
||||
]
|
||||
|
||||
prop "produces the maximum line count in inequal sources" $
|
||||
\ sources -> let ranges = actualLineRanges <$> (totalRange <$> sources) <*> sources in
|
||||
length (splitDiffByLines sources (free . Free $ ((\ s -> Info (totalRange s) mempty 0) <$> sources) :< (Indexed $ leafWithRangesInSources sources <$> runBothWith (zipWith both) ranges))) `shouldBe` runBothWith max ((+ 1) . length . filter (== '\n') . toString <$> sources)
|
||||
it "produces asymmetrical context" $
|
||||
alignBranch getRange ([] :: [Join These (SplitDiff String Info)]) (both [Range 0 2, Range 2 4] [Range 0 1]) `shouldBe`
|
||||
[ Join (These (Range 0 2, [])
|
||||
(Range 0 1, []))
|
||||
, Join (This (Range 2 4, []))
|
||||
]
|
||||
|
||||
describe "splitAbstractedTerm" $ do
|
||||
prop "preserves line count" $
|
||||
\ source -> let range = totalRange source in
|
||||
splitAbstractedTerm ((cofree .) . (:<)) (Identity source) (Identity (Info range mempty 0) :< Leaf source) `shouldBe` (Identity . lineMap (fmap (cofree . (:< Leaf source) . (\ r -> Info r mempty 0) &&& identity)) <$> linesInRangeOfSource range source)
|
||||
prop "covers every input line" $
|
||||
\ elements -> let (_, children, ranges) = toAlignBranchInputs elements in
|
||||
join <$> (traverse (modifyJoin (fromThese [] []) . fmap pure . fmap Prologue.fst) (alignBranch Prologue.snd children ranges)) `shouldBe` ranges
|
||||
|
||||
let makeTerm = ((free .) . (Free .) . (:<)) :: Info -> Syntax (Source Char) (SplitDiff (Source Char) Info) -> SplitDiff (Source Char) Info
|
||||
prop "outputs one row for single-line unchanged leaves" $
|
||||
forAll (arbitraryLeaf `suchThat` isOnSingleLine) $
|
||||
\ (source, (Info range categories _), syntax) -> splitAbstractedTerm makeTerm (pure source) (pure (Info range categories 0) :< syntax) `shouldBe` fromList [
|
||||
both (pure (makeTerm (Info range categories 0) $ Leaf source, Range 0 (length source))) (pure (makeTerm (Info range categories 0) $ Leaf source, Range 0 (length source))) ]
|
||||
prop "covers every input child" $
|
||||
\ elements -> let (_, children, ranges) = toAlignBranchInputs elements in
|
||||
sort (nub (keysOfAlignedChildren (alignBranch Prologue.snd children ranges))) `shouldBe` sort (nub (catMaybes (branchElementKey <$> elements)))
|
||||
|
||||
prop "outputs one row for single-line empty unchanged indexed nodes" $
|
||||
forAll (arbitrary `suchThat` (\ a -> filter (/= '\n') (toString a) == toString a)) $
|
||||
\ source -> splitAbstractedTerm makeTerm (pure source) (pure (Info (totalRange source) mempty 0) :< Indexed []) `shouldBe` fromList [
|
||||
both (pure (makeTerm (Info (totalRange source) mempty 0) $ Indexed [], Range 0 (length source))) (pure (makeTerm (Info (totalRange source) mempty 0) $ Indexed [], Range 0 (length source))) ]
|
||||
prop "covers every line of every input child" $
|
||||
\ elements -> let (_, children, ranges) = toAlignBranchInputs elements in
|
||||
sort (keysOfAlignedChildren (alignBranch Prologue.snd children ranges)) `shouldBe` sort (do
|
||||
line <- children
|
||||
these (pure . Prologue.fst) (pure . Prologue.fst) (\ (k1, _) (k2, _) -> [ k1, k2 ]) . runJoin $ line)
|
||||
|
||||
where
|
||||
isOnSingleLine (a, _, _) = filter (/= '\n') (toString a) == toString a
|
||||
describe "alignDiff" $ do
|
||||
it "aligns identical branches on a single line" $
|
||||
let sources = both (Source.fromList "[ foo ]") (Source.fromList "[ foo ]") in
|
||||
align sources (pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 7 `branch` [ info 2 5 `leaf` "foo" ])
|
||||
(info 0 7 `branch` [ info 2 5 `leaf` "foo" ])) ]
|
||||
|
||||
combineIntoLeaves (leaves, start) char = (leaves ++ [ free . Free $ (Info <$> pure (Range start $ start + 1) <*> mempty <*> pure 1) :< Leaf [ char ] ], start + 1)
|
||||
it "aligns identical branches spanning multiple lines" $
|
||||
let sources = both (Source.fromList "[\nfoo\n]") (Source.fromList "[\nfoo\n]") in
|
||||
align sources (pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 2 `branch` [])
|
||||
(info 0 2 `branch` []))
|
||||
, Join (These (info 2 6 `branch` [ info 2 5 `leaf` "foo" ])
|
||||
(info 2 6 `branch` [ info 2 5 `leaf` "foo" ]))
|
||||
, Join (These (info 6 7 `branch` [])
|
||||
(info 6 7 `branch` []))
|
||||
]
|
||||
|
||||
leafWithRangesInSources sources ranges = free . Free $ (Info <$> ranges <*> pure mempty <*> pure 1) :< (Leaf $ runBothWith (++) (toString <$> sources))
|
||||
it "aligns reformatted branches" $
|
||||
let sources = both (Source.fromList "[ foo ]") (Source.fromList "[\nfoo\n]") in
|
||||
align sources (pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (That (info 0 2 `branch` []))
|
||||
, Join (These (info 0 7 `branch` [ info 2 5 `leaf` "foo" ])
|
||||
(info 2 6 `branch` [ info 2 5 `leaf` "foo" ]))
|
||||
, Join (That (info 6 7 `branch` []))
|
||||
]
|
||||
|
||||
leafWithRangeInSource source range = Info range mempty 1 :< Leaf source
|
||||
it "aligns nodes following reformatted branches" $
|
||||
let sources = both (Source.fromList "[ foo ]\nbar\n") (Source.fromList "[\nfoo\n]\nbar\n") in
|
||||
align sources (pure (info 0 12) `branch` [ pure (info 0 7) `branch` [ pure (info 2 5) `leaf` "foo" ], pure (info 8 11) `leaf` "bar" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (That (info 0 2 `branch` [ info 0 2 `branch` [] ]))
|
||||
, Join (These (info 0 8 `branch` [ info 0 7 `branch` [ info 2 5 `leaf` "foo" ] ])
|
||||
(info 2 6 `branch` [ info 2 6 `branch` [ info 2 5 `leaf` "foo" ] ]))
|
||||
, Join (That (info 6 8 `branch` [ info 6 7 `branch` [] ]))
|
||||
, Join (These (info 8 12 `branch` [ info 8 11 `leaf` "bar" ])
|
||||
(info 8 12 `branch` [ info 8 11 `leaf` "bar" ]))
|
||||
, Join (These (info 12 12 `branch` [])
|
||||
(info 12 12 `branch` []))
|
||||
]
|
||||
|
||||
patchWithBoth (Insert ()) = Insert . snd
|
||||
patchWithBoth (Delete ()) = Delete . fst
|
||||
patchWithBoth (Replace () ()) = runBothWith Replace
|
||||
it "aligns identical branches with multiple children on the same line" $
|
||||
let sources = pure (Source.fromList "[ foo, bar ]") in
|
||||
align sources (pure (info 0 12) `branch` [ pure (info 2 5) `leaf` "foo", pure (info 7 10) `leaf` "bar" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (runBothWith These (pure (info 0 12 `branch` [ info 2 5 `leaf` "foo", info 7 10 `leaf` "bar" ])) ) ]
|
||||
|
||||
it "aligns insertions" $
|
||||
let sources = both (Source.fromList "a") (Source.fromList "a\nb") in
|
||||
align sources (both (info 0 1) (info 0 3) `branch` [ pure (info 0 1) `leaf` "a", insert (info 2 3 `leaf` "b") ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 1 `branch` [ info 0 1 `leaf` "a" ])
|
||||
(info 0 2 `branch` [ info 0 1 `leaf` "a" ]))
|
||||
, Join (That (info 2 3 `branch` [ insert (info 2 3 `leaf` "b") ]))
|
||||
]
|
||||
|
||||
it "aligns total insertions" $
|
||||
let sources = both (Source.fromList "") (Source.fromList "a") in
|
||||
align sources (insert (info 0 1 `leaf` "a")) `shouldBe` prettyDiff sources
|
||||
[ Join (That (insert (info 0 1 `leaf` "a"))) ]
|
||||
|
||||
it "aligns insertions into empty branches" $
|
||||
let sources = both (Source.fromList "[ ]") (Source.fromList "[a]") in
|
||||
align sources (pure (info 0 3) `branch` [ insert (info 1 2 `leaf` "a") ]) `shouldBe` prettyDiff sources
|
||||
[ Join (That (info 0 3 `branch` [ insert (info 1 2 `leaf` "a") ]))
|
||||
, Join (This (info 0 3 `branch` []))
|
||||
]
|
||||
|
||||
it "aligns symmetrically following insertions" $
|
||||
let sources = both (Source.fromList "a\nc") (Source.fromList "a\nb\nc") in
|
||||
align sources (both (info 0 3) (info 0 5) `branch` [ pure (info 0 1) `leaf` "a", insert (info 2 3 `leaf` "b"), both (info 2 3) (info 4 5) `leaf` "c" ])
|
||||
`shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 2 `branch` [ info 0 1 `leaf` "a" ])
|
||||
(info 0 2 `branch` [ info 0 1 `leaf` "a" ]))
|
||||
, Join (That (info 2 4 `branch` [ insert (info 2 3 `leaf` "b") ]))
|
||||
, Join (These (info 2 3 `branch` [ info 2 3 `leaf` "c" ])
|
||||
(info 4 5 `branch` [ info 4 5 `leaf` "c" ]))
|
||||
]
|
||||
|
||||
it "symmetrical nodes force the alignment of asymmetrical nodes on both sides" $
|
||||
let sources = both (Source.fromList "[ a, b ]") (Source.fromList "[ b, c ]") in
|
||||
align sources (pure (info 0 8) `branch` [ delete (info 2 3 `leaf` "a"), both (info 5 6) (info 2 3) `leaf` "b", insert (info 5 6 `leaf` "c") ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 8 `branch` [ delete (info 2 3 `leaf` "a"), info 5 6 `leaf` "b" ])
|
||||
(info 0 8 `branch` [ info 2 3 `leaf` "b", insert (info 5 6 `leaf` "c") ])) ]
|
||||
|
||||
it "when one of two symmetrical nodes must be split, splits the latter" $
|
||||
let sources = both (Source.fromList "[ a, b ]") (Source.fromList "[ a\n, b\n]") in
|
||||
align sources (both (info 0 8) (info 0 9) `branch` [ pure (info 2 3) `leaf` "a", both (info 5 6) (info 6 7) `leaf` "b" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 8 `branch` [ info 2 3 `leaf` "a", info 5 6 `leaf` "b" ])
|
||||
(info 0 4 `branch` [ info 2 3 `leaf` "a" ]))
|
||||
, Join (That (info 4 8 `branch` [ info 6 7 `leaf` "b" ]))
|
||||
, Join (That (info 8 9 `branch` []))
|
||||
]
|
||||
|
||||
it "aligns deletions before insertions" $
|
||||
let sources = both (Source.fromList "[ a ]") (Source.fromList "[ b ]") in
|
||||
align sources (pure (info 0 5) `branch` [ delete (info 2 3 `leaf` "a"), insert (info 2 3 `leaf` "b") ]) `shouldBe` prettyDiff sources
|
||||
[ Join (This (info 0 5 `branch` [ delete (info 2 3 `leaf` "a") ]))
|
||||
, Join (That (info 0 5 `branch` [ insert (info 2 3 `leaf` "b") ]))
|
||||
]
|
||||
|
||||
it "aligns context-only lines symmetrically" $
|
||||
let sources = both (Source.fromList "[\n a\n,\n b\n]") (Source.fromList "[\n a, b\n\n\n]") in
|
||||
align sources (both (info 0 13) (info 0 12) `branch` [ pure (info 4 5) `leaf` "a", both (info 10 11) (info 7 8) `leaf` "b" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 2 `branch` [])
|
||||
(info 0 2 `branch` []))
|
||||
, Join (These (info 2 6 `branch` [ info 4 5 `leaf` "a" ])
|
||||
(info 2 9 `branch` [ info 4 5 `leaf` "a", info 7 8 `leaf` "b" ]))
|
||||
, Join (These (info 6 8 `branch` [])
|
||||
(info 9 10 `branch` []))
|
||||
, Join (This (info 8 12 `branch` [ info 10 11 `leaf` "b" ]))
|
||||
, Join (These (info 12 13 `branch` [])
|
||||
(info 10 11 `branch` []))
|
||||
, Join (That (info 11 12 `branch` []))
|
||||
]
|
||||
|
||||
it "aligns asymmetrical nodes preceding their symmetrical siblings conservatively" $
|
||||
let sources = both (Source.fromList "[ b, c ]") (Source.fromList "[ a\n, c\n]") in
|
||||
align sources (both (info 0 8) (info 0 9) `branch` [ insert (info 2 3 `leaf` "a"), delete (info 2 3 `leaf` "b"), both (info 5 6) (info 6 7) `leaf` "c" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (That (info 0 4 `branch` [ insert (info 2 3 `leaf` "a") ]))
|
||||
, Join (These (info 0 8 `branch` [ delete (info 2 3 `leaf` "b"), info 5 6 `leaf` "c" ])
|
||||
(info 4 8 `branch` [ info 6 7 `leaf` "c" ]))
|
||||
, Join (That (info 8 9 `branch` []))
|
||||
]
|
||||
|
||||
it "aligns symmetrical reformatted nodes" $
|
||||
let sources = both (Source.fromList "a [ b ]\nc") (Source.fromList "a [\nb\n]\nc") in
|
||||
align sources (pure (info 0 9) `branch` [ pure (info 0 1) `leaf` "a", pure (info 2 7) `branch` [ pure (info 4 5) `leaf` "b" ], pure (info 8 9) `leaf` "c" ]) `shouldBe` prettyDiff sources
|
||||
[ Join (These (info 0 8 `branch` [ info 0 1 `leaf` "a", info 2 7 `branch` [ info 4 5 `leaf` "b" ] ])
|
||||
(info 0 4 `branch` [ info 0 1 `leaf` "a", info 2 4 `branch` [] ]))
|
||||
, Join (That (info 4 6 `branch` [ info 4 6 `branch` [ info 4 5 `leaf` "b" ] ]))
|
||||
, Join (That (info 6 8 `branch` [ info 6 7 `branch` [] ]))
|
||||
, Join (These (info 8 9 `branch` [ info 8 9 `leaf` "c" ])
|
||||
(info 8 9 `branch` [ info 8 9 `leaf` "c" ]))
|
||||
]
|
||||
|
||||
describe "numberedRows" $
|
||||
prop "counts only non-empty values" $
|
||||
\ xs -> counts (numberedRows (xs :: [Join These Char])) `shouldBe` length . catMaybes <$> Join (unalign (runJoin <$> xs))
|
||||
|
||||
data BranchElement
|
||||
= Child String (Join These String)
|
||||
| Margin (Join These String)
|
||||
deriving Show
|
||||
|
||||
branchElementKey :: BranchElement -> Maybe String
|
||||
branchElementKey (Child key _) = Just key
|
||||
branchElementKey _ = Nothing
|
||||
|
||||
toAlignBranchInputs :: [BranchElement] -> (Both (Source.Source Char), [Join These (String, Range)], Both [Range])
|
||||
toAlignBranchInputs elements = (sources, join . (`evalState` both 0 0) . traverse go $ elements, ranges)
|
||||
where go :: BranchElement -> State (Both Int) [Join These (String, Range)]
|
||||
go child@(Child key _) = do
|
||||
lines <- traverse (\ (Child _ contents) -> do
|
||||
prev <- get
|
||||
let next = (+) <$> prev <*> modifyJoin (fromThese 0 0) (length <$> contents)
|
||||
put next
|
||||
pure $! modifyJoin (runBothWith bimap (const <$> (Range <$> prev <*> next))) contents) (alignBranchElement child)
|
||||
pure $! fmap ((,) key) <$> lines
|
||||
go (Margin contents) = do
|
||||
prev <- get
|
||||
put $ (+) <$> prev <*> modifyJoin (fromThese 0 0) (length <$> contents)
|
||||
pure []
|
||||
alignBranchElement element = case element of
|
||||
Child key contents -> Child key <$> crosswalk lines contents
|
||||
Margin contents -> Margin <$> crosswalk lines contents
|
||||
where lines = fmap toList . Source.actualLines . Source.fromList
|
||||
sources = foldMap Source.fromList <$> bothContents elements
|
||||
ranges = fmap (filter (\ (Range start end) -> start /= end)) $ Source.actualLineRanges <$> (totalRange <$> sources) <*> sources
|
||||
bothContents = foldMap (modifyJoin (fromThese [] []) . fmap (:[]) . branchElementContents)
|
||||
branchElementContents (Child _ contents) = contents
|
||||
branchElementContents (Margin contents) = contents
|
||||
|
||||
keysOfAlignedChildren :: [Join These (Range, [(String, Range)])] -> [String]
|
||||
keysOfAlignedChildren lines = lines >>= these identity identity (++) . runJoin . fmap (fmap Prologue.fst . Prologue.snd)
|
||||
|
||||
instance Arbitrary BranchElement where
|
||||
arbitrary = oneof [ key >>= \ key -> Child key <$> joinTheseOf (contents key)
|
||||
, Margin <$> joinTheseOf margin ]
|
||||
where key = listOf1 (elements (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']))
|
||||
contents key = wrap key <$> listOf (padding '*')
|
||||
wrap key contents = "(" ++ key ++ contents ++ ")" :: String
|
||||
margin = listOf (padding '-')
|
||||
padding char = frequency [ (10, pure char)
|
||||
, (1, pure '\n') ]
|
||||
joinTheseOf g = oneof [ Join . This <$> g
|
||||
, Join . That <$> g
|
||||
, (Join .) . These <$> g <*> g ]
|
||||
|
||||
shrink (Child key contents) = Child key <$> crosswalk shrinkContents contents
|
||||
where shrinkContents string = (++ suffix) . (prefix ++) <$> shrinkList (const []) (drop (length prefix) (take (length string - length suffix) string))
|
||||
(prefix, suffix) = ('(' : key, ")" :: String)
|
||||
shrink (Margin contents) = Margin <$> crosswalk (shrinkList (const [])) contents
|
||||
|
||||
counts :: [Join These (Int, a)] -> Both Int
|
||||
counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered))
|
||||
|
||||
align :: Both (Source.Source Char) -> ConstructibleFree (Patch (Term String Info)) (Both Info) -> PrettyDiff (SplitDiff String Info)
|
||||
align sources = PrettyDiff sources . fmap (fmap (getRange &&& identity)) . alignDiff sources . deconstruct
|
||||
|
||||
info :: Int -> Int -> Info
|
||||
info = ((\ r -> Info r mempty 0) .) . Range
|
||||
|
||||
prettyDiff :: Both (Source.Source Char) -> [Join These (ConstructibleFree (SplitPatch (Term String Info)) Info)] -> PrettyDiff (SplitDiff String Info)
|
||||
prettyDiff sources = PrettyDiff sources . fmap (fmap ((getRange &&& identity) . deconstruct))
|
||||
|
||||
data PrettyDiff a = PrettyDiff { unPrettySources :: Both (Source.Source Char), unPrettyLines :: [Join These (Range, a)] }
|
||||
deriving Eq
|
||||
|
||||
instance Show a => Show (PrettyDiff a) where
|
||||
showsPrec _ (PrettyDiff sources lines) = (prettyPrinted ++) -- . (("\n" ++ show lines) ++)
|
||||
where prettyPrinted = showLine (maximum (0 : (maximum . fmap length <$> shownLines))) <$> shownLines >>= ('\n':)
|
||||
shownLines = catMaybes $ toBoth <$> lines
|
||||
showLine n line = uncurry ((++) . (++ " | ")) (fromThese (replicate n ' ') (replicate n ' ') (runJoin (pad n <$> line)))
|
||||
showDiff (range, _) = filter (/= '\n') . toList . Source.slice range
|
||||
pad n string = (++) (take n string) (replicate (max 0 (n - length string)) ' ')
|
||||
toBoth them = showDiff <$> them `applyThese` modifyJoin (uncurry These) sources
|
||||
|
||||
newtype ConstructibleFree patch annotation = ConstructibleFree { deconstruct :: Free (CofreeF (Syntax String) annotation) patch }
|
||||
|
||||
|
||||
class PatchConstructible p where
|
||||
insert :: Term String Info -> p
|
||||
delete :: Term String Info -> p
|
||||
|
||||
instance PatchConstructible (Patch (Term String Info)) where
|
||||
insert = Insert
|
||||
delete = Delete
|
||||
|
||||
instance PatchConstructible (SplitPatch (Term String Info)) where
|
||||
insert = SplitInsert
|
||||
delete = SplitDelete
|
||||
|
||||
instance PatchConstructible patch => PatchConstructible (ConstructibleFree patch annotation) where
|
||||
insert = ConstructibleFree . pure . insert
|
||||
delete = ConstructibleFree . pure . delete
|
||||
|
||||
class SyntaxConstructible s where
|
||||
leaf :: annotation -> String -> s annotation
|
||||
branch :: annotation -> [s annotation] -> s annotation
|
||||
|
||||
instance SyntaxConstructible (ConstructibleFree patch) where
|
||||
leaf info = ConstructibleFree . free . Free . (info :<) . Leaf
|
||||
branch info = ConstructibleFree . free . Free . (info :<) . Indexed . fmap deconstruct
|
||||
|
||||
instance SyntaxConstructible (Cofree (Syntax String)) where
|
||||
info `leaf` value = cofree $ info :< Leaf value
|
||||
info `branch` children = cofree $ info :< Indexed children
|
||||
|
@ -1,14 +1,17 @@
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
module ArbitraryTerm where
|
||||
|
||||
import Category
|
||||
import Data.Functor.Foldable
|
||||
import Data.Bifunctor.Join
|
||||
import Data.Functor.Both
|
||||
import Data.Functor.Foldable
|
||||
import qualified Data.OrderedMap as Map
|
||||
import qualified Data.List as List
|
||||
import qualified Data.Set as Set
|
||||
import Data.Text.Arbitrary ()
|
||||
import Data.These
|
||||
import Info
|
||||
import Line
|
||||
import Patch
|
||||
import Prologue hiding (fst, snd)
|
||||
import Range
|
||||
@ -27,14 +30,14 @@ unTerm = unfold unpack
|
||||
instance (Eq a, Eq annotation, Arbitrary a, Arbitrary annotation) => Arbitrary (ArbitraryTerm a annotation) where
|
||||
arbitrary = scale (`div` 2) $ sized (\ x -> boundedTerm x x) -- first indicates the cube of the max length of lists, second indicates the cube of the max depth of the tree
|
||||
where boundedTerm maxLength maxDepth = ArbitraryTerm <$> ((,) <$> arbitrary <*> boundedSyntax maxLength maxDepth)
|
||||
boundedSyntax _ maxDepth | maxDepth <= 0 = liftM Leaf arbitrary
|
||||
boundedSyntax _ maxDepth | maxDepth <= 0 = Leaf <$> arbitrary
|
||||
boundedSyntax maxLength maxDepth = frequency
|
||||
[ (12, liftM Leaf arbitrary),
|
||||
(1, liftM Indexed $ take maxLength <$> listOf (smallerTerm maxLength maxDepth)),
|
||||
(1, liftM Fixed $ take maxLength <$> listOf (smallerTerm maxLength maxDepth)),
|
||||
(1, liftM (Keyed . Map.fromList) $ take maxLength <$> listOf (arbitrary >>= (\x -> (,) x <$> smallerTerm maxLength maxDepth))) ]
|
||||
[ (12, Leaf <$> arbitrary),
|
||||
(1, Indexed . take maxLength <$> listOf (smallerTerm maxLength maxDepth)),
|
||||
(1, Fixed . take maxLength <$> listOf (smallerTerm maxLength maxDepth)),
|
||||
(1, Keyed . Map.fromList . take maxLength <$> listOf (arbitrary >>= (\x -> (,) x <$> smallerTerm maxLength maxDepth))) ]
|
||||
smallerTerm maxLength maxDepth = boundedTerm (div maxLength 3) (div maxDepth 3)
|
||||
shrink term@(ArbitraryTerm (annotation, syntax)) = (++) (subterms term) $ filter (/= term) $
|
||||
shrink term@(ArbitraryTerm (annotation, syntax)) = (subterms term ++) $ filter (/= term) $
|
||||
ArbitraryTerm <$> ((,) <$> shrink annotation <*> case syntax of
|
||||
Leaf a -> Leaf <$> shrink a
|
||||
Indexed i -> Indexed <$> (List.subsequences i >>= recursivelyShrink)
|
||||
@ -52,13 +55,19 @@ instance Categorizable CategorySet where
|
||||
instance Arbitrary CategorySet where
|
||||
arbitrary = elements [ A, B, C, D ]
|
||||
|
||||
instance Arbitrary a => Arbitrary (Both a) where
|
||||
arbitrary = pure (curry Both) <*> arbitrary <*> arbitrary
|
||||
shrink b = both <$> (shrink (fst b)) <*> (shrink (snd b))
|
||||
instance Arbitrary a => Arbitrary (Join (,) a) where
|
||||
arbitrary = both <$> arbitrary <*> arbitrary
|
||||
shrink b = both <$> shrink (fst b) <*> shrink (snd b)
|
||||
|
||||
instance Arbitrary a => Arbitrary (Line a) where
|
||||
arbitrary = oneof [ Line <$> arbitrary, Closed <$> arbitrary ]
|
||||
shrink line = (`lineMap` line) . const <$> shrinkList shrink (unLine line)
|
||||
instance (Arbitrary a, Arbitrary b) => Arbitrary (These a b) where
|
||||
arbitrary = oneof [ This <$> arbitrary
|
||||
, That <$> arbitrary
|
||||
, These <$> arbitrary <*> arbitrary ]
|
||||
shrink = these (fmap This . shrink) (fmap That . shrink) (\ a b -> (This <$> shrink a) ++ (That <$> shrink b) ++ (These <$> shrink a <*> shrink b))
|
||||
|
||||
instance Arbitrary a => Arbitrary (Join These a) where
|
||||
arbitrary = Join <$> arbitrary
|
||||
shrink (Join a) = Join <$> shrink a
|
||||
|
||||
instance Arbitrary a => Arbitrary (Patch a) where
|
||||
arbitrary = oneof [
|
||||
|
@ -1,3 +1,4 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
module CorpusSpec where
|
||||
|
||||
import System.IO
|
||||
@ -10,14 +11,11 @@ import qualified Renderer.Split as Split
|
||||
|
||||
import Control.DeepSeq
|
||||
import Data.Functor.Both
|
||||
import qualified Data.ByteString.Lazy.Char8 as B
|
||||
import Data.List as List
|
||||
import Data.Map as Map
|
||||
import Data.Set as Set
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Lazy as TL
|
||||
import Prologue hiding (fst, snd)
|
||||
import qualified Prologue
|
||||
import qualified Source as S
|
||||
import System.FilePath
|
||||
import System.FilePath.Glob
|
||||
@ -35,7 +33,7 @@ spec = parallel $ do
|
||||
examples "test/diffs/" `shouldNotReturn` []
|
||||
|
||||
where
|
||||
runTestsIn :: String -> (T.Text -> T.Text -> Expectation) -> SpecWith ()
|
||||
runTestsIn :: FilePath -> (Verbatim -> Verbatim -> Expectation) -> SpecWith ()
|
||||
runTestsIn directory matcher = do
|
||||
paths <- runIO $ examples directory
|
||||
let tests = correctTests =<< paths
|
||||
@ -58,9 +56,9 @@ examples directory = do
|
||||
patches <- toDict <$> globFor "*.patch.*"
|
||||
splits <- toDict <$> globFor "*.split.*"
|
||||
let keys = Set.unions $ keysSet <$> [as, bs]
|
||||
pure $ (\name -> (Both (as ! name, bs ! name), Map.lookup name jsons, Map.lookup name patches, Map.lookup name splits)) <$> sort (Set.toList keys)
|
||||
pure $ (\name -> (both (as ! name) (bs ! name), Map.lookup name jsons, Map.lookup name patches, Map.lookup name splits)) <$> sort (Set.toList keys)
|
||||
where
|
||||
globFor :: String -> IO [FilePath]
|
||||
globFor :: FilePath -> IO [FilePath]
|
||||
globFor p = globDir1 (compile p) directory
|
||||
toDict list = Map.fromList ((normalizeName <$> list) `zip` list)
|
||||
|
||||
@ -71,14 +69,21 @@ normalizeName path = addExtension (dropExtension $ dropExtension path) (takeExte
|
||||
-- | Given file paths for A, B, and, optionally, a diff, return whether diffing
|
||||
-- | the files will produce the diff. If no diff is provided, then the result
|
||||
-- | is true, but the diff will still be calculated.
|
||||
testDiff :: Renderer -> Both FilePath -> Maybe FilePath -> (T.Text -> T.Text -> Expectation) -> Expectation
|
||||
testDiff :: Renderer -> Both FilePath -> Maybe FilePath -> (Verbatim -> Verbatim -> Expectation) -> Expectation
|
||||
testDiff renderer paths diff matcher = do
|
||||
sources <- sequence $ readAndTranscodeFile <$> paths
|
||||
actual <- diffFiles parser renderer (sourceBlobs sources)
|
||||
actual <- Verbatim <$> diffFiles parser renderer (sourceBlobs sources)
|
||||
case diff of
|
||||
Nothing -> matcher actual actual
|
||||
Just file -> do
|
||||
expected <- T.pack <$> readFile file
|
||||
expected <- Verbatim . T.pack <$> readFile file
|
||||
matcher actual expected
|
||||
where parser = parserForFilepath (fst paths)
|
||||
sourceBlobs sources = Both (S.SourceBlob, S.SourceBlob) <*> sources <*> pure mempty <*> paths <*> pure (Just S.defaultPlainBlob)
|
||||
sourceBlobs sources = pure S.SourceBlob <*> sources <*> pure mempty <*> paths <*> pure (Just S.defaultPlainBlob)
|
||||
|
||||
-- | A wrapper around `Text` with a more readable `Show` instance.
|
||||
newtype Verbatim = Verbatim Text
|
||||
deriving (Eq, NFData)
|
||||
|
||||
instance Show Verbatim where
|
||||
showsPrec _ (Verbatim text) = ('\n':) . (T.unpack text ++)
|
||||
|
@ -1,94 +0,0 @@
|
||||
module Data.Adjoined.Spec (spec) where
|
||||
|
||||
import Prologue
|
||||
import Data.String
|
||||
import ArbitraryTerm ()
|
||||
import Data.Adjoined
|
||||
import Data.Coalescent
|
||||
import Data.Functor.Both
|
||||
import Data.Typeable
|
||||
import Line
|
||||
import Test.Hspec
|
||||
import Test.Hspec.QuickCheck
|
||||
import Test.QuickCheck
|
||||
|
||||
spec :: Spec
|
||||
spec = do
|
||||
prop "equality is reflexive" $
|
||||
\ a -> a `shouldBe` (a :: Adjoined (Uncoalesced Char))
|
||||
|
||||
monoid (arbitrary :: Gen (Adjoined (Coalesced String)))
|
||||
monoid (arbitrary :: Gen (Adjoined (Uncoalesced String)))
|
||||
monoid (arbitrary :: Gen (Adjoined (Semicoalesced String)))
|
||||
monoid (arbitrary :: Gen (Adjoined (Line Char)))
|
||||
-- monoid (arbitrary :: Gen (Adjoined (Both (Line Char))))
|
||||
|
||||
monoid :: (Arbitrary a, Coalescent a, Eq a, Show a, Typeable a) => Gen (Adjoined a) -> Spec
|
||||
monoid gen =
|
||||
describe ("Monoid (" ++ showTypeOf (`asGeneratedTypeOf` gen) ++ ")") $ do
|
||||
describe "mempty" $ do
|
||||
prop "left identity" $ forAll gen $
|
||||
\ a -> mempty `mappend` a `shouldBe` a
|
||||
|
||||
prop "right identity" $ forAll gen $
|
||||
\ a -> a `mappend` mempty `shouldBe` a
|
||||
|
||||
describe "mappend" $ do
|
||||
prop "associativity" $ forAll gen $
|
||||
\ a b c -> (a `mappend` b) `mappend` c `shouldBe` a `mappend` (b `mappend` c)
|
||||
|
||||
|
||||
instance Arbitrary a => Arbitrary (Adjoined a) where
|
||||
arbitrary = fromList <$> arbitrary
|
||||
shrink arbitrary = fromList <$> shrinkList shrink (toList arbitrary)
|
||||
|
||||
|
||||
-- | A wrapper which never coalesces values.
|
||||
newtype Uncoalesced a = Uncoalesced { runUncoalesced :: a }
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance Arbitrary a => Arbitrary (Uncoalesced a) where
|
||||
arbitrary = Uncoalesced <$> arbitrary
|
||||
|
||||
instance Coalescent (Uncoalesced a) where
|
||||
coalesce a b = pure a <|> pure b
|
||||
|
||||
|
||||
-- | A wrapper which always coalesces values.
|
||||
newtype Coalesced a = Coalesced { runCoalesced :: a }
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance Arbitrary a => Arbitrary (Coalesced a) where
|
||||
arbitrary = Coalesced <$> arbitrary
|
||||
|
||||
instance Monoid a => Coalescent (Coalesced a) where
|
||||
coalesce a b = pure (Coalesced (runCoalesced a `mappend` runCoalesced b))
|
||||
|
||||
|
||||
-- | A wrapper which coalesces asymmetrically.
|
||||
-- |
|
||||
-- | Specifically, it coalesces only when the value at the left has `True` set.
|
||||
newtype Semicoalesced a = Semicoalesced { runSemicoalesced :: (Bool, a) }
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance Arbitrary a => Arbitrary (Semicoalesced a) where
|
||||
arbitrary = Semicoalesced <$> arbitrary
|
||||
|
||||
instance Monoid a => Coalescent (Semicoalesced a) where
|
||||
Semicoalesced (True, a) `coalesce` Semicoalesced (flag, b) = pure (Semicoalesced (flag, a `mappend` b))
|
||||
a `coalesce` b = pure a <|> pure b
|
||||
|
||||
|
||||
-- | Returns a string with the name of a type.
|
||||
-- |
|
||||
-- | Use with `asTypeOf` or `asGeneratedTypeOf` to show type names for parameters without fighting type variable scoping:
|
||||
-- |
|
||||
-- | showTypeOf (`asTypeOf` someTypeParametricValue)
|
||||
showTypeOf :: Typeable a => (a -> a) -> String
|
||||
showTypeOf f = show (typeRep (proxyOf f))
|
||||
where proxyOf :: (a -> a) -> Proxy a
|
||||
proxyOf _ = Proxy
|
||||
|
||||
-- | Type-restricted `const`, usually written infix or as an operator section with `showTypeOf`.
|
||||
asGeneratedTypeOf :: a -> Gen a -> a
|
||||
asGeneratedTypeOf = const
|
@ -1,23 +0,0 @@
|
||||
module Data.Functor.Both.Spec (spec) where
|
||||
|
||||
import Prologue
|
||||
import Data.Adjoined
|
||||
import Data.Coalescent
|
||||
import Data.Functor.Both
|
||||
import Line
|
||||
import Test.Hspec
|
||||
|
||||
spec :: Spec
|
||||
spec = do
|
||||
describe "Coalescent" $ do
|
||||
it "should coalesce when both sides coalesce" $
|
||||
(pure (Line [True]) `coalesce` pure (Line [True]) :: Adjoined (Both (Line Bool))) `shouldBe` fromList [pure (Line [True, True])]
|
||||
|
||||
it "should not coalesce when neither side coalesces" $
|
||||
(pure (Closed [True]) `coalesce` pure (Line [True]) :: Adjoined (Both (Line Bool))) `shouldBe` fromList [pure (Closed [True]), pure (Line [True])]
|
||||
|
||||
it "should coalesce asymmetrically at left" $
|
||||
(both (Line [True]) (Closed [True]) `coalesce` pure (Line [True]) :: Adjoined (Both (Line Bool))) `shouldBe` fromList [both (Line []) (Closed [True]), both (Line [True, True]) (Line [True])]
|
||||
|
||||
it "should coalesce asymmetrically at right" $
|
||||
(both (Closed [True]) (Line [True]) `coalesce` pure (Line [True]) :: Adjoined (Both (Line Bool))) `shouldBe` fromList [both (Closed [True]) (Line []), both (Line [True]) (Line [True, True])]
|
@ -2,6 +2,8 @@ module PatchOutputSpec where
|
||||
|
||||
import Prologue
|
||||
import Data.Functor.Both
|
||||
import Data.String
|
||||
import Diff
|
||||
import Info
|
||||
import Range
|
||||
import Renderer.Patch
|
||||
@ -13,4 +15,4 @@ spec :: Spec
|
||||
spec = parallel $
|
||||
describe "hunks" $
|
||||
it "empty diffs have empty hunks" $
|
||||
hunks (free . Free $ pure (Info (Range 0 0) mempty 1) :< Leaf "") (Both (SourceBlob (fromList "") "abcde" "path2.txt" (Just defaultPlainBlob), SourceBlob (fromList "") "xyz" "path2.txt" (Just defaultPlainBlob))) `shouldBe` [Hunk {offset = Both (0, 0), changes = [], trailingContext = []}]
|
||||
hunks (free . Free $ pure (Info (Range 0 0) mempty 1) :< Leaf "") (both (SourceBlob (fromList "") "abcde" "path2.txt" (Just defaultPlainBlob)) (SourceBlob (fromList "") "xyz" "path2.txt" (Just defaultPlainBlob))) `shouldBe` [Hunk {offset = pure 0, changes = [], trailingContext = []}]
|
||||
|
@ -3,8 +3,6 @@ module Main where
|
||||
import Prologue
|
||||
import qualified AlignmentSpec
|
||||
import qualified CorpusSpec
|
||||
import qualified Data.Adjoined.Spec
|
||||
import qualified Data.Functor.Both.Spec
|
||||
import qualified InterpreterSpec
|
||||
import qualified OrderedMapSpec
|
||||
import qualified PatchOutputSpec
|
||||
@ -16,8 +14,6 @@ main :: IO ()
|
||||
main = hspec $ parallel $ do
|
||||
describe "Alignment" AlignmentSpec.spec
|
||||
describe "Corpus" CorpusSpec.spec
|
||||
describe "Data.Adjoined" Data.Adjoined.Spec.spec
|
||||
describe "Data.Functor.Both" Data.Functor.Both.Spec.spec
|
||||
describe "Interpreter" InterpreterSpec.spec
|
||||
describe "OrderedMap" OrderedMapSpec.spec
|
||||
describe "PatchOutput" PatchOutputSpec.spec
|
||||
|
2
test/crashers/573.A.js
Normal file
2
test/crashers/573.A.js
Normal file
@ -0,0 +1,2 @@
|
||||
if (a &&
|
||||
b) {}
|
1
test/crashers/573.B.js
Normal file
1
test/crashers/573.B.js
Normal file
@ -0,0 +1 @@
|
||||
if (a && b) {}
|
@ -1,30 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">7</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="5"><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,27 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="1"><ul class="category-if_statement">if (<li><span class="category-true">true</span></li>) <li><ul class="category-statement_block">{
|
||||
</ul></li></ul></div></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="5"><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">cruel</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></div></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">4</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="0"><ul class="category-if_statement"><li><ul class="category-statement_block">}</ul></li></ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,24 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement">if (<li><span class="category-true">true</span></li>) <li><ul class="category-statement_block">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement">if (<li><span class="category-true">true</span></li>) <li><ul class="category-statement_block">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block"> <li><div class="patch insert" data="5"><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">'</span></li><li><span class="category-string">world</span></li><li><span class="category-string">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block">}</ul></li><li><ul class="category-statement_block"></ul></li></ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"><li><ul class="category-if_statement"><li><ul class="category-statement_block">}</ul></li></ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1,18 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="5"><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">insertion</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1 +0,0 @@
|
||||
console.log("hello, world");
|
@ -1,3 +0,0 @@
|
||||
console.log("hello, world");
|
||||
|
||||
console.log("insertion");
|
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">hello</span></li><li><span class="category-string">,</span></li> <li><span class="category-string">world</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program"><li><div class="patch insert" data="5"><ul class="category-expression_statement"><li><ul class="category-function_call"><li><ul class="category-member_access"><li><span class="category-identifier">console</span></li>.<li><span class="category-identifier">log</span></li></ul></li>(<li><ul class="category-arguments"><li><ul class="category-string"><li><span class="category-string">"</span></li><li><span class="category-string">insertion</span></li><li><span class="category-string">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1 +0,0 @@
|
||||
[ bar ]
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array">[ <li><span class="category-identifier">bar</span></li> ]</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array">[
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array"> <li><span class="category-identifier">bar</span></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"><li><ul class="category-array">]</ul></li>
|
||||
</ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"></ul></li></ul></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program"><li><ul class="category-expression_statement"></ul></li></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
31
test/diffs/asymmetrical-context.split.js
Normal file
31
test/diffs/asymmetrical-context.split.js
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="21">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="21">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="21">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="21">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="21"></ul></td>
|
||||
<td class="blob-num">7</td><td class="blob-code"><ul class="category-program" data="21"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1 +1 @@
|
||||
{"rows":[[{"terms":[{"children":[{"children":[{"children":{},"categories":["DictionaryLiteral"],"range":[0,2]}],"categories":["expression_statement"],"range":[0,2]}],"categories":["program"],"range":[0,2]}],"hasChanges":false,"range":[0,2],"number":1},{"terms":[{"children":[{"children":[{"children":{},"categories":["DictionaryLiteral"],"range":[0,2]}],"categories":["expression_statement"],"range":[0,2]}],"categories":["program"],"range":[0,2]}],"hasChanges":false,"range":[0,2],"number":1}],[{"terms":[{"children":[{"children":[{"children":{"\"b\"":{"children":[{"children":[{"categories":["StringLiteral"],"range":[4,5]},{"categories":["StringLiteral"],"range":[5,6]},{"categories":["StringLiteral"],"range":[6,7]}],"categories":["StringLiteral"],"range":[4,7]},{"patch":"replace","categories":["number"],"range":[9,10]}],"categories":["Pair"],"range":[4,10]}},"categories":["DictionaryLiteral"],"range":[2,12]}],"categories":["expression_statement"],"range":[2,12]}],"categories":["program"],"range":[2,12]}],"hasChanges":true,"range":[2,12],"number":2},{"terms":[{"children":[{"children":[{"children":{"\"b\"":{"children":[{"children":[{"categories":["StringLiteral"],"range":[4,5]},{"categories":["StringLiteral"],"range":[5,6]},{"categories":["StringLiteral"],"range":[6,7]}],"categories":["StringLiteral"],"range":[4,7]},{"patch":"replace","categories":["number"],"range":[9,10]}],"categories":["Pair"],"range":[4,10]}},"categories":["DictionaryLiteral"],"range":[2,12]}],"categories":["expression_statement"],"range":[2,12]}],"categories":["program"],"range":[2,12]}],"hasChanges":true,"range":[2,12],"number":2}],[{"terms":[{"children":[{"children":[{"children":{"\"a\"":{"children":[{"children":[{"categories":["StringLiteral"],"range":[14,15]},{"categories":["StringLiteral"],"range":[15,16]},{"categories":["StringLiteral"],"range":[16,17]}],"categories":["StringLiteral"],"range":[14,17]},{"categories":["number"],"range":[19,20]}],"categories":["Pair"],"range":[14,20]}},"categories":["DictionaryLiteral"],"range":[12,21]}],"categories":["expression_statement"],"range":[12,21]}],"categories":["program"],"range":[12,21]}],"hasChanges":false,"range":[12,21],"number":3},{"terms":[{"children":[{"children":[{"children":{"\"a\"":{"children":[{"children":[{"categories":["StringLiteral"],"range":[14,15]},{"categories":["StringLiteral"],"range":[15,16]},{"categories":["StringLiteral"],"range":[16,17]}],"categories":["StringLiteral"],"range":[14,17]},{"categories":["number"],"range":[19,20]}],"categories":["Pair"],"range":[14,20]}},"categories":["DictionaryLiteral"],"range":[12,21]}],"categories":["expression_statement"],"range":[12,21]}],"categories":["program"],"range":[12,21]}],"hasChanges":false,"range":[12,21],"number":3}],[{"terms":[{"children":[{"children":[{"children":{},"categories":["DictionaryLiteral"],"range":[21,22]}],"categories":["expression_statement"],"range":[21,23]}],"categories":["program"],"range":[21,23]}],"hasChanges":false,"range":[21,23],"number":4},{"terms":[{"children":[{"children":[{"children":{},"categories":["DictionaryLiteral"],"range":[21,22]}],"categories":["expression_statement"],"range":[21,23]}],"categories":["program"],"range":[21,23]}],"hasChanges":false,"range":[21,23],"number":4}],[{"terms":[{"children":[{"children":[],"categories":["expression_statement"],"range":[23,23]}],"categories":["program"],"range":[23,23]}],"hasChanges":false,"range":[23,23],"number":5},{"terms":[{"children":[{"children":[],"categories":["expression_statement"],"range":[23,23]}],"categories":["program"],"range":[23,23]}],"hasChanges":false,"range":[23,23],"number":5}]],"paths":["test/diffs/dictionary.A.js","test/diffs/dictionary.B.js"],"oids":["",""]}
|
||||
{"rows":[[{"number":1,"terms":[{"range":[0,2],"categories":["program"],"children":[{"range":[0,2],"categories":["expression_statement"],"children":[{"range":[0,2],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[0,2],"hasChanges":false},{"number":1,"terms":[{"range":[0,2],"categories":["program"],"children":[{"range":[0,2],"categories":["expression_statement"],"children":[{"range":[0,2],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[0,2],"hasChanges":false}],[{"number":2,"terms":[{"range":[2,12],"categories":["program"],"children":[{"range":[2,12],"categories":["expression_statement"],"children":[{"range":[2,12],"categories":["DictionaryLiteral"],"children":{"\"b\"":{"range":[4,10],"categories":["Pair"],"children":[{"range":[4,7],"categories":["StringLiteral"],"children":[{"range":[4,5],"categories":["StringLiteral"]},{"range":[5,6],"categories":["StringLiteral"]},{"range":[6,7],"categories":["StringLiteral"]}]},{"patch":"replace","range":[9,10],"categories":["number"]}]}}}]}]}],"range":[2,12],"hasChanges":true},{"number":2,"terms":[{"range":[2,12],"categories":["program"],"children":[{"range":[2,12],"categories":["expression_statement"],"children":[{"range":[2,12],"categories":["DictionaryLiteral"],"children":{"\"b\"":{"range":[4,10],"categories":["Pair"],"children":[{"range":[4,7],"categories":["StringLiteral"],"children":[{"range":[4,5],"categories":["StringLiteral"]},{"range":[5,6],"categories":["StringLiteral"]},{"range":[6,7],"categories":["StringLiteral"]}]},{"patch":"replace","range":[9,10],"categories":["number"]}]}}}]}]}],"range":[2,12],"hasChanges":true}],[{"number":3,"terms":[{"range":[12,21],"categories":["program"],"children":[{"range":[12,21],"categories":["expression_statement"],"children":[{"range":[12,21],"categories":["DictionaryLiteral"],"children":{"\"a\"":{"range":[14,20],"categories":["Pair"],"children":[{"range":[14,17],"categories":["StringLiteral"],"children":[{"range":[14,15],"categories":["StringLiteral"]},{"range":[15,16],"categories":["StringLiteral"]},{"range":[16,17],"categories":["StringLiteral"]}]},{"range":[19,20],"categories":["number"]}]}}}]}]}],"range":[12,21],"hasChanges":false},{"number":3,"terms":[{"range":[12,21],"categories":["program"],"children":[{"range":[12,21],"categories":["expression_statement"],"children":[{"range":[12,21],"categories":["DictionaryLiteral"],"children":{"\"a\"":{"range":[14,20],"categories":["Pair"],"children":[{"range":[14,17],"categories":["StringLiteral"],"children":[{"range":[14,15],"categories":["StringLiteral"]},{"range":[15,16],"categories":["StringLiteral"]},{"range":[16,17],"categories":["StringLiteral"]}]},{"range":[19,20],"categories":["number"]}]}}}]}]}],"range":[12,21],"hasChanges":false}],[{"number":4,"terms":[{"range":[21,23],"categories":["program"],"children":[{"range":[21,23],"categories":["expression_statement"],"children":[{"range":[21,22],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[21,23],"hasChanges":false},{"number":4,"terms":[{"range":[21,23],"categories":["program"],"children":[{"range":[21,23],"categories":["expression_statement"],"children":[{"range":[21,22],"categories":["DictionaryLiteral"],"children":{}}]}]}],"range":[21,23],"hasChanges":false}],[{"number":5,"terms":[{"range":[23,23],"categories":["program"],"children":[]}],"range":[23,23],"hasChanges":false},{"number":5,"terms":[{"range":[23,23],"categories":["program"],"children":[]}],"range":[23,23],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/dictionary.A.js","test/diffs/dictionary.B.js"]}
|
@ -19,7 +19,7 @@
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"><li><dl class="category-dictionary" data="13">}</dl></li>
|
||||
</ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"></ul></li></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="15"><li><ul class="category-expression_statement" data="14"></ul></li></ul></td>
|
||||
</tr><tr><td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="15"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="15"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
14
test/diffs/insert.split.js
Normal file
14
test/diffs/insert.split.js
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="11"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="21"><li><div class="patch insert" data="10"><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="11"></ul></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="21"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -2,16 +2,16 @@ diff --git a/test/diffs/jquery.A.js b/test/diffs/jquery.B.js
|
||||
index .. 100644
|
||||
--- a/test/diffs/jquery.A.js
|
||||
+++ b/test/diffs/jquery.B.js
|
||||
@@ -3,7 +3,4 @@ define( function() {
|
||||
// We have to close these tags to support XHTML (#13200)
|
||||
var wrapMap = {
|
||||
|
||||
- // Support: IE9
|
||||
+ // Support: IE <=9 only
|
||||
option: [ 1, "<select multiple='multiple'>", "</select>" ],
|
||||
@@ -3,7 +3,7 @@
|
||||
// We have to close these tags to support XHTML (#13200)
|
||||
var wrapMap = {
|
||||
|
||||
// XHTML parsers do not magically insert elements in the
|
||||
@@ -17,7 +17,7 @@ var wrapMap = {
|
||||
- // Support: IE9
|
||||
+ // Support: IE <=9 only
|
||||
option: [ 1, "<select multiple='multiple'>", "</select>" ],
|
||||
|
||||
// XHTML parsers do not magically insert elements in the
|
||||
@@ -17,7 +17,7 @@
|
||||
_default: [ 0, "", "" ]
|
||||
};
|
||||
|
27
test/diffs/multiline-insert.split.js
Normal file
27
test/diffs/multiline-insert.split.js
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="34"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">2</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="34"><li><div class="patch insert" data="13"><ul class="category-if_statement" data="13">if (<li><span class="category-true" data="1">true</span></li>) <li><ul class="category-statement_block" data="11">{
|
||||
</ul></li></ul></div></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="34"><li><div class="patch insert" data="13"><ul class="category-if_statement" data="13"><li><ul class="category-statement_block" data="11"> <li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">cruel</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></div></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">4</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="34"><li><div class="patch insert" data="13"><ul class="category-if_statement" data="13"><li><ul class="category-statement_block" data="11">}</ul></li></ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="21"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="34"><li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="21"></ul></td>
|
||||
<td class="blob-num">6</td><td class="blob-code"><ul class="category-program" data="34"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
24
test/diffs/nested-insert.split.js
Normal file
24
test/diffs/nested-insert.split.js
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="14"><li><ul class="category-if_statement" data="13">if (<li><span class="category-true" data="1">true</span></li>) <li><ul class="category-statement_block" data="11">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23">if (<li><span class="category-true" data="1">true</span></li>) <li><ul class="category-statement_block" data="21">{
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="14"><li><ul class="category-if_statement" data="13"><li><ul class="category-statement_block" data="11"> <li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23"><li><ul class="category-statement_block" data="21"> <li><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23"><li><ul class="category-statement_block" data="21"> <li><div class="patch insert" data="10"><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">'</span></li><li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">'</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="14"><li><ul class="category-if_statement" data="13"><li><ul class="category-statement_block" data="11">}</ul></li></ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="24"><li><ul class="category-if_statement" data="23"><li><ul class="category-statement_block" data="21">}</ul></li></ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="14"></ul></td>
|
||||
<td class="blob-num">5</td><td class="blob-code"><ul class="category-program" data="24"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1 +1 @@
|
||||
{"rows":[[{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,29],"categories":["program"],"children":[]}],"range":[29,29],"hasChanges":false},{"number":2,"terms":[{"range":[29,30],"categories":["program"],"children":[]}],"range":[29,30],"hasChanges":false}],[{},{"number":3,"terms":[{"range":[30,56],"categories":["program"],"children":[{"patch":"insert","range":[30,55],"categories":["expression_statement"],"children":[{"range":[30,54],"categories":["FunctionCall"],"children":[{"range":[30,41],"categories":["member_access"],"children":[{"range":[30,37],"categories":["identifier"]},{"range":[38,41],"categories":["identifier"]}]},{"range":[42,53],"categories":["arguments"],"children":[{"range":[42,53],"categories":["StringLiteral"],"children":[{"range":[42,43],"categories":["StringLiteral"]},{"range":[43,52],"categories":["StringLiteral"]},{"range":[52,53],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[30,56],"hasChanges":true}],[{},{"number":4,"terms":[{"range":[56,56],"categories":["program"],"children":[]}],"range":[56,56],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/newline-at-eof.A.js","test/diffs/newline-at-eof.B.js"]}
|
||||
{"rows":[[{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,29],"categories":["program"],"children":[]}],"range":[29,29],"hasChanges":false},{"number":2,"terms":[{"range":[29,30],"categories":["program"],"children":[]}],"range":[29,30],"hasChanges":false}],[{"number":3,"terms":[{"range":[30,56],"categories":["program"],"children":[{"patch":"insert","range":[30,55],"categories":["expression_statement"],"children":[{"range":[30,54],"categories":["FunctionCall"],"children":[{"range":[30,41],"categories":["member_access"],"children":[{"range":[30,37],"categories":["identifier"]},{"range":[38,41],"categories":["identifier"]}]},{"range":[42,53],"categories":["arguments"],"children":[{"range":[42,53],"categories":["StringLiteral"],"children":[{"range":[42,43],"categories":["StringLiteral"]},{"range":[43,52],"categories":["StringLiteral"]},{"range":[52,53],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[30,56],"hasChanges":true}],[{"number":4,"terms":[{"range":[56,56],"categories":["program"],"children":[]}],"range":[56,56],"hasChanges":false}]],"oids":["",""],"paths":["test/diffs/newline-at-eof.A.js","test/diffs/newline-at-eof.B.js"]}
|
18
test/diffs/newline-at-eof.split.js
Normal file
18
test/diffs/newline-at-eof.split.js
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="13"><li><ul class="category-expression_statement" data="12"><li><ul class="category-function_call" data="11"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="23"><li><ul class="category-expression_statement" data="12"><li><ul class="category-function_call" data="11"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="13"></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="23">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="23"><li><div class="patch insert" data="10"><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">insertion</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">4</td><td class="blob-code"><ul class="category-program" data="23"></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
@ -1 +1 @@
|
||||
{"rows":[[{"number":1,"terms":[{"range":[0,28],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,28],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{},{"number":2,"terms":[{"range":[29,30],"categories":["program"],"children":[]}],"range":[29,30],"hasChanges":false}],[{},{"number":3,"terms":[{"range":[30,55],"categories":["program"],"children":[{"patch":"insert","range":[30,55],"categories":["expression_statement"],"children":[{"range":[30,54],"categories":["FunctionCall"],"children":[{"range":[30,41],"categories":["member_access"],"children":[{"range":[30,37],"categories":["identifier"]},{"range":[38,41],"categories":["identifier"]}]},{"range":[42,53],"categories":["arguments"],"children":[{"range":[42,53],"categories":["StringLiteral"],"children":[{"range":[42,43],"categories":["StringLiteral"]},{"range":[43,52],"categories":["StringLiteral"]},{"range":[52,53],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[30,55],"hasChanges":true}]],"oids":["",""],"paths":["test/diffs/no-newline-at-eof.A.js","test/diffs/no-newline-at-eof.B.js"]}
|
||||
{"rows":[[{"number":1,"terms":[{"range":[0,28],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,28],"hasChanges":false},{"number":1,"terms":[{"range":[0,29],"categories":["program"],"children":[{"range":[0,28],"categories":["expression_statement"],"children":[{"range":[0,27],"categories":["FunctionCall"],"children":[{"range":[0,11],"categories":["member_access"],"children":[{"range":[0,7],"categories":["identifier"]},{"range":[8,11],"categories":["identifier"]}]},{"range":[12,26],"categories":["arguments"],"children":[{"range":[12,26],"categories":["StringLiteral"],"children":[{"range":[12,13],"categories":["StringLiteral"]},{"range":[13,18],"categories":["StringLiteral"]},{"range":[18,19],"categories":["StringLiteral"]},{"range":[20,25],"categories":["StringLiteral"]},{"range":[25,26],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[0,29],"hasChanges":false}],[{"number":2,"terms":[{"range":[29,30],"categories":["program"],"children":[]}],"range":[29,30],"hasChanges":false}],[{"number":3,"terms":[{"range":[30,55],"categories":["program"],"children":[{"patch":"insert","range":[30,55],"categories":["expression_statement"],"children":[{"range":[30,54],"categories":["FunctionCall"],"children":[{"range":[30,41],"categories":["member_access"],"children":[{"range":[30,37],"categories":["identifier"]},{"range":[38,41],"categories":["identifier"]}]},{"range":[42,53],"categories":["arguments"],"children":[{"range":[42,53],"categories":["StringLiteral"],"children":[{"range":[42,43],"categories":["StringLiteral"]},{"range":[43,52],"categories":["StringLiteral"]},{"range":[52,53],"categories":["StringLiteral"]}]}]}]}]}]}],"range":[30,55],"hasChanges":true}]],"oids":["",""],"paths":["test/diffs/no-newline-at-eof.A.js","test/diffs/no-newline-at-eof.B.js"]}
|
@ -2,7 +2,7 @@ diff --git a/test/diffs/no-newline-at-eof.A.js b/test/diffs/no-newline-at-eof.B.
|
||||
index .. 100644
|
||||
--- a/test/diffs/no-newline-at-eof.A.js
|
||||
+++ b/test/diffs/no-newline-at-eof.B.js
|
||||
@@ -1,2 +1,3 @@
|
||||
@@ -1,1 +1,3 @@
|
||||
console.log("hello, world");
|
||||
|
||||
+console.log("insertion");
|
||||
|
13
test/diffs/no-newline-at-eof.split.js
Normal file
13
test/diffs/no-newline-at-eof.split.js
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="13"><li><ul class="category-expression_statement" data="12"><li><ul class="category-function_call" data="11"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="23"><li><ul class="category-expression_statement" data="12"><li><ul class="category-function_call" data="11"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="7"><li><ul class="category-string" data="6"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">hello</span></li><li><span class="category-string" data="1">,</span></li> <li><span class="category-string" data="1">world</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></li>
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="23">
|
||||
</ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num blob-num-replacement">3</td><td class="blob-code blob-code-replacement"><ul class="category-program" data="23"><li><div class="patch insert" data="10"><ul class="category-expression_statement" data="10"><li><ul class="category-function_call" data="9"><li><ul class="category-member_access" data="3"><li><span class="category-identifier" data="1">console</span></li>.<li><span class="category-identifier" data="1">log</span></li></ul></li>(<li><ul class="category-arguments" data="5"><li><ul class="category-string" data="4"><li><span class="category-string" data="1">"</span></li><li><span class="category-string" data="1">insertion</span></li><li><span class="category-string" data="1">"</span></li></ul></li></ul></li>)</ul></li>;</ul></div></li></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
1
test/diffs/reformat.A.js
Normal file
1
test/diffs/reformat.A.js
Normal file
@ -0,0 +1 @@
|
||||
[ bar ];
|
@ -1,3 +1,3 @@
|
||||
[
|
||||
bar
|
||||
]
|
||||
];
|
13
test/diffs/reformat.split.js
Normal file
13
test/diffs/reformat.split.js
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html><head><link rel="stylesheet" href="style.css"></head><body><table class="diff"><colgroup><col width="40"><col><col width="40"><col></colgroup><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2">[
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num">1</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2">[ <li><span class="category-identifier" data="1">bar</span></li> ]</ul></li>;</ul></li></ul></td>
|
||||
<td class="blob-num">2</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2"> <li><span class="category-identifier" data="1">bar</span></li>
|
||||
</ul></li></ul></li></ul></td>
|
||||
|
||||
</tr><tr><td class="blob-num blob-num-empty empty-cell"></td><td class="blob-code blob-code-empty empty-cell"></td>
|
||||
<td class="blob-num">3</td><td class="blob-code"><ul class="category-program" data="4"><li><ul class="category-expression_statement" data="3"><li><ul class="category-array" data="2">]</ul></li>;</ul></li></ul></td>
|
||||
|
||||
</tr></table></body></html>
|
81
weekly/2016-05-31.md
Normal file
81
weekly/2016-05-31.md
Normal file
@ -0,0 +1,81 @@
|
||||
# May 31th, 2016
|
||||
|
||||
NB: On Tuesday this week since Monday was Memorial Day.
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Retrospective on last week:
|
||||
- What went well?
|
||||
- What was challenging?
|
||||
- What did you learn?
|
||||
|
||||
|
||||
## What went well?
|
||||
|
||||
@joshvera:
|
||||
|
||||
- Diff summaries merged.
|
||||
- Introduced a new prelude.
|
||||
- Pairing with Rick.
|
||||
- Alignment!
|
||||
|
||||
@rewinfrey:
|
||||
|
||||
- Pairing with Josh.
|
||||
- Was at MoonConf and enjoyed the conference.
|
||||
- Almost finished with Haskell Tic Tac Toe.
|
||||
|
||||
@robrix:
|
||||
|
||||
- Alignment resolved!
|
||||
- Getting diff summaries merged.
|
||||
|
||||
|
||||
## What was challenging?
|
||||
|
||||
@joshvera:
|
||||
|
||||
- Didn't make as much progress on the structure of diff summaries as desired.
|
||||
|
||||
@rewinfrey:
|
||||
|
||||
- Continuing to adjust to the project.
|
||||
- Hit an edge case in Minimax that is tricky.
|
||||
|
||||
@robrix:
|
||||
|
||||
- Not sure why the line approach in alignment solved the problem.
|
||||
- In stack 1.1 you cannot rely on it rebuilding internal packages (must clean and rebuild semantic diff tool)
|
||||
|
||||
|
||||
## What did you learn?
|
||||
|
||||
@joshvera:
|
||||
|
||||
- Learned about the different types of preludes.
|
||||
- Learned about an extension in GHC 8.0 that introduces Applicative Do syntax, but is tricky to use in parallel computations because of the order of executation.
|
||||
|
||||
@rewinfrey:
|
||||
|
||||
- Learned about different recursion schemes (zygotmorphism
|
||||
- The transition from thinking about function application to thinking about function composition was an eye-opener.
|
||||
- Understanding a lot more about why certain shapes are advantageous.
|
||||
- http://livestream.com/accounts/16500216/events/5467460 (first video)
|
||||
- Greg Pfeil's talk: 02:24 Recursion Where FP Hits Bottom
|
||||
- Amar Shah's talk: 04:24 Point Free or Die
|
||||
- Ashley Powell's talk: 05:57 Negotiating Salary for Women in Tech
|
||||
- Patrick Thomson's talk: 06:25 Bracer: Transforming Real-World Languages with Coproducts and Recursion Schemes
|
||||
|
||||
@robrix:
|
||||
|
||||
- Learned a lot about GHCi debugging.
|
||||
- Remembered to question assumptions, specifically about alignment property tests that were throwing results off.
|
||||
- Using Arbitrary for purposes other than QuickCheck.
|
||||
|
||||
|
||||
## Other Items
|
||||
|
||||
@robrix:
|
||||
|
||||
- Planning this week about the timeline for staffshipping diff summaries.
|
||||
- Planning for a possible mini-summit in late June.
|
Loading…
Reference in New Issue
Block a user