{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}

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

import Control.Monad
import qualified Data.Algorithm.Diff as D
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import Data.List (foldl')
import Data.Maybe (listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Types.SrcLoc
import Ormolu.Terminal

----------------------------------------------------------------------------
-- 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
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TextDiff -> TextDiff -> Bool
$c/= :: TextDiff -> TextDiff -> Bool
== :: TextDiff -> TextDiff -> Bool
$c== :: TextDiff -> TextDiff -> Bool
Eq, Int -> TextDiff -> ShowS
[TextDiff] -> ShowS
TextDiff -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [TextDiff] -> ShowS
$cshowList :: [TextDiff] -> ShowS
show :: TextDiff -> FilePath
$cshow :: TextDiff -> FilePath
showsPrec :: Int -> TextDiff -> ShowS
$cshowsPrec :: Int -> 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
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [Hunk] -> ShowS
$cshowList :: [Hunk] -> ShowS
show :: Hunk -> FilePath
$cshow :: Hunk -> FilePath
showsPrec :: Int -> Hunk -> ShowS
$cshowsPrec :: Int -> 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 forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all forall {a} {b}. PolyDiff a b -> Bool
isBoth DiffList
xs
    then forall a. Maybe a
Nothing
    else
      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 = 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]
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 :: IntSet
textDiffSelectedLines = IntSet
xs}
  where
    xs :: IntSet
xs = 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
textDiffSelectedLines :: IntSet
textDiffDiffList :: DiffList
textDiffPath :: FilePath
textDiffSelectedLines :: TextDiff -> IntSet
textDiffDiffList :: TextDiff -> DiffList
textDiffPath :: TextDiff -> FilePath
..} = do
  (forall a. Term a -> Term a
bold forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> Term ()
putS) FilePath
textDiffPath
  Term ()
newline
  forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (DiffList' -> [Hunk]
toHunks (DiffList -> DiffList'
assignLines DiffList
textDiffDiffList)) forall a b. (a -> b) -> a -> b
$ \hunk :: Hunk
hunk@Hunk {Int
DiffList
hunkDiff :: DiffList
hunkSecondLength :: Int
hunkSecondStartLine :: Int
hunkFirstLength :: Int
hunkFirstStartLine :: Int
hunkDiff :: Hunk -> DiffList
hunkSecondLength :: Hunk -> Int
hunkSecondStartLine :: Hunk -> Int
hunkFirstLength :: Hunk -> Int
hunkFirstStartLine :: Hunk -> Int
..} ->
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (IntSet -> Hunk -> Bool
isSelectedLine IntSet
textDiffSelectedLines Hunk
hunk) forall a b. (a -> b) -> a -> b
$ do
      forall a. Term a -> Term a
cyan forall a b. (a -> b) -> a -> b
$ do
        Text -> Term ()
put Text
"@@ -"
        FilePath -> Term ()
putS (forall a. Show a => a -> FilePath
show Int
hunkFirstStartLine)
        Text -> Term ()
put Text
","
        FilePath -> Term ()
putS (forall a. Show a => a -> FilePath
show Int
hunkFirstLength)
        Text -> Term ()
put Text
" +"
        FilePath -> Term ()
putS (forall a. Show a => a -> FilePath
show Int
hunkSecondStartLine)
        Text -> Term ()
put Text
","
        FilePath -> Term ()
putS (forall a. Show a => a -> FilePath
show Int
hunkSecondLength)
        Text -> Term ()
put Text
" @@"
      Term ()
newline
      forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ DiffList
hunkDiff forall a b. (a -> b) -> a -> b
$ \case
        D.Both [Text]
ys [Text]
_ ->
          forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Text]
ys forall a b. (a -> b) -> a -> b
$ \Text
y -> do
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Bool
T.null Text
y) forall a b. (a -> b) -> a -> b
$
              Text -> Term ()
put Text
"  "
            Text -> Term ()
put Text
y
            Term ()
newline
        D.First [Text]
ys ->
          forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Text]
ys forall a b. (a -> b) -> a -> b
$ \Text
y -> forall a. Term a -> Term a
red forall a b. (a -> b) -> a -> b
$ do
            Text -> Term ()
put Text
"-"
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Bool
T.null Text
y) forall a b. (a -> b) -> a -> b
$
              Text -> Term ()
put Text
" "
            Text -> Term ()
put Text
y
            Term ()
newline
        D.Second [Text]
ys ->
          forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Text]
ys forall a b. (a -> b) -> a -> b
$ \Text
y -> forall a. Term a -> Term a
green forall a b. (a -> b) -> a -> b
$ do
            Text -> Term ()
put Text
"+"
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Bool
T.null Text
y) 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 = forall {c} {c}.
Int
-> Int
-> ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c)
-> [PolyDiff [c] [c]]
-> c
go Int
1 Int
1 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 = forall (t :: * -> *) a. Foldable t => t a -> Int
length [c]
a
              secondInc :: Int
secondInc = forall (t :: * -> *) a. Foldable t => t a -> Int
length [c]
b
              a' :: [(Int, Int, c)]
a' =
                forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3
                  (forall a. (a -> a) -> a -> [a]
iterate (forall a. Num a => a -> a -> a
+ Int
1) Int
firstLine)
                  (forall a. (a -> a) -> a -> [a]
iterate (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 forall a. Num a => a -> a -> a
+ Int
firstInc)
                (Int
secondLine forall a. Num a => a -> a -> a
+ Int
secondInc)
                ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. a -> b -> PolyDiff a b
D.Both [(Int, Int, c)]
a' [(Int, Int, c)]
a' forall a. a -> [a] -> [a]
:))
                [PolyDiff [c] [c]]
xs
        D.First [c]
a ->
          let firstInc :: Int
firstInc = forall (t :: * -> *) a. Foldable t => t a -> Int
length [c]
a
              a' :: [(Int, Int, c)]
a' =
                forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3
                  (forall a. (a -> a) -> a -> [a]
iterate (forall a. Num a => a -> a -> a
+ Int
1) Int
firstLine)
                  (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 forall a. Num a => a -> a -> a
+ Int
firstInc)
                Int
secondLine
                ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. a -> PolyDiff a b
D.First [(Int, Int, c)]
a' forall a. a -> [a] -> [a]
:))
                [PolyDiff [c] [c]]
xs
        D.Second [c]
b ->
          let secondInc :: Int
secondInc = forall (t :: * -> *) a. Foldable t => t a -> Int
length [c]
b
              b' :: [(Int, Int, c)]
b' =
                forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3
                  (forall a. a -> [a]
repeat Int
firstLine)
                  (forall a. (a -> a) -> a -> [a]
iterate (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 forall a. Num a => a -> a -> a
+ Int
secondInc)
                ([PolyDiff [(Int, Int, c)] [(Int, Int, c)]] -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. b -> PolyDiff a b
D.Second [(Int, Int, c)]
b' forall a. a -> [a] -> [a]
:))
                [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 forall a. a -> a
id 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' = 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 = forall a. Int -> [a] -> [a]
take Int
margin (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' = 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 = forall a. [a] -> [a]
reverse (forall a. Int -> [a] -> [a]
drop (Int
n' 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 forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Hunk
hunk forall a. a -> [a] -> [a]
:)
                bothHistory' :: [(Int, Int, Text)]
bothHistory' = forall a. [a] -> [a]
reverse [(Int, Int, Text)]
a forall a. [a] -> [a] -> [a]
++ [(Int, Int, Text)]
bothHistory
                lena :: Int
lena = forall (t :: * -> *) a. Foldable t => t a -> Int
length [(Int, Int, Text)]
a
                n' :: Int
n' = Int
n forall a. Num a => a -> a -> a
+ Int
lena
             in if Bool
gotChanges Bool -> Bool -> Bool
&& Int
n' forall a. Ord a => a -> a -> Bool
> Int
margin 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' 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 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {a} {a}.
[a] -> (a -> [PolyDiff [a] [a]]) -> a -> [PolyDiff [a] [a]]
addBothBefore [(Int, Int, Text)]
p (Diff [(Int, Int, Text)]
piece forall a. a -> [a] -> [a]
:)
                    p :: [(Int, Int, Text)]
p = 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' = forall {a} {a}.
[a] -> (a -> [PolyDiff [a] [a]]) -> a -> [PolyDiff [a] [a]]
addBothBefore [(Int, Int, Text)]
p (Diff [(Int, Int, Text)]
piece forall a. a -> [a] -> [a]
:)
                    p :: [(Int, Int, Text)]
p = forall a. [a] -> [a]
reverse (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 = (forall a b. a -> b -> PolyDiff a b
D.Both [a]
p [a]
p forall a. a -> [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 forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. a -> b -> PolyDiff a b
D.Both [a]
p [a]
p forall a. a -> [a] -> [a]
:)

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

-- | Trim empty “both” lines from beginning and end of a 'DiffList''.
trimEmpty :: DiffList' -> DiffList'
trimEmpty :: DiffList' -> DiffList'
trimEmpty = forall {c}. Bool -> (DiffList' -> c) -> DiffList' -> c
go Bool
True 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' = forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Text -> Bool
T.null forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, Int, Text) -> Text
third) (forall a. [a] -> [a]
reverse [(Int, Int, Text)]
x)
         in Bool -> (DiffList' -> c) -> DiffList' -> c
go Bool
False (DiffList' -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. a -> b -> PolyDiff a b
D.Both [(Int, Int, Text)]
x' [(Int, Int, Text)]
x' forall a. a -> [a] -> [a]
:)) []
      (D.Both [(Int, Int, Text)]
x [(Int, Int, Text)]
_ : DiffList'
xs) ->
        let x' :: [(Int, Int, Text)]
x' = forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Text -> Bool
T.null 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 forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. a -> b -> PolyDiff a b
D.Both [(Int, Int, Text)]
x' [(Int, Int, Text)]
x' forall a. a -> [a] -> [a]
:)) DiffList'
xs
              else Bool -> (DiffList' -> c) -> DiffList' -> c
go Bool
False (DiffList' -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a b. a -> b -> PolyDiff a b
D.Both [(Int, Int, Text)]
x [(Int, Int, Text)]
x forall a. a -> [a] -> [a]
:)) DiffList'
xs
      (Diff [(Int, Int, Text)]
x : DiffList'
xs) ->
        Bool -> (DiffList' -> c) -> DiffList' -> c
go Bool
False (DiffList' -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Diff [(Int, Int, Text)]
x forall a. a -> [a] -> [a]
:)) 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)]
_ -> forall a. a -> Maybe a
Just Int
x
  D.First ((Int
x, Int
_, a
_) : [(Int, Int, a)]
_) -> forall a. a -> Maybe a
Just Int
x
  D.Second ((Int
x, Int
_, a
_) : [(Int, Int, a)]
_) -> forall a. a -> Maybe a
Just Int
x
  Diff [(Int, Int, a)]
_ -> forall a. Maybe a
Nothing

firstLength :: [D.Diff [(Int, Int, a)]] -> Int
firstLength :: forall a. [Diff [(Int, Int, a)]] -> Int
firstLength = 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 forall a. Num a => a -> a -> a
+ 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 forall a. Num a => a -> a -> a
+ 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)]
_ -> forall a. a -> Maybe a
Just Int
x
  D.First ((Int
_, Int
x, a
_) : [(Int, Int, a)]
_) -> forall a. a -> Maybe a
Just Int
x
  D.Second ((Int
_, Int
x, a
_) : [(Int, Int, a)]
_) -> forall a. a -> Maybe a
Just Int
x
  Diff [(Int, Int, a)]
_ -> forall a. Maybe a
Nothing

secondLength :: [D.Diff [(Int, Int, a)]] -> Int
secondLength :: forall a. [Diff [(Int, Int, a)]] -> Int
secondLength = 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 forall a. Num a => a -> a -> a
+ 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 forall a. Num a => a -> a -> a
+ 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 = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a -> b) -> a -> b
$ \case
  D.Both a
a a
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 -> forall a b. a -> PolyDiff a b
D.First (a -> b
f a
a)
  D.Second a
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
hunkDiff :: DiffList
hunkSecondLength :: Int
hunkSecondStartLine :: Int
hunkFirstLength :: Int
hunkFirstStartLine :: Int
hunkDiff :: Hunk -> DiffList
hunkSecondLength :: Hunk -> Int
hunkSecondStartLine :: Hunk -> Int
hunkFirstLength :: Hunk -> Int
hunkFirstStartLine :: Hunk -> Int
..} =
  -- 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 (forall a. Int -> [a] -> [a]
take Int
hunkFirstLength [Int
hunkFirstStartLine ..])