packages feed

edits-0.1.1.0: src/Data/Text/Difference.hs

-- | This module provides data types and functions to display
--   the difference between 2 strings according to the number of edit operations
--   necessary to go from one to the other
module Data.Text.Difference where

import Data.Text qualified as T
import Data.Text.Color
import Data.Text.EditOperation
import Data.Text.Shorten
import Data.Text.Token
import Protolude

-- | Separators are used to highlight a difference between 2 pieces of text
--   for example
data Separators = Separators
  { startSeparator :: Text,
    endSeparator :: Text
  }
  deriving (Eq, Show)

-- | Make parens separators
parensSeparators :: Separators
parensSeparators = makeCharSeparators '(' ')'

-- | Make brackets separators
bracketsSeparators :: Separators
bracketsSeparators = makeCharSeparators '[' ']'

-- | Make separators with simple Chars
makeCharSeparators :: Char -> Char -> Separators
makeCharSeparators c1 c2 = Separators (T.singleton c1) (T.singleton c2)

-- | Options to use for displaying differences
data DisplayOptions = DisplayOptions
  { _separators :: Separators,
    _shortenOptions :: ShortenOptions,
    _displayEditOperation :: EditOperation Char -> Text
  }

-- | Default display options
defaultDisplayOptions :: DisplayOptions
defaultDisplayOptions = DisplayOptions bracketsSeparators (ShortenOptions 20 "...") defaultDisplayEditOperations

-- | Display an edit operation by prepending a symbol showing which operation is used
defaultDisplayEditOperations :: EditOperation Char -> Text
defaultDisplayEditOperations (Insert c) = "+" <> T.singleton c
defaultDisplayEditOperations (Delete c) = "-" <> T.singleton c
defaultDisplayEditOperations (Substitute c1 c2) = "~" <> T.singleton c1 <> "/" <> T.singleton c2
defaultDisplayEditOperations (Keep c) = T.singleton c

-- | Display an edit operation using ascii colors: green = added, red = removed, blue = substituted
coloredDisplayEditOperation :: EditOperation Char -> Text
coloredDisplayEditOperation (Insert c) = colorAs Green (T.singleton c)
coloredDisplayEditOperation (Delete c) = colorAs Red (T.singleton c)
coloredDisplayEditOperation (Substitute c _) = colorAs Cyan (T.singleton c)
coloredDisplayEditOperation (Keep c) = T.singleton c

-- | Show the differences by enclosing them in separators
--   Additionally shorten the text outside the separators if it is too long
displayDiffs :: DisplayOptions -> [EditOperation Char] -> Text
displayDiffs (DisplayOptions (Separators start end) shortenOptions displayEditOperation) operations = do
  let (isDifferent, result) =
        foldl'
          ( \(different, res) operation ->
              --  different keeps track of the fact that we entered a section of the text having some differences
              --  this allows us to open a 'start' delimiter
              --  then when we go back to keeping the same character, we can close with an 'end' delimiter
              case operation of
                Insert {} -> (True, res <> [Delimiter start | not different] <> [Kept $ displayEditOperation operation])
                Delete {} -> (True, res <> [Delimiter start | not different] <> [Kept $ displayEditOperation operation])
                Substitute {} -> (True, res <> [Delimiter start | not different] <> [Kept $ displayEditOperation operation])
                Keep {} -> (False, res <> [Delimiter end | different] <> [Kept $ displayEditOperation operation])
          )
          (False, [])
          operations

  let fullResult = if isDifferent then result <> [Delimiter end] else result
  T.concat (showToken <$> shortenTokens shortenOptions (Delimiter start) (Delimiter end) fullResult)