    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE QualifiedDo #-}
    {-# LANGUAGE RecordWildCards #-}

    -- | This module allows us to diff two 'Text' values.
    module Ormolu.Diff.Text
      ( TextDiff,
        diffText,
        selectSpans,
        printTextDiff,
      )
    where

    import Control.Monad (unless, when)
    import Data.Algorithm.Diff qualified as D
    import Data.Foldable (for_)
    import Data.IntSet (IntSet)
    import Data.IntSet qualified as IntSet
    import Data.List (foldl')
    import Data.Maybe (listToMaybe)
    import Data.Text (Text)
    import Data.Text qualified as T
    import GHC.Types.SrcLoc
    import Ormolu.Terminal
    import Ormolu.Terminal.QualifiedDo qualified as Term

    ----------------------------------------------------------------------------
    -- Types

    -- | Result of diffing two 'Text's.
    data TextDiff = TextDiff
      { -- | Path to the file that is being diffed
        TextDiff -> FilePath
    textDiffPath :: FilePath,
        -- | The list of differences
        TextDiff -> DiffList
    textDiffDiffList :: DiffList,
        -- | Selected lines. Only hunks that contain selected lines will be
        -- displayed, unless 'textDiffSelectedLines' is empty, in which case the
        -- whole diff will be displayed.
        TextDiff -> IntSet
    textDiffSelectedLines :: IntSet
      }
      deriving (TextDiff -> TextDiff -> Bool
    (TextDiff -> TextDiff -> Bool)
    -> (TextDiff -> TextDiff -> Bool) -> Eq TextDiff
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: TextDiff -> TextDiff -> Bool
    == :: TextDiff -> TextDiff -> Bool
    $c/= :: TextDiff -> TextDiff -> Bool
    /= :: TextDiff -> TextDiff -> Bool
    Eq, Int -> TextDiff -> ShowS
    [TextDiff] -> ShowS
    TextDiff -> FilePath
    (Int -> TextDiff -> ShowS)
    -> (TextDiff -> FilePath) -> ([TextDiff] -> ShowS) -> Show TextDiff
    forall a.
    (Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> TextDiff -> ShowS
    showsPrec :: Int -> TextDiff -> ShowS
    $cshow :: TextDiff -> FilePath
    show :: TextDiff -> FilePath
    $cshowList :: [TextDiff] -> ShowS
    showList :: [TextDiff] -> ShowS
    Show)

    -- | List of lines tagged by 'D.Both', 'D.First', or 'D.Second'.
    type DiffList = [D.Diff [Text]]

    -- | Similar to 'DiffList', but with line numbers assigned.
    type DiffList' = [D.Diff [(Int, Int, Text)]]

    -- | Diff hunk.
    data Hunk = Hunk
      { Hunk -> Int
    hunkFirstStartLine :: Int,
        Hunk -> Int
    hunkFirstLength :: Int,
        Hunk -> Int
    hunkSecondStartLine :: Int,
        Hunk -> Int
    hunkSecondLength :: Int,
        Hunk -> DiffList
    hunkDiff :: DiffList
      }
      deriving (Int -> Hunk -> ShowS
    [Hunk] -> ShowS
    Hunk -> FilePath
    (Int -> Hunk -> ShowS)
    -> (Hunk -> FilePath) -> ([Hunk] -> ShowS) -> Show Hunk
    forall a.
    (Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> Hunk -> ShowS
    showsPrec :: Int -> Hunk -> ShowS
    $cshow :: Hunk -> FilePath
    show :: Hunk -> FilePath
    $cshowList :: [Hunk] -> ShowS
    showList :: [Hunk] -> ShowS
    Show)

    ----------------------------------------------------------------------------
    -- API

    -- | Diff two texts and produce a 'TextDiff'.
    diffText ::
      -- | Text before
      Text ->
      -- | Text after
      Text ->
      -- | Path to use
      FilePath ->
      -- | The resulting diff or 'Nothing' if the inputs are identical
      Maybe TextDiff
    diffText :: Text -> Text -> FilePath -> Maybe TextDiff
    diffText Text
    a Text
    b FilePath
    path =
      if (PolyDiff [Text] [Text] -> Bool) -> DiffList -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all PolyDiff [Text] [Text] -> Bool
    forall {a} {b}. PolyDiff a b -> Bool
    isBoth DiffList
    xs
        then Maybe TextDiff
    forall a. Maybe a
    Nothing
        else
          TextDiff -> Maybe TextDiff
    forall a. a -> Maybe a
    Just
            TextDiff
              { textDiffPath :: FilePath
    textDiffPath = FilePath
    path,
                textDiffDiffList :: DiffList
    textDiffDiffList = DiffList
    xs,
                textDiffSelectedLines :: IntSet
    textDiffSelectedLines = IntSet
    IntSet.empty
              }
      where
        xs :: DiffList
    xs = [Text] -> [Text] -> DiffList
    forall a. Eq a => [a] -> [a] -> [Diff [a]]
    D.getGroupedDiff (Text -> [Text]
    lines' Text
    a) (Text -> [Text]
    lines' Text
    b)
        isBoth :: PolyDiff a b -> Bool
    isBoth = \case
          D.Both a
    _ b
    _ -> Bool
    True
          D.First a
    _ -> Bool
    False
          D.Second b
    _ -> Bool
    False
        -- T.lines ignores trailing blank lines
        lines' :: Text -> [Text]
    lines' = HasCallStack => Text -> Text -> [Text]
    Text -> Text -> [Text]
    T.splitOn Text
    "\n"

    -- | Select certain spans in the diff (line numbers are interpreted as
    -- belonging to the “before” state). Only selected spans will be printed.
    selectSpans :: [RealSrcSpan] -> TextDiff -> TextDiff
    selectSpans :: [RealSrcSpan] -> TextDiff -> TextDiff
    selectSpans [RealSrcSpan]
    ss TextDiff
    textDiff = TextDiff
    textDiff {textDiffSelectedLines = xs}
      where
        xs :: IntSet
    xs = (IntSet -> RealSrcSpan -> IntSet)
    -> IntSet -> [RealSrcSpan] -> IntSet
    forall b a. (b -> a -> b) -> b -> [a] -> b
    forall (t :: * -> *) b a.
    Foldable t =>
    (b -> a -> b) -> b -> t a -> b
    foldl' IntSet -> RealSrcSpan -> IntSet
    addOneSpan (TextDiff -> IntSet
    textDiffSelectedLines TextDiff
    textDiff) [RealSrcSpan]
    ss
        addOneSpan :: IntSet -> RealSrcSpan -> IntSet
    addOneSpan IntSet
    linesSoFar RealSrcSpan
    s =
          let start :: Int
    start = RealSrcSpan -> Int
    srcSpanStartLine RealSrcSpan
    s
              end :: Int
    end = RealSrcSpan -> Int
    srcSpanEndLine RealSrcSpan
    s
           in IntSet -> IntSet -> IntSet
    IntSet.union
                IntSet
    linesSoFar
                ([Int] -> IntSet
    IntSet.fromAscList [Int
    start .. Int
    end])

    -- | Print the given 'TextDiff' as a 'Term' action. This function tries to
    -- mimic the style of @git diff@.
    printTextDiff :: TextDiff -> Term
    printTextDiff :: TextDiff -> Term
    printTextDiff TextDiff {FilePath
    DiffList
    IntSet
    textDiffPath :: TextDiff -> FilePath
    textDiffDiffList :: TextDiff -> DiffList
    textDiffSelectedLines :: TextDiff -> IntSet
    textDiffPath :: FilePath
    textDiffDiffList :: DiffList
    textDiffSelectedLines :: IntSet
    ..} = Term.do
      (Term -> Term
    bold (Term -> Term) -> (FilePath -> Term) -> FilePath -> Term
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Text -> Term
    put (Text -> Term) -> (FilePath -> Text) -> FilePath -> Term
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> Text
    T.pack) FilePath
    textDiffPath
      Term
    newline
      [Hunk] -> (Hunk -> Term) -> Term
    forall (t :: * -> *) (f :: * -> *) a b.
    (Foldable t, Applicative f) =>
    t a -> (a -> f b) -> f ()
    for_ (DiffList' -> [Hunk]
    toHunks (DiffList -> DiffList'
    assignLines DiffList
    textDiffDiffList)) ((Hunk -> Term) -> Term) -> (Hunk -> Term) -> Term
    forall a b. (a -> b) -> a -> b
    $ \hunk :: Hunk
    hunk@Hunk {Int
    DiffList
    hunkFirstStartLine :: Hunk -> Int
    hunkFirstLength :: Hunk -> Int
    hunkSecondStartLine :: Hunk -> Int
    hunkSecondLength :: Hunk -> Int
    hunkDiff :: Hunk -> DiffList
    hunkFirstStartLine :: Int
    hunkFirstLength :: Int
    hunkSecondStartLine :: Int
    hunkSecondLength :: Int
    hunkDiff :: DiffList
    ..} ->
        Bool -> Term -> Term
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when (IntSet -> Hunk -> Bool
    isSelectedLine IntSet
    textDiffSelectedLines Hunk
    hunk) (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $ Term.do
          Term -> Term
    cyan (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $ Term.do
            Text -> Term
    put Text
    "@@ -"
            Int -> Term
    forall a. Show a => a -> Term
    putShow Int
    hunkFirstStartLine
            Text -> Term
    put Text
    ","
            Int -> Term
    forall a. Show a => a -> Term
    putShow Int
    hunkFirstLength
            Text -> Term
    put Text
    " +"
            Int -> Term
    forall a. Show a => a -> Term
    putShow Int
    hunkSecondStartLine
            Text -> Term
    put Text
    ","
            Int -> Term
    forall a. Show a => a -> Term
    putShow Int
    hunkSecondLength
            Text -> Term
    put Text
    " @@"
          Term
    newline
          DiffList -> (PolyDiff [Text] [Text] -> Term) -> Term
    forall (t :: * -> *) (f :: * -> *) a b.
    (Foldable t, Applicative f) =>
    t a -> (a -> f b) -> f ()
    for_ DiffList
    hunkDiff ((PolyDiff [Text] [Text] -> Term) -> Term)
    -> (PolyDiff [Text] [Text] -> Term) -> Term
    forall a b. (a -> b) -> a -> b
    $ \case
            D.Both [Text]
    ys [Text]
    _ ->
              [Text] -> (Text -> Term) -> Term
    forall (t :: * -> *) (f :: * -> *) a b.
    (Foldable t, Applicative f) =>
    t a -> (a -> f b) -> f ()
    for_ [Text]
    ys ((Text -> Term) -> Term) -> (Text -> Term) -> Term
    forall a b. (a -> b) -> a -> b
    $ \Text
    y -> Term.do
                Bool -> Term -> Term
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    unless (Text -> Bool
    T.null Text
    y) (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $
                  Text -> Term
    put Text
    "  "
                Text -> Term
    put Text
    y
                Term
    newline
            D.First [Text]
    ys ->
              [Text] -> (Text -> Term) -> Term
    forall (t :: * -> *) (f :: * -> *) a b.
    (Foldable t, Applicative f) =>
    t a -> (a -> f b) -> f ()
    for_ [Text]
    ys ((Text -> Term) -> Term) -> (Text -> Term) -> Term
    forall a b. (a -> b) -> a -> b
    $ \Text
    y -> Term -> Term
    red (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $ Term.do
                Text -> Term
    put Text
    "-"
                Bool -> Term -> Term
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    unless (Text -> Bool
    T.null Text
    y) (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $
                  Text -> Term
    put Text
    " "
                Text -> Term
    put Text
    y
                Term
    newline
            D.Second [Text]
    ys ->
              [Text] -> (Text -> Term) -> Term
    forall (t :: * -> *) (f :: * -> *) a b.
    (Foldable t, Applicative f) =>
    t a -> (a -> f b) -> f ()
    for_ [Text]
    ys ((Text -> Term) -> Term) -> (Text -> Term) -> Term
    forall a b. (a -> b) -> a -> b
    $ \Text
    y -> Term -> Term
    green (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $ Term.do
                Text -> Term
    put Text
    "+"
                Bool -> Term -> Term
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    unless (Text -> Bool
    T.null Text
    y) (Term -> Term) -> Term -> Term
    forall a b. (a -> b) -> a -> b
    $
                  Text -> Term
    put Text
    " "
                Text -> Term
    put Text
    y
                Term
    newline

    ----------------------------------------------------------------------------
    -- Helpers

    -- | Assign lines to a 'DiffList'.
    assignLines :: DiffList -> DiffList'
    assignLines :: DiffList -> DiffList'
    assignLines = Int -> Int -> (DiffList' -> DiffList') -> DiffList -> DiffList'
    forall {c} {c}.
    Int
    -> Int
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> [PolyDiff [c] [c]]
    -> c
    go Int
    1 Int
    1 DiffList' -> DiffList'
    forall a. a -> a
    id
      where
        go :: Int
    -> Int
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> [PolyDiff [c] [c]]
    -> c
    go Int
    _ Int
    _ [PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
    acc [] = [PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
    acc []
        go !Int
    firstLine !Int
    secondLine [PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
    acc (PolyDiff [c] [c]
    x : [PolyDiff [c] [c]]
    xs) =
          case PolyDiff [c] [c]
    x of
            D.Both [c]
    a [c]
    b ->
              let firstInc :: Int
    firstInc = [c] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [c]
    a
                  secondInc :: Int
    secondInc = [c] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [c]
    b
                  a' :: [(Int, Int, c)]
    a' =
                    [Int] -> [Int] -> [c] -> [(Int, Int, c)]
    forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
    zip3
                      ((Int -> Int) -> Int -> [Int]
    forall a. (a -> a) -> a -> [a]
    iterate (Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) Int
    firstLine)
                      ((Int -> Int) -> Int -> [Int]
    forall a. (a -> a) -> a -> [a]
    iterate (Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) Int
    secondLine)
                      [c]
    a
               in Int
    -> Int
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> [PolyDiff [c] [c]]
    -> c
    go
                    (Int
    firstLine Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    firstInc)
                    (Int
    secondLine Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    secondInc)
                    ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
    acc ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]]
        -> [PolyDiff [(Int, Int, c)] [(Int, Int, c)]])
    -> [PolyDiff [(Int, Int, c)] [(Int, Int, c)]]
    -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([(Int, Int, c)]
    -> [(Int, Int, c)] -> PolyDiff [(Int, Int, c)] [(Int, Int, c)]
    forall a b. a -> b -> PolyDiff a b
    D.Both [(Int, Int, c)]
    a' [(Int, Int, c)]
    a' :))
                    [PolyDiff [c] [c]]
    xs
            D.First [c]
    a ->
              let firstInc :: Int
    firstInc = [c] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [c]
    a
                  a' :: [(Int, Int, c)]
    a' =
                    [Int] -> [Int] -> [c] -> [(Int, Int, c)]
    forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
    zip3
                      ((Int -> Int) -> Int -> [Int]
    forall a. (a -> a) -> a -> [a]
    iterate (Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) Int
    firstLine)
                      (Int -> [Int]
    forall a. a -> [a]
    repeat Int
    secondLine)
                      [c]
    a
               in Int
    -> Int
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> [PolyDiff [c] [c]]
    -> c
    go
                    (Int
    firstLine Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    firstInc)
                    Int
    secondLine
                    ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
    acc ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]]
        -> [PolyDiff [(Int, Int, c)] [(Int, Int, c)]])
    -> [PolyDiff [(Int, Int, c)] [(Int, Int, c)]]
    -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([(Int, Int, c)] -> PolyDiff [(Int, Int, c)] [(Int, Int, c)]
    forall a b. a -> PolyDiff a b
    D.First [(Int, Int, c)]
    a' :))
                    [PolyDiff [c] [c]]
    xs
            D.Second [c]
    b ->
              let secondInc :: Int
    secondInc = [c] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [c]
    b
                  b' :: [(Int, Int, c)]
    b' =
                    [Int] -> [Int] -> [c] -> [(Int, Int, c)]
    forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
    zip3
                      (Int -> [Int]
    forall a. a -> [a]
    repeat Int
    firstLine)
                      ((Int -> Int) -> Int -> [Int]
    forall a. (a -> a) -> a -> [a]
    iterate (Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    1) Int
    secondLine)
                      [c]
    b
               in Int
    -> Int
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> [PolyDiff [c] [c]]
    -> c
    go
                    Int
    firstLine
                    (Int
    secondLine Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    secondInc)
                    ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
    acc ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
    -> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]]
        -> [PolyDiff [(Int, Int, c)] [(Int, Int, c)]])
    -> [PolyDiff [(Int, Int, c)] [(Int, Int, c)]]
    -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([(Int, Int, c)] -> PolyDiff [(Int, Int, c)] [(Int, Int, c)]
    forall a b. b -> PolyDiff a b
    D.Second [(Int, Int, c)]
    b' :))
                    [PolyDiff [c] [c]]
    xs

    -- | Form 'Hunk's from a 'DiffList''.
    toHunks :: DiffList' -> [Hunk]
    toHunks :: DiffList' -> [Hunk]
    toHunks = Int
    -> Bool
    -> ([Hunk] -> [Hunk])
    -> (DiffList' -> DiffList')
    -> [(Int, Int, Text)]
    -> DiffList'
    -> [Hunk]
    go Int
    0 Bool
    False [Hunk] -> [Hunk]
    forall a. a -> a
    id DiffList' -> DiffList'
    forall a. a -> a
    id []
      where
        -- How many lines of context (that is, lines present in both texts) to
        -- show per hunk.
        margin :: Int
    margin = Int
    3
        go ::
          Int ->
          Bool ->
          ([Hunk] -> [Hunk]) ->
          (DiffList' -> DiffList') ->
          [(Int, Int, Text)] ->
          DiffList' ->
          [Hunk]
        go :: Int
    -> Bool
    -> ([Hunk] -> [Hunk])
    -> (DiffList' -> DiffList')
    -> [(Int, Int, Text)]
    -> DiffList'
    -> [Hunk]
    go !Int
    n Bool
    gotChanges [Hunk] -> [Hunk]
    hunksAcc DiffList' -> DiffList'
    currentAcc [(Int, Int, Text)]
    bothHistory = \case
          [] ->
            if Bool
    gotChanges
              then
                let currentAcc' :: DiffList' -> DiffList'
    currentAcc' = [(Int, Int, Text)]
    -> (DiffList' -> DiffList') -> DiffList' -> DiffList'
    forall {a} {c}.
    [a] -> ([PolyDiff [a] [a]] -> c) -> [PolyDiff [a] [a]] -> c
    addBothAfter [(Int, Int, Text)]
    p DiffList' -> DiffList'
    currentAcc
                    p :: [(Int, Int, Text)]
    p = Int -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. Int -> [a] -> [a]
    take Int
    margin ([(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse [(Int, Int, Text)]
    bothHistory)
                 in case DiffList' -> Maybe Hunk
    formHunk (DiffList' -> DiffList'
    currentAcc' []) of
                      Maybe Hunk
    Nothing -> [Hunk] -> [Hunk]
    hunksAcc []
                      Just Hunk
    hunk -> [Hunk] -> [Hunk]
    hunksAcc [Hunk
    hunk]
              else [Hunk] -> [Hunk]
    hunksAcc []
          (Diff [(Int, Int, Text)]
    x : DiffList'
    xs) ->
            case Diff [(Int, Int, Text)]
    x of
              D.Both [(Int, Int, Text)]
    a [(Int, Int, Text)]
    _ ->
                let currentAcc' :: DiffList' -> DiffList'
    currentAcc' = [(Int, Int, Text)]
    -> (DiffList' -> DiffList') -> DiffList' -> DiffList'
    forall {a} {c}.
    [a] -> ([PolyDiff [a] [a]] -> c) -> [PolyDiff [a] [a]] -> c
    addBothAfter [(Int, Int, Text)]
    p DiffList' -> DiffList'
    currentAcc
                    p :: [(Int, Int, Text)]
    p = [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse (Int -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. Int -> [a] -> [a]
    drop (Int
    n' Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    margin) [(Int, Int, Text)]
    bothHistory')
                    hunksAcc' :: [Hunk] -> [Hunk]
    hunksAcc' =
                      case DiffList' -> Maybe Hunk
    formHunk (DiffList' -> DiffList'
    currentAcc' []) of
                        Maybe Hunk
    Nothing -> [Hunk] -> [Hunk]
    hunksAcc
                        Just Hunk
    hunk -> [Hunk] -> [Hunk]
    hunksAcc ([Hunk] -> [Hunk]) -> ([Hunk] -> [Hunk]) -> [Hunk] -> [Hunk]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Hunk
    hunk :)
                    bothHistory' :: [(Int, Int, Text)]
    bothHistory' = [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse [(Int, Int, Text)]
    a [(Int, Int, Text)] -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a] -> [a]
    ++ [(Int, Int, Text)]
    bothHistory
                    lena :: Int
    lena = [(Int, Int, Text)] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [(Int, Int, Text)]
    a
                    n' :: Int
    n' = Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    lena
                 in if Bool
    gotChanges Bool -> Bool -> Bool
    && Int
    n' Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    > Int
    margin Int -> Int -> Int
    forall a. Num a => a -> a -> a
    * Int
    2
                      then Int
    -> Bool
    -> ([Hunk] -> [Hunk])
    -> (DiffList' -> DiffList')
    -> [(Int, Int, Text)]
    -> DiffList'
    -> [Hunk]
    go Int
    0 Bool
    False [Hunk] -> [Hunk]
    hunksAcc' DiffList' -> DiffList'
    forall a. a -> a
    id [(Int, Int, Text)]
    bothHistory' DiffList'
    xs
                      else Int
    -> Bool
    -> ([Hunk] -> [Hunk])
    -> (DiffList' -> DiffList')
    -> [(Int, Int, Text)]
    -> DiffList'
    -> [Hunk]
    go Int
    n' Bool
    gotChanges [Hunk] -> [Hunk]
    hunksAcc DiffList' -> DiffList'
    currentAcc [(Int, Int, Text)]
    bothHistory' DiffList'
    xs
              Diff [(Int, Int, Text)]
    piece ->
                if Bool
    gotChanges
                  then
                    let currentAcc' :: DiffList' -> DiffList'
    currentAcc' = DiffList' -> DiffList'
    currentAcc (DiffList' -> DiffList')
    -> (DiffList' -> DiffList') -> DiffList' -> DiffList'
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [(Int, Int, Text)]
    -> (DiffList' -> DiffList') -> DiffList' -> DiffList'
    forall {a} {a}.
    [a] -> (a -> [PolyDiff [a] [a]]) -> a -> [PolyDiff [a] [a]]
    addBothBefore [(Int, Int, Text)]
    p (Diff [(Int, Int, Text)]
    piece :)
                        p :: [(Int, Int, Text)]
    p = [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse [(Int, Int, Text)]
    bothHistory
                     in Int
    -> Bool
    -> ([Hunk] -> [Hunk])
    -> (DiffList' -> DiffList')
    -> [(Int, Int, Text)]
    -> DiffList'
    -> [Hunk]
    go Int
    0 Bool
    True [Hunk] -> [Hunk]
    hunksAcc DiffList' -> DiffList'
    currentAcc' [] DiffList'
    xs
                  else
                    let currentAcc' :: DiffList' -> DiffList'
    currentAcc' = [(Int, Int, Text)]
    -> (DiffList' -> DiffList') -> DiffList' -> DiffList'
    forall {a} {a}.
    [a] -> (a -> [PolyDiff [a] [a]]) -> a -> [PolyDiff [a] [a]]
    addBothBefore [(Int, Int, Text)]
    p (Diff [(Int, Int, Text)]
    piece :)
                        p :: [(Int, Int, Text)]
    p = [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse (Int -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. Int -> [a] -> [a]
    take Int
    margin [(Int, Int, Text)]
    bothHistory)
                     in Int
    -> Bool
    -> ([Hunk] -> [Hunk])
    -> (DiffList' -> DiffList')
    -> [(Int, Int, Text)]
    -> DiffList'
    -> [Hunk]
    go Int
    0 Bool
    True [Hunk] -> [Hunk]
    hunksAcc DiffList' -> DiffList'
    currentAcc' [] DiffList'
    xs
        addBothBefore :: [a] -> (a -> [PolyDiff [a] [a]]) -> a -> [PolyDiff [a] [a]]
    addBothBefore [] a -> [PolyDiff [a] [a]]
    acc = a -> [PolyDiff [a] [a]]
    acc
        addBothBefore [a]
    p a -> [PolyDiff [a] [a]]
    acc = ([a] -> [a] -> PolyDiff [a] [a]
    forall a b. a -> b -> PolyDiff a b
    D.Both [a]
    p [a]
    p :) ([PolyDiff [a] [a]] -> [PolyDiff [a] [a]])
    -> (a -> [PolyDiff [a] [a]]) -> a -> [PolyDiff [a] [a]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . a -> [PolyDiff [a] [a]]
    acc
        addBothAfter :: [a] -> ([PolyDiff [a] [a]] -> c) -> [PolyDiff [a] [a]] -> c
    addBothAfter [] [PolyDiff [a] [a]] -> c
    acc = [PolyDiff [a] [a]] -> c
    acc
        addBothAfter [a]
    p [PolyDiff [a] [a]] -> c
    acc = [PolyDiff [a] [a]] -> c
    acc ([PolyDiff [a] [a]] -> c)
    -> ([PolyDiff [a] [a]] -> [PolyDiff [a] [a]])
    -> [PolyDiff [a] [a]]
    -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([a] -> [a] -> PolyDiff [a] [a]
    forall a b. a -> b -> PolyDiff a b
    D.Both [a]
    p [a]
    p :)

    -- | Form a 'Hunk'.
    formHunk :: DiffList' -> Maybe Hunk
    formHunk :: DiffList' -> Maybe Hunk
    formHunk DiffList'
    xsRaw = do
      let xs :: DiffList'
    xs = DiffList' -> DiffList'
    trimEmpty DiffList'
    xsRaw
      Int
    hunkFirstStartLine <- DiffList' -> Maybe (Diff [(Int, Int, Text)])
    forall a. [a] -> Maybe a
    listToMaybe DiffList'
    xs Maybe (Diff [(Int, Int, Text)])
    -> (Diff [(Int, Int, Text)] -> Maybe Int) -> Maybe Int
    forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Diff [(Int, Int, Text)] -> Maybe Int
    forall a. Diff [(Int, Int, a)] -> Maybe Int
    firstStartLine
      let hunkFirstLength :: Int
    hunkFirstLength = DiffList' -> Int
    forall a. [Diff [(Int, Int, a)]] -> Int
    firstLength DiffList'
    xs
      Int
    hunkSecondStartLine <- DiffList' -> Maybe (Diff [(Int, Int, Text)])
    forall a. [a] -> Maybe a
    listToMaybe DiffList'
    xs Maybe (Diff [(Int, Int, Text)])
    -> (Diff [(Int, Int, Text)] -> Maybe Int) -> Maybe Int
    forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Diff [(Int, Int, Text)] -> Maybe Int
    forall a. Diff [(Int, Int, a)] -> Maybe Int
    secondStartLine
      let hunkSecondLength :: Int
    hunkSecondLength = DiffList' -> Int
    forall a. [Diff [(Int, Int, a)]] -> Int
    secondLength DiffList'
    xs
          hunkDiff :: DiffList
    hunkDiff = ([(Int, Int, Text)] -> [Text]) -> DiffList' -> DiffList
    forall a b. (a -> b) -> [Diff a] -> [Diff b]
    mapDiff (((Int, Int, Text) -> Text) -> [(Int, Int, Text)] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Int, Int, Text) -> Text
    third) DiffList'
    xs
      Hunk -> Maybe Hunk
    forall a. a -> Maybe a
    forall (m :: * -> *) a. Monad m => a -> m a
    return Hunk {Int
    DiffList
    hunkFirstStartLine :: Int
    hunkFirstLength :: Int
    hunkSecondStartLine :: Int
    hunkSecondLength :: Int
    hunkDiff :: DiffList
    hunkFirstStartLine :: Int
    hunkFirstLength :: Int
    hunkSecondStartLine :: Int
    hunkSecondLength :: Int
    hunkDiff :: DiffList
    ..}

    -- | Trim empty “both” lines from beginning and end of a 'DiffList''.
    trimEmpty :: DiffList' -> DiffList'
    trimEmpty :: DiffList' -> DiffList'
    trimEmpty = Bool -> (DiffList' -> DiffList') -> DiffList' -> DiffList'
    forall {c}. Bool -> (DiffList' -> c) -> DiffList' -> c
    go Bool
    True DiffList' -> DiffList'
    forall a. a -> a
    id
      where
        go :: Bool -> (DiffList' -> c) -> DiffList' -> c
    go Bool
    isFirst DiffList' -> c
    acc = \case
          [] -> DiffList' -> c
    acc []
          [D.Both [(Int, Int, Text)]
    x [(Int, Int, Text)]
    _] ->
            let x' :: [(Int, Int, Text)]
    x' = [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse ([(Int, Int, Text)] -> [(Int, Int, Text)])
    -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a b. (a -> b) -> a -> b
    $ ((Int, Int, Text) -> Bool)
    -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile (Text -> Bool
    T.null (Text -> Bool)
    -> ((Int, Int, Text) -> Text) -> (Int, Int, Text) -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Int, Int, Text) -> Text
    third) ([(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. [a] -> [a]
    reverse [(Int, Int, Text)]
    x)
             in Bool -> (DiffList' -> c) -> DiffList' -> c
    go Bool
    False (DiffList' -> c
    acc (DiffList' -> c) -> (DiffList' -> DiffList') -> DiffList' -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([(Int, Int, Text)] -> [(Int, Int, Text)] -> Diff [(Int, Int, Text)]
    forall a b. a -> b -> PolyDiff a b
    D.Both [(Int, Int, Text)]
    x' [(Int, Int, Text)]
    x' :)) []
          (D.Both [(Int, Int, Text)]
    x [(Int, Int, Text)]
    _ : DiffList'
    xs) ->
            let x' :: [(Int, Int, Text)]
    x' = ((Int, Int, Text) -> Bool)
    -> [(Int, Int, Text)] -> [(Int, Int, Text)]
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile (Text -> Bool
    T.null (Text -> Bool)
    -> ((Int, Int, Text) -> Text) -> (Int, Int, Text) -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Int, Int, Text) -> Text
    third) [(Int, Int, Text)]
    x
             in if Bool
    isFirst
                  then Bool -> (DiffList' -> c) -> DiffList' -> c
    go Bool
    False (DiffList' -> c
    acc (DiffList' -> c) -> (DiffList' -> DiffList') -> DiffList' -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([(Int, Int, Text)] -> [(Int, Int, Text)] -> Diff [(Int, Int, Text)]
    forall a b. a -> b -> PolyDiff a b
    D.Both [(Int, Int, Text)]
    x' [(Int, Int, Text)]
    x' :)) DiffList'
    xs
                  else Bool -> (DiffList' -> c) -> DiffList' -> c
    go Bool
    False (DiffList' -> c
    acc (DiffList' -> c) -> (DiffList' -> DiffList') -> DiffList' -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([(Int, Int, Text)] -> [(Int, Int, Text)] -> Diff [(Int, Int, Text)]
    forall a b. a -> b -> PolyDiff a b
    D.Both [(Int, Int, Text)]
    x [(Int, Int, Text)]
    x :)) DiffList'
    xs
          (Diff [(Int, Int, Text)]
    x : DiffList'
    xs) ->
            Bool -> (DiffList' -> c) -> DiffList' -> c
    go Bool
    False (DiffList' -> c
    acc (DiffList' -> c) -> (DiffList' -> DiffList') -> DiffList' -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Diff [(Int, Int, Text)]
    x :)) DiffList'
    xs

    firstStartLine :: D.Diff [(Int, Int, a)] -> Maybe Int
    firstStartLine :: forall a. Diff [(Int, Int, a)] -> Maybe Int
    firstStartLine = \case
      D.Both ((Int
    x, Int
    _, a
    _) : [(Int, Int, a)]
    _) [(Int, Int, a)]
    _ -> Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    x
      D.First ((Int
    x, Int
    _, a
    _) : [(Int, Int, a)]
    _) -> Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    x
      D.Second ((Int
    x, Int
    _, a
    _) : [(Int, Int, a)]
    _) -> Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    x
      Diff [(Int, Int, a)]
    _ -> Maybe Int
    forall a. Maybe a
    Nothing

    firstLength :: [D.Diff [(Int, Int, a)]] -> Int
    firstLength :: forall a. [Diff [(Int, Int, a)]] -> Int
    firstLength = Int -> [PolyDiff [(Int, Int, a)] [(Int, Int, a)]] -> Int
    forall {t :: * -> *} {a} {b}.
    Foldable t =>
    Int -> [PolyDiff (t a) b] -> Int
    go Int
    0
      where
        go :: Int -> [PolyDiff (t a) b] -> Int
    go Int
    n [] = Int
    n
        go !Int
    n (PolyDiff (t a) b
    x : [PolyDiff (t a) b]
    xs) = case PolyDiff (t a) b
    x of
          D.Both t a
    as b
    _ -> Int -> [PolyDiff (t a) b] -> Int
    go (Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + t a -> Int
    forall a. t a -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length t a
    as) [PolyDiff (t a) b]
    xs
          D.First t a
    as -> Int -> [PolyDiff (t a) b] -> Int
    go (Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + t a -> Int
    forall a. t a -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length t a
    as) [PolyDiff (t a) b]
    xs
          D.Second b
    _ -> Int -> [PolyDiff (t a) b] -> Int
    go Int
    n [PolyDiff (t a) b]
    xs

    secondStartLine :: D.Diff [(Int, Int, a)] -> Maybe Int
    secondStartLine :: forall a. Diff [(Int, Int, a)] -> Maybe Int
    secondStartLine = \case
      D.Both ((Int
    _, Int
    x, a
    _) : [(Int, Int, a)]
    _) [(Int, Int, a)]
    _ -> Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    x
      D.First ((Int
    _, Int
    x, a
    _) : [(Int, Int, a)]
    _) -> Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    x
      D.Second ((Int
    _, Int
    x, a
    _) : [(Int, Int, a)]
    _) -> Int -> Maybe Int
    forall a. a -> Maybe a
    Just Int
    x
      Diff [(Int, Int, a)]
    _ -> Maybe Int
    forall a. Maybe a
    Nothing

    secondLength :: [D.Diff [(Int, Int, a)]] -> Int
    secondLength :: forall a. [Diff [(Int, Int, a)]] -> Int
    secondLength = Int -> [PolyDiff [(Int, Int, a)] [(Int, Int, a)]] -> Int
    forall {t :: * -> *} {t :: * -> *} {a} {a}.
    (Foldable t, Foldable t) =>
    Int -> [PolyDiff (t a) (t a)] -> Int
    go Int
    0
      where
        go :: Int -> [PolyDiff (t a) (t a)] -> Int
    go Int
    n [] = Int
    n
        go !Int
    n (PolyDiff (t a) (t a)
    x : [PolyDiff (t a) (t a)]
    xs) = case PolyDiff (t a) (t a)
    x of
          D.Both t a
    as t a
    _ -> Int -> [PolyDiff (t a) (t a)] -> Int
    go (Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + t a -> Int
    forall a. t a -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length t a
    as) [PolyDiff (t a) (t a)]
    xs
          D.First t a
    _ -> Int -> [PolyDiff (t a) (t a)] -> Int
    go Int
    n [PolyDiff (t a) (t a)]
    xs
          D.Second t a
    as -> Int -> [PolyDiff (t a) (t a)] -> Int
    go (Int
    n Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + t a -> Int
    forall a. t a -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length t a
    as) [PolyDiff (t a) (t a)]
    xs

    mapDiff :: (a -> b) -> [D.Diff a] -> [D.Diff b]
    mapDiff :: forall a b. (a -> b) -> [Diff a] -> [Diff b]
    mapDiff a -> b
    f = (Diff a -> Diff b) -> [Diff a] -> [Diff b]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((Diff a -> Diff b) -> [Diff a] -> [Diff b])
    -> (Diff a -> Diff b) -> [Diff a] -> [Diff b]
    forall a b. (a -> b) -> a -> b
    $ \case
      D.Both a
    a a
    b -> b -> b -> Diff b
    forall a b. a -> b -> PolyDiff a b
    D.Both (a -> b
    f a
    a) (a -> b
    f a
    b)
      D.First a
    a -> b -> Diff b
    forall a b. a -> PolyDiff a b
    D.First (a -> b
    f a
    a)
      D.Second a
    b -> b -> Diff b
    forall a b. b -> PolyDiff a b
    D.Second (a -> b
    f a
    b)

    third :: (Int, Int, Text) -> Text
    third :: (Int, Int, Text) -> Text
    third (Int
    _, Int
    _, Text
    x) = Text
    x

    isSelectedLine :: IntSet -> Hunk -> Bool
    isSelectedLine :: IntSet -> Hunk -> Bool
    isSelectedLine IntSet
    selected Hunk {Int
    DiffList
    hunkFirstStartLine :: Hunk -> Int
    hunkFirstLength :: Hunk -> Int
    hunkSecondStartLine :: Hunk -> Int
    hunkSecondLength :: Hunk -> Int
    hunkDiff :: Hunk -> DiffList
    hunkFirstStartLine :: Int
    hunkFirstLength :: Int
    hunkSecondStartLine :: Int
    hunkSecondLength :: Int
    hunkDiff :: DiffList
    ..} =
      -- If the set of selected lines is empty, everything is selected.
      IntSet -> Bool
    IntSet.null IntSet
    selected
        Bool -> Bool -> Bool
    || Bool -> Bool
    not (IntSet -> IntSet -> Bool
    IntSet.disjoint IntSet
    selected IntSet
    hunkOriginalLines)
      where
        hunkOriginalLines :: IntSet
    hunkOriginalLines =
          [Int] -> IntSet
    IntSet.fromAscList (Int -> [Int] -> [Int]
    forall a. Int -> [a] -> [a]
    take Int
    hunkFirstLength [Int
    hunkFirstStartLine ..])