packages feed

lipstick-0.1.0.0: internal/Lipstick/Internal/Mod.hs

-- | The representation of modifiers and the records they change, and the
-- modifiers themselves. Hidden: the public API is "Lipstick.Mod".
--
-- A @'Mod' f@ is two changes: one to the fields of the thing @f@, and one
-- to the 'Props' every thing has. "Lipstick.Mod" explains how they are used.
module Lipstick.Internal.Mod
  ( Mod (..)
  , fieldMod
  , propMod
  , runMod
  , appMod

    -- * Properties of every thing
  , Props (..)
  , defProps
  , propsOf
  , withProps
  , cropToProps

    -- * Components
  , HasBox (..)
  , HasBorder (..)
  , HasSize (..)
  , HasAlign (..)
  , HasWrap (..)
  , HasCrop
  , Sides (..)
  , uniformSides
  , Box (..)
  , defBox
  , BorderProps (..)
  , defBorderProps
  , Edges (..)
  , borderEdges
  , blendBorder
  , Size (..)
  , defSize
  , Align (..)
  , defAlign

    -- * Text attributes
  , bold
  , faint
  , italic
  , blink
  , reverseVideo
  , strikethrough
  , flag
  , unsetFlag
  , underline
  , underlineStyle
  , underlineColor
  , fg
  , bg
  , unsetFg
  , unsetBg
  , unsetUnderline
  , unsetUnderlineColor
  , link
  , unsetLink

    -- * Padding and margins
  , padding
  , paddingAll
  , paddingTop
  , paddingRight
  , paddingBottom
  , paddingLeft
  , margin
  , marginAll
  , marginTop
  , marginRight
  , marginBottom
  , marginLeft
  , marginBg
  , paddingChar
  , marginChar
  , unsetPadding
  , unsetMargins
  , unsetMarginBg

    -- * Borders
  , border
  , borderFg
  , borderBg
  , borderFgBlend
  , borderFgBlendOffset
  , borderTopFg
  , borderRightFg
  , borderBottomFg
  , borderLeftFg
  , borderTopBg
  , borderRightBg
  , borderBottomBg
  , borderLeftBg
  , unsetBorder
  , borderTop
  , borderRight
  , borderBottom
  , borderLeft

    -- * Size
  , width
  , height
  , maxWidth
  , maxHeight
  , grow
  , fillWidth
  , stretchHeight
  , unsetWidth
  , unsetHeight
  , unsetMaxWidth
  , unsetMaxHeight

    -- * Alignment
  , align
  , valign
  , unsetAlign

    -- * Wrapping
  , noWrap
  , unsetNoWrap
  ) where

import Data.Maybe (fromMaybe, isJust)
import Data.Text (Text)
import Lipstick.Internal.Border (Border, normalBorder)
import Lipstick.Internal.Color (Color, blend)
import Lipstick.Internal.Grid
import Lipstick.Internal.Types

-- | A modification of a thing @f@: of its own fields, and of the
-- properties every thing has. '<>' applies the left modifier first, so
-- settings on the right win.
data Mod f = Mod (f -> f) (Props -> Props)

instance Semigroup (Mod f) where
  Mod f1 g1 <> Mod f2 g2 = Mod (f2 . f1) (g2 . g1)

instance Monoid (Mod f) where
  mempty = Mod id id

-- | A modifier of the thing's own fields.
fieldMod :: (f -> f) -> Mod f
fieldMod f = Mod f id

-- | A modifier of the properties every thing has.
propMod :: (Props -> Props) -> Mod f
propMod = Mod id

-- | Apply a modifier to a thing's defaults.
runMod :: Mod f -> f -> (f, Props)
runMod (Mod f g) x = (f x, g defProps)

-- | Apply only the field part of a modifier.
appMod :: Mod f -> f -> f
appMod (Mod f _) = f

-- | Properties every thing has.
data Props = Props
  { propAttrs :: !Attrs
  -- ^ base text attributes for everything the thing draws
  , propMaxWidth :: !(Maybe Int)
  , propMaxHeight :: !(Maybe Int)
  }
  deriving (Eq, Show)

defProps :: Props
defProps = Props mempty Nothing Nothing

-- | The common properties a modifier sets, starting from the defaults.
propsOf :: Mod f -> Props
propsOf (Mod _ g) = g defProps

-- | What most builders do with the properties: lay the attributes under
-- everything, then crop.
withProps :: Props -> [[Cell]] -> Grid
withProps p = cropToProps p . underlay (propAttrs p) (fillAttrs (propAttrs p))

-- | Crop lines to 'maxWidth' and 'maxHeight'.
cropToProps :: Props -> [[Cell]] -> Grid
cropToProps p ls =
  let croppedW = maybe ls (\n -> map (cropCells n) ls) (propMaxWidth p)
   in fromCellLines (maybe croppedW (`take` croppedW) (propMaxHeight p))

-- | Things with padding and margins: 'Lipstick.Style.Style'. The
-- modifiers 'padding', 'margin', 'marginBg', 'paddingChar', 'marginChar'
-- and their variants apply to them.
class HasBox f where
  overBox :: (Box -> Box) -> f -> f

-- | Things with a border: 'Lipstick.Style.Style' and
-- 'Lipstick.Table.Table'. The modifiers 'border', 'borderFg', 'borderTop'
-- and the other border modifiers apply to them.
class HasBorder f where
  overBorder :: (BorderProps -> BorderProps) -> f -> f

-- | Things with a size: 'Lipstick.Style.Style', 'Lipstick.Table.Table',
-- 'Lipstick.Outline.Outline' and 'Lipstick.Place.Place'. The modifiers
-- 'width', 'height', 'grow', 'fillWidth', 'stretchHeight', 'unsetWidth' and
-- 'unsetHeight' apply to them. ('maxWidth' and 'maxHeight' work on
-- anything.)
class HasSize f where
  overSize :: (Size -> Size) -> f -> f

-- | Things whose content can be aligned: 'Lipstick.Style.Style' and
-- 'Lipstick.Place.Place'. The modifiers 'align', 'valign' and 'unsetAlign'
-- apply to them.
class HasAlign f where
  overAlign :: (Align -> Align) -> f -> f

-- | Things whose text can be wrapped: 'Lipstick.Style.Style' and
-- 'Lipstick.Table.Table'. The modifiers 'noWrap' and 'unsetNoWrap' apply
-- to them.
class HasWrap f where
  overWrap :: (Bool -> Bool) -> f -> f

-- | Things whose result can be cropped: every thing but
-- 'Lipstick.Block.Inline', whose text is laid out with the text around it.
-- The modifiers 'maxWidth', 'maxHeight', 'unsetMaxWidth' and
-- 'unsetMaxHeight' apply to them.
class HasCrop f

-- | One value for each side: top, right, bottom and left, as in CSS.
data Sides a = Sides {sideTop, sideRight, sideBottom, sideLeft :: !a}
  deriving (Eq, Show, Functor)

uniformSides :: a -> Sides a
uniformSides a = Sides a a a a

data Box = Box
  { boxPadding :: !(Sides Int)
  , boxMargin :: !(Sides Int)
  , boxMarginBg :: !(Maybe Color)
  , boxPaddingChar :: !Char
  , boxMarginChar :: !Char
  }
  deriving (Eq, Show)

defBox :: Box
defBox = Box (uniformSides 0) (uniformSides 0) Nothing ' ' ' '

data BorderProps = BorderProps
  { borderChars :: !(Maybe Border)
  , borderSides :: !(Sides (Maybe Bool))
  , borderFgColors :: !(Sides (Maybe Color))
  , borderBgColors :: !(Sides (Maybe Color))
  , borderFgStops :: ![Color]
  -- ^ when not empty, a gradient that replaces 'borderFgColors'
  , borderFgOffset :: !Int
  -- ^ how many cells the gradient is rotated clockwise
  }
  deriving (Eq, Show)

defBorderProps :: BorderProps
defBorderProps = BorderProps Nothing (uniformSides Nothing) (uniformSides Nothing) (uniformSides Nothing) [] 0

-- | Which sides of a border are drawn.
data Edges = Edges {edgeTop, edgeRight, edgeBottom, edgeLeft :: !Bool}
  deriving (Eq, Show)

-- | The characters to draw a border with, and the sides to draw. A side
-- left as 'Nothing' is drawn only when the characters are set; without
-- characters, sides switched on explicitly use 'normalBorder'.
borderEdges :: BorderProps -> (Border, Edges)
borderEdges p =
  let on side = fromMaybe (isJust (borderChars p)) (side (borderSides p))
   in ( fromMaybe normalBorder (borderChars p)
      , Edges (on sideTop) (on sideRight) (on sideBottom) (on sideLeft)
      )

-- | Recolour the border cells with a gradient, clockwise from the top-left
-- and rotated clockwise by an offset. The rows are the bordered box; @nl@
-- and @nr@ are the number of cells in the left and right border columns.
blendBorder :: [Color] -> Int -> Edges -> Int -> Int -> [[Cell]] -> [[Cell]]
blendBorder [] _ _ _ _ rows = rows
blendBorder stops offset edges nl nr rows =
  let (topRows, rest) = splitAt (if edgeTop edges then 1 else 0) rows
      (middleRows, bottomRows) = splitAt (length rest - (if edgeBottom edges then 1 else 0)) rest
      n = sum (map length topRows) + length middleRows * (nr + nl) + sum (map length bottomRows)
      colors = rotate offset (blend n stops)
      (cTop, c1) = splitAt (sum (map length topRows)) colors
      (cRight, c2) = splitAt (length middleRows * nr) c1
      (cBottom, cLeft) = splitAt (sum (map length bottomRows)) c2
      paint cs cells = zipWith (\c cell -> cell {cellAttrs = (cellAttrs cell) {attrFg = Just c}}) cs cells
      rightCols = chunks nr cRight
      leftCols = reverse (chunks nl cLeft)
      middle' =
        [ let (l, rest') = splitAt nl row
              (inner, r) = splitAt (length rest' - nr) rest'
           in paint (reverse lc) l ++ inner ++ paint rc r
        | (row, lc, rc) <- zip3 middleRows (leftCols ++ repeat []) (rightCols ++ repeat [])
        ]
   in [paint cTop row | row <- topRows]
        ++ middle'
        ++ [reverse (paint cBottom (reverse row)) | row <- bottomRows]
  where
    rotate k xs
      | null xs = xs
      | otherwise = let r = length xs - k `mod` length xs in drop r xs ++ take r xs
    chunks k xs
      | k <= 0 = []
      | null xs = []
      | otherwise = let (a, b) = splitAt k xs in a : chunks k b

-- | Width and height include padding and border, but not margins.
data Size = Size
  { sizeWidth :: !(Maybe Int)
  , sizeHeight :: !(Maybe Int)
  , sizeGrow :: !Int
  -- ^ weight for spare width; 0 for none
  , sizeStretch :: !Bool
  -- ^ grow to the height of the row
  }
  deriving (Eq, Show)

defSize :: Size
defSize = Size Nothing Nothing 0 False

data Align = Align
  { alignH :: !Position
  , alignV :: !Position
  }
  deriving (Eq, Show)

defAlign :: Align
defAlign = Align left top

-- Text attributes -------------------------------------------------------------

attrsMod :: (Attrs -> Attrs) -> Mod f
attrsMod g = propMod (\p -> p {propAttrs = g (propAttrs p)})

-- | Bold text.
bold :: Mod f
bold = flag Bold True

-- | Faint (dim) text.
faint :: Mod f
faint = flag Faint True

-- | Italic text.
italic :: Mod f
italic = flag Italic True

-- | Blinking text.
blink :: Mod f
blink = flag Blink True

-- | Swap the foreground and background colours.
reverseVideo :: Mod f
reverseVideo = flag Reverse True

-- | Text with a line through it.
strikethrough :: Mod f
strikethrough = flag Strikethrough True

-- | Set a flag explicitly; @flag Bold False@ turns off bold inherited from
-- an enclosing style.
flag :: Flag -> Bool -> Mod f
flag f v = attrsMod (setFlag f (Just v))

-- | Forget a flag, so an enclosing style's setting shows through.
unsetFlag :: Flag -> Mod f
unsetFlag f = attrsMod (setFlag f Nothing)

-- | A single underline: @underlineStyle SingleUnderline@.
underline :: Mod f
underline = underlineStyle SingleUnderline

-- | The kind of underline. Double, curly, dotted and dashed underlines are
-- sent only to true-colour terminals; on other profiles they are shown as
-- a single underline.
underlineStyle :: Underline -> Mod f
underlineStyle u = attrsMod (\a -> a {attrUnderline = Just u})

-- | The colour of the underline, when there is one. It is printed only
-- for the 256-colour and true-colour profiles.
underlineColor :: Color -> Mod f
underlineColor c = attrsMod (\a -> a {attrUnderlineColor = Just c})

-- | The foreground (text) colour.
fg :: Color -> Mod f
fg c = attrsMod (\a -> a {attrFg = Just c})

-- | The background colour.
bg :: Color -> Mod f
bg c = attrsMod (\a -> a {attrBg = Just c})

-- | Forget the foreground colour, so an enclosing style's shows through.
unsetFg :: Mod f
unsetFg = attrsMod (\a -> a {attrFg = Nothing})

-- | Forget the background colour, so an enclosing style's shows through.
unsetBg :: Mod f
unsetBg = attrsMod (\a -> a {attrBg = Nothing})

-- | Forget the underline, so an enclosing style's shows through.
unsetUnderline :: Mod f
unsetUnderline = attrsMod (\a -> a {attrUnderline = Nothing})

-- | Forget the underline colour, so an enclosing style's shows through.
unsetUnderlineColor :: Mod f
unsetUnderlineColor = attrsMod (\a -> a {attrUnderlineColor = Nothing})

-- | Make the text a hyperlink (OSC 8).
link :: Text -> Mod f
link url = attrsMod (\a -> a {attrLink = Just url})

-- | Forget the hyperlink, so an enclosing style's shows through.
unsetLink :: Mod f
unsetLink = attrsMod (\a -> a {attrLink = Nothing})

-- Padding and margins ---------------------------------------------------------

paddingMod :: HasBox f => (Sides Int -> Sides Int) -> Mod f
paddingMod g = fieldMod (overBox (\b -> b {boxPadding = fmap (max 0) (g (boxPadding b))}))

marginMod :: HasBox f => (Sides Int -> Sides Int) -> Mod f
marginMod g = fieldMod (overBox (\b -> b {boxMargin = fmap (max 0) (g (boxMargin b))}))

-- | @padding vertical horizontal@. Negative amounts count as 0, as for
-- all padding and margins.
padding :: HasBox f => Int -> Int -> Mod f
padding v h = paddingMod (const (Sides v h v h))

-- | The same padding on all four sides.
paddingAll :: HasBox f => Int -> Mod f
paddingAll n = paddingMod (const (uniformSides n))

-- | Padding above the content, in lines.
paddingTop :: HasBox f => Int -> Mod f
paddingTop n = paddingMod (\s -> s {sideTop = n})

-- | Padding to the right of the content, in columns.
paddingRight :: HasBox f => Int -> Mod f
paddingRight n = paddingMod (\s -> s {sideRight = n})

-- | Padding below the content, in lines.
paddingBottom :: HasBox f => Int -> Mod f
paddingBottom n = paddingMod (\s -> s {sideBottom = n})

-- | Padding to the left of the content, in columns.
paddingLeft :: HasBox f => Int -> Mod f
paddingLeft n = paddingMod (\s -> s {sideLeft = n})

-- | @margin vertical horizontal@
margin :: HasBox f => Int -> Int -> Mod f
margin v h = marginMod (const (Sides v h v h))

-- | The same margin on all four sides.
marginAll :: HasBox f => Int -> Mod f
marginAll n = marginMod (const (uniformSides n))

-- | The margin above the box, in lines.
marginTop :: HasBox f => Int -> Mod f
marginTop n = marginMod (\s -> s {sideTop = n})

-- | The margin to the right of the box, in columns.
marginRight :: HasBox f => Int -> Mod f
marginRight n = marginMod (\s -> s {sideRight = n})

-- | The margin below the box, in lines.
marginBottom :: HasBox f => Int -> Mod f
marginBottom n = marginMod (\s -> s {sideBottom = n})

-- | The margin to the left of the box, in columns.
marginLeft :: HasBox f => Int -> Mod f
marginLeft n = marginMod (\s -> s {sideLeft = n})

-- | Background colour of the margins.
marginBg :: HasBox f => Color -> Mod f
marginBg c = fieldMod (overBox (\b -> b {boxMarginBg = Just c}))

-- | No padding on any side.
unsetPadding :: HasBox f => Mod f
unsetPadding = paddingMod (const (uniformSides 0))

-- | No margins on any side.
unsetMargins :: HasBox f => Mod f
unsetMargins = marginMod (const (uniformSides 0))

-- | Forget the 'marginBg': the margins have no background colour of
-- their own.
unsetMarginBg :: HasBox f => Mod f
unsetMarginBg = fieldMod (overBox (\b -> b {boxMarginBg = Nothing}))

-- | The character padding is filled with (default a space).
paddingChar :: HasBox f => Char -> Mod f
paddingChar c = fieldMod (overBox (\b -> b {boxPaddingChar = c}))

-- | The character margins are filled with (default a space).
marginChar :: HasBox f => Char -> Mod f
marginChar c = fieldMod (overBox (\b -> b {boxMarginChar = c}))

-- Borders ---------------------------------------------------------------------

borderMod :: HasBorder f => (BorderProps -> BorderProps) -> Mod f
borderMod = fieldMod . overBorder

-- | Draw a border with these characters, on all sides not switched off.
border :: HasBorder f => Border -> Mod f
border b = borderMod (\p -> p {borderChars = Just b})

-- | The colour of the whole border. It replaces a 'borderFgBlend'
-- gradient.
borderFg :: HasBorder f => Color -> Mod f
borderFg c = borderMod (\p -> p {borderFgColors = uniformSides (Just c), borderFgStops = []})

-- | The background colour of the whole border.
borderBg :: HasBorder f => Color -> Mod f
borderBg c = borderMod (\p -> p {borderBgColors = uniformSides (Just c)})

-- | The colour of the top side of the border, corners included. Like
-- every side colour, it replaces a 'borderFgBlend' gradient.
borderTopFg :: HasBorder f => Color -> Mod f
borderTopFg c = borderFgSide (\s -> s {sideTop = Just c})

-- | The colour of the right side of the border, without the corners.
borderRightFg :: HasBorder f => Color -> Mod f
borderRightFg c = borderFgSide (\s -> s {sideRight = Just c})

-- | The colour of the bottom side of the border, corners included.
borderBottomFg :: HasBorder f => Color -> Mod f
borderBottomFg c = borderFgSide (\s -> s {sideBottom = Just c})

-- | The colour of the left side of the border, without the corners.
borderLeftFg :: HasBorder f => Color -> Mod f
borderLeftFg c = borderFgSide (\s -> s {sideLeft = Just c})

-- | The background colour of the top side of the border, corners
-- included.
borderTopBg :: HasBorder f => Color -> Mod f
borderTopBg c = borderBgSide (\s -> s {sideTop = Just c})

-- | The background colour of the right side of the border, without the
-- corners.
borderRightBg :: HasBorder f => Color -> Mod f
borderRightBg c = borderBgSide (\s -> s {sideRight = Just c})

-- | The background colour of the bottom side of the border, corners
-- included.
borderBottomBg :: HasBorder f => Color -> Mod f
borderBottomBg c = borderBgSide (\s -> s {sideBottom = Just c})

-- | The background colour of the left side of the border, without the
-- corners.
borderLeftBg :: HasBorder f => Color -> Mod f
borderLeftBg c = borderBgSide (\s -> s {sideLeft = Just c})

borderFgSide, borderBgSide :: HasBorder f => (Sides (Maybe Color) -> Sides (Maybe Color)) -> Mod f
borderFgSide g = borderMod (\p -> p {borderFgColors = g (borderFgColors p), borderFgStops = []})
borderBgSide g = borderMod (\p -> p {borderBgColors = g (borderBgColors p)})

-- | Rotate a 'borderFgBlend' gradient clockwise by this many cells
-- (anticlockwise if negative).
borderFgBlendOffset :: HasBorder f => Int -> Mod f
borderFgBlendOffset n = borderMod (\p -> p {borderFgOffset = n})

-- | No border, and no border colours.
unsetBorder :: HasBorder f => Mod f
unsetBorder = borderMod (const defBorderProps)

-- | Colour the border with a gradient through these colours, going
-- clockwise from the top-left corner.
borderFgBlend :: HasBorder f => [Color] -> Mod f
borderFgBlend cs = borderMod (\p -> p {borderFgStops = cs})

-- | Whether the top side of the border is drawn. By default a side is
-- drawn when a 'border' is set; switched on without one, it is drawn with
-- 'normalBorder'.
borderTop :: HasBorder f => Bool -> Mod f
borderTop v = borderSide (\s -> s {sideTop = Just v})

-- | Whether the right side of the border is drawn, as for 'borderTop'.
borderRight :: HasBorder f => Bool -> Mod f
borderRight v = borderSide (\s -> s {sideRight = Just v})

-- | Whether the bottom side of the border is drawn, as for 'borderTop'.
borderBottom :: HasBorder f => Bool -> Mod f
borderBottom v = borderSide (\s -> s {sideBottom = Just v})

-- | Whether the left side of the border is drawn, as for 'borderTop'.
borderLeft :: HasBorder f => Bool -> Mod f
borderLeft v = borderSide (\s -> s {sideLeft = Just v})

borderSide :: HasBorder f => (Sides (Maybe Bool) -> Sides (Maybe Bool)) -> Mod f
borderSide g = borderMod (\p -> p {borderSides = g (borderSides p)})

-- Size ------------------------------------------------------------------------

sizeMod :: HasSize f => (Size -> Size) -> Mod f
sizeMod = fieldMod . overSize

-- | @width n@: a total width of @n@ columns when there is room for it.
-- Laid out in less space (a narrow terminal, a full row), the thing
-- shrinks like any other rather than overflowing. For a style, the width
-- includes padding and border, but not margins. A negative width counts
-- as 0, as do negative heights and crops.
width :: HasSize f => Int -> Mod f
width n = sizeMod (\s -> s {sizeWidth = Just (max 0 n)})

-- | @height n@: the thing is at least @n@ lines tall. For a style, the
-- height includes padding and border, but not margins.
height :: HasSize f => Int -> Mod f
height n = sizeMod (\s -> s {sizeHeight = Just (max 0 n)})

-- | Take a share of the spare width, in proportion to the weight, when the
-- thing is laid out next to others or on its own in the output's width.
-- With a 'width', that width is the least it takes.
grow :: HasSize f => Int -> Mod f
grow k = sizeMod (\s -> s {sizeGrow = max 0 k})

-- | Fill the width available: @grow 1@.
fillWidth :: HasSize f => Mod f
fillWidth = grow 1

-- | Grow to the height of the row the thing is in (as the tallest thing
-- next to it).
stretchHeight :: HasSize f => Mod f
stretchHeight = sizeMod (\s -> s {sizeStretch = True})

-- | Crop the final result (for a style, margins included) to at most this
-- many columns.
maxWidth :: HasCrop f => Int -> Mod f
maxWidth n = propMod (\p -> p {propMaxWidth = Just (max 0 n)})

-- | Crop the final result (for a style, margins included) to at most this
-- many lines. A table leaves out the body rows that do not fit instead,
-- with a row of ellipses in their place where there is room for it (see
-- 'Lipstick.Table.yOffset').
maxHeight :: HasCrop f => Int -> Mod f
maxHeight n = propMod (\p -> p {propMaxHeight = Just (max 0 n)})

-- | Forget the 'width'.
unsetWidth :: HasSize f => Mod f
unsetWidth = sizeMod (\s -> s {sizeWidth = Nothing})

-- | Forget the 'height'.
unsetHeight :: HasSize f => Mod f
unsetHeight = sizeMod (\s -> s {sizeHeight = Nothing})

-- | Forget the 'maxWidth': no crop to a width.
unsetMaxWidth :: HasCrop f => Mod f
unsetMaxWidth = propMod (\p -> p {propMaxWidth = Nothing})

-- | Forget the 'maxHeight': no crop to a height.
unsetMaxHeight :: HasCrop f => Mod f
unsetMaxHeight = propMod (\p -> p {propMaxHeight = Nothing})

-- Alignment -------------------------------------------------------------------

-- | Horizontal alignment.
align :: HasAlign f => Position -> Mod f
align p = fieldMod (overAlign (\a -> a {alignH = p}))

-- | Vertical alignment.
valign :: HasAlign f => Position -> Mod f
valign p = fieldMod (overAlign (\a -> a {alignV = p}))

-- | Back to the default alignment, 'left' and 'top'.
unsetAlign :: HasAlign f => Mod f
unsetAlign = fieldMod (overAlign (const defAlign))

-- Wrapping --------------------------------------------------------------------

-- | Cut lines that are too long with an ellipsis instead of wrapping them.
noWrap :: HasWrap f => Mod f
noWrap = fieldMod (overWrap (const False))

-- | Wrap lines again, even inside a style that sets 'noWrap'.
unsetNoWrap :: HasWrap f => Mod f
unsetNoWrap = fieldMod (overWrap (const True))