packages feed

dbus-menu-0.1.4.0: src/DBusMenu/Reconcile.hs

module DBusMenu.Reconcile
  ( ReconcileAction (..),
    planReconciliation,
    planLabeledReconciliation,
  )
where

import Data.List (mapAccumL)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (catMaybes)
import qualified Data.Set as Set

-- | 'ReuseItem' names the existing item to retain. With
-- 'planLabeledReconciliation' it can differ from the desired key when the
-- service renumbered an otherwise unchanged item.
data ReconcileAction key
  = ReuseItem key
  | BuildItem key
  deriving (Eq, Show)

-- | Decide which existing items can be retained in a new ordered layout.
-- Existing and desired items match by key and by shape. A duplicate desired
-- key is built rather than reusing the same widget twice.
planReconciliation :: (Ord key, Eq shape) => Map key shape -> [(key, shape)] -> [ReconcileAction key]
planReconciliation existing = snd . mapAccumL plan Set.empty
  where
    plan used (key, desiredShape) =
      let canReuse =
            Set.notMember key used
              && Map.lookup key existing == Just desiredShape
          action = if canReuse then ReuseItem key else BuildItem key
       in (Set.insert key used, action)

-- | Like 'planReconciliation', but items that do not match by key then claim
-- unclaimed existing items with the same shape and label, in key order, so
-- services that renumber every item on each update (nm-applet does this
-- several times a minute) keep their widgets. No existing item is reused
-- twice.
planLabeledReconciliation ::
  (Ord key, Eq shape, Eq label) =>
  Map key (shape, label) ->
  [(key, shape, label)] ->
  [ReconcileAction key]
planLabeledReconciliation existing desired =
  snd (mapAccumL byShapeAndLabel claimedByKey (zip desired byKeyMatches))
  where
    byKeyMatches = snd (mapAccumL byKey Set.empty desired)
    byKey claimed (key, shape, _) =
      let hit =
            Set.notMember key claimed
              && (fst <$> Map.lookup key existing) == Just shape
       in if hit then (Set.insert key claimed, Just key) else (claimed, Nothing)
    claimedByKey = Set.fromList (catMaybes byKeyMatches)
    byShapeAndLabel claimed (_, Just key) = (claimed, ReuseItem key)
    byShapeAndLabel claimed ((key, shape, label), Nothing) =
      case [ k
           | (k, (s, l)) <- Map.toAscList existing,
             Set.notMember k claimed,
             s == shape,
             l == label
           ] of
        (k : _) -> (Set.insert k claimed, ReuseItem k)
        [] -> (claimed, BuildItem key)