diff --git a/prototype/UI/style.css b/prototype/UI/style.css index c0d290f9d..50cbbaccf 100644 --- a/prototype/UI/style.css +++ b/prototype/UI/style.css @@ -1,3 +1,6 @@ +table { + width: 100%; +} table.diff td { width: 50%; height: 15px; diff --git a/src/Split.hs b/src/Split.hs index f5323b8b8..deeefa728 100644 --- a/src/Split.hs +++ b/src/Split.hs @@ -5,22 +5,26 @@ import Diff import Patch import Term import Syntax -import Control.Monad + import Control.Comonad.Cofree import Range +import Control.Monad import Control.Monad.Free import Data.ByteString.Lazy.Internal -import Text.Blaze.Html5 +import Text.Blaze.Html5 hiding (map) import qualified Text.Blaze.Html5.Attributes as A import Text.Blaze.Html.Renderer.Utf8 -import qualified Data.Maybe as Maybe +import Data.Maybe import Data.Monoid import qualified Data.Set as Set +import Debug.Trace +import Data.List (intersperse) type ClassName = String data HTML = - Text String + Break + | Text String | Span (Maybe ClassName) String | Ul (Maybe ClassName) [HTML] | Dl (Maybe ClassName) [HTML] @@ -41,6 +45,7 @@ toDd (Text s) = string s toDd e = dd $ toMarkup e instance ToMarkup HTML where + toMarkup Break = br toMarkup (Text s) = string s toMarkup (Span className s) = classifyMarkup className . span $ string s toMarkup (Ul className children) = classifyMarkup className . ul $ mconcat (toLi <$> children) @@ -48,6 +53,9 @@ instance ToMarkup HTML where toMarkup (Div className children) = classifyMarkup className . div $ mconcat (toMarkup <$> children) toMarkup (Dt key) = dt $ string key +trace' :: Show a => a -> a +trace' a = traceShow a a + split :: Diff a Info -> String -> String -> IO ByteString split diff before after = return . renderHtml . docTypeHtml @@ -56,78 +64,105 @@ split diff before after = return . renderHtml . (table ! A.class_ (stringValue "diff")) . mconcat $ toMarkup <$> (fst $ diffToRows diff (0, 0) before after) -data Row = Row [HTML] [HTML] - deriving (Show, Eq) +data Row = Row Line Line + deriving Eq + +instance Show Row where + show (Row left right) = "\n" ++ show left ++ " | " ++ show right instance ToMarkup Row where - toMarkup (Row left right) = (tr $ (td . mconcat $ toMarkup <$> left) <> (td . mconcat $ toMarkup <$> right)) + toMarkup (Row left right) = (tr $ toMarkup left <> toMarkup right) <> string "\n" -bimap :: ([HTML] -> [HTML]) -> ([HTML] -> [HTML]) -> Row -> Row -bimap f g (Row a b) = Row (f a) (g b) +instance ToMarkup Line where + toMarkup EmptyLine = td (string "") + toMarkup (Line html) = td . mconcat $ toMarkup <$> html + +data Line = + Line [HTML] + | EmptyLine + deriving Eq + +unLine :: Line -> [HTML] +unLine EmptyLine = [] +unLine (Line htmls) = htmls + +instance Show Line where + show (Line elements) = "[" ++ (concat . intersperse ", " $ show <$> elements) ++ "]" + show EmptyLine = "EmptyLine" + +instance Monoid Line where + mempty = EmptyLine + mappend EmptyLine EmptyLine = EmptyLine + mappend EmptyLine (Line ys) = Line ys + mappend (Line xs) EmptyLine = Line xs + mappend (Line xs) (Line ys) = Line (xs <> ys) instance Monoid Row where - mempty = Row [] [] + mempty = Row EmptyLine EmptyLine mappend (Row x1 y1) (Row x2 y2) = Row (x1 <> x2) (y1 <> y2) diffToRows :: Diff a Info -> (Int, Int) -> String -> String -> ([Row], (Range, Range)) diffToRows (Free annotated) _ before after = annotatedToRows annotated before after -diffToRows (Pure (Insert term)) (previousIndex, _) _ after = (rowWithInsertedLine <$> afterLines, (range, Range previousIndex previousIndex)) +diffToRows (Pure (Insert term)) (previousIndex, _) _ after = (rowWithInsertedLine <$> afterLines, (Range previousIndex previousIndex, range)) where (afterLines, range) = termToLines term after - rowWithInsertedLine (Line elements) = Row [] [ Div (Just "insert") elements ] + rowWithInsertedLine (Line elements) = Row EmptyLine $ Line [ Div (Just "insert") elements ] + rowWithInsertedLine EmptyLine = mempty diffToRows (Pure (Delete term)) (_, previousIndex) before _ = (rowWithDeletedLine <$> lines, (range, Range previousIndex previousIndex)) where (lines, range) = termToLines term before - rowWithDeletedLine (Line elements) = Row [ Div (Just "delete") elements ] [] + rowWithDeletedLine (Line elements) = Row (Line [ Div (Just "delete") elements ]) EmptyLine + rowWithDeletedLine EmptyLine = mempty diffToRows (Pure (Replace a b)) _ before after = (replacedRows, (leftRange, rightRange)) where replacedRows = zipWithMaybe rowFromMaybeRows (replace <$> leftElements) (replace <$> rightElements) replace = (:[]) . Div (Just "replace") . unLine rowFromMaybeRows :: Maybe [HTML] -> Maybe [HTML] -> Row - rowFromMaybeRows a b = Row (join $ Maybe.maybeToList a) (join $ Maybe.maybeToList b) + rowFromMaybeRows a b = Row (maybe EmptyLine Line a) (maybe EmptyLine Line b) (leftElements, leftRange) = termToLines a before (rightElements, rightRange) = termToLines b after -newtype Line = Line { unLine :: [HTML] } deriving (Show, Eq) - -instance Monoid Line where - mempty = Line [] - mappend (Line xs) (Line ys) = Line (xs <> ys) - +-- | Takes a term and a `source` and returns a list of HTML lines +-- | and their range within `source`. termToLines :: Term a Info -> String -> ([Line], Range) termToLines (Info range _ categories :< syntax) source = (rows syntax, range) where - rows (Leaf _) = Line . (:[]) <$> elements + rows (Leaf _) = reverse $ foldl adjoin2Lines [] $ Line . (:[]) <$> elements rows (Indexed i) = rewrapLineContentsInUl <$> childLines i rewrapLineContentsInUl (Line elements) = Line [ Ul (classify categories) elements ] + rewrapLineContentsInUl EmptyLine = EmptyLine lineElements r s = Line . (:[]) <$> textElements r s childLines i = appendRemainder $ foldl sumLines ([], start range) i - appendRemainder (lines, previous) = adjoinLines lines $ lineElements (Range previous (end range)) source + appendRemainder (lines, previous) = reverse . foldl adjoin2Lines [] $ lines ++ lineElements (Range previous (end range)) source sumLines (lines, previous) child = (allLines, end childRange) where separatorLines = lineElements (Range previous $ start childRange) source - allLines = lines `adjoinLines` separatorLines `adjoinLines` childLines + unadjoinedLines = lines ++ separatorLines ++ childLines + allLines = reverse $ foldl adjoin2Lines [] unadjoinedLines (childLines, childRange) = termToLines child source - elements = Span (classify categories) <$> actualLines (substring range source) + elements = (elementAndBreak $ Span (classify categories)) =<< actualLines (substring range source) -- | Given an Annotated and before/after strings, returns a list of `Row`s representing the newline-separated diff. annotatedToRows :: Annotated a (Info, Info) (Diff a Info) -> String -> String -> ([Row], (Range, Range)) annotatedToRows (Annotated (Info left _ leftCategories, Info right _ rightCategories) (Leaf _)) before after = (zipWithMaybe rowFromMaybeRows leftElements rightElements, (left, right)) where - leftElements = Span (classify leftCategories) <$> actualLines (substring left before) - rightElements = Span (classify rightCategories) <$> actualLines (substring right after) + leftElements = (elementAndBreak $ Span (classify leftCategories)) =<< actualLines (substring left before) + rightElements = (elementAndBreak $ Span (classify rightCategories)) =<< actualLines (substring right after) -annotatedToRows (Annotated (Info left _ leftCategories, Info right _ rightCategories) (Indexed i)) before after = (bimap ((:[]) . Ul (classify leftCategories)) ((:[]) . Ul (classify rightCategories)) <$> rows, ranges) +annotatedToRows (Annotated (Info left _ leftCategories, Info right _ rightCategories) (Indexed i)) before after = (rewrap <$> rows, ranges) where + wrap _ EmptyLine = EmptyLine + wrap f (Line elements) = Line [ f elements ] + rewrap (Row left right) = Row (wrap (Ul $ classify leftCategories) left) (wrap (Ul $ classify rightCategories) right) ranges = (left, right) rows = appendRemainder $ foldl sumRows ([], starts ranges) i sources = (before, after) - appendRemainder (rows, previousIndices) = adjoinRows rows $ contextRows (ends ranges) previousIndices sources + appendRemainder (rows, previousIndices) = reverse . foldl adjoin2 [] $ rows ++ (contextRows (ends ranges) previousIndices sources) sumRows (rows, previousIndices) child = (allRows, ends childRanges) where separatorRows = contextRows (starts childRanges) previousIndices sources - allRows = rows `adjoinRows` separatorRows `adjoinRows` childRows + allRows = rows ++ separatorRows ++ childRows (childRows, childRanges) = diffToRows child previousIndices before after contextRows :: (Int, Int) -> (Int, Int) -> (String, String) -> [Row] @@ -136,8 +171,15 @@ contextRows childIndices previousIndices sources = zipWithMaybe rowFromMaybeRows leftElements = textElements (Range (fst previousIndices) (fst childIndices)) (fst sources) rightElements = textElements (Range (snd previousIndices) (snd childIndices)) (snd sources) +elementAndBreak :: (String -> HTML) -> String -> [HTML] +elementAndBreak _ "" = [] +elementAndBreak _ "\n" = [ Break ] +elementAndBreak constructor x | '\n' <- last x = [ constructor $ init x, Break ] +elementAndBreak constructor x = [ constructor x ] + textElements :: Range -> String -> [HTML] -textElements range source = Text <$> actualLines (substring range source) +textElements range source = (elementAndBreak Text) =<< actualLines s + where s = substring range source starts :: (Range , Range) -> (Int, Int) starts (left, right) = (start left, start right) @@ -146,22 +188,65 @@ ends :: (Range, Range) -> (Int, Int) ends (left, right) = (end left, end right) rowFromMaybeRows :: Maybe HTML -> Maybe HTML -> Row -rowFromMaybeRows a b = Row (Maybe.maybeToList a) (Maybe.maybeToList b) +rowFromMaybeRows a b = Row (maybe EmptyLine (Line . (:[])) a) (maybe EmptyLine (Line . (:[])) b) --- | Adjoin a list of rows onto an existing list of rows. -adjoinRows :: [Row] -> [Row] -> [Row] -adjoinRows [] rows = rows -adjoinRows rows [] = rows -adjoinRows accum (row : rows) = reverse (adjoin2 (reverse accum) row) ++ rows +maybeLast :: [a] -> Maybe a +maybeLast list = listToMaybe $ reverse list adjoin2 :: [Row] -> Row -> [Row] adjoin2 [] row = [row] -adjoin2 (Row [] [] : init) row = adjoin2 init row -adjoin2 (Row [] rights : Row lefts rights' : init) (Row xs ys) = - Row [] (rights <> ys) : Row (lefts <> xs) rights' : init -adjoin2 (Row lefts [] : Row lefts' rights : init) (Row xs ys) = - Row (lefts <> xs) [] : Row lefts' (rights <> ys) : init -adjoin2 (last:init) row = (last <> row) : init + +adjoin2 rows (Row EmptyLine EmptyLine) = rows + +adjoin2 (Row EmptyLine EmptyLine : rows) row = adjoin2 rows row + +adjoin2 rows (Row left' right') | Just _ <- openLine $ leftLines rows, Just _ <- openLine $ rightLines rows = zipWith Row lefts rights + where lefts = adjoin2Lines (leftLines rows) left' + rights = adjoin2Lines (rightLines rows) right' + +adjoin2 rows (Row left' right') | Just _ <- openLine $ leftLines rows = case right' of + EmptyLine -> rest + _ -> Row EmptyLine right' : rest + where rest = zipWith Row lefts rights + lefts = adjoin2Lines (leftLines rows) left' + rights = rightLines rows + +adjoin2 rows (Row left' right') | Just _ <- openLine $ rightLines rows = case left' of + EmptyLine -> rest + _ -> Row left' EmptyLine : rest + where rest = zipWith Row lefts rights + lefts = leftLines rows + rights = adjoin2Lines (rightLines rows) right' + +adjoin2 rows row = row : rows + +leftLines :: [Row] -> [Line] +leftLines rows = left <$> rows + where + left (Row left _) = left + +rightLines :: [Row] -> [Line] +rightLines rows = right <$> rows + where + right (Row _ right) = right + +openElement :: HTML -> Maybe HTML +openElement Break = Nothing +openElement (Ul _ elements) = openElement =<< maybeLast elements +openElement (Dl _ elements) = openElement =<< maybeLast elements +openElement (Div _ elements) = openElement =<< maybeLast elements +openElement h = Just h + +openLine :: [Line] -> Maybe Line +openLine [] = Nothing +openLine (EmptyLine : rest) = openLine rest +openLine (line : _) = const line <$> (openElement =<< (maybeLast $ unLine line)) + +adjoin2Lines :: [Line] -> Line -> [Line] +adjoin2Lines [] line = [line] +adjoin2Lines (EmptyLine : xs) line | Just _ <- openLine xs = EmptyLine : adjoin2Lines xs line +adjoin2Lines (prev:rest) line | Just _ <- openLine [ prev ] = (prev <> line) : rest +adjoin2Lines lines line = line : lines adjoinLines :: [Line] -> [Line] -> [Line] adjoinLines [] lines = lines @@ -181,6 +266,6 @@ classify = foldr (const . Just . ("category-" ++)) Nothing actualLines :: String -> [String] actualLines "" = [""] actualLines lines = case break (== '\n') lines of - (l, lines') -> l : (case lines' of - [] -> [] - _:lines' -> actualLines lines') + (l, lines') -> (case lines' of + [] -> [ l ] + _:lines' -> (l ++ "\n") : actualLines lines') diff --git a/test/Spec.hs b/test/Spec.hs index 5c8b75219..dae6f6fc4 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1,55 +1,35 @@ module Main where import Diff +import Patch import Range import Split import Syntax +import Control.Comonad.Cofree import Control.Monad.Free import qualified Data.Set as Set import Test.Hspec main :: IO () main = hspec $ do - describe "adjoinRows" $ do - it "empty lines are the left unit" $ - adjoinRows [ Row [] [] ] [ Row [ Text "a" ] [ Text "b" ] ] `shouldBe` [ Row [ Text "a" ] [ Text "b" ] ] - - it "empty lines are the left unit for multiple lines" $ - adjoinRows [ Row [] [] ] [ Row [ Text "a" ] [ Text "b" ], Row [ Text "a" ] [ Text "b" ] ] `shouldBe` [ Row [ Text "a" ] [ Text "b" ], Row [ Text "a" ] [ Text "b" ] ] - - it "two single line elements should concatenate into a single line" $ - adjoinRows [ Row [ Text "a" ] [ Text "b" ] ] [ Row [ Text "a" ] [ Text "b" ] ] `shouldBe` [ Row [ Text "a", Text "a" ] [ Text "b", Text "b" ] ] - - it "single line elements on the left concatenate onto the first of multiple lines on the right" $ - adjoinRows [ Row [ Text "a1" ] [ Text "b1" ] ] [ Row [ Text "a2" ] [ Text "b2" ], Row [ Text "a3" ] [ Text "b3" ] ] `shouldBe` [ Row [ Text "a1", Text "a2" ] [ Text "b1", Text "b2" ], Row [ Text "a3" ] [ Text "b3" ] ] - - it "the last of multiple line elements on the left concatenate onto the first of multiple lines on the right" $ - adjoinRows [ Row [ Text "a1" ] [ Text "b1" ], Row [ Text "a2" ] [ Text "b2" ] ] - [ Row [ Text "a3" ] [ Text "b3" ], Row [ Text "a4" ] [ Text "b4" ] ] - `shouldBe` - [ Row [ Text "a1" ] [ Text "b1" ], - Row [ Text "a2", Text "a3" ] [ Text "b2", Text "b3" ], - Row [ Text "a4" ] [ Text "b4" ] ] - - describe "annotatedToRows" $ do it "outputs one row for single-line unchanged leaves" $ - annotatedToRows (unchanged "a" "leaf" (Leaf "")) "a" "a" `shouldBe` ([ Row [ span "a" ] [ span "a" ] ], (Range 0 1, Range 0 1)) + annotatedToRows (unchanged "a" "leaf" (Leaf "")) "a" "a" `shouldBe` ([ Row (Line [ span "a" ]) (Line [ span "a" ]) ], (Range 0 1, Range 0 1)) it "outputs one row for single-line empty unchanged indexed nodes" $ - annotatedToRows (unchanged "[]" "branch" (Indexed [])) "[]" "[]" `shouldBe` ([ Row [ Ul (Just "category-branch") [ Text "[]" ] ] [ Ul (Just "category-branch") [ Text "[]" ] ] ], (Range 0 2, Range 0 2)) + annotatedToRows (unchanged "[]" "branch" (Indexed [])) "[]" "[]" `shouldBe` ([ Row (Line [ Ul (Just "category-branch") [ Text "[]" ] ]) (Line [ Ul (Just "category-branch") [ Text "[]" ] ]) ], (Range 0 2, Range 0 2)) it "outputs one row for single-line non-empty unchanged indexed nodes" $ annotatedToRows (unchanged "[ a, b ]" "branch" (Indexed [ Free . offsetAnnotated 2 2 $ unchanged "a" "leaf" (Leaf ""), Free . offsetAnnotated 5 5 $ unchanged "b" "leaf" (Leaf "") - ])) "[ a, b ]" "[ a, b ]" `shouldBe` ([ Row [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ] [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ] ], (Range 0 8, Range 0 8)) + ])) "[ a, b ]" "[ a, b ]" `shouldBe` ([ Row (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ]) (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ]) ], (Range 0 8, Range 0 8)) it "outputs one row for single-line non-empty formatted indexed nodes" $ annotatedToRows (formatted "[ a, b ]" "[ a, b ]" "branch" (Indexed [ Free . offsetAnnotated 2 2 $ unchanged "a" "leaf" (Leaf ""), Free . offsetAnnotated 5 6 $ unchanged "b" "leaf" (Leaf "") - ])) "[ a, b ]" "[ a, b ]" `shouldBe` ([ Row [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ] [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ] ], (Range 0 8, Range 0 9)) + ])) "[ a, b ]" "[ a, b ]" `shouldBe` ([ Row (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ]) (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ", ", span "b", Text " ]" ] ]) ], (Range 0 8, Range 0 9)) it "outputs two rows for two-line non-empty unchanged indexed nodes" $ annotatedToRows (unchanged "[ a,\nb ]" "branch" (Indexed [ @@ -57,10 +37,10 @@ main = hspec $ do Free . offsetAnnotated 5 5 $ unchanged "b" "leaf" (Leaf "") ])) "[ a,\nb ]" "[ a,\nb ]" `shouldBe` ([ - Row [ Ul (Just "category-branch") [ Text "[ ", span "a", Text "," ] ] - [ Ul (Just "category-branch") [ Text "[ ", span "a", Text "," ] ], - Row [ Ul (Just "category-branch") [ Text "", span "b", Text " ]" ] ] - [ Ul (Just "category-branch") [ Text "", span "b", Text " ]" ] ] + Row (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ",", Break ] ]) + (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ",", Break] ]), + Row (Line [ Ul (Just "category-branch") [ span "b", Text " ]" ] ]) + (Line [ Ul (Just "category-branch") [ span "b", Text " ]" ] ]) ], (Range 0 8, Range 0 8)) it "outputs two rows for two-line non-empty formatted indexed nodes" $ @@ -69,15 +49,92 @@ main = hspec $ do Free . offsetAnnotated 5 5 $ unchanged "b" "leaf" (Leaf "") ])) "[ a,\nb ]" "[\na,\nb ]" `shouldBe` ([ - Row [ Ul (Just "category-branch") [ Text "[ ", span "a", Text "," ] ] - [ Ul (Just "category-branch") [ Text "[" ] ], - Row [ Ul (Just "category-branch") [] ] - [ Ul (Just "category-branch") [ Text "", span "a", Text "," ] ], - Row [ Ul (Just "category-branch") [ Text "", span "b", Text " ]" ] ] - [ Ul (Just "category-branch") [ Text "", span "b", Text " ]" ] ] + Row (Line [ Ul (Just "category-branch") [ Text "[ ", span "a", Text ",", Break ] ]) + (Line [ Ul (Just "category-branch") [ Text "[", Break ] ]), + Row EmptyLine + (Line [ Ul (Just "category-branch") [ span "a", Text ",", Break ] ]), + Row (Line [ Ul (Just "category-branch") [ span "b", Text " ]" ] ]) + (Line [ Ul (Just "category-branch") [ span "b", Text " ]" ] ]) ], (Range 0 8, Range 0 8)) + it "" $ + let (sourceA, sourceB) = ("[\na\n,\nb]", "[a,b]") in + annotatedToRows (formatted sourceA sourceB "branch" (Indexed [ + Free . offsetAnnotated 2 1 $ unchanged "a" "leaf" (Leaf ""), + Free . offsetAnnotated 6 3 $ unchanged "b" "leaf" (Leaf "") + ])) sourceA sourceB `shouldBe` + ([ + Row (Line [ Ul (Just "category-branch") [ Text "[", Break ] ]) + (Line [ Ul (Just "category-branch") [ Text "[", span "a", Text ",", span "b", Text "]" ] ]), + Row (Line [ Ul (Just "category-branch") [ span "a", Break ] ]) + EmptyLine, + Row (Line [ Ul (Just "category-branch") [ Text ",", Break ] ]) + EmptyLine, + Row (Line [ Ul (Just "category-branch") [ span "b", Text "]" ] ]) + EmptyLine + ], (Range 0 8, Range 0 5)) + + it "should split multi-line deletions across multiple rows" $ + let (sourceA, sourceB) = ("/*\n*/\na", "a") in + annotatedToRows (formatted sourceA sourceB "branch" (Indexed [ + Pure . Delete $ (Info (Range 0 5) (Range 0 2) (Set.fromList ["leaf"]) :< (Leaf "")), + Free . offsetAnnotated 6 0 $ unchanged "a" "leaf" (Leaf "") + ])) sourceA sourceB `shouldBe` + ([ + Row (Line [ Ul (Just "category-branch") [ Div (Just "delete") [ span "/*", Break ] ] ]) EmptyLine, + Row (Line [ Ul (Just "category-branch") [ Div (Just "delete") [ span "*/" ], Break ] ]) EmptyLine, + Row (Line [ Ul (Just "category-branch") [ span "a" ] ]) (Line [ Ul (Just "category-branch") [ span "a" ] ]) + ], (Range 0 7, Range 0 1)) + + describe "adjoin2" $ do + it "appends appends HTML onto incomplete lines" $ + adjoin2 [ rightRowText "[" ] (rightRowText "a") `shouldBe` + [ rightRow [ Text "[", Text "a" ] ] + + it "does not append HTML onto complete lines" $ + adjoin2 [ leftRow [ Break ] ] (leftRowText ",") `shouldBe` + [ leftRowText ",", leftRow [ Break ] ] + + it "appends breaks onto incomplete lines" $ + adjoin2 [ leftRowText "a" ] (leftRow [ Break ]) `shouldBe` + [ leftRow [ Text "a", Break ] ] + + it "does not promote HTML through empty lines onto complete lines" $ + adjoin2 [ rightRowText "b", leftRow [ Break ] ] (leftRowText "a") `shouldBe` + [ leftRowText "a", rightRowText "b", leftRow [ Break ] ] + + it "promotes breaks through empty lines onto incomplete lines" $ + adjoin2 [ rightRowText "c", rowText "a" "b" ] (leftRow [ Break ]) `shouldBe` + [ rightRowText "c", Row (Line [ Text "a", Break ]) (Line [ Text "b" ]) ] + + describe "termToLines" $ do + it "splits multi-line terms into multiple lines" $ + termToLines (Info (Range 0 5) (Range 0 2) (Set.singleton "leaf") :< (Leaf "")) "/*\n*/" + `shouldBe` + ([ + Line [ span "/*", Break ], + Line [ span "*/" ] + ], Range 0 5) + + describe "openLine" $ do + it "should produce the earliest non-empty line in a list, if open" $ + openLine [ + Line [ Div (Just "delete") [ span "*/" ] ], + Line [ Div (Just "delete") [ span " * Debugging", Break ] ], + Line [ Div (Just "delete") [ span "/*", Break ] ] + ] `shouldBe` (Just $ Line [ Div (Just "delete") [ span "*/" ] ]) + + it "should return Nothing if the earliest non-empty line is closed" $ + openLine [ + Line [ Div (Just "delete") [ span " * Debugging", Break ] ] + ] `shouldBe` Nothing + where + rightRowText text = rightRow [ Text text ] + rightRow xs = Row EmptyLine (Line xs) + leftRowText text = leftRow [ Text text ] + leftRow xs = Row (Line xs) EmptyLine + rowText a b = Row (Line [ Text a ]) (Line [ Text b ]) info source category = Info (totalRange source) (Range 0 0) (Set.fromList [ category ]) unchanged source category = formatted source source category formatted source1 source2 category = Annotated (info source1 category, info source2 category)