packages feed

brick 0.73 → 1.0

raw patch · 49 files changed

+2664/−1680 lines, 49 filesdep +bimapdep +mtldep −transformersdep ~basedep ~microlensdep ~vtynew-component:exe:brick-custom-keybinding-demoPVP ok

version bump matches the API change (PVP)

Dependencies added: bimap, mtl

Dependencies removed: transformers

Dependency ranges changed: base, microlens, vty

API changes (from Hackage documentation)

- Brick.AttrMap: instance Data.String.IsString Brick.AttrMap.AttrName
- Brick.Main: continue :: s -> EventM n (Next s)
- Brick.Types: EventM :: ReaderT (EventRO n) (StateT (EventState n) IO) a -> EventM n a
- Brick.Types: Max :: Padding
- Brick.Types: Pad :: Int -> Padding
- Brick.Types: [runEventM] :: EventM n a -> ReaderT (EventRO n) (StateT (EventState n) IO) a
- Brick.Types: data Next a
- Brick.Types: data Padding
- Brick.Types: handleEventLensed :: a -> Lens' a b -> (e -> b -> EventM n b) -> e -> EventM n a
- Brick.Types: instance Control.Monad.Catch.MonadCatch (Brick.Types.EventM n)
- Brick.Types: instance Control.Monad.Catch.MonadMask (Brick.Types.EventM n)
- Brick.Types: instance Control.Monad.Catch.MonadThrow (Brick.Types.EventM n)
- Brick.Types: instance Control.Monad.Fail.MonadFail (Brick.Types.EventM n)
- Brick.Types: instance Control.Monad.IO.Class.MonadIO (Brick.Types.EventM n)
- Brick.Types: instance GHC.Base.Applicative (Brick.Types.EventM n)
- Brick.Types: instance GHC.Base.Functor (Brick.Types.EventM n)
- Brick.Types: instance GHC.Base.Monad (Brick.Types.EventM n)
- Brick.Types: newtype EventM n a
+ Brick.Keybindings.KeyConfig: Binding :: Key -> Set Modifier -> Binding
+ Brick.Keybindings.KeyConfig: BindingList :: [Binding] -> BindingState
+ Brick.Keybindings.KeyConfig: Unbound :: BindingState
+ Brick.Keybindings.KeyConfig: [kbKey] :: Binding -> Key
+ Brick.Keybindings.KeyConfig: [kbMods] :: Binding -> Set Modifier
+ Brick.Keybindings.KeyConfig: allActiveBindings :: (Show k, Ord k) => KeyConfig k -> k -> [Binding]
+ Brick.Keybindings.KeyConfig: allDefaultBindings :: Ord k => KeyConfig k -> k -> [Binding]
+ Brick.Keybindings.KeyConfig: bind :: ToBinding a => a -> Binding
+ Brick.Keybindings.KeyConfig: binding :: Key -> [Modifier] -> Binding
+ Brick.Keybindings.KeyConfig: class ToBinding a
+ Brick.Keybindings.KeyConfig: ctrl :: ToBinding a => a -> Binding
+ Brick.Keybindings.KeyConfig: data Binding
+ Brick.Keybindings.KeyConfig: data BindingState
+ Brick.Keybindings.KeyConfig: data KeyConfig k
+ Brick.Keybindings.KeyConfig: firstActiveBinding :: (Show k, Ord k) => KeyConfig k -> k -> Maybe Binding
+ Brick.Keybindings.KeyConfig: firstDefaultBinding :: (Show k, Ord k) => KeyConfig k -> k -> Maybe Binding
+ Brick.Keybindings.KeyConfig: fn :: Int -> Binding
+ Brick.Keybindings.KeyConfig: instance Brick.Keybindings.KeyConfig.ToBinding Brick.Keybindings.KeyConfig.Binding
+ Brick.Keybindings.KeyConfig: instance Brick.Keybindings.KeyConfig.ToBinding GHC.Types.Char
+ Brick.Keybindings.KeyConfig: instance Brick.Keybindings.KeyConfig.ToBinding Graphics.Vty.Input.Events.Key
+ Brick.Keybindings.KeyConfig: instance GHC.Classes.Eq Brick.Keybindings.KeyConfig.Binding
+ Brick.Keybindings.KeyConfig: instance GHC.Classes.Eq Brick.Keybindings.KeyConfig.BindingState
+ Brick.Keybindings.KeyConfig: instance GHC.Classes.Eq k => GHC.Classes.Eq (Brick.Keybindings.KeyConfig.KeyConfig k)
+ Brick.Keybindings.KeyConfig: instance GHC.Classes.Ord Brick.Keybindings.KeyConfig.Binding
+ Brick.Keybindings.KeyConfig: instance GHC.Classes.Ord Brick.Keybindings.KeyConfig.BindingState
+ Brick.Keybindings.KeyConfig: instance GHC.Show.Show Brick.Keybindings.KeyConfig.Binding
+ Brick.Keybindings.KeyConfig: instance GHC.Show.Show Brick.Keybindings.KeyConfig.BindingState
+ Brick.Keybindings.KeyConfig: instance GHC.Show.Show k => GHC.Show.Show (Brick.Keybindings.KeyConfig.KeyConfig k)
+ Brick.Keybindings.KeyConfig: keyConfigEvents :: KeyConfig k -> KeyEvents k
+ Brick.Keybindings.KeyConfig: lookupKeyConfigBindings :: Ord k => KeyConfig k -> k -> Maybe BindingState
+ Brick.Keybindings.KeyConfig: meta :: ToBinding a => a -> Binding
+ Brick.Keybindings.KeyConfig: newKeyConfig :: Ord k => KeyEvents k -> [(k, [Binding])] -> [(k, BindingState)] -> KeyConfig k
+ Brick.Keybindings.KeyConfig: shift :: ToBinding a => a -> Binding
+ Brick.Keybindings.KeyDispatcher: ByEvent :: k -> EventTrigger k
+ Brick.Keybindings.KeyDispatcher: ByKey :: Binding -> EventTrigger k
+ Brick.Keybindings.KeyDispatcher: Handler :: Text -> m () -> Handler m
+ Brick.Keybindings.KeyDispatcher: KeyEventHandler :: Handler m -> EventTrigger k -> KeyEventHandler k m
+ Brick.Keybindings.KeyDispatcher: KeyHandler :: KeyEventHandler k m -> Binding -> KeyHandler k m
+ Brick.Keybindings.KeyDispatcher: [handlerAction] :: Handler m -> m ()
+ Brick.Keybindings.KeyDispatcher: [handlerDescription] :: Handler m -> Text
+ Brick.Keybindings.KeyDispatcher: [kehEventTrigger] :: KeyEventHandler k m -> EventTrigger k
+ Brick.Keybindings.KeyDispatcher: [kehHandler] :: KeyEventHandler k m -> Handler m
+ Brick.Keybindings.KeyDispatcher: [khBinding] :: KeyHandler k m -> Binding
+ Brick.Keybindings.KeyDispatcher: [khHandler] :: KeyHandler k m -> KeyEventHandler k m
+ Brick.Keybindings.KeyDispatcher: data EventTrigger k
+ Brick.Keybindings.KeyDispatcher: data Handler m
+ Brick.Keybindings.KeyDispatcher: data KeyDispatcher k m
+ Brick.Keybindings.KeyDispatcher: data KeyEventHandler k m
+ Brick.Keybindings.KeyDispatcher: data KeyHandler k m
+ Brick.Keybindings.KeyDispatcher: handleKey :: Monad m => KeyDispatcher k m -> Key -> [Modifier] -> m Bool
+ Brick.Keybindings.KeyDispatcher: instance GHC.Classes.Eq k => GHC.Classes.Eq (Brick.Keybindings.KeyDispatcher.EventTrigger k)
+ Brick.Keybindings.KeyDispatcher: instance GHC.Classes.Ord k => GHC.Classes.Ord (Brick.Keybindings.KeyDispatcher.EventTrigger k)
+ Brick.Keybindings.KeyDispatcher: instance GHC.Show.Show k => GHC.Show.Show (Brick.Keybindings.KeyDispatcher.EventTrigger k)
+ Brick.Keybindings.KeyDispatcher: keyDispatcher :: Ord k => KeyConfig k -> [KeyEventHandler k m] -> KeyDispatcher k m
+ Brick.Keybindings.KeyDispatcher: keyDispatcherToList :: KeyDispatcher k m -> [(Binding, KeyHandler k m)]
+ Brick.Keybindings.KeyDispatcher: lookupVtyEvent :: Key -> [Modifier] -> KeyDispatcher k m -> Maybe (KeyHandler k m)
+ Brick.Keybindings.KeyDispatcher: onEvent :: k -> Text -> m () -> KeyEventHandler k m
+ Brick.Keybindings.KeyDispatcher: onKey :: ToBinding a => a -> Text -> m () -> KeyEventHandler k m
+ Brick.Keybindings.KeyEvents: data KeyEvents k
+ Brick.Keybindings.KeyEvents: instance GHC.Classes.Eq k => GHC.Classes.Eq (Brick.Keybindings.KeyEvents.KeyEvents k)
+ Brick.Keybindings.KeyEvents: instance GHC.Show.Show k => GHC.Show.Show (Brick.Keybindings.KeyEvents.KeyEvents k)
+ Brick.Keybindings.KeyEvents: keyEventName :: Ord k => KeyEvents k -> k -> Maybe Text
+ Brick.Keybindings.KeyEvents: keyEvents :: Ord k => [(Text, k)] -> KeyEvents k
+ Brick.Keybindings.KeyEvents: keyEventsList :: KeyEvents k -> [(Text, k)]
+ Brick.Keybindings.KeyEvents: lookupKeyEvent :: Ord k => KeyEvents k -> Text -> Maybe k
+ Brick.Keybindings.Parse: keybindingIniParser :: KeyEvents k -> Text -> IniParser (Maybe [(k, BindingState)])
+ Brick.Keybindings.Parse: keybindingsFromFile :: KeyEvents k -> Text -> FilePath -> IO (Either String (Maybe [(k, BindingState)]))
+ Brick.Keybindings.Parse: keybindingsFromIni :: KeyEvents k -> Text -> Text -> Either String (Maybe [(k, BindingState)])
+ Brick.Keybindings.Parse: parseBinding :: Text -> Either String Binding
+ Brick.Keybindings.Parse: parseBindingList :: Text -> Either String BindingState
+ Brick.Keybindings.Pretty: eventDescriptionAttr :: AttrName
+ Brick.Keybindings.Pretty: eventNameAttr :: AttrName
+ Brick.Keybindings.Pretty: keybindingAttr :: AttrName
+ Brick.Keybindings.Pretty: keybindingHelpBaseAttr :: AttrName
+ Brick.Keybindings.Pretty: keybindingHelpWidget :: Ord k => KeyConfig k -> [KeyEventHandler k m] -> Widget n
+ Brick.Keybindings.Pretty: keybindingMarkdownTable :: Ord k => KeyConfig k -> [(Text, [KeyEventHandler k m])] -> Text
+ Brick.Keybindings.Pretty: keybindingTextTable :: Ord k => KeyConfig k -> [(Text, [KeyEventHandler k m])] -> Text
+ Brick.Keybindings.Pretty: ppBinding :: Binding -> Text
+ Brick.Keybindings.Pretty: ppKey :: Key -> Text
+ Brick.Keybindings.Pretty: ppMaybeBinding :: Maybe Binding -> Text
+ Brick.Keybindings.Pretty: ppModifier :: Modifier -> Text
+ Brick.Main: suspendAndResume' :: Ord n => IO a -> EventM n s a
+ Brick.Types: data EventM n s a
+ Brick.Types: get :: MonadState s m => m s
+ Brick.Types: gets :: MonadState s m => (s -> a) -> m a
+ Brick.Types: infixr 2 `zoom`
+ Brick.Types: modify :: MonadState s m => (s -> s) -> m ()
+ Brick.Types: nestEventM :: a -> EventM n a b -> EventM n s (a, b)
+ Brick.Types: nestEventM' :: a -> EventM n a b -> EventM n s a
+ Brick.Types: put :: MonadState s m => s -> m ()
+ Brick.Types: zoom :: Zoom m n s t => LensLike' (Zoomed m c) t s -> m c -> n c
+ Brick.Widgets.Core: Max :: Padding
+ Brick.Widgets.Core: Pad :: Int -> Padding
+ Brick.Widgets.Core: data Padding
+ Brick.Widgets.List: listSelectedElementL :: (Splittable t, Traversable t, Semigroup (t e)) => Traversal' (GenericList n t e) e
- Brick.BorderMap: eBottomL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.BorderMap: eBottomL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.BorderMap: eLeftL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.BorderMap: eLeftL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.BorderMap: eRightL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.BorderMap: eRightL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.BorderMap: eTopL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.BorderMap: eTopL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.Forms: FormField :: n -> (b -> Maybe a) -> Bool -> (Bool -> b -> Widget n) -> (BrickEvent n e -> b -> EventM n b) -> FormField a b e n
+ Brick.Forms: FormField :: n -> (b -> Maybe a) -> Bool -> (Bool -> b -> Widget n) -> (BrickEvent n e -> EventM n b ()) -> FormField a b e n
- Brick.Forms: [formFieldHandleEvent] :: FormField a b e n -> BrickEvent n e -> b -> EventM n b
+ Brick.Forms: [formFieldHandleEvent] :: FormField a b e n -> BrickEvent n e -> EventM n b ()
- Brick.Forms: handleFormEvent :: Eq n => BrickEvent n e -> Form s e n -> EventM n (Form s e n)
+ Brick.Forms: handleFormEvent :: Eq n => BrickEvent n e -> EventM n (Form s e n) ()
- Brick.Main: App :: (s -> [Widget n]) -> (s -> [CursorLocation n] -> Maybe (CursorLocation n)) -> (s -> BrickEvent n e -> EventM n (Next s)) -> (s -> EventM n s) -> (s -> AttrMap) -> App s e n
+ Brick.Main: App :: (s -> [Widget n]) -> (s -> [CursorLocation n] -> Maybe (CursorLocation n)) -> (BrickEvent n e -> EventM n s ()) -> EventM n s () -> (s -> AttrMap) -> App s e n
- Brick.Main: [appHandleEvent] :: App s e n -> s -> BrickEvent n e -> EventM n (Next s)
+ Brick.Main: [appHandleEvent] :: App s e n -> BrickEvent n e -> EventM n s ()
- Brick.Main: [appStartEvent] :: App s e n -> s -> EventM n s
+ Brick.Main: [appStartEvent] :: App s e n -> EventM n s ()
- Brick.Main: continueWithoutRedraw :: s -> EventM n (Next s)
+ Brick.Main: continueWithoutRedraw :: EventM n s ()
- Brick.Main: findClickedExtents :: (Int, Int) -> EventM n [Extent n]
+ Brick.Main: findClickedExtents :: (Int, Int) -> EventM n s [Extent n]
- Brick.Main: getRenderState :: EventM n (RenderState n)
+ Brick.Main: getRenderState :: EventM n s (RenderState n)
- Brick.Main: getVtyHandle :: EventM n Vty
+ Brick.Main: getVtyHandle :: EventM n s Vty
- Brick.Main: hScrollBy :: ViewportScroll n -> Int -> EventM n ()
+ Brick.Main: hScrollBy :: ViewportScroll n -> forall s. Int -> EventM n s ()
- Brick.Main: hScrollPage :: ViewportScroll n -> Direction -> EventM n ()
+ Brick.Main: hScrollPage :: ViewportScroll n -> forall s. Direction -> EventM n s ()
- Brick.Main: hScrollToBeginning :: ViewportScroll n -> EventM n ()
+ Brick.Main: hScrollToBeginning :: ViewportScroll n -> forall s. EventM n s ()
- Brick.Main: hScrollToEnd :: ViewportScroll n -> EventM n ()
+ Brick.Main: hScrollToEnd :: ViewportScroll n -> forall s. EventM n s ()
- Brick.Main: halt :: s -> EventM n (Next s)
+ Brick.Main: halt :: EventM n s ()
- Brick.Main: invalidateCache :: Ord n => EventM n ()
+ Brick.Main: invalidateCache :: Ord n => EventM n s ()
- Brick.Main: invalidateCacheEntry :: Ord n => n -> EventM n ()
+ Brick.Main: invalidateCacheEntry :: Ord n => n -> EventM n s ()
- Brick.Main: lookupExtent :: Eq n => n -> EventM n (Maybe (Extent n))
+ Brick.Main: lookupExtent :: Eq n => n -> EventM n s (Maybe (Extent n))
- Brick.Main: lookupViewport :: Ord n => n -> EventM n (Maybe Viewport)
+ Brick.Main: lookupViewport :: Ord n => n -> EventM n s (Maybe Viewport)
- Brick.Main: makeVisible :: Ord n => n -> EventM n ()
+ Brick.Main: makeVisible :: Ord n => n -> EventM n s ()
- Brick.Main: resizeOrQuit :: s -> BrickEvent n e -> EventM n (Next s)
+ Brick.Main: resizeOrQuit :: BrickEvent n e -> EventM n s ()
- Brick.Main: setLeft :: ViewportScroll n -> Int -> EventM n ()
+ Brick.Main: setLeft :: ViewportScroll n -> forall s. Int -> EventM n s ()
- Brick.Main: setTop :: ViewportScroll n -> Int -> EventM n ()
+ Brick.Main: setTop :: ViewportScroll n -> forall s. Int -> EventM n s ()
- Brick.Main: suspendAndResume :: IO s -> EventM n (Next s)
+ Brick.Main: suspendAndResume :: Ord n => IO s -> EventM n s ()
- Brick.Main: vScrollBy :: ViewportScroll n -> Int -> EventM n ()
+ Brick.Main: vScrollBy :: ViewportScroll n -> forall s. Int -> EventM n s ()
- Brick.Main: vScrollPage :: ViewportScroll n -> Direction -> EventM n ()
+ Brick.Main: vScrollPage :: ViewportScroll n -> forall s. Direction -> EventM n s ()
- Brick.Main: vScrollToBeginning :: ViewportScroll n -> EventM n ()
+ Brick.Main: vScrollToBeginning :: ViewportScroll n -> forall s. EventM n s ()
- Brick.Main: vScrollToEnd :: ViewportScroll n -> EventM n ()
+ Brick.Main: vScrollToEnd :: ViewportScroll n -> forall s. EventM n s ()
- Brick.Types: Extent :: n -> Location -> (Int, Int) -> Extent n
+ Brick.Types: Extent :: !n -> !Location -> !(Int, Int) -> Extent n
- Brick.Types: Result :: Image -> [CursorLocation n] -> [VisibilityRequest] -> [Extent n] -> BorderMap DynBorder -> Result n
+ Brick.Types: Result :: !Image -> ![CursorLocation n] -> ![VisibilityRequest] -> ![Extent n] -> !BorderMap DynBorder -> Result n
- Brick.Types: [borders] :: Result n -> BorderMap DynBorder
+ Brick.Types: [borders] :: Result n -> !BorderMap DynBorder
- Brick.Types: [cursors] :: Result n -> [CursorLocation n]
+ Brick.Types: [cursors] :: Result n -> ![CursorLocation n]
- Brick.Types: [extentName] :: Extent n -> n
+ Brick.Types: [extentName] :: Extent n -> !n
- Brick.Types: [extentSize] :: Extent n -> (Int, Int)
+ Brick.Types: [extentSize] :: Extent n -> !(Int, Int)
- Brick.Types: [extentUpperLeft] :: Extent n -> Location
+ Brick.Types: [extentUpperLeft] :: Extent n -> !Location
- Brick.Types: [extents] :: Result n -> [Extent n]
+ Brick.Types: [extents] :: Result n -> ![Extent n]
- Brick.Types: [image] :: Result n -> Image
+ Brick.Types: [image] :: Result n -> !Image
- Brick.Types: [visibilityRequests] :: Result n -> [VisibilityRequest]
+ Brick.Types: [visibilityRequests] :: Result n -> ![VisibilityRequest]
- Brick.Types: availHeightL :: forall n_awEq. Lens' (Context n_awEq) Int
+ Brick.Types: availHeightL :: forall n_axz2. Lens' (Context n_axz2) Int
- Brick.Types: availWidthL :: forall n_awEq. Lens' (Context n_awEq) Int
+ Brick.Types: availWidthL :: forall n_axz2. Lens' (Context n_axz2) Int
- Brick.Types: bordersL :: forall n_awEu. Lens' (Result n_awEu) (BorderMap DynBorder)
+ Brick.Types: bordersL :: forall n_axz6. Lens' (Result n_axz6) (BorderMap DynBorder)
- Brick.Types: ctxAttrMapL :: forall n_awEq. Lens' (Context n_awEq) AttrMap
+ Brick.Types: ctxAttrMapL :: forall n_axz2. Lens' (Context n_axz2) AttrMap
- Brick.Types: ctxAttrNameL :: forall n_awEq. Lens' (Context n_awEq) AttrName
+ Brick.Types: ctxAttrNameL :: forall n_axz2. Lens' (Context n_axz2) AttrName
- Brick.Types: ctxBorderStyleL :: forall n_awEq. Lens' (Context n_awEq) BorderStyle
+ Brick.Types: ctxBorderStyleL :: forall n_axz2. Lens' (Context n_axz2) BorderStyle
- Brick.Types: ctxDynBordersL :: forall n_awEq. Lens' (Context n_awEq) Bool
+ Brick.Types: ctxDynBordersL :: forall n_axz2. Lens' (Context n_axz2) Bool
- Brick.Types: ctxHScrollBarOrientationL :: forall n_awEq. Lens' (Context n_awEq) (Maybe HScrollBarOrientation)
+ Brick.Types: ctxHScrollBarOrientationL :: forall n_axz2. Lens' (Context n_axz2) (Maybe HScrollBarOrientation)
- Brick.Types: ctxHScrollBarRendererL :: forall n_awEq. Lens' (Context n_awEq) (Maybe (ScrollbarRenderer n_awEq))
+ Brick.Types: ctxHScrollBarRendererL :: forall n_axz2. Lens' (Context n_axz2) (Maybe (ScrollbarRenderer n_axz2))
- Brick.Types: ctxVScrollBarOrientationL :: forall n_awEq. Lens' (Context n_awEq) (Maybe VScrollBarOrientation)
+ Brick.Types: ctxVScrollBarOrientationL :: forall n_axz2. Lens' (Context n_axz2) (Maybe VScrollBarOrientation)
- Brick.Types: ctxVScrollBarRendererL :: forall n_awEq. Lens' (Context n_awEq) (Maybe (ScrollbarRenderer n_awEq))
+ Brick.Types: ctxVScrollBarRendererL :: forall n_axz2. Lens' (Context n_axz2) (Maybe (ScrollbarRenderer n_axz2))
- Brick.Types: cursorLocationL :: forall n_awEv. Lens' (CursorLocation n_awEv) Location
+ Brick.Types: cursorLocationL :: forall n_axz7. Lens' (CursorLocation n_axz7) Location
- Brick.Types: cursorLocationNameL :: forall n_awEv n_ayOD. Lens (CursorLocation n_awEv) (CursorLocation n_ayOD) (Maybe n_awEv) (Maybe n_ayOD)
+ Brick.Types: cursorLocationNameL :: forall n_axz7 n_azHU. Lens (CursorLocation n_axz7) (CursorLocation n_azHU) (Maybe n_axz7) (Maybe n_azHU)
- Brick.Types: cursorsL :: forall n_awEu. Lens' (Result n_awEu) [CursorLocation n_awEu]
+ Brick.Types: cursorsL :: forall n_axz6. Lens' (Result n_axz6) [CursorLocation n_axz6]
- Brick.Types: eBottomL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.Types: eBottomL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.Types: eLeftL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.Types: eLeftL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.Types: eRightL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.Types: eRightL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.Types: eTopL :: forall a_acLe. Lens' (Edges a_acLe) a_acLe
+ Brick.Types: eTopL :: forall a_aeiU. Lens' (Edges a_aeiU) a_aeiU
- Brick.Types: extentsL :: forall n_awEu. Lens' (Result n_awEu) [Extent n_awEu]
+ Brick.Types: extentsL :: forall n_axz6. Lens' (Result n_axz6) [Extent n_axz6]
- Brick.Types: imageL :: forall n_awEu. Lens' (Result n_awEu) Image
+ Brick.Types: imageL :: forall n_axz6. Lens' (Result n_axz6) Image
- Brick.Types: visibilityRequestsL :: forall n_awEu. Lens' (Result n_awEu) [VisibilityRequest]
+ Brick.Types: visibilityRequestsL :: forall n_axz6. Lens' (Result n_axz6) [VisibilityRequest]
- Brick.Types: windowHeightL :: forall n_awEq. Lens' (Context n_awEq) Int
+ Brick.Types: windowHeightL :: forall n_axz2. Lens' (Context n_axz2) Int
- Brick.Types: windowWidthL :: forall n_awEq. Lens' (Context n_awEq) Int
+ Brick.Types: windowWidthL :: forall n_axz2. Lens' (Context n_axz2) Int
- Brick.Widgets.Dialog: dialogButtonsL :: forall a_a1fTP a_a1fUt. Lens (Dialog a_a1fTP) (Dialog a_a1fUt) [(String, a_a1fTP)] [(String, a_a1fUt)]
+ Brick.Widgets.Dialog: dialogButtonsL :: forall a_a1hXl a_a1hXZ. Lens (Dialog a_a1hXl) (Dialog a_a1hXZ) [(String, a_a1hXl)] [(String, a_a1hXZ)]
- Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a1fTP. Lens' (Dialog a_a1fTP) (Maybe Int)
+ Brick.Widgets.Dialog: dialogSelectedIndexL :: forall a_a1hXl. Lens' (Dialog a_a1hXl) (Maybe Int)
- Brick.Widgets.Dialog: dialogTitleL :: forall a_a1fTP. Lens' (Dialog a_a1fTP) (Maybe String)
+ Brick.Widgets.Dialog: dialogTitleL :: forall a_a1hXl. Lens' (Dialog a_a1hXl) (Maybe String)
- Brick.Widgets.Dialog: dialogWidthL :: forall a_a1fTP. Lens' (Dialog a_a1fTP) Int
+ Brick.Widgets.Dialog: dialogWidthL :: forall a_a1hXl. Lens' (Dialog a_a1hXl) Int
- Brick.Widgets.Dialog: handleDialogEvent :: Event -> Dialog a -> EventM n (Dialog a)
+ Brick.Widgets.Dialog: handleDialogEvent :: Event -> EventM n (Dialog a) ()
- Brick.Widgets.Edit: editContentsL :: forall t_a19Y9 n_a19Ya t_a19YO. Lens (Editor t_a19Y9 n_a19Ya) (Editor t_a19YO n_a19Ya) (TextZipper t_a19Y9) (TextZipper t_a19YO)
+ Brick.Widgets.Edit: editContentsL :: forall t_a1c0h n_a1c0i t_a1c0W. Lens (Editor t_a1c0h n_a1c0i) (Editor t_a1c0W n_a1c0i) (TextZipper t_a1c0h) (TextZipper t_a1c0W)
- Brick.Widgets.Edit: handleEditorEvent :: (Eq n, DecodeUtf8 t, Eq t, GenericTextZipper t) => BrickEvent n e -> Editor t n -> EventM n (Editor t n)
+ Brick.Widgets.Edit: handleEditorEvent :: (Eq n, DecodeUtf8 t, Eq t, GenericTextZipper t) => BrickEvent n e -> EventM n (Editor t n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserBeginSearch :: FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserBeginSearch :: EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListBottom :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListBottom :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListHalfPageDown :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListHalfPageDown :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListHalfPageUp :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListHalfPageUp :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListNext :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListNext :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListPageDown :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListPageDown :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListPageUp :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListPageUp :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListPrev :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListPrev :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserListTop :: Ord n => FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserListTop :: Ord n => EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserSelectCurrent :: FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserSelectCurrent :: EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: actionFileBrowserSelectEnter :: FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: actionFileBrowserSelectEnter :: EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: fileBrowserEntriesG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) (List n_a1qes FileInfo)
+ Brick.Widgets.FileBrowser: fileBrowserEntriesG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) (List n_a1tbq FileInfo)
- Brick.Widgets.FileBrowser: fileBrowserEntryFilterG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) (Maybe (FileInfo -> Bool))
+ Brick.Widgets.FileBrowser: fileBrowserEntryFilterG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) (Maybe (FileInfo -> Bool))
- Brick.Widgets.FileBrowser: fileBrowserExceptionG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) (Maybe IOException)
+ Brick.Widgets.FileBrowser: fileBrowserExceptionG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) (Maybe IOException)
- Brick.Widgets.FileBrowser: fileBrowserLatestResultsG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) [FileInfo]
+ Brick.Widgets.FileBrowser: fileBrowserLatestResultsG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) [FileInfo]
- Brick.Widgets.FileBrowser: fileBrowserNameG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) n_a1qes
+ Brick.Widgets.FileBrowser: fileBrowserNameG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) n_a1tbq
- Brick.Widgets.FileBrowser: fileBrowserSearchStringG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) (Maybe Text)
+ Brick.Widgets.FileBrowser: fileBrowserSearchStringG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) (Maybe Text)
- Brick.Widgets.FileBrowser: fileBrowserSelectableG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) (FileInfo -> Bool)
+ Brick.Widgets.FileBrowser: fileBrowserSelectableG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) (FileInfo -> Bool)
- Brick.Widgets.FileBrowser: fileBrowserSelectableL :: forall n_a1qes. Lens' (FileBrowser n_a1qes) (FileInfo -> Bool)
+ Brick.Widgets.FileBrowser: fileBrowserSelectableL :: forall n_a1tbq. Lens' (FileBrowser n_a1tbq) (FileInfo -> Bool)
- Brick.Widgets.FileBrowser: fileBrowserSelectedFilesG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) (Set String)
+ Brick.Widgets.FileBrowser: fileBrowserSelectedFilesG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) (Set String)
- Brick.Widgets.FileBrowser: fileBrowserWorkingDirectoryG :: forall n_a1qes. SimpleGetter (FileBrowser n_a1qes) FilePath
+ Brick.Widgets.FileBrowser: fileBrowserWorkingDirectoryG :: forall n_a1tbq. SimpleGetter (FileBrowser n_a1tbq) FilePath
- Brick.Widgets.FileBrowser: handleFileBrowserEvent :: Ord n => Event -> FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: handleFileBrowserEvent :: Ord n => Event -> EventM n (FileBrowser n) ()
- Brick.Widgets.FileBrowser: maybeSelectCurrentEntry :: FileBrowser n -> EventM n (FileBrowser n)
+ Brick.Widgets.FileBrowser: maybeSelectCurrentEntry :: EventM n (FileBrowser n) ()
- Brick.Widgets.List: handleListEvent :: (Foldable t, Splittable t, Ord n) => Event -> GenericList n t e -> EventM n (GenericList n t e)
+ Brick.Widgets.List: handleListEvent :: (Foldable t, Splittable t, Ord n) => Event -> EventM n (GenericList n t e) ()
- Brick.Widgets.List: handleListEventVi :: (Foldable t, Splittable t, Ord n) => (Event -> GenericList n t e -> EventM n (GenericList n t e)) -> Event -> GenericList n t e -> EventM n (GenericList n t e)
+ Brick.Widgets.List: handleListEventVi :: (Foldable t, Splittable t, Ord n) => (Event -> EventM n (GenericList n t e) ()) -> Event -> EventM n (GenericList n t e) ()
- Brick.Widgets.List: listElementsL :: forall n_a1l2A t_a1l2B e_a1l2C t_a1lgG e_a1lgH. Lens (GenericList n_a1l2A t_a1l2B e_a1l2C) (GenericList n_a1l2A t_a1lgG e_a1lgH) (t_a1l2B e_a1l2C) (t_a1lgG e_a1lgH)
+ Brick.Widgets.List: listElementsL :: forall n_a1nZK t_a1nZL e_a1nZM t_a1odX e_a1odY. Lens (GenericList n_a1nZK t_a1nZL e_a1nZM) (GenericList n_a1nZK t_a1odX e_a1odY) (t_a1nZL e_a1nZM) (t_a1odX e_a1odY)
- Brick.Widgets.List: listItemHeightL :: forall n_a1l2A t_a1l2B e_a1l2C. Lens' (GenericList n_a1l2A t_a1l2B e_a1l2C) Int
+ Brick.Widgets.List: listItemHeightL :: forall n_a1nZK t_a1nZL e_a1nZM. Lens' (GenericList n_a1nZK t_a1nZL e_a1nZM) Int
- Brick.Widgets.List: listModify :: Traversable t => (e -> e) -> GenericList n t e -> GenericList n t e
+ Brick.Widgets.List: listModify :: (Traversable t, Splittable t, Semigroup (t e)) => (e -> e) -> GenericList n t e -> GenericList n t e
- Brick.Widgets.List: listMoveByPages :: (Foldable t, Splittable t, Ord n, RealFrac m) => m -> GenericList n t e -> EventM n (GenericList n t e)
+ Brick.Widgets.List: listMoveByPages :: (Foldable t, Splittable t, Ord n, RealFrac m) => m -> EventM n (GenericList n t e) ()
- Brick.Widgets.List: listMovePageDown :: (Foldable t, Splittable t, Ord n) => GenericList n t e -> EventM n (GenericList n t e)
+ Brick.Widgets.List: listMovePageDown :: (Foldable t, Splittable t, Ord n) => EventM n (GenericList n t e) ()
- Brick.Widgets.List: listMovePageUp :: (Foldable t, Splittable t, Ord n) => GenericList n t e -> EventM n (GenericList n t e)
+ Brick.Widgets.List: listMovePageUp :: (Foldable t, Splittable t, Ord n) => EventM n (GenericList n t e) ()
- Brick.Widgets.List: listNameL :: forall n_a1l2A t_a1l2B e_a1l2C n_a1lgI. Lens (GenericList n_a1l2A t_a1l2B e_a1l2C) (GenericList n_a1lgI t_a1l2B e_a1l2C) n_a1l2A n_a1lgI
+ Brick.Widgets.List: listNameL :: forall n_a1nZK t_a1nZL e_a1nZM n_a1odZ. Lens (GenericList n_a1nZK t_a1nZL e_a1nZM) (GenericList n_a1odZ t_a1nZL e_a1nZM) n_a1nZK n_a1odZ
- Brick.Widgets.List: listSelectedElement :: (Splittable t, Foldable t) => GenericList n t e -> Maybe (Int, e)
+ Brick.Widgets.List: listSelectedElement :: (Splittable t, Traversable t, Semigroup (t e)) => GenericList n t e -> Maybe (Int, e)
- Brick.Widgets.List: listSelectedL :: forall n_a1l2A t_a1l2B e_a1l2C. Lens' (GenericList n_a1l2A t_a1l2B e_a1l2C) (Maybe Int)
+ Brick.Widgets.List: listSelectedL :: forall n_a1nZK t_a1nZL e_a1nZM. Lens' (GenericList n_a1nZK t_a1nZL e_a1nZM) (Maybe Int)

Files

CHANGELOG.md view
@@ -2,6 +2,85 @@ Brick changelog --------------- +1.0+---++Version 1.0 of `brick` comes with some improvements that will require+you to update your programs. This section details the list of API+changes in 1.0 that are likely to introduce breakage and how to deal+with each one. You can also consult the demonstration+programs to see orking examples of the new API. For those+interested in a bit of discussion on the changes, see [this+ticket](https://github.com/jtdaugherty/brick/issues/379).++* The event-handling monad `EventM` was improved and changed in some+  substantial ways, all aimed at making `EventM` code cleaner, more+  composable, and more amenable to lens updates to the application+  state.+  * The type has changed from `EventM n a` to `EventM n s a` and is now+    an `mtl`-compatible state monad over `s`. Some consequences and+    related changes are:+    * Event handlers no longer take and return an explicit state value;+      an event handler that formerly had the type `handler :: s ->+      BrickEvent n e -> EventM n (Next s)` now has type `handler ::+      BrickEvent n e -> EventM n s ()`. This also affected all of+      Brick's built-in event handler functions for `List`, `Editor`,+      etc.+    * The `appHandleEvent` and `appStartEvent` fields of `App` changed+      types to reflect the new structure of `EventM`. `appStartEvent`+      will just be `return ()` rather than `return` for most+      applications.+    * `EventM` can be used with the `MonadState` API from `mtl` as well+      as with the very nice lens combinators in `microlens-mtl`.+    * The `Next` type was removed.+    * State-specific event handlers like `handleListEvent` and+      `handleEditorEvent` are now statically typed to be scoped to+      just the states they manage, so `zoom` from `microlens-mtl` must+      be used to invoke them. `Brick.Types` re-exports `zoom` for+      convenience. `handleEventLensed` was removed from the API in lieu+      of the new `zoom` behavior. Code that previously handled events+      with `handleEventLensed s someLens someHandler e` is now just+      written `zoom someLens $ someHandler e`.+    * If an `EventM` block needs to operate on some state `s` that is+      not accessible via a lens into the application state, the `EventM`+      block can be set up with `Brick.Types.nestEventM`.+  * Since `Next` was removed, control flow is now as follows:+    * Without any explicit specification, an `EventM` block always+      continues execution of the `brick` event loop when it finishes.+      `continue` was removed from the API. What was previously `continue+      $ s & someLens .~ value` will become `someLens .= value`.+    * `halt` is still used to indicate that the event loop should halt+      after the calling handler is finished, but `halt` no longer takes+      an explicit state value argument.+    * `suspendAndResume` is now immediate; previously,+      `suspendAndResume` indicated that the specified action should run+      once the event handler finished. Now, the event handler is paused+      while the specified action is run. This allows `EventM` code to+      continue to run after `suspendAndResume` is called and before+      control is returned to `brick`.+  * Brick now depends on `mtl` rather than `transformers`.+* The `IsString` instance for `AttrName` was removed.+  * This change is motivated by the API wart that resulted from the+    overloading of both `<>` and string literals (via+    `OverloadedStrings`) that resulted in code such as `someAttrName+    = "blah" <> "things"`. While that worked to create an `AttrName`+    with two segments, it was far too easy to read as two strings+    concatenated. The overloading hid what is really going on with the+    segments of the attribute name. The way to write the above example+    after this change is `someAttrName = attrName "blah" <> attrName+    "things"`.++Other changes in this release:++* Brick now provides an optional API for user-defined keybindings+  for applications! See the User Guide section "Customizable+  Keybindings", the Haddock for `Brick.Keybindings.KeyDispatcher`,+  and the new demo program `programs/CustomKeybindingDemo.hs` to get+  started.+* `Brick.Widgets.List` got `listSelectedElementL`, a traversal for+  accessing the currently selected element of a list. (Thanks Fraser+  Tweedale)+ 0.73 ---- 
README.md view
@@ -119,7 +119,6 @@ Documentation for `brick` comes in a variety of forms:  * [The official brick user guide](https://github.com/jtdaugherty/brick/blob/master/docs/guide.rst)-* [Samuel Tay's brick tutorial](https://github.com/jtdaugherty/brick/blob/master/docs/samtay-tutorial.md) * Haddock (all modules) * [Demo programs](https://github.com/jtdaugherty/brick/blob/master/programs) ([Screenshots](https://github.com/jtdaugherty/brick/blob/master/docs/programs-screenshots.md)) * [FAQ](https://github.com/jtdaugherty/brick/blob/master/FAQ.md)
brick.cabal view
@@ -1,5 +1,5 @@ name:                brick-version:             0.73+version:             1.0 synopsis:            A declarative terminal user interface library description:   Write terminal user interfaces (TUIs) painlessly with 'brick'! You@@ -42,9 +42,9 @@  extra-doc-files:     README.md,                      docs/guide.rst,-                     docs/samtay-tutorial.md,                      docs/snake-demo.gif,                      CHANGELOG.md,+                     programs/custom_keys.ini,                      docs/programs-screenshots.md,                      docs/programs-screenshots/brick-attr-demo.png,                      docs/programs-screenshots/brick-border-demo.png,@@ -88,6 +88,12 @@     Brick.AttrMap     Brick.BChan     Brick.BorderMap+    Brick.Keybindings+    Brick.Keybindings.KeyConfig+    Brick.Keybindings.KeyEvents+    Brick.Keybindings.KeyDispatcher+    Brick.Keybindings.Parse+    Brick.Keybindings.Pretty     Brick.Focus     Brick.Forms     Brick.Main@@ -108,12 +114,13 @@   other-modules:     Brick.Types.Common     Brick.Types.TH+    Brick.Types.EventM     Brick.Types.Internal     Brick.Widgets.Internal    build-depends:       base >= 4.9.0.0 && < 4.17.0.0,                        vty >= 5.36,-                       transformers,+                       bimap >= 0.5 && < 0.6,                        data-clist >= 0.1,                        directory >= 1.2.5.0,                        dlist,@@ -123,6 +130,7 @@                        microlens >= 0.3.0.0,                        microlens-th,                        microlens-mtl,+                       mtl,                        config-ini,                        vector,                        contravariant,@@ -135,6 +143,22 @@                        bytestring,                        word-wrap >= 0.2 +executable brick-custom-keybinding-demo+  if !flag(demos)+    Buildable: False+  hs-source-dirs:      programs+  ghc-options:         -threaded -Wall -Wcompat -O2+  default-language:    Haskell2010+  default-extensions:  CPP+  main-is:             CustomKeybindingDemo.hs+  build-depends:       base,+                       brick,+                       text,+                       vty,+                       microlens,+                       microlens-mtl,+                       microlens-th+ executable brick-table-demo   if !flag(demos)     Buildable: False@@ -160,7 +184,9 @@                        brick,                        text,                        vty,-                       random+                       random,+                       microlens-th,+                       microlens-mtl  executable brick-readme-demo   if !flag(demos)@@ -185,7 +211,8 @@   build-depends:       base,                        vty,                        brick,-                       text+                       text,+                       mtl  executable brick-form-demo   if !flag(demos)@@ -228,7 +255,8 @@                        vty,                        text,                        microlens >= 0.3.0.0,-                       microlens-th+                       microlens-th,+                       mtl  executable brick-visibility-demo   if !flag(demos)@@ -242,7 +270,8 @@                        vty,                        text,                        microlens >= 0.3.0.0,-                       microlens-th+                       microlens-th,+                       microlens-mtl  executable brick-viewport-scrollbars-demo   if !flag(demos)@@ -256,7 +285,9 @@                        brick,                        vty,                        text,-                       microlens+                       microlens,+                       microlens-mtl,+                       microlens-th  executable brick-viewport-scroll-demo   if !flag(demos)@@ -298,7 +329,9 @@                        text,                        microlens >= 0.3.0.0,                        microlens-th,-                       text-zipper+                       microlens-mtl,+                       text-zipper,+                       mtl  executable brick-layer-demo   if !flag(demos)@@ -312,7 +345,8 @@                        vty,                        text,                        microlens >= 0.3.0.0,-                       microlens-th+                       microlens-th,+                       microlens-mtl  executable brick-suspend-resume-demo   if !flag(demos)@@ -365,6 +399,7 @@                        brick,                        vty,                        text,+                       mtl,                        microlens  executable brick-attr-demo@@ -392,6 +427,8 @@                        vty,                        text,                        microlens >= 0.3.0.0,+                       microlens-mtl,+                       mtl,                        vector  executable brick-list-vi-demo@@ -406,6 +443,8 @@                        vty,                        text,                        microlens >= 0.3.0.0,+                       microlens-mtl,+                       mtl,                        vector  executable brick-custom-event-demo@@ -420,7 +459,8 @@                        vty,                        text,                        microlens >= 0.3.0.0,-                       microlens-th+                       microlens-th,+                       microlens-mtl  executable brick-fill-demo   if !flag(demos)@@ -460,8 +500,10 @@                        vty,                        text,                        vector,+                       mtl,                        microlens >= 0.3.0.0,-                       microlens-th+                       microlens-th,+                       microlens-mtl  executable brick-border-demo   if !flag(demos)@@ -503,7 +545,9 @@                        brick,                        vty,                        text,-                       microlens+                       microlens,+                       microlens-mtl,+                       microlens-th  test-suite brick-tests   type:                exitcode-stdio-1.0
docs/guide.rst view
@@ -68,16 +68,17 @@ documentation and as you explore the library source and write your own programs. -- Use of `microlens`_ packages: ``brick`` uses ``microlens`` family of-  packages internally and also exposes lenses for many types in the+- Use of `microlens`_ packages: ``brick`` uses the ``microlens`` family+  of packages internally and also exposes lenses for many types in the   library. However, if you prefer not to use the lens interface in your   program, all lens interfaces have non-lens equivalents exported by   the same module. In general, the "``L``" suffix on something tells   you it is a lens; the name without the "``L``" suffix is the non-lens-  version. You can get by without using ``brick``'s lens interface but-  your life will probably be much more pleasant once your application-  state becomes sufficiently complex if you use lenses to modify it (see-  `appHandleEvent: Handling Events`_).+  version. You can get by without using ``brick``'s lens interface+  but your life will probably be much more pleasant if you use lenses+  to modify your application state once it state becomes sufficiently+  complex (see `appHandleEvent: Handling Events`_ and `Event Handlers+  for Component State`_). - Attribute names: some modules export attribute names (see `How   Attributes Work`_) associated with user interface elements. These tend   to end in an "``Attr``" suffix (e.g. ``borderAttr``). In addition,@@ -96,8 +97,8 @@ Brick applications must be compiled with the threaded RTS using the GHC ``-threaded`` option. -The App Type-============+The ``App`` Type+================  To use the library we must provide it with a value of type ``Brick.Main.App``. This type is a record type whose fields perform@@ -108,8 +109,8 @@    data App s e n =        App { appDraw         :: s -> [Widget n]            , appChooseCursor :: s -> [CursorLocation n] -> Maybe (CursorLocation n)-           , appHandleEvent  :: s -> BrickEvent n e -> EventM n (Next s)-           , appStartEvent   :: s -> EventM n s+           , appHandleEvent  :: BrickEvent n e -> EventM n s ()+           , appStartEvent   :: EventM n s ()            , appAttrMap      :: s -> AttrMap            } @@ -150,16 +151,16 @@     main :: IO ()    main = do-     let app = App { ... }-         initialState = ...-     finalState <- defaultMain app initialState-     -- Use finalState and exit+       let app = App { ... }+           initialState = ...+       finalState <- defaultMain app initialState+       -- Use finalState and exit  The ``customMain`` function is for more advanced uses; for details see `Using Your Own Event Type`_. -appDraw: Drawing an Interface------------------------------+``appDraw``: Drawing an Interface+---------------------------------  The value of ``appDraw`` is a function that turns the current application state into a list of *layers* of type ``Widget``, listed@@ -201,116 +202,162 @@ ``Brick.Widgets.Core``. Beyond that, any module in the ``Brick.Widgets`` namespace provides specific kinds of functionality. -appHandleEvent: Handling Events--------------------------------+``appHandleEvent``: Handling Events+-----------------------------------  The value of ``appHandleEvent`` is a function that decides how to modify the application state as a result of an event:  .. code:: haskell -   appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)+   appHandleEvent :: BrickEvent n e -> EventM n s () -The first parameter of type ``s`` is your application's state at the-time the event arrives. ``appHandleEvent`` is responsible for deciding-how to change the state based on the event and then return it.+``appHandleEvent`` is responsible for deciding how to change the state+based on the event. The single parameter to the event handler is the+event to be handled. Its type variables ``n`` and ``e`` correspond+to the *resource name type* and *event type* of your application,+respectively, and must match the corresponding types in ``App`` and+``EventM``. -The second parameter of type ``BrickEvent n e`` is the event itself.-The type variables ``n`` and ``e`` correspond to the *resource name-type* and *event type* of your application, respectively, and must match-the corresponding types in ``App`` and ``EventM``.+The ``EventM`` monad is parameterized on the *resource name type*+``n`` and your application's state type ``s``. The ``EventM`` monad+is a state monad over ``s``, so one way to access and modify your+application's state in an event handler is to use the ``MonadState``+type class and associated operations from the ``mtl`` package. The+recommended approach, however, is to use the lens operations from the+``microlens-mtl`` package with lenses to perform concise state updates.+We'll cover this topic in more detail in `Event Handlers for Component+State`_. -The return value type ``Next s`` value describes what should happen-after the event handler is finished. We have four choices:+Once the event handler has performed any relevant state updates, it can+also indicate what should happen once the event handler has finished+executing. By default, after an event handler has completed, Brick will+redraw the screen with the application state (by calling ``appDraw``)+and wait for the next input event. However, there are two other options: -* ``Brick.Main.continue s``: continue executing the event loop with the-  specified application state ``s`` as the next value. Commonly this is-  where you'd modify the state based on the event and return it.-* ``Brick.Main.continueWithoutRedraw s``: continue executing the event-  loop with the specified application state ``s`` as the next value, but-  unlike ``continue``, do not redraw the screen using the new state.-  This is a faster version of ``continue`` since it doesn't redraw the-  screen; it just leaves up the previous screen contents. This function-  is only useful when you know that your state change won't cause-  anything on the screen to change. When in doubt, use ``continue``.-* ``Brick.Main.halt s``: halt the event loop and return the final-  application state value ``s``. This state value is returned to the-  caller of ``defaultMain`` or ``customMain`` where it can be used prior-  to finally exiting ``main``.-* ``Brick.Main.suspendAndResume act``: suspend the ``brick`` event loop-  and execute the specified ``IO`` action ``act``. The action ``act``-  must be of type ``IO s``, so when it executes it must return the next-  application state. When ``suspendAndResume`` is used, the ``brick``-  event loop is shut down and the terminal state is restored to its-  state when the ``brick`` event loop began execution. When it finishes-  executing, the event loop will be resumed using the returned state-  value. This is useful for situations where your program needs to-  suspend your interface and execute some other program that needs to-  gain control of the terminal (such as an external editor).+* ``Brick.Main.halt``: halt the event loop. The application state as it+  exists after the event handler completes is returned to the caller+  of ``defaultMain`` or ``customMain``.+* ``Brick.Main.continueWithoutRedraw``: continue executing the event+  loop, but do not redraw the screen using the new state before waiting+  for another input event. This is faster than the default continue+  behavior since it doesn't redraw the screen; it just leaves up the+  previous screen contents. This function is only useful when you know+  that your event handler's state change(s) won't cause anything on+  the screen to change. Use this only when you are certain that no+  redraw of the screen is needed *and* when you are trying to address a+  performance problem. (See also `The Rendering Cache`_ for details on+  how to detail with rendering performance issues.) -The ``EventM`` monad is the event-handling monad. This monad is a-transformer around ``IO`` so you are free to do I/O in this monad by-using ``liftIO``. Beyond I/O, this monad is used to make scrolling-requests to the renderer (see `Viewports`_) and obtain named extents-(see `Extents`_). Keep in mind that time spent blocking in your event-handler is time during which your UI is unresponsive, so consider this-when deciding whether to have background threads do work instead of-inlining the work in the event handler.+The ``EventM`` monad is a transformer around ``IO`` so I/O is possible+in this monad by using ``liftIO``. Keep in mind, however, that event+handlers should execute as quickly as possible to avoid introducing+screen redraw latency. Consider using background threads to work+asynchronously when it would otherwise cause redraw latency. -Widget Event Handlers-*********************+Beyond I/O, ``EventM`` is used to make scrolling requests to the+renderer (see `Viewports`_), obtain named extents (see `Extents`_), and+other duties. -Event handlers are responsible for transforming the application state.-While you can use ordinary methods to do this such as pattern matching-and pure function calls, some widget state types such as the ones-provided by the ``Brick.Widgets.List`` and ``Brick.Widgets.Edit``-modules provide their own widget-specific event-handling functions.-For example, ``Brick.Widgets.Edit`` provides ``handleEditorEvent`` and-``Brick.Widgets.List`` provides ``handleListEvent``.+Event Handlers for Component State+********************************** -Since these event handlers run in ``EventM``, they have access to-rendering viewport states via ``Brick.Main.lookupViewport`` and the-``IO`` monad via ``liftIO``.+The top-level ``appHandleEvent`` handler is responsible for managing+the application state, but it also needs to be able to update the state+associated with UI components such as those that come with Brick. -To use these handlers in your program, invoke them on the relevant piece-of state in your application state. In the following example we use an-``Edit`` state from ``Brick.Widgets.Edit``:+For example, consider an application that uses Brick's built-in text+editor from ``Brick.Widgets.Edit``. The built-in editor is similar to+the main application in that it has three important elements: -.. code:: haskell+* The editor state of type ``Editor t n``: this stores the editor's+  contents, cursor position, etc.+* The editor's drawing function, ``renderEditor``: this is responsible+  for drawing the editor in the UI.+* The editor's event handler, ``handleEditorEvent``: this is responsible+  for updating the editor's contents and cursor position in response to+  key events. -   data Name = Edit1-   type MyState = Editor String Name+To use the built-in editor, the application must: -   myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)-   myEvent s (VtyEvent e) = continue =<< handleEditorEvent e s+* Embed an ``Editor t n`` somewhere in the application state ``s``,+* Render the editor's state at the appropriate place in ``appDraw`` with+  ``renderEditor``, and+* Dispatch events to the editor in the ``appHandleEvent`` with+  ``handleEditorEvent``. -This pattern works well enough when your application state has an-event handler as shown in the ``Edit`` example above, but it can-become unpleasant if the value on which you want to invoke a handler-is embedded deeply within your application state. If you have chosen-to generate lenses for your application state fields, you can use the-convenience function ``handleEventLensed`` by specifying your state, a-lens, and the event:+An example application state using an editor might look like this:  .. code:: haskell -   data Name = Edit1-   data MyState = MyState { _theEdit :: Editor String Name-                          }+   data MyState = MyState { _editor :: Editor Text n }    makeLenses ''MyState -   myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)-   myEvent s (VtyEvent e) = continue =<< handleEventLensed s theEdit handleEditorEvent e+This declares the ``MyState`` type with an ``Editor`` contained within+it and uses Template Haskell to generate a lens, ``editor``, to allow us+to easily update the editor state in our event handler. -You might consider that preferable to the desugared version:+To dispatch events to the ``editor`` we'd start by writing the+application event handler:  .. code:: haskell -   myEvent :: MyState -> BrickEvent n e -> EventM Name (Next MyState)-   myEvent s (VtyEvent e) = do-     newVal <- handleEditorEvent e (s^.theEdit)-     continue $ s & theEdit .~ newVal+   handleEvent :: BrickEvent n e -> EventM n MyState ()+   handleEvent e = do+       ... +But there's a problem: ``handleEditorEvent``'s type indicates that it+can only run over a state of type ``Editor t n``, but our handler runs+on ``MyState``. Specifically, ``handleEditorEvent`` has this type:++.. code:: haskell++   handleEditorEvent :: BrickEvent n e -> EventM n (Editor t n) ()++This means that to use ``handleEditorEvent``, it must be composed+into the application's event handler, but since the state types ``s``+and ``Editor t n`` do not match, we need a way to compose these event+handlers. There are two ways to do this:++* Use ``Lens.Micro.Mtl.zoom`` from the ``microlens-mtl`` package+  (re-exported by ``Brick.Types`` for convenience). This function is+  required when you want to change the state type to a field embedded in+  your application state using a lens. For example:++.. code:: haskell++   handleEvent :: BrickEvent n e -> EventM n MyState ()+   handleEvent e = do+       zoom editor $ handleEditorEvent e++* Use ``Brick.Types.nestEventM``: this function lets you provide a state+  value and run ``EventM`` using that state. The following+  ``nestEventM`` example is equivalent to the ``zoom`` example above:++.. code:: haskell++   import Lens.Micro (_1)+   import Lens.Micro.Mtl (use, (.=))++   handleEvent :: BrickEvent n e -> EventM n MyState ()+   handleEvent e = do+       editorState <- use editor+       (newEditorState, ()) <- nestEventM editorState $ do+           handleEditorEvent e+       editor .= newEditorState++The ``zoom`` function, together with lenses for your application state's+fields, is by far the best way to manage your state in ``EventM``. As+you can see from the examples above, the ``zoom`` approach avoids a lot+of boilerplate. The ``nestEventM`` approach is provided in cases where+the state that you need to mutate is not easily accessed by ``zoom``.++Finally, if you prefer to avoid the use of lenses, you can always use+the ``MonadState`` API to get, put, and modify your state. Keep in+mind that the ``MonadState`` approach will still require the use of+``nestEventM`` when events scoped to widget states such as ``Editor``+need to be handled.+ Using Your Own Event Type ************************* @@ -339,8 +386,8 @@  .. code:: haskell -   myEvent :: s -> BrickEvent n CounterEvent -> EventM n (Next s)-   myEvent s (AppEvent (Counter i)) = ...+   myEvent :: BrickEvent n CounterEvent -> EventM n s ()+   myEvent (AppEvent (Counter i)) = ...  The next step is to actually *generate* our custom events and inject them into the ``brick`` event stream so they make it to the@@ -391,8 +438,8 @@ your event handler when choosing the channel capacity and design event producers so that they can block if the channel is full. -appStartEvent: Starting up---------------------------+``appStartEvent``: Starting up+------------------------------  When an application starts, it may be desirable to perform some of the duties typically only possible when an event has arrived, such as@@ -403,16 +450,17 @@  .. code:: haskell -   appStartEvent :: s -> EventM n s+   appStartEvent :: EventM n s () -This function takes the initial application state and returns it in-``EventM``, possibly changing it and possibly making viewport requests.-This function is invoked once and only once, at application startup.-For more details, see `Viewports`_. You will probably just want to use-``return`` as the implementation of this function for most applications.+This function is a handler action to run on the initial application+state. This function is invoked once and only once, at application+startup. This might be a place to make initial viewport scroll requests+or make changes to the Vty environment. You will probably just want+to use ``return ()`` as the implementation of this function for most+applications. -appChooseCursor: Placing the Cursor------------------------------------+``appChooseCursor``: Placing the Cursor+---------------------------------------  The rendering process for a ``Widget`` may return information about where that widget would like to place the cursor. For example, a text@@ -469,9 +517,10 @@  .. code:: haskell -   myApp = App { ...-               , appChooseCursor = \_ -> showCursorNamed CustomName-               }+   myApp =+       App { ...+           , appChooseCursor = \_ -> showCursorNamed CustomName+           }  See the next section for more information on using names. @@ -522,9 +571,8 @@  .. code:: haskell -   do-     let vp = viewportScroll Viewport1-     vScrollBy vp 1+   let vp = viewportScroll Viewport1+   vScrollBy vp 1  The solution is to ensure that for a given resource type (in this case viewport), a unique name is assigned in each use.@@ -537,8 +585,8 @@    ui = (viewport Viewport1 Vertical $ str "Foo") <+>         (viewport Viewport2 Vertical $ str "Bar") <+> -appAttrMap: Managing Attributes--------------------------------+``appAttrMap``: Managing Attributes+-----------------------------------  In ``brick`` we use an *attribute map* to assign attributes to elements of the interface. Rather than specifying specific attributes when@@ -918,16 +966,20 @@ If the theme is further customized at runtime, any changes can be saved with ``Brick.Themes.saveCustomizations``. -Wide Character Support and the TextWidth class-==============================================+Wide Character Support and the ``TextWidth`` class+================================================== -Brick supports rendering wide characters in all widgets, and the brick-editor supports entering and editing wide characters. Wide characters-are those such as many Asian characters and emoji that need more than-a single terminal column to be displayed. Brick relies on Vty's use of-the `utf8proc`_ library to determine the column width of each character-rendered.+Brick attempts to support rendering wide characters in all widgets,+and the brick editor supports entering and editing wide characters.+Wide characters are those such as many Asian characters and emoji+that need more than a single terminal column to be displayed. +Unfortunatley, there is not a fully correct solution to determining+the character width that the user's terminal will use for a given+character. The current recommendation is to avoid use of wide characters+due to these issues. If you still must use them, you can read `vty`_'s+documentation for options that will affect character width calculations.+ As a result of supporting wide characters, it is important to know that computing the length of a string to determine its screen width will *only* work for single-column characters. So, for example, if you want@@ -937,26 +989,28 @@     let width = Data.Text.length t -because if the string contains any wide characters, their widths-will not be counted properly. In order to get this right, use the-``TextWidth`` type class to compute the width:+If the string contains any wide characters, their widths will not be+counted properly. In order to get this right, use the ``TextWidth`` type+class to compute the width:  .. code:: haskell     let width = Brick.Widgets.Core.textWidth t -The ``TextWidth`` type class uses Vty's character width routine (and-thus ``utf8proc``) to compute the correct width. If you need to compute-the width of a single character, use ``Graphics.Text.wcwidth``.+The ``TextWidth`` type class uses Vty's character width routine to+compute the width by looking up the string's characdters in a Unicode+width table. If you need to compute the width of a single character, use+``Graphics.Text.wcwidth``.  Extents ======= -When an application needs to know where a particular widget was drawn by-the renderer, the application can request that the renderer record the-*extent* of the widget--its upper-left corner and size--and provide it-in an event handler. In the following example, the application needs to-know where the bordered box containing "Foo" is rendered:+When an application needs to know where a particular widget was drawn+by the renderer, the application can request that the renderer record+the *extent* of the widget--its upper-left corner and size--and provide+access to it in an event handler. Extents are represented using Brick's+``Brick.Types.Extent`` type. In the following example, the application+needs to know where the bordered box containing "Foo" is rendered:  .. code:: haskell @@ -975,15 +1029,14 @@         reportExtent FooBox $         border $ str "Foo" -Now, whenever the ``ui`` is rendered, the location and size of the-bordered box containing "Foo" will be recorded. We can then look it up-in event handlers in ``EventM``:+Now, whenever the ``ui`` is rendered, the extent of the bordered box+containing "Foo" will be recorded. We can then look it up in event+handlers in ``EventM``:  .. code:: haskell -   do-     mExtent <- Brick.Main.lookupExtent FooBox-     case mExtent of+   mExtent <- Brick.Main.lookupExtent FooBox+   case mExtent of        Nothing -> ...        Just (Extent _ upperLeft (width, height)) -> ... @@ -1008,10 +1061,10 @@    import qualified Graphics.Vty as V     do-     vty <- Brick.Main.getVtyHandle-     let output = V.outputIface vty-     when (V.supportsMode output V.BracketedPaste) $-         liftIO $ V.setMode output V.BracketedPaste True+       vty <- Brick.Main.getVtyHandle+       let output = V.outputIface vty+       when (V.supportsMode output V.BracketedPaste) $+           liftIO $ V.setMode output V.BracketedPaste True  Once enabled, paste mode will generate Vty ``EvPaste`` events. These events will give you the entire pasted content as a ``ByteString`` which@@ -1029,10 +1082,10 @@ .. code:: haskell     do-     vty <- Brick.Main.getVtyHandle-     let output = outputIface vty-     when (supportsMode output Mouse) $-       liftIO $ setMode output Mouse True+       vty <- Brick.Main.getVtyHandle+       let output = outputIface vty+       when (supportsMode output Mouse) $+           liftIO $ setMode output Mouse True  Bear in mind that some terminals do not support mouse interaction, so use Vty's ``getModeStatus`` to find out whether your terminal will@@ -1053,7 +1106,7 @@  .. code:: haskell -   handleEvent s (VtyEvent (EvMouseDown col row button mods) = ...+   handleEvent (VtyEvent (EvMouseDown col row button mods) = ...  Brick Mouse Events ------------------@@ -1063,27 +1116,60 @@ language. The disadvantage to the low-level interface described above is that you still need to determine *what* was clicked, i.e., the part of the interface that was under the mouse cursor. There are two ways to do-this with ``brick``: with *extent checking* and *click reporting*.+this with ``brick``: with *click reporting* and *extent checking*. +Click reporting+***************++The *click reporting* approach is the most high-level approach offered+by ``brick`` and the one that we recommend you use. In this approach,+we use ``Brick.Widgets.Core.clickable`` when drawing the interface to+request that a given widget generate ``MouseDown`` and ``MouseUp``+events when it is clicked.++.. code:: haskell++   data Name = MyButton++   ui :: Widget Name+   ui = center $+        clickable MyButton $+        border $+        str "Click me"++   handleEvent (MouseDown MyButton button modifiers coords) = ...+   handleEvent (MouseUp MyButton button coords) = ...++This approach enables event handlers to use pattern matching to check+for mouse clicks on specific regions; this uses `Extent checking`_+under the hood but makes it possible to denote which widgets are+clickable in the interface description. The event's click coordinates+are local to the widget being clicked. In the above example, a click+on the upper-left corner of the border would result in coordinates of+``(0,0)``.+ Extent checking ***************  The *extent checking* approach entails requesting extents (see `Extents`_) for parts of your interface, then checking the Vty mouse-click event's coordinates against one or more extents.+click event's coordinates against one or more extents. This approach+is slightly lower-level than the direct mouse click reporting approach+above but is provided in case you need more control over how mouse+clicks are dealt with.  The most direct way to do this is to check a specific extent:  .. code:: haskell -   handleEvent s (VtyEvent (EvMouseDown col row _ _)) = do-     mExtent <- lookupExtent SomeExtent-     case mExtent of-       Nothing -> continue s-       Just e -> do-         if Brick.Main.clickedExtent (col, row) e-           then ...-           else ...+   handleEvent (VtyEvent (EvMouseDown col row _ _)) = do+       mExtent <- lookupExtent SomeExtent+       case mExtent of+           Nothing -> return ()+           Just e -> do+               if Brick.Main.clickedExtent (col, row) e+                   then ...+                   else ...  This approach works well enough if you know which extent you're interested in checking, but what if there are many extents and you@@ -1092,10 +1178,10 @@  .. code:: haskell -   handleEvent s (VtyEvent (EvMouseDown col row _ _)) = do-     extents <- Brick.Main.findClickedExtents (col, row)-     -- Then check to see if a specific extent is in the list, or just-     -- take the first one in the list.+   handleEvent (VtyEvent (EvMouseDown col row _ _)) = do+       extents <- Brick.Main.findClickedExtents (col, row)+       -- Then check to see if a specific extent is in the list, or just+       -- take the first one in the list.  This approach finds all clicked extents and returns them in a list with the following properties:@@ -1109,35 +1195,6 @@ As a result, the extents are ordered in a natural way, starting with the most specific extents and proceeding to the most general. -Click reporting-***************--The *click reporting* approach is the most high-level approach-offered by ``brick``. When rendering the interface we use-``Brick.Widgets.Core.clickable`` to request that a given widget generate-``MouseDown`` and ``MouseUp`` events when it is clicked.--.. code:: haskell--   data Name = MyButton--   ui :: Widget Name-   ui = center $-        clickable MyButton $-        border $-        str "Click me"--   handleEvent s (MouseDown MyButton button modifiers coords) = ...-   handleEvent s (MouseUp MyButton button coords) = ...--This approach enables event handlers to use pattern matching to check-for mouse clicks on specific regions; this uses extent reporting-under the hood but makes it possible to denote which widgets are-clickable in the interface description. The event's click coordinates-are local to the widget being clicked. In the above example, a click-on the upper-left corner of the border would result in coordinates of-``(0,0)``.- Viewports ========= @@ -1210,14 +1267,14 @@  .. code:: haskell -   hScrollPage        :: Direction -> EventM n ()-   hScrollBy          :: Int       -> EventM n ()-   hScrollToBeginning ::              EventM n ()-   hScrollToEnd       ::              EventM n ()-   vScrollPage        :: Direction -> EventM n ()-   vScrollBy          :: Int       -> EventM n ()-   vScrollToBeginning ::              EventM n ()-   vScrollToEnd       ::              EventM n ()+   hScrollPage        :: Direction -> EventM n s ()+   hScrollBy          :: Int       -> EventM n s ()+   hScrollToBeginning ::              EventM n s ()+   hScrollToEnd       ::              EventM n s ()+   vScrollPage        :: Direction -> EventM n s ()+   vScrollBy          :: Int       -> EventM n s ()+   vScrollToBeginning ::              EventM n s ()+   vScrollToEnd       ::              EventM n s ()  In each case the scrolling function scrolls the viewport by the specified amount in the specified direction; functions prefixed with@@ -1233,11 +1290,10 @@  .. code:: haskell -   myHandler :: s -> e -> EventM n (Next s)-   myHandler s e = do+   myHandler :: e -> EventM n s ()+   myHandler e = do        let vp = viewportScroll Viewport1        hScrollBy vp 1-       continue s  Scrolling Viewports With Visibility Requests --------------------------------------------@@ -1469,7 +1525,7 @@ .. code:: haskell      (str "Name: " <+>) @@=-      editTextField name NameField (Just 1)+        editTextField name NameField (Just 1)  Now when we invoke ``renderForm`` on a form using the above example, we'll see a ``"Name:"`` label to the left of the editor field for@@ -1504,14 +1560,14 @@ Handling Form Events -------------------- -Handling form events is easy: we just call-``Brick.Forms.handleFormEvent`` with the ``BrickEvent`` and the-``Form``. This automatically dispatches input events to the-currently-focused input field, and it also manages focus changes with-``Tab`` and ``Shift-Tab`` keybindings. (For details on all of its-behaviors, see the Haddock documentation for ``handleFormEvent``.) It's-still up to the application to decide when events should go to the form-in the first place.+Handling form events is easy: we just use ``zoom`` to call+``Brick.Forms.handleFormEvent`` with the ``BrickEvent`` and a lens+to access the ``Form`` in the application state. This automatically+dispatches input events to the currently-focused input field, and it+also manages focus changes with ``Tab`` and ``Shift-Tab`` keybindings.+(For details on all of its behaviors, see the Haddock documentation for+``handleFormEvent``.) It's still up to the application to decide when+events should go to the form in the first place.  Since the form field handlers take ``BrickEvent`` values, that means that custom fields could even handle application-specific events (of the@@ -1557,6 +1613,80 @@ the ``FormFieldState`` and ``FormField`` data types along with the implementations of the built-in form field types. +Customizable Keybindings+========================++Brick applications typically start out by explicitly checking incoming+events for specific keys in ``appHandleEvent``. While this works well+enough, it results in *tight coupling* between the input key events and+the event handlers that get run. As applications evolve, it becomes+important to decouple the input key events and their handlers to allow+the input keys to be customized by the user. That's where Brick's+customizable keybindings API comes in.++The customizable keybindings API provides:++* ``Brick.Keybindings.Parse``: parsing and loading user-provided+  keybinding configuration files,+* ``Brick.Keybindings.Pretty``: pretty-printing keybindings and+  generating keybinding help text in ``Widget``, plain text, and+  Markdown formats so you can provide help to users both within the+  program and outside of it,+* ``Brick.Keybindings.KeyEvents``: specifying the application's abstract+  key events and their configuration names,+* ``Brick.Keybindings.KeyConfig``: bundling default and customized+  keybindings for each abstract event into a structure for use by the+  dispatcher, and+* ``Brick.Keybindings.KeyDispatcher``: specifying handlers and+  dispatching incoming key events to them.++This section of the User Guide describes the API at a high level,+but Brick also provides a complete working example of the custom+keybinding API in ``programs/CustomKeybindingDemo.hs`` and+provides detailed documentation on how to use the API, including a+step-by-step process for using it, in the module documentation for+``Brick.Keybindings.KeyDispatcher``.++The following table compares Brick application design decisions and+runtime behaviors in a typical application compared to one that uses the+customizable keybindings API:+++---------------------+------------------------+-------------------------++| **Approach**        | **Before runtime**     | **At runtime**          |++---------------------+------------------------+-------------------------++| Typical application | The application author | #. An input event       |+| (no custom          | decides which keys will|    arrives when the user|+| keybindings)        | trigger application    |    presses a key.       |+|                     | behaviors. The event   | #. The event handler    |+|                     | handler is written to  |    pattern-mathces on   |+|                     | pattern-match on       |    the input event to   |+|                     | specific keys.         |    check for a match and|+|                     |                        |    then runs the        |+|                     |                        |    corresponding        |+|                     |                        |    handler.             |++---------------------+------------------------+-------------------------++| Application with    | The application author | #. A Vty input event    |+| custom keybindings  | specifies the possible |    arrives when the user|+| API integrated      | *abstract events* that |    presses a key.       |+|                     | correspond to the      | #. The input event is   |+|                     | application's          |    provided to          |+|                     | behaviors. The events  |    ``appHandleEvent``.  |+|                     | are given default      | #. ``appHandleEvent``   |+|                     | keybindings. The       |    passes the event on  |+|                     | application provides   |    to a                 |+|                     | event handlers for the |    ``KeyDispatcher``.   |+|                     | abstract events, not   | #. The key dispatcher   |+|                     | specific keys. If      |    checks to see whether|+|                     | desired, the           |    the input key event  |+|                     | application can load   |    maps to an abstract  |+|                     | user-defined custom    |    event.               |+|                     | keybindings from an INI| #. If the dispatcher    |+|                     | file at startup to     |    finds a match, the   |+|                     | override the           |    corresponding        |+|                     | application's defaults.|    abstract event's key |+|                     |                        |    handler is run.      |++---------------------+------------------------+-------------------------++ Joining Borders =============== @@ -1778,12 +1908,12 @@  .. code:: haskell -   handleEvent s ... = do-     -- Invalidate just a single cache entry:-     Brick.Main.invalidateCacheEntry ExpensiveThing+   handleEvent ... = do+       -- Invalidate just a single cache entry:+       Brick.Main.invalidateCacheEntry ExpensiveThing -     -- Invalidate the entire cache (useful on a resize):-     Brick.Main.invalidateCache+       -- Invalidate the entire cache (useful on a resize):+       Brick.Main.invalidateCache  Implementing Custom Widgets ===========================@@ -1924,4 +2054,3 @@ .. _Hackage: http://hackage.haskell.org/ .. _microlens: http://hackage.haskell.org/package/microlens .. _bracketed paste mode: https://cirw.in/blog/bracketed-paste-.. _utf8proc: http://julialang.org/utf8proc/
− docs/samtay-tutorial.md
@@ -1,546 +0,0 @@--# Brick Tutorial by Samuel Tay--This tutorial was written by Samuel Tay, Copyright 2017-(https://github.com/samtay, https://samtay.github.io/). It is provided-as part of the brick distribution with permission.--## Introduction--I'm going to give a short introduction to-[brick](https://hackage.haskell.org/package/brick), a Haskell library-for building terminal user interfaces. So far I've used `brick` to-implement [Conway's Game of Life](https://github.com/samtay/conway) and-a [Tetris clone](https://github.com/samtay/tetris). I'll explain the-basics, walk through an example [snake](https://github.com/samtay/snake)-application, and then explain some more complicated scenarios.--The first thing I'll say is that this package has some of the most-impressive documentation and resources, which makes it easy to figure-out pretty much anything you need to do. I'll try to make this useful,-but I imagine if you're reading this then it is mostly being used as a-reference in addition to the existing resources:--1. [Demo programs](https://github.com/jtdaugherty/brick/tree/master/programs)-(clone down to explore the code and run them locally)-2. [User guide](https://github.com/jtdaugherty/brick/blob/master/docs/guide.rst)-3. [Haddock docs](https://hackage.haskell.org/package/brick)-4. [Google group](https://groups.google.com/forum/#!forum/brick-users)--### The basic idea--`brick` is very declarative. Once your base application logic is in-place, the interface is generally built by two functions: drawing and-handling events. The drawing function--```haskell-appDraw :: s -> [Widget n]-```--takes your app state `s` and produces the visuals `[Widget n]`. The-handler--```haskell-appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)-```--takes your app state, an event (e.g. user presses the `'m'` key), and-produces the resulting app state. *That's pretty much it.*--## `snake`--We're going to build the [classic-snake](https://en.wikipedia.org/wiki/Snake_(video_game)) game that you-might recall from arcades or the first cell phones. The full source code-is [here](https://github.com/samtay/snake). This is the end product:--![](snake-demo.gif)--### Structure of the app--The library makes it easy to separate the concerns of your application-and the interface; I like to have a module with all of the core business-logic that exports the core state of the app and functions for modifying-it, and then have an interface module that just handles the setup,-drawing, and handling events. So let's just use the `simple` stack-template and add two modules--```-├── LICENSE-├── README.md-├── Setup.hs-├── snake.cabal-├── src-│   ├── Main.hs-│   ├── Snake.hs-│   └── UI.hs-└── stack.yaml-```--and our dependencies to `test.cabal`--```yaml-executable snake-  hs-source-dirs:      src-  main-is:             Main.hs-  ghc-options:         -threaded-  exposed-modules:     Snake-                     , UI-  default-language:    Haskell2010-  build-depends:       base >= 4.7 && < 5-                     , brick-                     , containers-                     , linear-                     , microlens-                     , microlens-th-                     , random-```--### `Snake`--Since this tutorial is about `brick`, I'll elide most of the-implementation details of the actual game, but here are some of the key-types and scaffolding:--```haskell-{-# LANGUAGE TemplateHaskell, FlexibleContexts #-}-module Snake where--import Control.Applicative ((<|>))-import Control.Monad (guard)-import Data.Maybe (fromMaybe)--import Data.Sequence (Seq, ViewL(..), ViewR(..), (<|))-import qualified Data.Sequence as S-import Lens.Micro.TH (makeLenses)-import Lens.Micro ((&), (.~), (%~), (^.))-import Linear.V2 (V2(..), _x, _y)-import System.Random (Random(..), newStdGen)---- Types--data Game = Game-  { _snake  :: Snake        -- ^ snake as a sequence of points in R2-  , _dir    :: Direction    -- ^ direction-  , _food   :: Coord        -- ^ location of the food-  , _foods  :: Stream Coord -- ^ infinite list of random food locations-  , _dead   :: Bool         -- ^ game over flag-  , _paused :: Bool         -- ^ paused flag-  , _score  :: Int          -- ^ score-  , _frozen :: Bool         -- ^ freeze to disallow duplicate turns-  } deriving (Show)--type Coord = V2 Int-type Snake = Seq Coord--data Stream a = a :| Stream a-  deriving (Show)--data Direction-  = North-  | South-  | East-  | West-  deriving (Eq, Show)-```--All of this is pretty self-explanatory, with the possible exception-of lenses if you haven't seen them. At first glance they may seem-complicated (and the underlying theory arguably is), but using them as-getters and setters is very straightforward. So, if you are following-along because you are writing a terminal app like this, I'd recommend-using them, but they are not required to use `brick`.--Here are the core functions for playing the game:--```haskell--- | Step forward in time-step :: Game -> Game-step g = fromMaybe g $ do-  guard (not $ g ^. paused || g ^. dead)-  let g' = g & frozen .~ False-  return . fromMaybe (move g') $ die g' <|> eatFood g'---- | Possibly die if next head position is disallowed-die :: Game -> Maybe Game---- | Possibly eat food if next head position is food-eatFood :: Game -> Maybe Game---- | Move snake along in a marquee fashion-move :: Game -> Game---- | Turn game direction (only turns orthogonally)------ Implicitly unpauses yet freezes game-turn :: Direction -> Game -> Game---- | Initialize a paused game with random food location-initGame :: IO Game-```--### `UI`--To start, we need to determine what our `App s e n` type parameters are.-This will completely describe the interface application and be passed-to one of the library's `main` style functions for execution. Note that-`s` is the app state, `e` is an event type, and `n` is a resource name.-The `e` is abstracted so that we can provide custom events. The `n`-is usually a custom sum type called `Name` which allows us to *name*-particular viewports. This is important so that we can keep track of-where the user currently has *focus*, such as typing in one of two-textboxes; however, for this simple snake game we don't need to worry-about that.--In simpler cases, the state `s` can directly coincide with a core-datatype such as our `Snake.Game`. In many cases however, it will be-necessary to wrap the core state within the ui state `s` to keep track-of things that are interface specific (more on this later).--Let's write out our app definition and leave some undefined functions:--```haskell-{-# LANGUAGE OverloadedStrings #-}-module UI where--import Control.Monad (forever, void)-import Control.Monad.IO.Class (liftIO)-import Control.Concurrent (threadDelay, forkIO)-import Data.Maybe (fromMaybe)--import Snake--import Brick-  ( App(..), AttrMap, BrickEvent(..), EventM, Next, Widget-  , customMain, neverShowCursor-  , continue, halt-  , hLimit, vLimit, vBox, hBox-  , padRight, padLeft, padTop, padAll, Padding(..)-  , withBorderStyle-  , str-  , attrMap, withAttr, emptyWidget, AttrName, on, fg-  , (<+>)-  )-import Brick.BChan (newBChan, writeBChan)-import qualified Brick.Widgets.Border as B-import qualified Brick.Widgets.Border.Style as BS-import qualified Brick.Widgets.Center as C-import qualified Graphics.Vty as V-import Data.Sequence (Seq)-import qualified Data.Sequence as S-import Linear.V2 (V2(..))-import Lens.Micro ((^.))---- Types---- | Ticks mark passing of time------ This is our custom event that will be constantly fed into the app.-data Tick = Tick---- | Named resources------ Not currently used, but will be easier to refactor--- if we call this "Name" now.-type Name = ()--data Cell = Snake | Food | Empty---- App definition--app :: App Game Tick Name-app = App { appDraw = drawUI-          , appChooseCursor = neverShowCursor-          , appHandleEvent = handleEvent-          , appStartEvent = return-          , appAttrMap = const theMap-          }--main :: IO ()-main = undefined---- Handling events--handleEvent :: Game -> BrickEvent Name Tick -> EventM Name (Next Game)-handleEvent = undefined---- Drawing--drawUI :: Game -> [Widget Name]-drawUI = undefined--theMap :: AttrMap-theMap = undefined-```--#### Custom Events--So far I've only used `brick` to make games which need to be redrawn-as time passes, with or without user input. This requires using-`Brick.customMain` with that `Tick` event type, and opening a forked-process to `forever` feed that event type into the channel. Since this-is a common scenario, there is a `Brick.BChan` module that makes this-pretty quick:--```haskell-main :: IO ()-main = do-  chan <- newBChan 10-  forkIO $ forever $ do-    writeBChan chan Tick-    threadDelay 100000 -- decides how fast your game moves-  g <- initGame-  let buildVty = V.mkVty V.defaultConfig-  initialVty <- buildVty-  void $ customMain initialVty buildVty (Just chan) app g-```--We do need to import `Vty.Graphics` since `customMain` allows us-to specify a custom `IO Vty.Graphics.Vty` handle, but we're only-customizing the existence of the event channel `BChan Tick`. The app-is now bootstrapped, and all we need to do is implement `handleEvent`,-`drawUI`, and `theMap` (handles styling).--#### Handling events--Handling events is largely straightforward, and can be very clean when-your underlying application logic is taken care of in a core module. All-we do is essentially map events to the proper state modifiers.--```haskell-handleEvent :: Game -> BrickEvent Name Tick -> EventM Name (Next Game)-handleEvent g (AppEvent Tick)                       = continue $ step g-handleEvent g (VtyEvent (V.EvKey V.KUp []))         = continue $ turn North g-handleEvent g (VtyEvent (V.EvKey V.KDown []))       = continue $ turn South g-handleEvent g (VtyEvent (V.EvKey V.KRight []))      = continue $ turn East g-handleEvent g (VtyEvent (V.EvKey V.KLeft []))       = continue $ turn West g-handleEvent g (VtyEvent (V.EvKey (V.KChar 'k') [])) = continue $ turn North g-handleEvent g (VtyEvent (V.EvKey (V.KChar 'j') [])) = continue $ turn South g-handleEvent g (VtyEvent (V.EvKey (V.KChar 'l') [])) = continue $ turn East g-handleEvent g (VtyEvent (V.EvKey (V.KChar 'h') [])) = continue $ turn West g-handleEvent g (VtyEvent (V.EvKey (V.KChar 'r') [])) = liftIO (initGame) >>= continue-handleEvent g (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt g-handleEvent g (VtyEvent (V.EvKey V.KEsc []))        = halt g-handleEvent g _                                     = continue g-```--It's probably obvious, but `continue` will continue execution with-the supplied state value, which is then drawn. We can also `halt` to-stop execution, which will essentially finish the evaluation of our-`customMain` and result in `IO Game`, where the resulting game is the-last value that we supplied to `halt`.--#### Drawing--Drawing is fairly simple as well but can require a good amount of code-to position things how you want them. I like to break up the visual-space into regions with drawing functions for each one.--```haskell-drawUI :: Game -> [Widget Name]-drawUI g =-  [ C.center $ padRight (Pad 2) (drawStats g) <+> drawGrid g ]--drawStats :: Game -> Widget Name-drawStats = undefined--drawGrid :: Game -> Widget Name-drawGrid = undefined-```--This will center the overall interface (`C.center`), put the stats and-grid widgets horizontally side by side (`<+>`), and separate them by a-2-character width (`padRight (Pad 2)`).--Let's move forward with the stats column:--```haskell-drawStats :: Game -> Widget Name-drawStats g = hLimit 11-  $ vBox [ drawScore (g ^. score)-         , padTop (Pad 2) $ drawGameOver (g ^. dead)-         ]--drawScore :: Int -> Widget Name-drawScore n = withBorderStyle BS.unicodeBold-  $ B.borderWithLabel (str "Score")-  $ C.hCenter-  $ padAll 1-  $ str $ show n--drawGameOver :: Bool -> Widget Name-drawGameOver dead =-  if dead-     then withAttr gameOverAttr $ C.hCenter $ str "GAME OVER"-     else emptyWidget--gameOverAttr :: AttrName-gameOverAttr = "gameOver"-```--I'm throwing in that `hLimit 11` to prevent the widget greediness caused-by the outer `C.center`. I'm also using `vBox` to show some other-options of aligning widgets; `vBox` and `hBox` align a list of widgets-vertically and horizontally, respectfully. They can be thought of as-folds over the binary `<=>` and `<+>` operations.--The score is straightforward, but it is the first border in-this tutorial. Borders are well documented in the [border-demo](https://github.com/jtdaugherty/brick/blob/master/programs/BorderDemo.hs)-and the Haddocks for that matter.--We also only show the "game over" widget if the game is actually over.-In that case, we are rendering the string widget with the `gameOverAttr`-attribute name. Attribute names are basically type safe *names* that-we can assign to widgets to apply predetermined styles, similar to-assigning a class name to a div in HTML and defining the CSS styles for-that class elsewhere.--Attribute names implement `IsString`, so they are easy to construct with-the `OverloadedStrings` pragma.--Now for the main event:--```haskell-drawGrid :: Game -> Widget Name-drawGrid g = withBorderStyle BS.unicodeBold-  $ B.borderWithLabel (str "Snake")-  $ vBox rows-  where-    rows         = [hBox $ cellsInRow r | r <- [height-1,height-2..0]]-    cellsInRow y = [drawCoord (V2 x y) | x <- [0..width-1]]-    drawCoord    = drawCell . cellAt-    cellAt c-      | c `elem` g ^. snake = Snake-      | c == g ^. food      = Food-      | otherwise           = Empty--drawCell :: Cell -> Widget Name-drawCell Snake = withAttr snakeAttr cw-drawCell Food  = withAttr foodAttr cw-drawCell Empty = withAttr emptyAttr cw--cw :: Widget Name-cw = str "  "--snakeAttr, foodAttr, emptyAttr :: AttrName-snakeAttr = "snakeAttr"-foodAttr = "foodAttr"-emptyAttr = "emptyAttr"--```--There's actually nothing new here! We've already covered all the-`brick` functions necessary to draw the grid. My approach to grids is-to render a square cell widget `cw` with different colors depending-on the cell state. The easiest way to draw a colored square is to-stick two characters side by side. If we assign an attribute with a-matching foreground and background, then it doesn't matter what the two-characters are (provided that they aren't some crazy Unicode characters-that might render to an unexpected size). However, if we want empty-cells to render with the same color as the user's default background-color, then spaces are a good choice.--Finally, we'll define the attribute map:--```haskell-theMap :: AttrMap-theMap = attrMap V.defAttr-  [ (snakeAttr, V.blue `on` V.blue)-  , (foodAttr, V.red `on` V.red)-  , (gameOverAttr, fg V.red `V.withStyle` V.bold)-  ]-```--Again, styles aren't terribly complicated, but it-will be one area where you might have to look in the-[vty](http://hackage.haskell.org/package/vty) package (specifically-[Graphics.Vty.Attributes](http://hackage.haskell.org/package/vty-5.15.1/docs/Graphics-Vty-Attributes.html)) to find what you need.--Another thing to mention is that the attributes form a hierarchy and-can be combined in a parent-child relationship via `mappend`. I haven't-actually used this feature, but it does sound quite handy. For a more-detailed discussion see the-[Brick.AttrMap](https://hackage.haskell.org/package/brick-0.18/docs/Brick-AttrMap.html) haddocks.--## Variable speed--One difficult problem I encountered was implementing a variable speed in-the GoL. I could have just used the same approach above with the minimum-thread delay (corresponding to the maximum speed) and counted `Tick`-events, only issuing an actual `step` in the game when the modular count-of `Tick`s reached an amount corresponding to the current game speed,-but that's kind of an ugly approach.--Instead, I reached out to the author and he advised me to use a `TVar`-within the app state.  I had never used `TVar`, but it's pretty easy!--```haskell-main :: IO ()-main = do-  chan <- newBChan 10-  tv   <- atomically $ newTVar (spToInt initialSpeed)-  forkIO $ forever $ do-    writeBChan chan Tick-    int <- atomically $ readTVar tv-    threadDelay int-  let buildVty = V.mkVty V.defaultConfig-  initialVty <- buildVty-  customMain initialVty buildVty (Just chan) app (initialGame tv)-    >>= printResult-```--The `tv <- atomically $ newTVar (value :: a)` creates a new mutable-reference to a value of type `a`, i.e. `TVar a`, and returns it in `IO`.-In this case `value` is an `Int` which represents the delay between game-steps. Then in the forked process, we read the delay from the `TVar`-reference and use that to space out the calls to `writeBChan chan Tick`.--I store that same `tv :: TVar Int` in the brick app state, so that the-user can change the speed:--```haskell-handleEvent :: Game -> BrickEvent Name Tick -> EventM Name (Next Game)-handleEvent g (VtyEvent (V.EvKey V.KRight [V.MCtrl])) = handleSpeed g (+)-handleEvent g (VtyEvent (V.EvKey V.KLeft [V.MCtrl]))  = handleSpeed g (-)--handleSpeed :: Game -> (Float -> Float -> Float) -> EventM n (Next Game)-handleSpeed g (+/-) = do-  let newSp = validS $ (g ^. speed) +/- speedInc-  liftIO $ atomically $ writeTVar (g ^. interval) (spToInt newSp)-  continue $ g & speed .~ newSp---- where---- | Speed increments = 0.01 gives 100 discrete speed settings-speedInc :: Float-speedInc = 0.01---- | Game state-data Game = Game-  { _board    :: Board -- ^ Board state-  , _time     :: Int   -- ^ Time elapsed-  , _paused   :: Bool  -- ^ Playing vs. paused-  , _speed    :: Float -- ^ Speed in [0..1]-  , _interval :: TVar Int -- ^ Interval kept in TVar-  , _focus    :: F.FocusRing Name -- ^ Keeps track of grid focus-  , _selected :: Cell -- ^ Keeps track of cell focus-  }-```--## Conclusion--`brick` let's you build TUIs very quickly. I was able to write `snake`-along with this tutorial within a few hours. More complicated interfaces-can be tougher, but if you can successfully separate the interface and-core functionality, you'll have an easier time tacking on the frontend.--Lastly, let me remind you to look in the-[demo programs](https://github.com/jtdaugherty/brick/tree/master/programs)-to figure stuff out, as *many* scenarios are covered throughout them.--## Links-* [brick](https://hackage.haskell.org/package/brick)-* [snake](https://github.com/samtay/snake)-* [tetris](https://github.com/samtay/tetris)-* [conway](https://github.com/samtay/conway)
programs/AttrDemo.hs view
@@ -23,28 +23,28 @@   , modifyDefAttr   ) import Brick.Util (on, fg)-import Brick.AttrMap (attrMap, AttrMap)+import Brick.AttrMap (attrMap, AttrMap, attrName)  ui :: Widget n ui =     vBox [ str "This text uses the global default attribute."-         , withAttr "foundFull" $+         , withAttr (attrName "foundFull") $            str "Specifying an attribute name means we look it up in the attribute tree."-         , (withAttr "foundFgOnly" $+         , (withAttr (attrName "foundFgOnly") $            str ("When we find a value, we merge it with its parent in the attribute")            <=> str "name tree all the way to the root (the global default).")-         , withAttr "missing" $+         , withAttr (attrName "missing") $            str "A missing attribute name just resumes the search at its parent."-         , withAttr ("general" <> "specific") $+         , withAttr (attrName "general" <> attrName "specific") $            str "In this way we build complete attribute values by using an inheritance scheme."-         , withAttr "foundFull" $+         , withAttr (attrName "foundFull") $            str "You can override everything ..."-         , withAttr "foundFgOnly" $+         , withAttr (attrName "foundFgOnly") $            str "... or only what you want to change and inherit the rest."          , str "Attribute names are assembled with the Monoid append operation to indicate"          , str "hierarchy levels, e.g. \"window\" <> \"title\"."          , str " "-         , withAttr "linked" $+         , withAttr (attrName "linked") $            str "This text is hyperlinked in terminals that support hyperlinking."          , str " "          , hyperlink "http://www.google.com/" $@@ -59,18 +59,19 @@  theMap :: AttrMap theMap = attrMap globalDefault-    [ ("foundFull",               white `on` green)-    , ("foundFgOnly",             fg red)-    , ("general",                 yellow `on` black)-    , ("general" <> "specific",   fg cyan)-    , ("linked",                  fg yellow `withURL` "http://www.google.com/")+    [ (attrName "foundFull",               white `on` green)+    , (attrName "foundFgOnly",             fg red)+    , (attrName "general",                 yellow `on` black)+    , (attrName "general" <> attrName "specific",+                                           fg cyan)+    , (attrName "linked",                  fg yellow `withURL` "http://www.google.com/")     ]  app :: App () e () app =     App { appDraw = const [ui]         , appHandleEvent = resizeOrQuit-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const theMap         , appChooseCursor = neverShowCursor         }
programs/BorderDemo.hs view
@@ -68,7 +68,7 @@     txt $ "  " <> styleName <> " style  "  titleAttr :: A.AttrName-titleAttr = "title"+titleAttr = A.attrName "title"  borderMappings :: [(A.AttrName, V.Attr)] borderMappings =
programs/CacheDemo.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-} module Main where  import Control.Monad (void)+import Control.Monad.State (modify) #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid ((<>)) #endif@@ -16,7 +16,8 @@   , BrickEvent(..)   ) import Brick.Widgets.Core-  ( vBox+  ( Padding(..)+  , vBox   , padTopBottom   , withDefAttr   , cached@@ -29,6 +30,7 @@   ) import Brick.AttrMap   ( AttrName+  , attrName   , attrMap   ) @@ -50,26 +52,26 @@              , padTopBottom 1 $                cached ExpensiveWidget $                withDefAttr emphAttr $ str $ "This widget is cached (state = " <> show i <> ")"-             , padBottom (T.Pad 1) $+             , padBottom (Pad 1) $                withDefAttr emphAttr $ str $ "This widget is not cached (state = " <> show i <> ")"              , hCenter $ str "Press 'i' to invalidate the cache,"              , str "'+' to change the state value, and"              , str "'Esc' to quit."              ] -appEvent :: Int -> BrickEvent Name e -> T.EventM Name (T.Next Int)-appEvent i (VtyEvent (V.EvKey (V.KChar '+') [])) = M.continue $ i + 1-appEvent i (VtyEvent (V.EvKey (V.KChar 'i') [])) = M.invalidateCacheEntry ExpensiveWidget >> M.continue i-appEvent i (VtyEvent (V.EvKey V.KEsc [])) = M.halt i-appEvent i _ = M.continue i+appEvent :: BrickEvent Name e -> T.EventM Name Int ()+appEvent (VtyEvent (V.EvKey (V.KChar '+') [])) = modify (+ 1)+appEvent (VtyEvent (V.EvKey (V.KChar 'i') [])) = M.invalidateCacheEntry ExpensiveWidget+appEvent (VtyEvent (V.EvKey V.KEsc [])) = M.halt+appEvent _ = return ()  emphAttr :: AttrName-emphAttr = "emphasis"+emphAttr = attrName "emphasis"  app :: M.App Int e Name app =     M.App { M.appDraw = drawUi-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appHandleEvent = appEvent           , M.appAttrMap = const $ attrMap V.defAttr [(emphAttr, V.white `on` V.blue)]           , M.appChooseCursor = M.neverShowCursor
programs/CroppingDemo.hs view
@@ -4,7 +4,6 @@ import Brick.Main (App(..), neverShowCursor, resizeOrQuit, defaultMain) import Brick.Types   ( Widget-  , Padding(..)   ) import Brick.Widgets.Core   ( vBox@@ -20,6 +19,7 @@   , cropRightTo   , cropTopTo   , cropBottomTo+  , Padding(..)   ) import Brick.Widgets.Border (border) import Brick.AttrMap (attrMap)@@ -52,7 +52,7 @@ app =     App { appDraw = const [ui]         , appHandleEvent = resizeOrQuit-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const $ attrMap V.defAttr []         , appChooseCursor = neverShowCursor         }
programs/CustomEventDemo.hs view
@@ -3,8 +3,9 @@ {-# LANGUAGE CPP #-} module Main where -import Lens.Micro ((^.), (&), (.~), (%~))+import Lens.Micro ((^.)) import Lens.Micro.TH (makeLenses)+import Lens.Micro.Mtl import Control.Monad (void, forever) import Control.Concurrent (threadDelay, forkIO) #if !(MIN_VERSION_base(4,11,0))@@ -17,7 +18,6 @@   ( App(..)   , showFirstCursor   , customMain-  , continue   , halt   ) import Brick.AttrMap@@ -25,7 +25,6 @@   ) import Brick.Types   ( Widget-  , Next   , EventM   , BrickEvent(..)   )@@ -50,14 +49,15 @@             <=>             (str $ "Counter value is: " <> (show $ st^.stCounter)) -appEvent :: St -> BrickEvent () CustomEvent -> EventM () (Next St)-appEvent st e =+appEvent :: BrickEvent () CustomEvent -> EventM () St ()+appEvent e =     case e of-        VtyEvent (V.EvKey V.KEsc []) -> halt st-        VtyEvent _ -> continue $ st & stLastBrickEvent .~ (Just e)-        AppEvent Counter -> continue $ st & stCounter %~ (+1)-                                          & stLastBrickEvent .~ (Just e)-        _ -> continue st+        VtyEvent (V.EvKey V.KEsc []) -> halt+        VtyEvent _ -> stLastBrickEvent .= (Just e)+        AppEvent Counter -> do+            stCounter %= (+1)+            stLastBrickEvent .= (Just e)+        _ -> return ()  initialState :: St initialState =@@ -70,7 +70,7 @@     App { appDraw = drawUI         , appChooseCursor = showFirstCursor         , appHandleEvent = appEvent-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const $ attrMap V.defAttr []         } 
+ programs/CustomKeybindingDemo.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+module Main where++import Lens.Micro ((^.))+import Lens.Micro.TH (makeLenses)+import Lens.Micro.Mtl ((<~), (.=), (%=), use)+import Control.Monad (void)+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+#if !(MIN_VERSION_base(4,11,0))+import Data.Monoid ((<>))+#endif+import qualified Graphics.Vty as V+import System.Environment (getArgs)+import System.Exit (exitFailure)++import qualified Brick.Types as T+import Brick.Types (Widget)+import qualified Brick.Keybindings as K+import Brick.AttrMap+import Brick.Util (fg)+import qualified Brick.Main as M+import qualified Brick.Widgets.Border as B+import qualified Brick.Widgets.Center as C+import Brick.Widgets.Core++-- | The abstract key events for the application.+data KeyEvent = QuitEvent+              | IncrementEvent+              | DecrementEvent+              deriving (Ord, Eq, Show)++-- | The mapping of key events to their configuration field names.+allKeyEvents :: K.KeyEvents KeyEvent+allKeyEvents =+    K.keyEvents [ ("quit",      QuitEvent)+                , ("increment", IncrementEvent)+                , ("decrement", DecrementEvent)+                ]++-- | Default key bindings for each abstract key event.+defaultBindings :: [(KeyEvent, [K.Binding])]+defaultBindings =+    [ (QuitEvent,      [K.ctrl 'q', K.bind V.KEsc])+    , (IncrementEvent, [K.bind '+'])+    , (DecrementEvent, [K.bind '-'])+    ]++data St =+    St { _keyConfig :: K.KeyConfig KeyEvent+       -- ^ The key config to use.+       , _lastKey :: Maybe (V.Key, [V.Modifier])+       -- ^ The last key that was pressed.+       , _lastKeyHandled :: Bool+       -- ^ Whether the last key was handled by a handler.+       , _counter :: Int+       -- ^ The counter value to manipulate in the handlers.+       , _customBindingsPath :: Maybe FilePath+       -- ^ Set if the application found custom keybindings in the+       -- specified file.+       }++makeLenses ''St++-- | Key event handlers for our application.+handlers :: [K.KeyEventHandler KeyEvent (T.EventM () St)]+handlers =+    -- The first three handlers are triggered by keys mapped to abstract+    -- events, thus decoupling the configured key bindings from these+    -- handlers.+    [ K.onEvent QuitEvent "Quit the program"+          M.halt+    , K.onEvent IncrementEvent "Increment the counter" $+          counter %= succ+    , K.onEvent DecrementEvent "Decrement the counter" $+          counter %= subtract 1++    -- These handlers are always triggered by specific keys and thus+    -- cannot be rebound.+    , K.onKey (K.bind '\t') "Increment the counter by 10" $+          counter %= (+ 10)+    , K.onKey (K.bind V.KBackTab) "Decrement the counter by 10" $+          counter %= subtract 10+    ]++appEvent :: T.BrickEvent () e -> T.EventM () St ()+appEvent (T.VtyEvent (V.EvKey k mods)) = do+    -- Dispatch the key to the event handler to which the key is mapped,+    -- if any. Also record in lastKeyHandled whether the dispatcher+    -- found a matching handler.+    kc <- use keyConfig+    let d = K.keyDispatcher kc handlers+    lastKey .= Just (k, mods)+    lastKeyHandled <~ K.handleKey d k mods+appEvent _ =+    return ()++drawUi :: St -> [Widget ()]+drawUi st = [body]+    where+        binding = uncurry K.binding <$> st^.lastKey++        -- Generate key binding help using the library so we can embed+        -- it in the UI.+        keybindingHelp = B.borderWithLabel (txt "Active Keybindings") $+                         K.keybindingHelpWidget (st^.keyConfig) handlers++        lastKeyDisplay = withDefAttr lastKeyAttr $+                         txt $ maybe "(none)" K.ppBinding binding++        -- Show the status of the last pressed key, whether we handled+        -- it, and other bits of the application state.+        status = B.borderWithLabel (txt "Status") $+                 hLimit 40 $+                 padRight Max $+                 vBox [ txt   "Last key:         " <+> lastKeyDisplay+                      , str $ "Last key handled: " <> show (st^.lastKeyHandled)+                      , str $ "Counter:          " <> show (st^.counter)+                      ]++        -- Show info about whether the application is currently using+        -- custom bindings loaded from an INI file.+        customBindingInfo =+            B.borderWithLabel (txt "Custom Bindings") $+            case st^.customBindingsPath of+                Nothing ->+                    hLimit 40 $+                    txtWrap $ "No custom bindings loaded. Create an INI file with a " <>+                              (Text.pack $ show sectionName) <>+                              " section or use 'programs/custom_keys.ini'. " <>+                              "Pass its path to this program on the command line."+                Just f -> str "Loaded custom bindings from:" <=> str (show f)++        body = C.center $+               (padRight (Pad 2) $ status <=> customBindingInfo) <+>+               keybindingHelp++lastKeyAttr :: AttrName+lastKeyAttr = attrName "lastKey"++app :: M.App St e ()+app =+    M.App { M.appDraw = drawUi+          , M.appStartEvent = return ()+          , M.appHandleEvent = appEvent+          , M.appAttrMap = const $ attrMap V.defAttr [ (K.eventNameAttr, fg V.magenta)+                                                     , (K.eventDescriptionAttr, fg V.cyan)+                                                     , (K.keybindingAttr, fg V.yellow)+                                                     , (lastKeyAttr, fg V.white `V.withStyle` V.bold)+                                                     ]+          , M.appChooseCursor = M.showFirstCursor+          }++sectionName :: Text.Text+sectionName = "keybindings"++main :: IO ()+main = do+    args <- getArgs++    -- If the command line specified the path to an INI file with custom+    -- bindings, attempt to load it.+    (customBindings, customFile) <- case args of+        [iniFilePath] -> do+            result <- K.keybindingsFromFile allKeyEvents sectionName iniFilePath+            case result of+                -- A section was found and had zero more bindings.+                Right (Just bindings) ->+                    return (bindings, Just iniFilePath)++                -- No section was found at all.+                Right Nothing -> do+                    putStrLn $ "Error: found no section " <> show sectionName <> " in " <> show iniFilePath+                    exitFailure++                -- There was some problem parsing the file as an INI+                -- file.+                Left e -> do+                    putStrLn $ "Error reading keybindings file " <> show iniFilePath <> ": " <> e+                    exitFailure++        _ -> return ([], Nothing)++    -- Create a key config that includes the default bindings as well as+    -- the custom bindings we loaded from the INI file, if any.+    let kc = K.newKeyConfig allKeyEvents defaultBindings customBindings++    void $ M.defaultMain app $ St { _keyConfig = kc+                                  , _lastKey = Nothing+                                  , _lastKeyHandled = False+                                  , _counter = 0+                                  , _customBindingsPath = customFile+                                  }++    -- Now demonstrate how the library's generated key binding help text+    -- looks in plain text and Markdown formats. These can be used to+    -- generate documentation for users. Note that the output generated+    -- here takes the active bindings into account! If you don't want+    -- that, use handlers that were built from a KeyConfig that didn't+    -- have any custom bindings applied.+    let sections = [("Main", handlers)]++    putStrLn "Generated plain text help:"+    Text.putStrLn $ K.keybindingTextTable kc sections++    putStrLn "Generated Markdown help:"+    Text.putStrLn $ K.keybindingMarkdownTable kc sections
programs/DialogDemo.hs view
@@ -30,13 +30,13 @@     where         ui = D.renderDialog d $ C.hCenter $ padAll 1 $ str "This is the dialog body." -appEvent :: D.Dialog Choice -> BrickEvent () e -> T.EventM () (T.Next (D.Dialog Choice))-appEvent d (VtyEvent ev) =+appEvent :: BrickEvent () e -> T.EventM () (D.Dialog Choice) ()+appEvent (VtyEvent ev) =     case ev of-        V.EvKey V.KEsc [] -> M.halt d-        V.EvKey V.KEnter [] -> M.halt d-        _ -> M.continue =<< D.handleDialogEvent ev d-appEvent d _ = M.continue d+        V.EvKey V.KEsc [] -> M.halt+        V.EvKey V.KEnter [] -> M.halt+        _ -> D.handleDialogEvent ev+appEvent _ = return ()  initialState :: D.Dialog Choice initialState = D.dialog (Just "Title") (Just (0, choices)) 50@@ -58,7 +58,7 @@     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appAttrMap = const theMap           } 
programs/EditDemo.hs view
@@ -5,6 +5,7 @@  import Lens.Micro import Lens.Micro.TH+import Lens.Micro.Mtl import qualified Graphics.Vty as V  import qualified Brick.Main as M@@ -47,18 +48,19 @@             str " " <=>             str "Press Tab to switch between editors, Esc to quit." -appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)-appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) =-    M.halt st-appEvent st (T.VtyEvent (V.EvKey (V.KChar '\t') [])) =-    M.continue $ st & focusRing %~ F.focusNext-appEvent st (T.VtyEvent (V.EvKey V.KBackTab [])) =-    M.continue $ st & focusRing %~ F.focusPrev-appEvent st ev =-    M.continue =<< case F.focusGetCurrent (st^.focusRing) of-      Just Edit1 -> T.handleEventLensed st edit1 E.handleEditorEvent ev-      Just Edit2 -> T.handleEventLensed st edit2 E.handleEditorEvent ev-      Nothing -> return st+appEvent :: T.BrickEvent Name e -> T.EventM Name St ()+appEvent (T.VtyEvent (V.EvKey V.KEsc [])) =+    M.halt+appEvent (T.VtyEvent (V.EvKey (V.KChar '\t') [])) =+    focusRing %= F.focusNext+appEvent (T.VtyEvent (V.EvKey V.KBackTab [])) =+    focusRing %= F.focusPrev+appEvent ev = do+    r <- use focusRing+    case F.focusGetCurrent r of+      Just Edit1 -> zoom edit1 $ E.handleEditorEvent ev+      Just Edit2 -> zoom edit2 $ E.handleEditorEvent ev+      Nothing -> return ()  initialState :: St initialState =@@ -80,7 +82,7 @@     M.App { M.appDraw = drawUI           , M.appChooseCursor = appCursor           , M.appHandleEvent = appEvent-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appAttrMap = const theMap           } 
programs/FileBrowserDemo.hs view
@@ -8,10 +8,11 @@ #endif import qualified Graphics.Vty as V +import Control.Monad.State (get) import qualified Data.Text as Text import qualified Brick.Main as M import qualified Brick.Widgets.List as L-import Brick.AttrMap (AttrName)+import Brick.AttrMap (AttrName, attrName) import Brick.Types   ( Widget   , BrickEvent(..)@@ -27,6 +28,7 @@   ( vBox, (<=>), padTop   , hLimit, vLimit, txt   , withDefAttr, emptyWidget+  , Padding(..)   ) import qualified Brick.Widgets.FileBrowser as FB import qualified Brick.AttrMap as A@@ -44,7 +46,7 @@              hLimit 50 $              borderWithLabel (txt "Choose a file") $              FB.renderFileBrowser True b-        help = padTop (T.Pad 1) $+        help = padTop (Pad 1) $                vBox [ case FB.fileBrowserException b of                           Nothing -> emptyWidget                           Just e -> hCenter $ withDefAttr errorAttr $@@ -55,25 +57,27 @@                     , hCenter $ txt "Esc: quit"                     ] -appEvent :: FB.FileBrowser Name -> BrickEvent Name e -> T.EventM Name (T.Next (FB.FileBrowser Name))-appEvent b (VtyEvent ev) =+appEvent :: BrickEvent Name e -> T.EventM Name (FB.FileBrowser Name) ()+appEvent (VtyEvent ev) = do+    b <- get     case ev of         V.EvKey V.KEsc [] | not (FB.fileBrowserIsSearching b) ->-            M.halt b+            M.halt         _ -> do-            b' <- FB.handleFileBrowserEvent ev b+            FB.handleFileBrowserEvent ev             -- If the browser has a selected file after handling the             -- event (because the user pressed Enter), shut down.             case ev of-                V.EvKey V.KEnter [] ->+                V.EvKey V.KEnter [] -> do+                    b' <- get                     case FB.fileBrowserSelection b' of-                        [] -> M.continue b'-                        _ -> M.halt b'-                _ -> M.continue b'-appEvent b _ = M.continue b+                        [] -> return ()+                        _ -> M.halt+                _ -> return ()+appEvent _ = return ()  errorAttr :: AttrName-errorAttr = "error"+errorAttr = attrName "error"  theMap :: A.AttrMap theMap = A.attrMap V.defAttr@@ -95,7 +99,7 @@     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appAttrMap = const theMap           } 
programs/FormDemo.hs view
@@ -112,22 +112,24 @@ app :: App (Form UserInfo e Name) e Name app =     App { appDraw = draw-        , appHandleEvent = \s ev ->+        , appHandleEvent = \ev -> do+            f <- gets formFocus             case ev of-                VtyEvent (V.EvResize {})     -> continue s-                VtyEvent (V.EvKey V.KEsc [])   -> halt s+                VtyEvent (V.EvResize {}) -> return ()+                VtyEvent (V.EvKey V.KEsc []) -> halt                 -- Enter quits only when we aren't in the multi-line editor.                 VtyEvent (V.EvKey V.KEnter [])-                    | focusGetCurrent (formFocus s) /= Just AddressField -> halt s+                    | focusGetCurrent f /= Just AddressField -> halt                 _ -> do-                    s' <- handleFormEvent ev s+                    handleFormEvent ev                      -- Example of external validation:                     -- Require age field to contain a value that is at least 18.-                    continue $ setFieldValid ((formState s')^.age >= 18) AgeField s'+                    st <- gets formState+                    modify $ setFieldValid (st^.age >= 18) AgeField          , appChooseCursor = focusRingCursor formFocus-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const theMap         } 
programs/LayerDemo.hs view
@@ -1,13 +1,13 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Main where  #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid ((<>)) #endif-import Lens.Micro ((^.), (&), (%~))+import Lens.Micro ((^.)) import Lens.Micro.TH (makeLenses)+import Lens.Micro.Mtl import Control.Monad (void) import qualified Graphics.Vty as V @@ -30,6 +30,7 @@ import Brick.AttrMap   ( attrMap   , AttrName+  , attrName   )  data St =@@ -72,35 +73,35 @@     translateBy (st^.bottomLayerLocation) $     B.border $ str "Bottom layer\n(Ctrl-arrow keys move)" -appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)-appEvent st (T.VtyEvent (V.EvKey V.KDown []))  =-    M.continue $ st & middleLayerLocation.locationRowL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KUp []))    =-    M.continue $ st & middleLayerLocation.locationRowL %~ (subtract 1)-appEvent st (T.VtyEvent (V.EvKey V.KRight [])) =-    M.continue $ st & middleLayerLocation.locationColumnL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KLeft []))  =-    M.continue $ st & middleLayerLocation.locationColumnL %~ (subtract 1)+appEvent :: T.BrickEvent Name e -> T.EventM Name St ()+appEvent (T.VtyEvent (V.EvKey V.KDown []))  =+    middleLayerLocation.locationRowL %= (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KUp []))    =+    middleLayerLocation.locationRowL %= (subtract 1)+appEvent (T.VtyEvent (V.EvKey V.KRight [])) =+    middleLayerLocation.locationColumnL %= (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KLeft []))  =+    middleLayerLocation.locationColumnL %= (subtract 1) -appEvent st (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) =-    M.continue $ st & bottomLayerLocation.locationRowL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) =-    M.continue $ st & bottomLayerLocation.locationRowL %~ (subtract 1)-appEvent st (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) =-    M.continue $ st & bottomLayerLocation.locationColumnL %~ (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) =-    M.continue $ st & bottomLayerLocation.locationColumnL %~ (subtract 1)+appEvent (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) =+    bottomLayerLocation.locationRowL %= (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) =+    bottomLayerLocation.locationRowL %= (subtract 1)+appEvent (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) =+    bottomLayerLocation.locationColumnL %= (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) =+    bottomLayerLocation.locationColumnL %= (subtract 1) -appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st-appEvent st _ = M.continue st+appEvent (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt+appEvent _ = return ()  arrowAttr :: AttrName-arrowAttr = "attr"+arrowAttr = attrName "attr"  app :: M.App St e Name app =     M.App { M.appDraw = drawUi-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appHandleEvent = appEvent           , M.appAttrMap = const $ attrMap V.defAttr [(arrowAttr, fg V.cyan)]           , M.appChooseCursor = M.neverShowCursor
programs/ListDemo.hs view
@@ -1,9 +1,10 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} module Main where  import Lens.Micro ((^.))+import Lens.Micro.Mtl import Control.Monad (void)+import Control.Monad.State (modify) #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid #endif@@ -48,26 +49,28 @@                               , C.hCenter $ str "Press Esc to exit."                               ] -appEvent :: L.List () Char -> T.BrickEvent () e -> T.EventM () (T.Next (L.List () Char))-appEvent l (T.VtyEvent e) =+appEvent :: T.BrickEvent () e -> T.EventM () (L.List () Char) ()+appEvent (T.VtyEvent e) =     case e of-        V.EvKey (V.KChar '+') [] ->-            let el = nextElement (L.listElements l)-                pos = Vec.length $ l^.(L.listElementsL)-            in M.continue $ L.listInsert pos el l+        V.EvKey (V.KChar '+') [] -> do+            els <- use L.listElementsL+            let el = nextElement els+                pos = Vec.length els+            modify $ L.listInsert pos el -        V.EvKey (V.KChar '-') [] ->-            case l^.(L.listSelectedL) of-                Nothing -> M.continue l-                Just i -> M.continue $ L.listRemove i l+        V.EvKey (V.KChar '-') [] -> do+            sel <- use L.listSelectedL+            case sel of+                Nothing -> return ()+                Just i -> modify $ L.listRemove i -        V.EvKey V.KEsc [] -> M.halt l+        V.EvKey V.KEsc [] -> M.halt -        ev -> M.continue =<< L.handleListEvent ev l+        ev -> L.handleListEvent ev     where       nextElement :: Vec.Vector Char -> Char       nextElement v = fromMaybe '?' $ Vec.find (flip Vec.notElem v) (Vec.fromList ['a' .. 'z'])-appEvent l _ = M.continue l+appEvent _ = return ()  listDrawElement :: (Show a) => Bool -> a -> Widget () listDrawElement sel a =@@ -80,7 +83,7 @@ initialState = L.list () (Vec.fromList ['a','b','c']) 1  customAttr :: A.AttrName-customAttr = L.listSelectedAttr <> "custom"+customAttr = L.listSelectedAttr <> A.attrName "custom"  theMap :: A.AttrMap theMap = A.attrMap V.defAttr@@ -94,7 +97,7 @@     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appAttrMap = const theMap           } 
programs/ListViDemo.hs view
@@ -1,14 +1,15 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} module Main where  import Control.Monad (void)+import Control.Monad.State (modify) import Data.Maybe (fromMaybe) #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid #endif import qualified Graphics.Vty as V import Lens.Micro ((^.))+import Lens.Micro.Mtl  import qualified Brick.AttrMap as A import qualified Brick.Main as M@@ -39,26 +40,28 @@                               , C.hCenter $ str "Press Esc to exit."                               ] -appEvent :: L.List () Char -> T.BrickEvent () e -> T.EventM () (T.Next (L.List () Char))-appEvent l (T.VtyEvent e) =+appEvent :: T.BrickEvent () e -> T.EventM () (L.List () Char) ()+appEvent (T.VtyEvent e) =     case e of-        V.EvKey (V.KChar '+') [] ->-            let el = nextElement (L.listElements l)-                pos = Vec.length $ l^.(L.listElementsL)-            in M.continue $ L.listInsert pos el l+        V.EvKey (V.KChar '+') [] -> do+            els <- use L.listElementsL+            let el = nextElement els+                pos = Vec.length els+            modify $ L.listInsert pos el -        V.EvKey (V.KChar '-') [] ->-            case l^.(L.listSelectedL) of-                Nothing -> M.continue l-                Just i  -> M.continue $ L.listRemove i l+        V.EvKey (V.KChar '-') [] -> do+            sel <- use L.listSelectedL+            case sel of+                Nothing -> return ()+                Just i  -> modify $ L.listRemove i -        V.EvKey V.KEsc [] -> M.halt l+        V.EvKey V.KEsc [] -> M.halt -        ev -> M.continue =<< (L.handleListEventVi L.handleListEvent) ev l+        ev -> L.handleListEventVi L.handleListEvent ev     where       nextElement :: Vec.Vector Char -> Char       nextElement v = fromMaybe '?' $ Vec.find (flip Vec.notElem v) (Vec.fromList ['a' .. 'z'])-appEvent l _ = M.continue l+appEvent _ = return ()  listDrawElement :: (Show a) => Bool -> a -> Widget () listDrawElement sel a =@@ -71,7 +74,7 @@ initialState = L.list () (Vec.fromList ['a','b','c']) 1  customAttr :: A.AttrName-customAttr = L.listSelectedAttr <> "custom"+customAttr = L.listSelectedAttr <> A.attrName "custom"  theMap :: A.AttrMap theMap = A.attrMap V.defAttr@@ -85,7 +88,7 @@     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appAttrMap = const theMap           } 
programs/MouseDemo.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Main where -import Lens.Micro ((^.), (&), (.~))+import Lens.Micro ((^.)) import Lens.Micro.TH (makeLenses)+import Lens.Micro.Mtl import Control.Monad (void)+import Control.Monad.Trans (liftIO) #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid ((<>)) #endif@@ -43,15 +44,15 @@ buttonLayer :: St -> Widget Name buttonLayer st =     C.vCenterLayer $-      C.hCenterLayer (padBottom (T.Pad 1) $ str "Click a button:") <=>+      C.hCenterLayer (padBottom (Pad 1) $ str "Click a button:") <=>       C.hCenterLayer (hBox $ padLeftRight 1 <$> buttons) <=>       C.hCenterLayer (padTopBottom 1 $ str "Or enter text and then click in this editor:") <=>       C.hCenterLayer (vLimit 3 $ hLimit 50 $ E.renderEditor (str . unlines) True (st^.edit))     where         buttons = mkButton <$> buttonData-        buttonData = [ (Button1, "Button 1", "button1")-                     , (Button2, "Button 2", "button2")-                     , (Button3, "Button 3", "button3")+        buttonData = [ (Button1, "Button 1", attrName "button1")+                     , (Button2, "Button 2", attrName "button2")+                     , (Button3, "Button 3", attrName "button3")                      ]         mkButton (name, label, attr) =             let wasClicked = (fst <$> st^.lastReportedClick) == Just name@@ -80,35 +81,41 @@                 Just (name, T.Location l) ->                     "Mouse down at " <> show name <> " @ " <> show l     T.render $ translateBy (T.Location (0, h-1)) $ clickable Info $-               withDefAttr "info" $+               withDefAttr (attrName "info") $                C.hCenter $ str msg -appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)-appEvent st ev@(T.MouseDown n _ _ loc) =-    M.continue =<< T.handleEventLensed (st & lastReportedClick .~ Just (n, loc))-                                       edit-                                       E.handleEditorEvent-                                       ev-appEvent st (T.MouseUp {}) = M.continue $ st & lastReportedClick .~ Nothing-appEvent st (T.VtyEvent (V.EvMouseUp {})) = M.continue $ st & lastReportedClick .~ Nothing-appEvent st (T.VtyEvent (V.EvKey V.KUp [V.MCtrl])) = M.vScrollBy (M.viewportScroll Prose) (-1) >> M.continue st-appEvent st (T.VtyEvent (V.EvKey V.KDown [V.MCtrl])) = M.vScrollBy (M.viewportScroll Prose) 1 >> M.continue st-appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st-appEvent st ev = M.continue =<< T.handleEventLensed st edit E.handleEditorEvent ev+appEvent :: T.BrickEvent Name e -> T.EventM Name St ()+appEvent ev@(T.MouseDown n _ _ loc) = do+    lastReportedClick .= Just (n, loc)+    zoom edit $ E.handleEditorEvent ev+appEvent (T.MouseUp {}) =+    lastReportedClick .= Nothing+appEvent (T.VtyEvent (V.EvMouseUp {})) =+    lastReportedClick .= Nothing+appEvent (T.VtyEvent (V.EvKey V.KUp [V.MCtrl])) =+    M.vScrollBy (M.viewportScroll Prose) (-1)+appEvent (T.VtyEvent (V.EvKey V.KDown [V.MCtrl])) =+    M.vScrollBy (M.viewportScroll Prose) 1+appEvent (T.VtyEvent (V.EvKey V.KEsc [])) =+    M.halt+appEvent ev =+    zoom edit $ E.handleEditorEvent ev  aMap :: AttrMap aMap = attrMap V.defAttr-    [ ("info",      V.white `on` V.magenta)-    , ("button1",   V.white `on` V.cyan)-    , ("button2",   V.white `on` V.green)-    , ("button3",   V.white `on` V.blue)+    [ (attrName "info",      V.white `on` V.magenta)+    , (attrName "button1",   V.white `on` V.cyan)+    , (attrName "button2",   V.white `on` V.green)+    , (attrName "button3",   V.white `on` V.blue)     , (E.editFocusedAttr, V.black `on` V.yellow)     ]  app :: M.App St e Name app =     M.App { M.appDraw = drawUi-          , M.appStartEvent = return+          , M.appStartEvent = do+              vty <- M.getVtyHandle+              liftIO $ V.setMode (V.outputIface vty) V.Mouse True           , M.appHandleEvent = appEvent           , M.appAttrMap = const aMap           , M.appChooseCursor = M.showFirstCursor@@ -116,13 +123,7 @@  main :: IO () main = do-    let buildVty = do-          v <- V.mkVty =<< V.standardIOConfig-          V.setMode (V.outputIface v) V.Mouse True-          return v--    initialVty <- buildVty-    void $ M.customMain initialVty buildVty Nothing app $ St [] Nothing+    void $ M.defaultMain app $ St [] Nothing            (unlines [ "Try clicking on various UI elements."                     , "Observe that the click coordinates identify the"                     , "underlying widget coordinates."
programs/PaddingDemo.hs view
@@ -4,7 +4,6 @@ import Brick.Main (App(..), neverShowCursor, resizeOrQuit, defaultMain) import Brick.Types   ( Widget-  , Padding(..)   ) import Brick.Widgets.Core   ( vBox@@ -17,6 +16,7 @@   , padBottom   , padTopBottom   , padLeftRight+  , Padding(..)   ) import qualified Brick.Widgets.Border as B import qualified Brick.Widgets.Center as C@@ -49,7 +49,7 @@ app =     App { appDraw = const [ui]         , appHandleEvent = resizeOrQuit-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const $ attrMap V.defAttr []         , appChooseCursor = neverShowCursor         }
programs/ProgressBarDemo.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-} module Main where  import Control.Monad (void)@@ -7,6 +8,8 @@ import Data.Monoid #endif import qualified Graphics.Vty as V+import Lens.Micro.Mtl+import Lens.Micro.TH  import qualified Brick.AttrMap as A import qualified Brick.Main as M@@ -23,8 +26,10 @@   ) import Brick.Util (fg, bg, on, clamp) -data MyAppState n = MyAppState { x, y, z :: Float }+data MyAppState n = MyAppState { _x, _y, _z :: Float } +makeLenses ''MyAppState+ drawUI :: MyAppState () -> [Widget ()] drawUI p = [ui]     where@@ -33,16 +38,16 @@              (A.mapAttrNames [ (xDoneAttr, P.progressCompleteAttr)                              , (xToDoAttr, P.progressIncompleteAttr)                              ]-             ) $ bar $ x p+             ) $ bar $ _x p       -- or use individual mapAttrName calls       yBar = updateAttrMap              (A.mapAttrName yDoneAttr P.progressCompleteAttr .               A.mapAttrName yToDoAttr P.progressIncompleteAttr) $-             bar $ y p+             bar $ _y p       -- or use overrideAttr calls       zBar = overrideAttr P.progressCompleteAttr zDoneAttr $              overrideAttr P.progressIncompleteAttr zToDoAttr $-             bar $ z p+             bar $ _z p       lbl c = Just $ show $ fromEnum $ c * 100       bar v = P.progressBar (lbl v) v       ui = (str "X: " <+> xBar) <=>@@ -51,16 +56,16 @@            str "" <=>            str "Hit 'x', 'y', or 'z' to advance progress, or 'q' to quit" -appEvent :: MyAppState () -> T.BrickEvent () e -> T.EventM () (T.Next (MyAppState ()))-appEvent p (T.VtyEvent e) =+appEvent :: T.BrickEvent () e -> T.EventM () (MyAppState ()) ()+appEvent (T.VtyEvent e) =     let valid = clamp (0.0 :: Float) 1.0     in case e of-         V.EvKey (V.KChar 'x') [] -> M.continue $ p { x = valid $ x p + 0.05 }-         V.EvKey (V.KChar 'y') [] -> M.continue $ p { y = valid $ y p + 0.03 }-         V.EvKey (V.KChar 'z') [] -> M.continue $ p { z = valid $ z p + 0.02 }-         V.EvKey (V.KChar 'q') [] -> M.halt p-         _ -> M.continue p-appEvent p _ = M.continue p+         V.EvKey (V.KChar 'x') [] -> x %= valid . (+ 0.05)+         V.EvKey (V.KChar 'y') [] -> y %= valid . (+ 0.03)+         V.EvKey (V.KChar 'z') [] -> z %= valid . (+ 0.02)+         V.EvKey (V.KChar 'q') [] -> M.halt+         _ -> return ()+appEvent _ = return ()  initialState :: MyAppState () initialState = MyAppState 0.25 0.18 0.63@@ -96,7 +101,7 @@     M.App { M.appDraw = drawUI           , M.appChooseCursor = M.showFirstCursor           , M.appHandleEvent = appEvent-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appAttrMap = const theMap           } 
programs/SuspendAndResumeDemo.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE CPP #-} module Main where -import Lens.Micro ((.~), (^.), (&))+import Lens.Micro ((^.)) import Lens.Micro.TH (makeLenses) import Control.Monad (void) #if !(MIN_VERSION_base(4,11,0))@@ -13,7 +13,7 @@  import Brick.Main   ( App(..), neverShowCursor, defaultMain-  , suspendAndResume, halt, continue+  , suspendAndResume, halt   ) import Brick.AttrMap   ( attrMap@@ -21,7 +21,6 @@ import Brick.Types   ( Widget   , EventM-  , Next   , BrickEvent(..)   ) import Brick.Widgets.Core@@ -42,16 +41,16 @@                   , str "(Press Esc to quit or Space to ask for input)"                   ] -appEvent :: St -> BrickEvent () e -> EventM () (Next St)-appEvent st (VtyEvent e) =+appEvent :: BrickEvent () e -> EventM () St ()+appEvent (VtyEvent e) =     case e of-        V.EvKey V.KEsc [] -> halt st+        V.EvKey V.KEsc [] -> halt         V.EvKey (V.KChar ' ') [] -> suspendAndResume $ do             putStrLn "Suspended. Please enter something and press enter to resume:"             s <- getLine-            return $ st & stExternalInput .~ s-        _ -> continue st-appEvent st _ = continue st+            return $ St { _stExternalInput = s }+        _ -> return ()+appEvent _ = return ()  initialState :: St initialState =@@ -63,7 +62,7 @@     App { appDraw = drawUI         , appChooseCursor = neverShowCursor         , appHandleEvent = appEvent-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const $ attrMap V.defAttr []         } 
programs/TailDemo.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} module Main where  #if !(MIN_VERSION_base(4,11,0))@@ -7,6 +8,8 @@ import qualified Data.Text as T import Control.Monad (void) import Control.Concurrent+import Lens.Micro.TH+import Lens.Micro.Mtl import System.Random  import Brick@@ -14,6 +17,14 @@ import Brick.Widgets.Border import qualified Graphics.Vty as V +data AppState =+    AppState { _textAreaHeight :: Int+             , _textAreaWidth :: Int+             , _textAreaContents :: [T.Text]+             }++makeLenses ''AppState+ draw :: AppState -> Widget n draw st =     header st <=> box st@@ -22,18 +33,18 @@ header st =     padBottom (Pad 1) $     hBox [ padRight (Pad 7) $-           (str $ "Max width: " <> show (textAreaWidth st)) <=>+           (str $ "Max width: " <> show (_textAreaWidth st)) <=>            (str "Left(-)/Right(+)")-         , (str $ "Max height: " <> show (textAreaHeight st)) <=>+         , (str $ "Max height: " <> show (_textAreaHeight st)) <=>            (str "Down(-)/Up(+)")          ]  box :: AppState -> Widget n box st =     border $-    hLimit (textAreaWidth st) $-    vLimit (textAreaHeight st) $-    (renderBottomUp (txtWrap <$> textAreaContents st))+    hLimit (_textAreaWidth st) $+    vLimit (_textAreaHeight st) $+    (renderBottomUp (txtWrap <$> _textAreaContents st))  -- | Given a list of widgets, draw them bottom-up in a vertical -- arrangement, i.e., the first widget in this list will appear at the@@ -69,45 +80,39 @@     , "cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."     ] -handleEvent :: AppState -> BrickEvent n CustomEvent -> EventM n (Next AppState)-handleEvent s (AppEvent (NewLine l)) =-    continue $ s { textAreaContents = l : textAreaContents s }-handleEvent s (VtyEvent (V.EvKey V.KUp [])) =-    continue $ s { textAreaHeight = textAreaHeight s + 1 }-handleEvent s (VtyEvent (V.EvKey V.KDown [])) =-    continue $ s { textAreaHeight = max 0 $ textAreaHeight s - 1 }-handleEvent s (VtyEvent (V.EvKey V.KRight [])) =-    continue $ s { textAreaWidth = textAreaWidth s + 1 }-handleEvent s (VtyEvent (V.EvKey V.KLeft [])) =-    continue $ s { textAreaWidth = max 0 $ textAreaWidth s - 1 }-handleEvent s (VtyEvent (V.EvKey V.KEsc [])) =-    halt s-handleEvent s _ =-    continue s+handleEvent :: BrickEvent n CustomEvent -> EventM n AppState ()+handleEvent (AppEvent (NewLine l)) =+    textAreaContents %= (l :)+handleEvent (VtyEvent (V.EvKey V.KUp [])) =+    textAreaHeight %= (+ 1)+handleEvent (VtyEvent (V.EvKey V.KDown [])) =+    textAreaHeight %= max 0 . subtract 1+handleEvent (VtyEvent (V.EvKey V.KRight [])) =+    textAreaWidth %= (+ 1)+handleEvent (VtyEvent (V.EvKey V.KLeft [])) =+    textAreaWidth %= max 0 . subtract 1+handleEvent (VtyEvent (V.EvKey V.KEsc [])) =+    halt+handleEvent _ =+    return ()  data CustomEvent =     NewLine T.Text -data AppState =-    AppState { textAreaHeight :: Int-             , textAreaWidth :: Int-             , textAreaContents :: [T.Text]-             }- app :: App AppState CustomEvent () app =     App { appDraw = (:[]) . draw         , appChooseCursor = neverShowCursor         , appHandleEvent = handleEvent         , appAttrMap = const $ attrMap V.defAttr []-        , appStartEvent = return+        , appStartEvent = return ()         }  initialState :: AppState initialState =-    AppState { textAreaHeight = 20-             , textAreaWidth = 40-             , textAreaContents = []+    AppState { _textAreaHeight = 20+             , _textAreaWidth = 40+             , _textAreaContents = []              }  -- | Run forever, generating new lines of text for the application
programs/ThemeDemo.hs view
@@ -1,7 +1,7 @@-{-# LANGUAGE OverloadedStrings #-} module Main where  import Control.Monad (void)+import Control.Monad.State (put) import Graphics.Vty   ( white, blue, green, yellow, black, magenta   , Event(EvKey)@@ -18,7 +18,6 @@   ( Widget   , BrickEvent(VtyEvent)   , EventM-  , Next   ) import Brick.Widgets.Center   ( hCenter@@ -32,7 +31,7 @@   , withDefAttr   ) import Brick.Util (on, fg)-import Brick.AttrMap (AttrName)+import Brick.AttrMap (AttrName, attrName)  ui :: Widget n ui =@@ -44,7 +43,7 @@          ]  keybindingAttr :: AttrName-keybindingAttr = "keybinding"+keybindingAttr = attrName "keybinding"  theme1 :: Theme theme1 =@@ -58,18 +57,18 @@              [ (keybindingAttr, fg yellow)              ] -appEvent :: Int -> BrickEvent () e -> EventM () (Next Int)-appEvent _ (VtyEvent (EvKey (KChar '1') [])) = continue 1-appEvent _ (VtyEvent (EvKey (KChar '2') [])) = continue 2-appEvent s (VtyEvent (EvKey (KChar 'q') [])) = halt s-appEvent s (VtyEvent (EvKey KEsc [])) = halt s-appEvent s _ = continue s+appEvent :: BrickEvent () e -> EventM () Int ()+appEvent (VtyEvent (EvKey (KChar '1') [])) = put 1+appEvent (VtyEvent (EvKey (KChar '2') [])) = put 2+appEvent (VtyEvent (EvKey (KChar 'q') [])) = halt+appEvent (VtyEvent (EvKey KEsc [])) = halt+appEvent _ = return ()  app :: App Int e () app =     App { appDraw = const [ui]         , appHandleEvent = appEvent-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = \s ->             -- Note that in practice this is not ideal: we don't want             -- to build an attribute from a theme every time this is
programs/ViewportScrollDemo.hs view
@@ -59,22 +59,22 @@ vp3Scroll :: M.ViewportScroll Name vp3Scroll = M.viewportScroll VP3 -appEvent :: () -> T.BrickEvent Name e -> T.EventM Name (T.Next ())-appEvent _ (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = M.vScrollBy vp3Scroll 1 >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = M.vScrollBy vp3Scroll (-1) >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.hScrollBy vp3Scroll 1 >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = M.hScrollBy vp3Scroll (-1) >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KDown []))  = M.vScrollBy vp1Scroll 1 >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KUp []))    = M.vScrollBy vp1Scroll (-1) >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KRight [])) = M.hScrollBy vp2Scroll 1 >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KLeft []))  = M.hScrollBy vp2Scroll (-1) >> M.continue ()-appEvent _ (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt ()-appEvent _ _ = M.continue ()+appEvent :: T.BrickEvent Name e -> T.EventM Name () ()+appEvent (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = M.vScrollBy vp3Scroll 1+appEvent (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = M.vScrollBy vp3Scroll (-1)+appEvent (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.hScrollBy vp3Scroll 1+appEvent (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = M.hScrollBy vp3Scroll (-1)+appEvent (T.VtyEvent (V.EvKey V.KDown []))  = M.vScrollBy vp1Scroll 1+appEvent (T.VtyEvent (V.EvKey V.KUp []))    = M.vScrollBy vp1Scroll (-1)+appEvent (T.VtyEvent (V.EvKey V.KRight [])) = M.hScrollBy vp2Scroll 1+appEvent (T.VtyEvent (V.EvKey V.KLeft []))  = M.hScrollBy vp2Scroll (-1)+appEvent (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt+appEvent _ = return ()  app :: M.App () e Name app =     M.App { M.appDraw = drawUi-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appHandleEvent = appEvent           , M.appAttrMap = const $ attrMap V.defAttr []           , M.appChooseCursor = M.neverShowCursor
programs/ViewportScrollbarsDemo.hs view
@@ -1,7 +1,10 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} module Main where +import Lens.Micro.TH+import Lens.Micro.Mtl import Control.Monad (void) #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid ((<>))@@ -26,7 +29,8 @@   , attrMap   ) import Brick.Widgets.Core-  ( hLimit+  ( Padding(..)+  , hLimit   , vLimit   , padRight   , hBox@@ -57,17 +61,19 @@ data Name = VP1 | VP2 | SBClick T.ClickableScrollbarElement Name           deriving (Ord, Show, Eq) -data St = St { lastClickedElement :: Maybe (T.ClickableScrollbarElement, Name) }+data St = St { _lastClickedElement :: Maybe (T.ClickableScrollbarElement, Name) } +makeLenses ''St+ drawUi :: St -> [Widget Name] drawUi st = [ui]     where         ui = C.center $ hLimit 70 $ vLimit 21 $              (vBox [ pair                    , C.hCenter (str "Last clicked scroll bar element:")-                   , str $ show $ lastClickedElement st+                   , str $ show $ _lastClickedElement st                    ])-        pair = hBox [ padRight (T.Pad 5) $+        pair = hBox [ padRight (Pad 5) $                       B.border $                       withClickableHScrollBars SBClick $                       withHScrollBars OnBottom $@@ -92,13 +98,13 @@ vp2Scroll :: M.ViewportScroll Name vp2Scroll = M.viewportScroll VP2 -appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)-appEvent st (T.VtyEvent (V.EvKey V.KRight []))  = M.hScrollBy vp1Scroll 1 >> M.continue st-appEvent st (T.VtyEvent (V.EvKey V.KLeft []))   = M.hScrollBy vp1Scroll (-1) >> M.continue st-appEvent st (T.VtyEvent (V.EvKey V.KDown []))   = M.vScrollBy vp2Scroll 1 >> M.continue st-appEvent st (T.VtyEvent (V.EvKey V.KUp []))     = M.vScrollBy vp2Scroll (-1) >> M.continue st-appEvent st (T.VtyEvent (V.EvKey V.KEsc []))    = M.halt st-appEvent st (T.MouseDown (SBClick el n) _ _ _) = do+appEvent :: T.BrickEvent Name e -> T.EventM Name St ()+appEvent (T.VtyEvent (V.EvKey V.KRight []))  = M.hScrollBy vp1Scroll 1+appEvent (T.VtyEvent (V.EvKey V.KLeft []))   = M.hScrollBy vp1Scroll (-1)+appEvent (T.VtyEvent (V.EvKey V.KDown []))   = M.vScrollBy vp2Scroll 1+appEvent (T.VtyEvent (V.EvKey V.KUp []))     = M.vScrollBy vp2Scroll (-1)+appEvent (T.VtyEvent (V.EvKey V.KEsc []))    = M.halt+appEvent (T.MouseDown (SBClick el n) _ _ _) = do     case n of         VP1 -> do             let vp = M.viewportScroll VP1@@ -119,8 +125,8 @@         _ ->             return () -    M.continue $ st { lastClickedElement = Just (el, n) }-appEvent st _ = M.continue st+    lastClickedElement .= Just (el, n)+appEvent _ = return ()  theme :: AttrMap theme =@@ -132,7 +138,7 @@ app :: M.App St e Name app =     M.App { M.appDraw = drawUi-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appHandleEvent = appEvent           , M.appAttrMap = const theme           , M.appChooseCursor = M.neverShowCursor
programs/VisibilityDemo.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} module Main where@@ -6,6 +5,7 @@ import Control.Monad (void) import Lens.Micro import Lens.Micro.TH+import Lens.Micro.Mtl #if !(MIN_VERSION_base(4,11,0)) import Data.Monoid #endif@@ -15,7 +15,7 @@ import qualified Brick.Main as M import qualified Brick.Widgets.Center as C import qualified Brick.Widgets.Border as B-import Brick.AttrMap (AttrMap, AttrName, attrMap)+import Brick.AttrMap (AttrMap, AttrName, attrMap, attrName) import Brick.Util (on) import Brick.Types   ( Widget@@ -55,7 +55,7 @@ vp3Size = (25, 25)  selectedAttr :: AttrName-selectedAttr = "selected"+selectedAttr = attrName "selected"  drawUi :: St -> [Widget Name] drawUi st = [ui]@@ -102,17 +102,17 @@ vp3Scroll :: M.ViewportScroll Name vp3Scroll = M.viewportScroll VP3 -appEvent :: St -> T.BrickEvent Name e -> T.EventM Name (T.Next St)-appEvent st (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = M.continue $ st & vp3Index._1 %~ min (vp3Size^._1) . (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = M.continue $ st & vp3Index._1 %~ max 1 . subtract 1-appEvent st (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = M.continue $ st & vp3Index._2 %~ min (vp3Size^._1) . (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = M.continue $ st & vp3Index._2 %~ max 1 .  subtract 1-appEvent st (T.VtyEvent (V.EvKey V.KDown []))         = M.continue $ st & vp1Index %~ min vp1Size . (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KUp []))           = M.continue $ st & vp1Index %~ max 1 . subtract 1-appEvent st (T.VtyEvent (V.EvKey V.KRight []))        = M.continue $ st & vp2Index %~ min vp2Size . (+ 1)-appEvent st (T.VtyEvent (V.EvKey V.KLeft []))         = M.continue $ st & vp2Index %~ max 1 . subtract 1-appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st-appEvent st _ = M.continue st+appEvent :: T.BrickEvent Name e -> T.EventM Name St ()+appEvent (T.VtyEvent (V.EvKey V.KDown  [V.MCtrl])) = vp3Index._1 %= min (vp3Size^._1) . (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KUp    [V.MCtrl])) = vp3Index._1 %= max 1 . subtract 1+appEvent (T.VtyEvent (V.EvKey V.KRight [V.MCtrl])) = vp3Index._2 %= min (vp3Size^._1) . (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KLeft  [V.MCtrl])) = vp3Index._2 %= max 1 .  subtract 1+appEvent (T.VtyEvent (V.EvKey V.KDown []))         = vp1Index %= min vp1Size . (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KUp []))           = vp1Index %= max 1 . subtract 1+appEvent (T.VtyEvent (V.EvKey V.KRight []))        = vp2Index %= min vp2Size . (+ 1)+appEvent (T.VtyEvent (V.EvKey V.KLeft []))         = vp2Index %= max 1 . subtract 1+appEvent (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt+appEvent _ = return ()  theMap :: AttrMap theMap = attrMap V.defAttr@@ -122,7 +122,7 @@ app :: M.App St e Name app =     M.App { M.appDraw = drawUi-          , M.appStartEvent = return+          , M.appStartEvent = return ()           , M.appHandleEvent = appEvent           , M.appAttrMap = const theMap           , M.appChooseCursor = M.neverShowCursor
+ programs/custom_keys.ini view
@@ -0,0 +1,4 @@+[keybindings]+quit = x+increment = i+decrement = d
src/Brick/AttrMap.hs view
@@ -2,9 +2,6 @@ {-# LANGUAGE DeriveGeneric #-} -- | This module provides types and functions for managing an attribute -- map which maps attribute names ('AttrName') to attributes ('Attr').--- This module is designed to be used with the 'OverloadedStrings'--- language extension to permit easy construction of 'AttrName' values--- and you should also use 'mappend' ('<>') to combine names. -- -- Attribute maps work by mapping hierarchical attribute names to -- attributes and inheriting parent names' attributes when child names@@ -52,7 +49,6 @@ import qualified Data.Map as M import Data.Maybe (catMaybes) import Data.List (inits)-import Data.String (IsString(..)) import GHC.Generics (Generic)  import Graphics.Vty (Attr(..), MaybeDefault(..), Style)@@ -65,9 +61,9 @@ -- example: -- -- @--- "window" <> "border"--- "window" <> "title"--- "header" <> "clock" <> "seconds"+-- attrName "window" <> attrName "border"+-- attrName "window" <> attrName "title"+-- attrName "header" <> attrName "clock" <> attrName "seconds" -- @ data AttrName = AttrName [String]               deriving (Show, Read, Eq, Ord, Generic, NFData)@@ -78,9 +74,6 @@ instance Monoid AttrName where     mempty = AttrName []     mappend = (Sem.<>)--instance IsString AttrName where-    fromString = AttrName . (:[])  -- | An attribute map which maps 'AttrName' values to 'Attr' values. data AttrMap = AttrMap Attr (M.Map AttrName Attr)
src/Brick/Forms.hs view
@@ -3,6 +3,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-} -- | Note - this API is designed to support a narrow (but common!) set -- of use cases. If you find that you need more customization than this -- offers, then you will need to consider building your own layout and@@ -103,6 +105,7 @@ import Text.Read (readMaybe)  import Lens.Micro+import Lens.Micro.Mtl  -- | A form field. This represents an interactive input field in the -- form. Its user input is validated and thus converted into a type of@@ -138,10 +141,8 @@               -- ^ A function to render this form field. Parameters are               -- whether the field is currently focused, followed by the               -- field state.-              , formFieldHandleEvent :: BrickEvent n e -> b -> EventM n b-              -- ^ An event handler for this field. This receives the-              -- event and the field state and returns a new field-              -- state.+              , formFieldHandleEvent :: BrickEvent n e -> EventM n b ()+              -- ^ An event handler for this field.               }  -- | A form field state accompanied by the fields that manipulate that@@ -216,6 +217,8 @@          -- ^ Concatenation function for this form's field renderings.          } +suffixLenses ''Form+ -- | Compose a new rendering augmentation function with the one in the -- form field collection. For example, we might put a label on the left -- side of a form field:@@ -328,9 +331,9 @@ checkboxCustomField lb check rb stLens name label initialState =     let initVal = initialState ^. stLens -        handleEvent (MouseDown n _ _ _) s | n == name = return $ not s-        handleEvent (VtyEvent (EvKey (KChar ' ') [])) s = return $ not s-        handleEvent _ s = return s+        handleEvent (MouseDown n _ _ _) | n == name = modify not+        handleEvent (VtyEvent (EvKey (KChar ' ') [])) = modify not+        handleEvent _ = return ()      in FormFieldState { formFieldState = initVal                       , formFields = [ FormField name Just True@@ -385,8 +388,8 @@                                Just e -> listMoveToElement e l                setList s l = s & stLens .~ (snd <$> listSelectedElement l) -        handleEvent (VtyEvent e) s = handleListEvent e s-        handleEvent _ s = return s+        handleEvent (VtyEvent e) = handleListEvent e+        handleEvent _ = return ()      in FormFieldState { formFieldState = initVal                       , formFields = [ FormField name Just True@@ -447,12 +450,12 @@                 [(val, _, _)] -> Just val                 _ -> Nothing -        handleEvent _ (MouseDown n _ _ _) s =+        handleEvent _ (MouseDown n _ _ _) =             case lookupOptionValue n of-                Nothing -> return s-                Just v -> return v-        handleEvent new (VtyEvent (EvKey (KChar ' ') [])) _ = return new-        handleEvent _ _ s = return s+                Nothing -> return ()+                Just v -> put v+        handleEvent new (VtyEvent (EvKey (KChar ' ') [])) = put new+        handleEvent _ _ = return ()          optionFields = mkOptionField <$> options         mkOptionField (val, name, label) =@@ -479,10 +482,11 @@         csr = if foc then putCursor name (Location (1,0)) else id     in clickable name $        addAttr $ csr $-       hBox [ txt $ T.singleton lb-            , txt $ if isSet then T.singleton check else " "-            , txt $ T.singleton rb <> " " <> label-            ]+       txt $ T.concat $+       [ T.singleton lb+       , if isSet then T.singleton check else " "+       , T.singleton rb <> " " <> label+       ]  -- | A form field for using an editor to edit the text representation of -- a value. The other editing fields in this module are special cases of@@ -638,15 +642,15 @@  -- | The namespace for the other form attributes. formAttr :: AttrName-formAttr = "brickForm"+formAttr = attrName "brickForm"  -- | The attribute for form input fields with invalid values. invalidFormInputAttr :: AttrName-invalidFormInputAttr = formAttr <> "invalidInput"+invalidFormInputAttr = formAttr <> attrName "invalidInput"  -- | The attribute for form input fields that have the focus. focusedFormInputAttr :: AttrName-focusedFormInputAttr = formAttr <> "focusedInput"+focusedFormInputAttr = formAttr <> attrName "focusedInput"  -- | Returns whether all form fields in the form currently have valid -- values according to the fields' validation functions. This is useful@@ -729,8 +733,8 @@             in maybeInvalid (renderField foc st) : renderFields fs     in helper $ concatFields $ renderFields fields --- | Dispatch an event to the appropriate form field and return a new--- form. This handles the following events in this order:+-- | Dispatch an event to the currently focused form field. This handles+-- the following events in this order: -- -- * On @Tab@ keypresses, this changes the focus to the next field in --   the form.@@ -755,44 +759,31 @@ -- lens. The external validation flag is ignored during this step to -- ensure that external validators have a chance to get the intermediate -- validated value.-handleFormEvent :: (Eq n) => BrickEvent n e -> Form s e n -> EventM n (Form s e n)-handleFormEvent (VtyEvent (EvKey (KChar '\t') [])) f =-    return $ f { formFocus = focusNext $ formFocus f }-handleFormEvent (VtyEvent (EvKey KBackTab [])) f =-    return $ f { formFocus = focusPrev $ formFocus f }-handleFormEvent e@(MouseDown n _ _ _) f =-    handleFormFieldEvent n e $ f { formFocus = focusSetCurrent n (formFocus f) }-handleFormEvent e@(MouseUp n _ _) f =-    handleFormFieldEvent n e $ f { formFocus = focusSetCurrent n (formFocus f) }-handleFormEvent e@(VtyEvent (EvKey KUp [])) f =-    case focusGetCurrent (formFocus f) of-        Nothing -> return f-        Just n  ->-            case getFocusGrouping f n of-                Nothing -> forwardToCurrent e f-                Just grp -> return $ f { formFocus = focusSetCurrent (entryBefore grp n) (formFocus f) }-handleFormEvent e@(VtyEvent (EvKey KDown [])) f =-    case focusGetCurrent (formFocus f) of-        Nothing -> return f-        Just n  ->-            case getFocusGrouping f n of-                Nothing -> forwardToCurrent e f-                Just grp -> return $ f { formFocus = focusSetCurrent (entryAfter grp n) (formFocus f) }-handleFormEvent e@(VtyEvent (EvKey KLeft [])) f =-    case focusGetCurrent (formFocus f) of-        Nothing -> return f-        Just n  ->-            case getFocusGrouping f n of-                Nothing -> forwardToCurrent e f-                Just grp -> return $ f { formFocus = focusSetCurrent (entryBefore grp n) (formFocus f) }-handleFormEvent e@(VtyEvent (EvKey KRight [])) f =-    case focusGetCurrent (formFocus f) of-        Nothing -> return f-        Just n  ->-            case getFocusGrouping f n of-                Nothing -> forwardToCurrent e f-                Just grp -> return $ f { formFocus = focusSetCurrent (entryAfter grp n) (formFocus f) }-handleFormEvent e f = forwardToCurrent e f+handleFormEvent :: (Eq n) => BrickEvent n e -> EventM n (Form s e n) ()+handleFormEvent (VtyEvent (EvKey (KChar '\t') [])) =+    formFocusL %= focusNext+handleFormEvent (VtyEvent (EvKey KBackTab [])) =+    formFocusL %= focusPrev+handleFormEvent e@(MouseDown n _ _ _) = do+    formFocusL %= focusSetCurrent n+    handleFormFieldEvent e n+handleFormEvent e@(MouseUp n _ _) = do+    formFocusL %= focusSetCurrent n+    handleFormFieldEvent e n+handleFormEvent e@(VtyEvent (EvKey KUp [])) =+    withFocusAndGrouping e $ \n grp ->+        formFocusL %= focusSetCurrent (entryBefore grp n)+handleFormEvent e@(VtyEvent (EvKey KDown [])) =+    withFocusAndGrouping e $ \n grp ->+        formFocusL %= focusSetCurrent (entryAfter grp n)+handleFormEvent e@(VtyEvent (EvKey KLeft [])) =+    withFocusAndGrouping e $ \n grp ->+        formFocusL %= focusSetCurrent (entryBefore grp n)+handleFormEvent e@(VtyEvent (EvKey KRight [])) =+    withFocusAndGrouping e $ \n grp ->+        formFocusL %= focusSetCurrent (entryAfter grp n)+handleFormEvent e =+    forwardToCurrent e  getFocusGrouping :: (Eq n) => Form s e n -> n -> Maybe [n] getFocusGrouping f n = findGroup (formFieldStates f)@@ -816,16 +807,31 @@         i' = if i == 0 then length as - 1 else i - 1     in as !! i' -forwardToCurrent :: (Eq n) => BrickEvent n e -> Form s e n -> EventM n (Form s e n)-forwardToCurrent e f =-    case focusGetCurrent (formFocus f) of-        Nothing -> return f-        Just n  -> handleFormFieldEvent n e f+withFocusAndGrouping :: (Eq n) => BrickEvent n e -> (n -> [n] -> EventM n (Form s e n) ()) -> EventM n (Form s e n) ()+withFocusAndGrouping e act = do+    foc <- gets formFocus+    case focusGetCurrent foc of+        Nothing -> return ()+        Just n -> do+            f <- get+            case getFocusGrouping f n of+                Nothing -> forwardToCurrent e+                Just grp -> act n grp -handleFormFieldEvent :: (Eq n) => n -> BrickEvent n e -> Form s e n -> EventM n (Form s e n)-handleFormFieldEvent n ev f = findFieldState [] (formFieldStates f)-    where-        findFieldState _ [] = return f+withFocus :: (n -> EventM n (Form s e n) ()) -> EventM n (Form s e n) ()+withFocus act = do+    foc <- gets formFocus+    case focusGetCurrent foc of+        Nothing -> return ()+        Just n -> act n++forwardToCurrent :: (Eq n) => BrickEvent n e -> EventM n (Form s e n) ()+forwardToCurrent =+    withFocus . handleFormFieldEvent++handleFormFieldEvent :: (Eq n) => BrickEvent n e -> n -> EventM n (Form s e n) ()+handleFormFieldEvent ev n = do+    let findFieldState _ [] = return ()         findFieldState prev (e:es) =             case e of                 FormFieldState st stLens upd fields helper concatAll -> do@@ -833,7 +839,7 @@                         findField (field:rest) =                             case field of                                 FormField n' validate _ _ handleFunc | n == n' -> do-                                    nextSt <- handleFunc ev st+                                    (nextSt, ()) <- nestEventM st (handleFunc ev)                                     -- If the new state validates, go ahead and update                                     -- the form state with it.                                     case validate nextSt of@@ -844,10 +850,12 @@                     result <- findField fields                     case result of                         Nothing -> findFieldState (prev <> [e]) es-                        Just (newSt, maybeSt) ->+                        Just (newSt, maybeSt) -> do                             let newFieldState = FormFieldState newSt stLens upd fields helper concatAll-                            in return $ f { formFieldStates = prev <> [newFieldState] <> es-                                          , formState = case maybeSt of-                                              Nothing -> formState f-                                              Just s  -> formState f & stLens .~ s-                                          }+                            formFieldStatesL .= prev <> [newFieldState] <> es+                            case maybeSt of+                              Nothing -> return ()+                              Just s  -> formStateL.stLens .= s++    states <- gets formFieldStates+    findFieldState [] states
+ src/Brick/Keybindings.hs view
@@ -0,0 +1,20 @@+-- | The re-exporting catch-all module for the customizable keybindings+-- API.+--+-- To get started using this API, see the documentation in+-- @KeyDispatcher@ as well as the User Guide section on customizable+-- keybindings.+module Brick.Keybindings+  ( module Brick.Keybindings.KeyEvents+  , module Brick.Keybindings.KeyConfig+  , module Brick.Keybindings.KeyDispatcher+  , module Brick.Keybindings.Pretty+  , module Brick.Keybindings.Parse+  )+where++import Brick.Keybindings.KeyEvents+import Brick.Keybindings.KeyConfig+import Brick.Keybindings.KeyDispatcher+import Brick.Keybindings.Pretty+import Brick.Keybindings.Parse
+ src/Brick/Keybindings/KeyConfig.hs view
@@ -0,0 +1,203 @@+-- | This module provides 'KeyConfig' and associated functions. A+-- 'KeyConfig' is the basis for the custom keybinding system in this+-- library.+--+-- To get started, see 'newKeyConfig'. Once a 'KeyConfig' has been+-- constructed, see 'Brick.Keybindings.KeyHandlerMap.keyDispatcher'.+module Brick.Keybindings.KeyConfig+  ( KeyConfig+  , newKeyConfig+  , BindingState(..)++  -- * Specifying bindings+  , Binding(..)+  , ToBinding(..)+  , binding+  , fn+  , meta+  , ctrl+  , shift++  -- * Querying KeyConfigs+  , firstDefaultBinding+  , firstActiveBinding+  , allDefaultBindings+  , allActiveBindings++  -- * Misc+  , keyConfigEvents+  , lookupKeyConfigBindings+  )+where++import Data.List (nub)+import qualified Data.Map.Strict as M+import qualified Data.Set as S+import Data.Maybe (fromMaybe, listToMaybe)+import qualified Graphics.Vty as Vty++import Brick.Keybindings.KeyEvents++-- | A key binding.+--+-- The easiest way to express 'Binding's is to use the helper functions+-- in this module that work with instances of 'ToBinding', e.g.+--+-- @+-- let ctrlB = 'ctrl' \'b\'+--     shiftX = 'shift' \'x\'+--     ctrlMetaK = 'ctrl' $ 'meta' \'k\'+--     -- Or with Vty keys directly:+--     ctrlDown = 'ctrl' 'Graphics.Vty.Input.KDown'+-- @+data Binding =+    Binding { kbKey :: Vty.Key+            -- ^ The key itself.+            , kbMods :: S.Set Vty.Modifier+            -- ^ The set of modifiers.+            } deriving (Eq, Show, Ord)++-- | Construct a 'Binding'. Modifier order is ignored.+binding :: Vty.Key -> [Vty.Modifier] -> Binding+binding k mods =+    Binding { kbKey = k+            , kbMods = S.fromList mods+            }++-- | An explicit configuration of key bindings for a key event.+data BindingState =+    BindingList [Binding]+    -- ^ Bind the event to the specified list of bindings.+    | Unbound+    -- ^ Disable all bindings for the event, including default bindings.+    deriving (Show, Eq, Ord)++-- | A configuration of custom key bindings. A 'KeyConfig'+-- stores everything needed to resolve a key event into one or+-- more key bindings. Make a 'KeyConfig' with 'newKeyConfig',+-- then use it to dispatch to 'KeyEventHandler's with+-- 'Brick.Keybindings.KeyHandlerMap.keyDispatcher'.+--+-- Make a new 'KeyConfig' with 'newKeyConfig'.+--+-- A 'KeyConfig' stores:+--+-- * A collection of named key events, mapping the event type @k@ to+--   'Text' labels.+-- * For each event @k@, optionally store a list of default key bindings+--   for that event.+-- * An optional customized binding list for each event, setting the+--   event to either 'Unbound' or providing explicit overridden bindings+--   with 'BindingList'.+data KeyConfig k =+    KeyConfig { keyConfigBindingMap :: M.Map k BindingState+              -- ^ The map of custom bindings for events with custom+              -- bindings.+              , keyConfigEvents :: KeyEvents k+              -- ^ The base mapping of events and their names that is+              -- used in this configuration.+              , keyConfigDefaultBindings :: M.Map k [Binding]+              -- ^ A mapping of events and their default key bindings,+              -- if any.+              }+              deriving (Show, Eq)++-- | Build a 'KeyConfig' with the specified 'KeyEvents' event-to-name+-- mapping, list of default bindings by event, and list of custom+-- bindings by event.+newKeyConfig :: (Ord k)+             => KeyEvents k+             -- ^ The base mapping of key events and names to use.+             -> [(k, [Binding])]+             -- ^ Default bindings by key event, such as from a+             -- configuration file or embedded code. Optional on a+             -- per-event basis.+             -> [(k, BindingState)]+             -- ^ Custom bindings by key event, such as from a+             -- configuration file. Explicitly setting an event to+             -- 'Unbound' here has the effect of disabling its default+             -- bindings. Optional on a per-event basis.+             -> KeyConfig k+newKeyConfig evs defaults bindings =+    KeyConfig { keyConfigBindingMap = M.fromList bindings+              , keyConfigEvents = evs+              , keyConfigDefaultBindings = M.fromList defaults+              }++-- | Look up the binding state for the specified event. This returns+-- 'Nothing' when the event has no explicitly configured custom+-- 'BindingState'.+lookupKeyConfigBindings :: (Ord k) => KeyConfig k -> k -> Maybe BindingState+lookupKeyConfigBindings kc e = M.lookup e $ keyConfigBindingMap kc++-- | A convenience function to return the first result of+-- 'allDefaultBindings', if any.+firstDefaultBinding :: (Show k, Ord k) => KeyConfig k -> k -> Maybe Binding+firstDefaultBinding kc ev = do+    bs <- M.lookup ev (keyConfigDefaultBindings kc)+    case bs of+        (b:_) -> Just b+        _ -> Nothing++-- | Returns the list of default bindings for the specified event,+-- irrespective of whether the event has been explicitly configured with+-- other bindings or set to 'Unbound'.+allDefaultBindings :: (Ord k) => KeyConfig k -> k -> [Binding]+allDefaultBindings kc ev =+    fromMaybe [] $ M.lookup ev (keyConfigDefaultBindings kc)++-- | A convenience function to return the first result of+-- 'allActiveBindings', if any.+firstActiveBinding :: (Show k, Ord k) => KeyConfig k -> k -> Maybe Binding+firstActiveBinding kc ev = listToMaybe $ allActiveBindings kc ev++-- | Return all active key bindings for the specified event. This+-- returns customized bindings if any have been set in the 'KeyConfig',+-- no bindings if the event has been explicitly set to 'Unbound', or the+-- default bindings if the event is absent from the customized bindings.+allActiveBindings :: (Show k, Ord k) => KeyConfig k -> k -> [Binding]+allActiveBindings kc ev = nub foundBindings+    where+        defaultBindings = allDefaultBindings kc ev+        foundBindings = case lookupKeyConfigBindings kc ev of+            Just (BindingList bs) -> bs+            Just Unbound -> []+            Nothing -> defaultBindings++-- | The class of types that can form the basis of 'Binding's.+--+-- This is provided to make it easy to write and modify bindings in less+-- verbose ways.+class ToBinding a where+    -- | Binding constructor.+    bind :: a -> Binding++instance ToBinding Vty.Key where+    bind k = Binding { kbMods = mempty, kbKey = k }++instance ToBinding Char where+    bind = bind . Vty.KChar++instance ToBinding Binding where+    bind = id++addModifier :: (ToBinding a) => Vty.Modifier -> a -> Binding+addModifier m val =+    let b = bind val+    in b { kbMods = S.insert m (kbMods b) }++-- | Add Meta to a binding.+meta :: (ToBinding a) => a -> Binding+meta = addModifier Vty.MMeta++-- | Add Ctrl to a binding.+ctrl :: (ToBinding a) => a -> Binding+ctrl = addModifier Vty.MCtrl++-- | Add Shift to a binding.+shift :: (ToBinding a) => a -> Binding+shift = addModifier Vty.MShift++-- | Function key binding.+fn :: Int -> Binding+fn = bind . Vty.KFun
+ src/Brick/Keybindings/KeyDispatcher.hs view
@@ -0,0 +1,220 @@+-- | This is the entry point into the keybinding infrastructure in+-- this library. Note that usage of this API is not required to create+-- working Brick applications; this API is provided for applications+-- that need to support custom keybindings that are less tightly coupled+-- to application behavior.+--+-- The workflow for this API is as follows:+--+-- * Create a data type @k@ with a constructor for each abstract+--   application event that you want to trigger with an input key.+-- * To each event @k@, assign a unique user-readable name (such as a+--   name you could imagine using in a configuration file to refer to+--   the event) and a list of default key bindings.+-- * Use the resulting data to create a 'KeyConfig' with 'newKeyConfig'.+--   If desired, provide custom keybindings to 'newKeyConfig' from+--   within the program or load them from an INI file with routines like+--   'Brick.Keybindings.Parse.keybindingsFromFile'.+-- * Implement application event handlers that will be run in response+--   to either specific hard-coded keys or events @k@, both in some+--   monad @m@ of your choosing, using constructors 'onKey' and+--   'onEvent'.+-- * Use the created 'KeyConfig' and handlers to create a+--   'KeyDispatcher' with 'keyDispatcher'.+-- * As user input events arrive, dispatch them to the appropriate+--   handler using 'handleKey'.+module Brick.Keybindings.KeyDispatcher+  ( -- * Key dispatching+    KeyDispatcher+  , keyDispatcher+  , handleKey++  -- * Building handlers+  , onEvent+  , onKey++  -- * Handlers and triggers+  , Handler(..)+  , KeyHandler(..)+  , KeyEventHandler(..)+  , EventTrigger(..)++  -- * Misc+  , keyDispatcherToList+  , lookupVtyEvent+  )+where++import qualified Data.Map.Strict as M+import qualified Data.Set as S+import qualified Data.Text as T+import qualified Graphics.Vty as Vty++import Brick.Keybindings.KeyConfig++-- | A set of handlers for specific keys whose handlers run in the monad+-- @m@.+newtype KeyDispatcher k m = KeyDispatcher (M.Map Binding (KeyHandler k m))++-- | An 'Handler' represents a handler implementation to be invoked in+-- response to some event that runs in the monad @m@.+--+-- In general, you should never need to make one of these manually.+-- Instead, use 'onEvent' and 'onKey'. This type's internals are exposed+-- for easy inspection, not construction.+data Handler m =+    Handler { handlerDescription :: T.Text+            -- ^ The description of this handler's behavior.+            , handlerAction :: m ()+            -- ^ The action to take when this handler is invoked.+            }++-- | A handler for a specific key.+--+-- In general, you should never need to create one of these. The+-- internals are exposed to make inspection easy.+data KeyHandler k m =+    KeyHandler { khHandler :: KeyEventHandler k m+               -- ^ The handler to invoke. Note that this maintains+               -- the original abstract key event handler; this allows+               -- us to obtain the original 'EventTrigger' for the+               -- 'KeyEventHandler' upon which this 'KeyHandler'+               -- is built. This can be important for keybinding+               -- consistency checks or collision checks as well as help+               -- text generation.+               , khBinding :: Binding+               -- ^ The specific key binding that should trigger this+               -- handler.+               }++-- | Find the key handler that matches a Vty key event, if any. Modifier+-- order is unimportant since the lookup for a matching binding ignores+-- modifier order.+--+-- This works by looking up an event handler whose binding is the+-- specified key and modifiers based on the 'KeyConfig' that was used to+-- build the 'KeyDispatcher'.+--+-- Ordinarily you will not need to use this function; use 'handleKey'+-- instead. This is provided for more direct access to the+-- 'KeyDispatcher' internals.+lookupVtyEvent :: Vty.Key -> [Vty.Modifier] -> KeyDispatcher k m -> Maybe (KeyHandler k m)+lookupVtyEvent k mods (KeyDispatcher m) = M.lookup (Binding k $ S.fromList mods) m++-- | Handle a keyboard event by looking it up in the 'KeyDispatcher'+-- and invoking the matching binding's handler if one is found. Return+-- @True@ if the a matching handler was found and run; return @False@ if+-- no matching binding was found.+handleKey :: (Monad m)+          => KeyDispatcher k m+          -- ^ The dispatcher to use.+          -> Vty.Key+          -- ^ The key to handle.+          -> [Vty.Modifier]+          -- ^ The modifiers for the key, if any.+          -> m Bool+handleKey d k mods = do+    case lookupVtyEvent k mods d of+        Just kh -> (handlerAction $ kehHandler $ khHandler kh) >> return True+        Nothing -> return False++-- | Build a 'KeyDispatcher' to dispatch keys to handle events of type+-- @k@ using actions in a Monad @m@.+--+-- This works by taking a list of abstract 'KeyEventHandler's and+-- building a 'KeyDispatcher' of event handlers based on specific Vty+-- keys using the provided 'KeyConfig' to map between abstract key+-- events of type @k@ and Vty keys. Event handlers triggered by an event+-- @k@ are set up to be triggered by either the customized bindings for+-- @k@ in the 'KeyConfig', no bindings at all if the 'KeyConfig' has+-- marked @k@ as 'Unbound', or the default bindings for @k@ otherwise.+--+-- Once you have a 'KeyDispatcher', you can dispatch an input key event+-- to it and invoke the corresponding handler (if any) with 'handleKey'.+keyDispatcher :: (Ord k)+              => KeyConfig k+              -> [KeyEventHandler k m]+              -> KeyDispatcher k m+keyDispatcher conf ks = KeyDispatcher $ M.fromList $ buildKeyDispatcherPairs ks conf++-- | Convert a key dispatcher to a list of pairs of bindings and their+-- handlers.+keyDispatcherToList :: KeyDispatcher k m+                    -> [(Binding, KeyHandler k m)]+keyDispatcherToList (KeyDispatcher m) = M.toList m++buildKeyDispatcherPairs :: (Ord k)+                        => [KeyEventHandler k m]+                        -> KeyConfig k+                        -> [(Binding, KeyHandler k m)]+buildKeyDispatcherPairs ks conf = pairs+    where+        pairs = mkPair <$> handlers+        mkPair h = (khBinding h, h)+        handlers = concat $ keyHandlersFromConfig conf <$> ks++keyHandlersFromConfig :: (Ord k)+                      => KeyConfig k+                      -> KeyEventHandler k m+                      -> [KeyHandler k m]+keyHandlersFromConfig kc eh =+    let allBindingsFor ev | Just (BindingList ks) <- lookupKeyConfigBindings kc ev = ks+                          | Just Unbound <- lookupKeyConfigBindings kc ev = []+                          | otherwise = allDefaultBindings kc ev+        bindings = case kehEventTrigger eh of+            ByKey b    -> [b]+            ByEvent ev -> allBindingsFor ev+    in [ KeyHandler { khHandler = eh, khBinding = b } | b <- bindings ]++mkHandler :: T.Text -> m () -> Handler m+mkHandler msg action =+    Handler { handlerDescription = msg+            , handlerAction = action+            }++-- | Specify a handler for the specified key event.+onEvent :: k+        -- ^ The key event whose bindings should trigger this handler.+        -> T.Text+        -- ^ The description of the handler.+        -> m ()+        -- ^ The handler to invoke.+        -> KeyEventHandler k m+onEvent ev msg action =+    KeyEventHandler { kehHandler = mkHandler msg action+                    , kehEventTrigger = ByEvent ev+                    }++-- | Specify a handler for the specified key.+onKey :: (ToBinding a)+      => a+      -- ^ The binding that should trigger this handler.+      -> T.Text+      -- ^ The description of the handler.+      -> m ()+      -- ^ The handler to invoke.+      -> KeyEventHandler k m+onKey b msg action =+    KeyEventHandler { kehHandler = mkHandler msg action+                    , kehEventTrigger = ByKey $ bind b+                    }++-- | A trigger for an event handler.+data EventTrigger k =+    ByKey Binding+    -- ^ The key event is always triggered by a specific key.+    | ByEvent k+    -- ^ The trigger is an abstract key event.+    deriving (Show, Eq, Ord)++-- | A handler for an abstract key event.+--+-- In general, you should never need to create these manually. Instead,+-- use 'onEvent' and 'onKey'. The internals of this type are exposed to+-- allow inspection of handler data for e.g. custom help generation.+data KeyEventHandler k m =+    KeyEventHandler { kehHandler :: Handler m+                    -- ^ The handler to invoke.+                    , kehEventTrigger :: EventTrigger k+                    -- ^ The trigger for the handler.+                    }
+ src/Brick/Keybindings/KeyEvents.hs view
@@ -0,0 +1,52 @@+-- | This module provides 'KeyEvents', a data type for mapping+-- application-defined abstract events to user-facing names (e.g.+-- for use in configuration files and documentation). This data+-- structure gives you a place to define the correspondence between+-- your application's key events and their names. A 'KeyEvents' also+-- effectively tells the key binding system about the collection of+-- possible abstract events that can be handled.+--+-- A 'KeyEvents' is used to construct a+-- 'Brick.Keybindings.KeyConfig.KeyConfig' with+-- 'Brick.Keybindings.KeyConfig.newKeyConfig'.+module Brick.Keybindings.KeyEvents+  ( KeyEvents+  , keyEvents+  , keyEventsList+  , lookupKeyEvent+  , keyEventName+  )+where++import qualified Data.Bimap as B+import qualified Data.Text as T++-- | A bidirectional mapping between events @k@ and their user-readable+-- names.+data KeyEvents k = KeyEvents (B.Bimap T.Text k)+                 deriving (Eq, Show)++-- | Build a new 'KeyEvents' map from the specified list of events and+-- names. Key event names are stored in lowercase.+--+-- Calls 'error' if any events have the same name (ignoring case) or if+-- multiple names map to the same event.+keyEvents :: (Ord k) => [(T.Text, k)] -> KeyEvents k+keyEvents pairs =+    let m = B.fromList [(T.strip $ T.toLower n, e) | (n, e) <- pairs]+    in if B.size m /= length pairs+       then error "keyEvents: input list contains duplicates by name or by event value"+       else KeyEvents $ B.fromList pairs++-- | Convert the 'KeyEvents' to a list.+keyEventsList :: KeyEvents k -> [(T.Text, k)]+keyEventsList (KeyEvents m) = B.toList m++-- | Look up the specified event name to get its abstract event. The+-- lookup ignores leading and trailing whitespace as well as case.+lookupKeyEvent :: (Ord k) => KeyEvents k -> T.Text -> Maybe k+lookupKeyEvent (KeyEvents m) name = B.lookup (T.strip $ T.toLower name) m++-- | Given an abstract event, get its event name.+keyEventName :: (Ord k) => KeyEvents k -> k -> Maybe T.Text+keyEventName (KeyEvents m) e = B.lookupR e m
+ src/Brick/Keybindings/Parse.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE OverloadedStrings #-}+-- | This module provides key binding string parsing functions for use+-- in e.g. reading key bindings from configuration files.+module Brick.Keybindings.Parse+  ( parseBinding+  , parseBindingList++  , keybindingsFromIni+  , keybindingsFromFile+  , keybindingIniParser+  )+where++import Control.Monad (forM)+import Data.Maybe (catMaybes)+import qualified Data.Set as S+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified Graphics.Vty as Vty+import Text.Read (readMaybe)+import qualified Data.Ini.Config as Ini++import Brick.Keybindings.KeyEvents+import Brick.Keybindings.KeyConfig++-- | Parse a key binding list into a 'BindingState'.+--+-- A key binding list either the string @"unbound"@ or is a+-- comma-separated list of 'Binding's parsed with 'parseBinding'.+parseBindingList :: T.Text -> Either String BindingState+parseBindingList t =+    if T.toLower t == "unbound"+    then return Unbound+    else BindingList <$> mapM (parseBinding . T.strip) (T.splitOn "," $ T.strip t)++-- | Parse a key binding string. Key binding strings specify zero or+-- more modifier keys and a base key, separated by hyphens.+--+-- @+-- (modifier "-")* key+-- @+--+-- e.g. @c-down@, @backspace@, @ctrl-shift-f1@.+--+-- where each @modifier@ is parsed case-insensitively as follows:+--+-- * @"s", "shift"@: 'Vty.MShift'+-- * @"m", "meta"@: 'Vty.MMeta'+-- * @"a", "alt"@: 'Vty.MAlt'+-- * @"c", "ctrl", "control"@: 'Vty.MCtrl'+--+-- and @key@ is parsed case-insensitively as follows:+--+-- * "f1", "f2", ...: 'Vty.KFun'+-- * "esc": 'Vty.KEsc'+-- * "backspace": 'Vty.KBS'+-- * "enter": 'Vty.KEnter'+-- * "left": 'Vty.KLeft'+-- * "right": 'Vty.KRight'+-- * "up": 'Vty.KUp'+-- * "down": 'Vty.KDown'+-- * "upleft": 'Vty.KUpLeft'+-- * "upright": 'Vty.KUpRight'+-- * "downleft": 'Vty.KDownLeft'+-- * "downright": 'Vty.KDownRight'+-- * "center": 'Vty.KCenter'+-- * "backtab": 'Vty.KBackTab'+-- * "printscreen": 'Vty.KPrtScr'+-- * "pause": 'Vty.KPause'+-- * "insert": 'Vty.KIns'+-- * "home": 'Vty.KHome'+-- * "pgup": 'Vty.KPageUp'+-- * "del": 'Vty.KDel'+-- * "end": 'Vty.KEnd'+-- * "pgdown": 'Vty.KPageDown'+-- * "begin": 'Vty.KBegin'+-- * "menu": 'Vty.KMenu'+-- * "space": @' '@+-- * "tab": @'\\t'@+-- * Otherwise, 'Vty.KChar'+parseBinding :: T.Text -> Either String Binding+parseBinding s = go (T.splitOn "-" $ T.toLower s) []+  where go [k] mods = do+          k' <- pKey k+          return Binding { kbMods = S.fromList mods, kbKey = k' }+        go (k:ks) mods = do+          m <- case k of+            "s"       -> return Vty.MShift+            "shift"   -> return Vty.MShift+            "m"       -> return Vty.MMeta+            "meta"    -> return Vty.MMeta+            "a"       -> return Vty.MAlt+            "alt"     -> return Vty.MAlt+            "c"       -> return Vty.MCtrl+            "ctrl"    -> return Vty.MCtrl+            "control" -> return Vty.MCtrl+            _         -> Left ("Unknown modifier prefix: " ++ show k)+          go ks (m:mods)+        go [] _ = Left "Empty keybinding not allowed"+        pKey "esc"       = return Vty.KEsc+        pKey "backspace" = return Vty.KBS+        pKey "enter"     = return Vty.KEnter+        pKey "left"      = return Vty.KLeft+        pKey "right"     = return Vty.KRight+        pKey "up"        = return Vty.KUp+        pKey "down"      = return Vty.KDown+        pKey "upleft"    = return Vty.KUpLeft+        pKey "upright"   = return Vty.KUpRight+        pKey "downleft"  = return Vty.KDownLeft+        pKey "downright" = return Vty.KDownRight+        pKey "center"    = return Vty.KCenter+        pKey "backtab"   = return Vty.KBackTab+        pKey "printscreen" = return Vty.KPrtScr+        pKey "pause"     = return Vty.KPause+        pKey "insert"    = return Vty.KIns+        pKey "home"      = return Vty.KHome+        pKey "pgup"      = return Vty.KPageUp+        pKey "del"       = return Vty.KDel+        pKey "end"       = return Vty.KEnd+        pKey "pgdown"    = return Vty.KPageDown+        pKey "begin"     = return Vty.KBegin+        pKey "menu"      = return Vty.KMenu+        pKey "space"     = return (Vty.KChar ' ')+        pKey "tab"       = return (Vty.KChar '\t')+        pKey t+          | T.length t == 1 =+              return (Vty.KChar $ T.last s)+          | Just n <- T.stripPrefix "f" t =+              case readMaybe (T.unpack n) of+                  Nothing -> Left ("Unknown keybinding: " ++ show t)+                  Just i -> return (Vty.KFun i)+          | otherwise = Left ("Unknown keybinding: " ++ show t)++-- | Parse custom key bindings from the specified INI file using the+-- provided event name mapping.+--+-- Each line in the specified section can take the form+--+-- > <event-name> = <"unbound"|[binding,...]>+--+-- where the event name must be a valid event name in the specified+-- 'KeyEvents' and each binding is valid as parsed by 'parseBinding'.+--+-- Returns @Nothing@ if the named section was not found; otherwise+-- returns a (possibly empty) list of binding states for each event in+-- @evs@.+keybindingsFromIni :: KeyEvents k+                   -- ^ The key event name mapping to use to parse the+                   -- configuration data.+                   -> T.Text+                   -- ^ The name of the INI configuration section to+                   -- read.+                   -> T.Text+                   -- ^ The text of the INI document to read.+                   -> Either String (Maybe [(k, BindingState)])+keybindingsFromIni evs section doc =+    Ini.parseIniFile doc (keybindingIniParser evs section)++-- | Parse custom key binidngs from the specified INI file path. This+-- does not catch or convert any exceptions resulting from I/O errors.+-- See 'keybindingsFromIni' for details.+keybindingsFromFile :: KeyEvents k+                    -- ^ The key event name mapping to use to parse the+                    -- configuration data.+                    -> T.Text+                    -- ^ The name of the INI configuration section to+                    -- read.+                    -> FilePath+                    -- ^ The path to the INI file to read.+                    -> IO (Either String (Maybe [(k, BindingState)]))+keybindingsFromFile evs section path =+    keybindingsFromIni evs section <$> T.readFile path++-- | The low-level INI parser for custom key bindings used by this+-- module, exported for applications that use the @config-ini@ package.+keybindingIniParser :: KeyEvents k -> T.Text -> Ini.IniParser (Maybe [(k, BindingState)])+keybindingIniParser evs section =+    Ini.sectionMb section $ do+        fmap catMaybes $ forM (keyEventsList evs) $ \(name, e) -> do+            fmap (e,) <$> Ini.fieldMbOf name parseBindingList
+ src/Brick/Keybindings/Pretty.hs view
@@ -0,0 +1,249 @@+{-# LANGUAGE OverloadedStrings #-}+-- | This module provides functions for pretty-printing key bindings+-- and for generating Markdown, plain text, and Brick displays of event+-- handler key binding configurations.+module Brick.Keybindings.Pretty+  (+  -- * Generating help output+    keybindingTextTable+  , keybindingMarkdownTable+  , keybindingHelpWidget++  -- * Pretty-printing primitives+  , ppBinding+  , ppMaybeBinding+  , ppKey+  , ppModifier++  -- * Attributes for Widget rendering+  , keybindingHelpBaseAttr+  , eventNameAttr+  , eventDescriptionAttr+  , keybindingAttr+  )+where++import Brick+import Data.List (sort, intersperse)+import Data.Maybe (fromJust)+#if !(MIN_VERSION_base(4,11,0))+import Data.Monoid ((<>))+#endif+import qualified Data.Set as S+import qualified Data.Text as T+import qualified Graphics.Vty as Vty++import Brick.Keybindings.KeyEvents+import Brick.Keybindings.KeyConfig+import Brick.Keybindings.KeyDispatcher++data TextHunk = Verbatim T.Text+              | Comment T.Text++-- | Generate a Markdown document of sections indicating the key binding+-- state for each event handler.+keybindingMarkdownTable :: (Ord k)+                        => KeyConfig k+                        -- ^ The key binding configuration in use.+                        -> [(T.Text, [KeyEventHandler k m])]+                        -- ^ Key event handlers by named section.+                        -> T.Text+keybindingMarkdownTable kc sections = title <> keybindSectionStrings+    where title = "# Keybindings\n"+          keybindSectionStrings = T.concat $ sectionText <$> sections+          sectionText (heading, handlers) =+              mkHeading heading <>+              mkKeybindEventSectionHelp kc keybindEventHelpMarkdown T.unlines handlers+          mkHeading n =+              "\n# " <> n <>+              "\n| Keybinding | Event Name | Description |" <>+              "\n| ---------- | ---------- | ----------- |\n"++-- | Generate a plain text document of sections indicating the key+-- binding state for each event handler.+keybindingTextTable :: (Ord k)+                    => KeyConfig k+                    -- ^ The key binding configuration in use.+                    -> [(T.Text, [KeyEventHandler k m])]+                    -- ^ Key event handlers by named section.+                    -> T.Text+keybindingTextTable kc sections = title <> keybindSectionStrings+    where title = "Keybindings\n===========\n"+          keybindSectionStrings = T.concat $ sectionText <$> sections+          sectionText (heading, handlers) =+              mkHeading heading <>+              mkKeybindEventSectionHelp kc (keybindEventHelpText keybindingWidth eventNameWidth) T.unlines handlers+          keybindingWidth = 15+          eventNameWidth = 30+          mkHeading n =+              "\n" <> n <>+              "\n" <> (T.replicate (T.length n) "=") <>+              "\n"++keybindEventHelpText :: Int -> Int -> (TextHunk, T.Text, [TextHunk]) -> T.Text+keybindEventHelpText width eventNameWidth (evName, desc, evs) =+    let getText (Comment s) = s+        getText (Verbatim s) = s+    in padTo width (T.intercalate ", " $ getText <$> evs) <> " " <>+       padTo eventNameWidth (getText evName) <> " " <>+       desc++padTo :: Int -> T.Text -> T.Text+padTo n s = s <> T.replicate (n - T.length s) " "++mkKeybindEventSectionHelp :: (Ord k)+                          => KeyConfig k+                          -> ((TextHunk, T.Text, [TextHunk]) -> a)+                          -> ([a] -> a)+                          -> [KeyEventHandler k m]+                          -> a+mkKeybindEventSectionHelp kc mkKeybindHelpFunc vertCat kbs =+  vertCat $ mkKeybindHelpFunc <$> (mkKeybindEventHelp kc <$> kbs)++keybindEventHelpMarkdown :: (TextHunk, T.Text, [TextHunk]) -> T.Text+keybindEventHelpMarkdown (evName, desc, evs) =+    let quote s = "`" <> s <> "`"+        format (Comment s) = s+        format (Verbatim s) = quote s+        name = case evName of+            Comment s -> s+            Verbatim s -> quote s+    in "| " <> (T.intercalate ", " $ format <$> evs) <>+       " | " <> name <>+       " | " <> desc <>+       " |"++mkKeybindEventHelp :: (Ord k)+                   => KeyConfig k+                   -> KeyEventHandler k m+                   -> (TextHunk, T.Text, [TextHunk])+mkKeybindEventHelp kc h =+  let trig = kehEventTrigger h+      unbound = [Comment "(unbound)"]+      (label, evText) = case trig of+          ByKey b ->+              (Comment "(non-customizable key)", [Verbatim $ ppBinding b])+          ByEvent ev ->+              let name = fromJust $ keyEventName (keyConfigEvents kc) ev+              in case lookupKeyConfigBindings kc ev of+                  Nothing ->+                      if not (null (allDefaultBindings kc ev))+                      then (Verbatim name, Verbatim <$> ppBinding <$> allDefaultBindings kc ev)+                      else (Verbatim name, unbound)+                  Just Unbound ->+                      (Verbatim name, unbound)+                  Just (BindingList bs) ->+                      let result = if not (null bs)+                                   then Verbatim <$> ppBinding <$> bs+                                   else unbound+                      in (Verbatim name, result)+  in (label, handlerDescription $ kehHandler h, evText)++-- | Build a 'Widget' displaying key binding information for a single+-- related group of event handlers. This is provided for convenience+-- so that basic help text for the application's event handlers can be+-- produced and embedded in the UI.+--+-- The resulting widget lists the key events (and keys) bound to the+-- specified handlers, along with the events' names and the list of+-- available key bindings for each handler.+keybindingHelpWidget :: (Ord k)+                     => KeyConfig k+                     -- ^ The key binding configuration in use.+                     -> [KeyEventHandler k m]+                     -- ^ The list of the event handlers to include in+                     -- the help display.+                     -> Widget n+keybindingHelpWidget kc =+    withDefAttr keybindingHelpBaseAttr .+    mkKeybindEventSectionHelp kc keybindEventHelpWidget (vBox . intersperse (str " "))++keybindEventHelpWidget :: (TextHunk, T.Text, [TextHunk]) -> Widget n+keybindEventHelpWidget (evName, desc, evs) =+    let evText = T.intercalate ", " (getText <$> evs)+        getText (Comment s) = s+        getText (Verbatim s) = s+        label = withDefAttr eventNameAttr $ case evName of+            Comment s -> txt s -- TODO: was "; " <> s+            Verbatim s -> txt s -- TODO: was: emph $ txt s+    in vBox [ withDefAttr eventDescriptionAttr $ txt desc+            , label <+> txt " = " <+> withDefAttr keybindingAttr (txt evText)+            ]++-- | Pretty-print a 'Binding' in the same format that is parsed by+-- 'Brick.Keybindings.Parse.parseBinding'.+ppBinding :: Binding -> T.Text+ppBinding (Binding k mods) =+    T.intercalate "-" $ (ppModifier <$> modifierList mods) <> [ppKey k]++modifierList :: S.Set Vty.Modifier -> [Vty.Modifier]+modifierList = sort . S.toList++-- | Pretty-print a 'Binding' in the same format that is parsed by+-- 'Brick.Keybindings.Parse.parseBinding'; if no binding is given,+-- produce a message indicating no binding.+ppMaybeBinding :: Maybe Binding -> T.Text+ppMaybeBinding Nothing =+    "(no binding)"+ppMaybeBinding (Just b) =+    ppBinding b++-- | Pretty-print a 'Vty.Key' in the same format that is parsed by+-- 'Brick.Keybindings.Parse.parseBinding'.+ppKey :: Vty.Key -> T.Text+ppKey (Vty.KChar c)   = ppChar c+ppKey (Vty.KFun n)    = "F" <> (T.pack $ show n)+ppKey Vty.KBackTab    = "BackTab"+ppKey Vty.KEsc        = "Esc"+ppKey Vty.KBS         = "Backspace"+ppKey Vty.KEnter      = "Enter"+ppKey Vty.KUp         = "Up"+ppKey Vty.KDown       = "Down"+ppKey Vty.KLeft       = "Left"+ppKey Vty.KRight      = "Right"+ppKey Vty.KHome       = "Home"+ppKey Vty.KEnd        = "End"+ppKey Vty.KPageUp     = "PgUp"+ppKey Vty.KPageDown   = "PgDown"+ppKey Vty.KDel        = "Del"+ppKey Vty.KUpLeft     = "UpLeft"+ppKey Vty.KUpRight    = "UpRight"+ppKey Vty.KDownLeft   = "DownLeft"+ppKey Vty.KDownRight  = "DownRight"+ppKey Vty.KCenter     = "Center"+ppKey Vty.KPrtScr     = "PrintScreen"+ppKey Vty.KPause      = "Pause"+ppKey Vty.KIns        = "Insert"+ppKey Vty.KBegin      = "Begin"+ppKey Vty.KMenu       = "Menu"++-- | Pretty-print a character in the same format that is parsed by+-- 'Brick.Keybindings.Parse.parseBinding'.+ppChar :: Char -> T.Text+ppChar '\t' = "Tab"+ppChar ' '  = "Space"+ppChar c    = T.singleton c++-- | Pretty-print a 'Vty.Modifier' in the same format that is parsed by+-- 'Brick.Keybindings.Parse.parseBinding'.+ppModifier :: Vty.Modifier -> T.Text+ppModifier Vty.MMeta  = "M"+ppModifier Vty.MAlt   = "A"+ppModifier Vty.MCtrl  = "C"+ppModifier Vty.MShift = "S"++-- | The base attribute for 'Widget' keybinding help.+keybindingHelpBaseAttr :: AttrName+keybindingHelpBaseAttr = attrName "keybindingHelp"++-- | The attribute for event names in keybinding help 'Widget's.+eventNameAttr :: AttrName+eventNameAttr = keybindingHelpBaseAttr <> attrName "eventName"++-- | The attribute for event descriptions in keybinding help 'Widget's.+eventDescriptionAttr :: AttrName+eventDescriptionAttr = keybindingHelpBaseAttr <> attrName "eventDescription"++-- | The attribute for keybinding lists in keybinding help 'Widget's.+keybindingAttr :: AttrName+keybindingAttr = keybindingHelpBaseAttr <> attrName "keybinding"
src/Brick/Main.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RankNTypes #-} module Brick.Main   ( App(..)   , defaultMain@@ -9,10 +10,10 @@   , simpleApp    -- * Event handler functions-  , continue   , continueWithoutRedraw   , halt   , suspendAndResume+  , suspendAndResume'   , makeVisible   , lookupViewport   , lookupExtent@@ -53,10 +54,8 @@  import qualified Control.Exception as E import Lens.Micro ((^.), (&), (.~), (%~), _1, _2)-import Control.Monad (forever)-import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.State-import Control.Monad.Trans.Reader+import Control.Monad.State.Strict+import Control.Monad.Reader import Control.Concurrent (forkIO, killThread) import qualified Data.Foldable as F import Data.List (find)@@ -81,7 +80,7 @@ import Graphics.Vty.Attributes (defAttr)  import Brick.BChan (BChan, newBChan, readBChan, readBChan2, writeBChan)-import Brick.Types (EventM(..))+import Brick.Types.EventM import Brick.Types.Internal import Brick.Widgets.Internal import Brick.AttrMap@@ -110,13 +109,13 @@         -- is that many widgets may request a cursor placement but your         -- application state is what you probably want to use to decide         -- which one wins.-        , appHandleEvent :: s -> BrickEvent n e -> EventM n (Next s)+        , appHandleEvent :: BrickEvent n e -> EventM n s ()         -- ^ This function takes the current application state and an         -- event and returns an action to be taken and a corresponding         -- transformed application state. Possible options are         -- 'continue', 'continueWithoutRedraw', 'suspendAndResume', and         -- 'halt'.-        , appStartEvent :: s -> EventM n s+        , appStartEvent :: EventM n s ()         -- ^ This function gets called once just prior to the first         -- drawing of your application. Here is where you can make         -- initial scrolling requests, for example.@@ -159,7 +158,7 @@ simpleApp w =     App { appDraw = const [w]         , appHandleEvent = resizeOrQuit-        , appStartEvent = return+        , appStartEvent = return ()         , appAttrMap = const $ attrMap defAttr []         , appChooseCursor = neverShowCursor         }@@ -169,46 +168,40 @@ -- a halt. This is a convenience function useful as an 'appHandleEvent' -- value for simple applications using the 'Event' type that do not need -- to get more sophisticated user input.-resizeOrQuit :: s -> BrickEvent n e -> EventM n (Next s)-resizeOrQuit s (VtyEvent (EvResize _ _)) = continue s-resizeOrQuit s _ = halt s--data InternalNext n a = InternalSuspendAndResume (RenderState n) (IO a)-                      | InternalHalt a+resizeOrQuit :: BrickEvent n e -> EventM n s ()+resizeOrQuit (VtyEvent (EvResize _ _)) = return ()+resizeOrQuit _ = halt  readBrickEvent :: BChan (BrickEvent n e) -> BChan e -> IO (BrickEvent n e) readBrickEvent brickChan userChan = either id AppEvent <$> readBChan2 brickChan userChan  runWithVty :: (Ord n)-           => Vty+           => VtyContext            -> BChan (BrickEvent n e)            -> Maybe (BChan e)            -> App s e n            -> RenderState n            -> s-           -> IO (InternalNext n s)-runWithVty vty brickChan mUserChan app initialRS initialSt = do-    pid <- forkIO $ supplyVtyEvents vty brickChan+           -> IO (s, VtyContext)+runWithVty vtyCtx brickChan mUserChan app initialRS initialSt = do     let readEvent = case mUserChan of           Nothing -> readBChan brickChan           Just uc -> readBrickEvent brickChan uc-        runInner rs es draw st = do+        runInner ctx rs es draw st = do           let nextRS = if draw                        then resetRenderState rs                        else rs-          (result, newRS, newExtents) <- runVty vty readEvent app st nextRS es draw+          (nextSt, result, newRS, newExtents, newCtx) <- runVty ctx readEvent app st nextRS es draw           case result of-              SuspendAndResume act -> do-                  killThread pid-                  return $ InternalSuspendAndResume newRS act-              Halt s -> do-                  killThread pid-                  return $ InternalHalt s-              Continue s -> runInner newRS newExtents True s-              ContinueWithoutRedraw s ->-                  runInner newRS newExtents False s-    runInner initialRS mempty True initialSt+              Halt ->+                  return (nextSt, newCtx)+              Continue ->+                  runInner newCtx newRS newExtents True nextSt+              ContinueWithoutRedraw ->+                  runInner newCtx newRS newExtents False nextSt +    runInner vtyCtx initialRS mempty True initialSt+ -- | The custom event loop entry point to use when the simpler ones -- don't permit enough control. Returns the final application state -- after the application halts.@@ -267,22 +260,19 @@                   -- ^ The initial application state.                   -> IO (s, Vty) customMainWithVty initialVty buildVty mUserChan app initialAppState = do-    let run vty rs st brickChan = do-            result <- runWithVty vty brickChan mUserChan app rs st-                `E.catch` (\(e::E.SomeException) -> shutdown vty >> E.throw e)-            case result of-                InternalHalt s -> return (s, vty)-                InternalSuspendAndResume newRS action -> do-                    shutdown vty-                    newAppState <- action-                    newVty <- buildVty-                    run newVty (newRS { renderCache = mempty }) newAppState brickChan+    brickChan <- newBChan 20+    vtyCtx <- newVtyContext buildVty (Just initialVty) (writeBChan brickChan . VtyEvent) -    let emptyES = ES [] mempty mempty+    let emptyES = ES { esScrollRequests = []+                     , cacheInvalidateRequests = mempty+                     , requestedVisibleNames = mempty+                     , nextAction = Continue+                     , vtyContext = vtyCtx+                     }         emptyRS = RS M.empty mempty S.empty mempty mempty mempty mempty-        eventRO = EventRO M.empty initialVty mempty emptyRS+        eventRO = EventRO M.empty mempty emptyRS -    (st, eState) <- runStateT (runReaderT (runEventM (appStartEvent app initialAppState)) eventRO) emptyES+    (((), appState), eState) <- runStateT (runStateT (runReaderT (runEventM (appStartEvent app)) eventRO) initialAppState) emptyES     let initialRS = RS { viewportMap = M.empty                        , rsScrollRequests = esScrollRequests eState                        , observedNames = S.empty@@ -291,27 +281,57 @@                        , requestedVisibleNames_ = requestedVisibleNames eState                        , reportedExtents = mempty                        }-    brickChan <- newBChan 20-    run initialVty initialRS st brickChan -supplyVtyEvents :: Vty -> BChan (BrickEvent n e) -> IO ()-supplyVtyEvents vty chan =-    forever $ do-        e <- nextEvent vty-        writeBChan chan $ VtyEvent e+    (s, ctx) <- runWithVty vtyCtx brickChan mUserChan app initialRS appState+                `E.catch` (\(e::E.SomeException) -> shutdownVtyContext vtyCtx >> E.throw e) +    -- Shut down the context's event thread but do NOT shut down Vty+    -- itself because we want the handle to be live when we return it to+    -- the caller.+    shutdownVtyContextThread ctx+    return (s, vtyContextHandle ctx)++supplyVtyEvents :: Vty -> (Event -> IO ()) -> IO ()+supplyVtyEvents vty putEvent =+    forever $ putEvent =<< nextEvent vty++newVtyContextFrom :: VtyContext -> IO VtyContext+newVtyContextFrom old =+    newVtyContext (vtyContextBuilder old) Nothing (vtyContextPutEvent old)++newVtyContext :: IO Vty -> Maybe Vty -> (Event -> IO ()) -> IO VtyContext+newVtyContext builder handle putEvent = do+    vty <- case handle of+        Just h -> return h+        Nothing -> builder+    tId <- forkIO $ supplyVtyEvents vty putEvent+    return VtyContext { vtyContextHandle = vty+                      , vtyContextBuilder = builder+                      , vtyContextThread = tId+                      , vtyContextPutEvent = putEvent+                      }++shutdownVtyContext :: VtyContext -> IO ()+shutdownVtyContext ctx = do+    shutdown $ vtyContextHandle ctx+    shutdownVtyContextThread ctx++shutdownVtyContextThread :: VtyContext -> IO ()+shutdownVtyContextThread ctx =+    killThread $ vtyContextThread ctx+ runVty :: (Ord n)-       => Vty+       => VtyContext        -> IO (BrickEvent n e)        -> App s e n        -> s        -> RenderState n        -> [Extent n]        -> Bool-       -> IO (Next s, RenderState n, [Extent n])-runVty vty readEvent app appState rs prevExtents draw = do+       -> IO (s, NextAction, RenderState n, [Extent n], VtyContext)+runVty vtyCtx readEvent app appState rs prevExtents draw = do     (firstRS, exts) <- if draw-                       then renderApp vty app appState rs+                       then renderApp vtyCtx app appState rs                        else return (rs, prevExtents)      e <- readEvent@@ -322,7 +342,7 @@         -- want the event handler to have access to accurate viewport         -- information.         VtyEvent (EvResize _ _) -> do-            (rs', exts') <- renderApp vty app appState $ firstRS & observedNamesL .~ S.empty+            (rs', exts') <- renderApp vtyCtx app appState $ firstRS & observedNamesL .~ S.empty             return (e, rs', exts')         VtyEvent (EvMouseDown c r button mods) -> do             let matching = findClickedExtents_ (c, r) exts@@ -374,18 +394,20 @@                 _ -> return (e, firstRS, exts)         _ -> return (e, firstRS, exts) -    let emptyES = ES [] mempty mempty-        eventRO = EventRO (viewportMap nextRS) vty nextExts nextRS+    let emptyES = ES [] mempty mempty Continue vtyCtx+        eventRO = EventRO (viewportMap nextRS) nextExts nextRS -    (next, eState) <- runStateT (runReaderT (runEventM (appHandleEvent app appState e'))-                                eventRO) emptyES-    return ( next+    (((), newAppState), eState) <- runStateT (runStateT (runReaderT (runEventM (appHandleEvent app e'))+                                eventRO) appState) emptyES+    return ( newAppState+           , nextAction eState            , nextRS { rsScrollRequests = esScrollRequests eState                     , renderCache = applyInvalidations (cacheInvalidateRequests eState) $                                     renderCache nextRS                     , requestedVisibleNames_ = requestedVisibleNames eState                     }            , nextExts+           , vtyContext eState            )  applyInvalidations :: (Ord n) => S.Set (CacheInvalidateRequest n) -> M.Map n v -> M.Map n v@@ -410,7 +432,7 @@ -- associated functions without relying on this function. Those -- functions queue up scrolling requests that can be made in advance of -- the next rendering to affect the viewport.-lookupViewport :: (Ord n) => n -> EventM n (Maybe Viewport)+lookupViewport :: (Ord n) => n -> EventM n s (Maybe Viewport) lookupViewport n = EventM $ asks (M.lookup n . eventViewportMap)  -- | Did the specified mouse coordinates (column, row) intersect the@@ -422,7 +444,7 @@  -- | Given a resource name, get the most recent rendering extent for the -- name (if any).-lookupExtent :: (Eq n) => n -> EventM n (Maybe (Extent n))+lookupExtent :: (Eq n) => n -> EventM n s (Maybe (Extent n)) lookupExtent n = EventM $ asks (find f . latestExtents)     where         f (Extent n' _ _) = n == n'@@ -432,28 +454,32 @@ -- the list is the most specific extent and the last extent is the most -- generic (top-level). So if two extents A and B both intersected the -- mouse click but A contains B, then they would be returned [B, A].-findClickedExtents :: (Int, Int) -> EventM n [Extent n]+findClickedExtents :: (Int, Int) -> EventM n s [Extent n] findClickedExtents pos = EventM $ asks (findClickedExtents_ pos . latestExtents)  findClickedExtents_ :: (Int, Int) -> [Extent n] -> [Extent n] findClickedExtents_ pos = reverse . filter (clickedExtent pos)  -- | Get the Vty handle currently in use.-getVtyHandle :: EventM n Vty-getVtyHandle = EventM $ asks eventVtyHandle+getVtyHandle :: EventM n s Vty+getVtyHandle = vtyContextHandle <$> getVtyContext +setVtyContext :: VtyContext -> EventM n s ()+setVtyContext ctx =+    EventM $ lift $ lift $ modify $ \s -> s { vtyContext = ctx }+ -- | Invalidate the rendering cache entry with the specified resource -- name.-invalidateCacheEntry :: (Ord n) => n -> EventM n ()+invalidateCacheEntry :: (Ord n) => n -> EventM n s () invalidateCacheEntry n = EventM $ do-    lift $ modify (\s -> s { cacheInvalidateRequests = S.insert (InvalidateSingle n) $ cacheInvalidateRequests s })+    lift $ lift $ modify (\s -> s { cacheInvalidateRequests = S.insert (InvalidateSingle n) $ cacheInvalidateRequests s })  -- | Invalidate the entire rendering cache.-invalidateCache :: (Ord n) => EventM n ()+invalidateCache :: (Ord n) => EventM n s () invalidateCache = EventM $ do-    lift $ modify (\s -> s { cacheInvalidateRequests = S.insert InvalidateEntire $ cacheInvalidateRequests s })+    lift $ lift $ modify (\s -> s { cacheInvalidateRequests = S.insert InvalidateEntire $ cacheInvalidateRequests s }) -getRenderState :: EventM n (RenderState n)+getRenderState :: EventM n s (RenderState n) getRenderState = EventM $ asks oldState  resetRenderState :: RenderState n -> RenderState n@@ -461,9 +487,9 @@     s & observedNamesL .~ S.empty       & clickableNamesL .~ mempty -renderApp :: (Ord n) => Vty -> App s e n -> s -> RenderState n -> IO (RenderState n, [Extent n])-renderApp vty app appState rs = do-    sz <- displayBounds $ outputIface vty+renderApp :: (Ord n) => VtyContext -> App s e n -> s -> RenderState n -> IO (RenderState n, [Extent n])+renderApp vtyCtx app appState rs = do+    sz <- displayBounds $ outputIface $ vtyContextHandle vtyCtx     let (newRS, pic, theCursor, exts) = renderFinal (appAttrMap app appState)                                         (appDraw app appState)                                         sz@@ -478,7 +504,7 @@                                            (cloc^.locationRowL)                              } -    update vty picWithCursor+    update (vtyContextHandle vtyCtx) picWithCursor      return (newRS, exts) @@ -509,38 +535,38 @@     ViewportScroll { viewportName :: n                    -- ^ The name of the viewport to be controlled by                    -- this scrolling handle.-                   , hScrollPage :: Direction -> EventM n ()+                   , hScrollPage :: forall s. Direction -> EventM n s ()                    -- ^ Scroll the viewport horizontally by one page in                    -- the specified direction.-                   , hScrollBy :: Int -> EventM n ()+                   , hScrollBy :: forall s. Int -> EventM n s ()                    -- ^ Scroll the viewport horizontally by the                    -- specified number of rows or columns depending on                    -- the orientation of the viewport.-                   , hScrollToBeginning :: EventM n ()+                   , hScrollToBeginning :: forall s. EventM n s ()                    -- ^ Scroll horizontally to the beginning of the                    -- viewport.-                   , hScrollToEnd :: EventM n ()+                   , hScrollToEnd :: forall s. EventM n s ()                    -- ^ Scroll horizontally to the end of the viewport.-                   , vScrollPage :: Direction -> EventM n ()+                   , vScrollPage :: forall s. Direction -> EventM n s ()                    -- ^ Scroll the viewport vertically by one page in                    -- the specified direction.-                   , vScrollBy :: Int -> EventM n ()+                   , vScrollBy :: forall s. Int -> EventM n s ()                    -- ^ Scroll the viewport vertically by the specified                    -- number of rows or columns depending on the                    -- orientation of the viewport.-                   , vScrollToBeginning :: EventM n ()+                   , vScrollToBeginning :: forall s. EventM n s ()                    -- ^ Scroll vertically to the beginning of the viewport.-                   , vScrollToEnd :: EventM n ()+                   , vScrollToEnd :: forall s. EventM n s ()                    -- ^ Scroll vertically to the end of the viewport.-                   , setTop :: Int -> EventM n ()+                   , setTop :: forall s. Int -> EventM n s ()                    -- ^ Set the top row offset of the viewport.-                   , setLeft :: Int -> EventM n ()+                   , setLeft :: forall s. Int -> EventM n s ()                    -- ^ Set the left column offset of the viewport.                    } -addScrollRequest :: (n, ScrollRequest) -> EventM n ()+addScrollRequest :: (n, ScrollRequest) -> EventM n s () addScrollRequest req = EventM $ do-    lift $ modify (\s -> s { esScrollRequests = req : esScrollRequests s })+    lift $ lift $ modify (\s -> s { esScrollRequests = req : esScrollRequests s })  -- | Build a viewport scroller for the viewport with the specified name. viewportScroll :: n -> ViewportScroll n@@ -559,11 +585,6 @@                    }  -- | Continue running the event loop with the specified application--- state.-continue :: s -> EventM n (Next s)-continue = return . Continue---- | Continue running the event loop with the specified application -- state without redrawing the screen. This is faster than 'continue' -- because it skips the redraw, but the drawback is that you need to -- be really sure that you don't want a screen redraw. If your state@@ -571,30 +592,51 @@ -- 'continue'. This function is for cases where you know that you did -- something that won't have an impact on the screen state and you want -- to save on redraw cost.-continueWithoutRedraw :: s -> EventM n (Next s)-continueWithoutRedraw = return . ContinueWithoutRedraw+continueWithoutRedraw :: EventM n s ()+continueWithoutRedraw =+    EventM $ lift $ lift $ modify $ \es -> es { nextAction = ContinueWithoutRedraw }  -- | Halt the event loop and return the specified application state as -- the final state value.-halt :: s -> EventM n (Next s)-halt = return . Halt+halt :: EventM n s ()+halt =+    EventM $ lift $ lift $ modify $ \es -> es { nextAction = Halt }  -- | Suspend the event loop, save the terminal state, and run the -- specified action. When it returns an application state value, restore--- the terminal state, empty the rendering cache, redraw the application--- from the new state, and resume the event loop.+-- the terminal state, empty the rendering cache, update the application+-- state with the returned state, and continue execution of the event+-- handler that called this. -- -- Note that any changes made to the terminal's input state are ignored -- when Brick resumes execution and are not preserved in the final -- terminal input state after the Brick application returns the terminal -- to the user.-suspendAndResume :: IO s -> EventM n (Next s)-suspendAndResume = return . SuspendAndResume+suspendAndResume :: (Ord n) => IO s -> EventM n s ()+suspendAndResume act = suspendAndResume' act >>= put +-- | Suspend the event loop, save the terminal state, and run the+-- specified action. When it completes, restore the terminal state,+-- empty the rendering cache, return the result, and continue execution+-- of the event handler that called this.+--+-- Note that any changes made to the terminal's input state are ignored+-- when Brick resumes execution and are not preserved in the final+-- terminal input state after the Brick application returns the terminal+-- to the user.+suspendAndResume' :: (Ord n) => IO a -> EventM n s a+suspendAndResume' act = do+    ctx <- getVtyContext+    liftIO $ shutdownVtyContext ctx+    result <- liftIO act+    setVtyContext =<< (liftIO $ newVtyContextFrom ctx)+    invalidateCache+    return result+ -- | Request that the specified UI element be made visible on the -- next rendering. This is provided to allow event handlers to make -- visibility requests in the same way that the 'visible' function does -- at rendering time.-makeVisible :: (Ord n) => n -> EventM n ()+makeVisible :: (Ord n) => n -> EventM n s () makeVisible n = EventM $ do-    lift $ modify (\s -> s { requestedVisibleNames = S.insert n $ requestedVisibleNames s })+    lift $ lift $ modify (\s -> s { requestedVisibleNames = S.insert n $ requestedVisibleNames s })
src/Brick/Types.hs view
@@ -1,7 +1,6 @@ -- | Basic types used by this library. {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Brick.Types   ( -- * The Widget type@@ -27,11 +26,11 @@   , ScrollbarRenderer(..)   , ClickableScrollbarElement(..) -  -- * Event-handling types-  , EventM(..)-  , Next+  -- * Event-handling types and functions+  , EventM   , BrickEvent(..)-  , handleEventLensed+  , nestEventM+  , nestEventM'    -- * Rendering infrastructure   , RenderM@@ -85,63 +84,72 @@    -- * Miscellaneous   , Size(..)-  , Padding(..)   , Direction(..)    -- * Renderer internals (for benchmarking)   , RenderState++  -- * Re-exports for convenience+  , get+  , gets+  , put+  , modify+  , zoom   ) where -import Lens.Micro (_1, _2, to, (^.), (&), (.~), Lens')+import Lens.Micro (_1, _2, to, (^.)) import Lens.Micro.Type (Getting)-import Control.Monad.Catch (MonadThrow, MonadCatch, MonadMask)+import Lens.Micro.Mtl (zoom) #if !MIN_VERSION_base(4,13,0) import Control.Monad.Fail (MonadFail) #endif-import Control.Monad.Trans.State.Lazy-import Control.Monad.Trans.Reader+import Control.Monad.State.Strict+import Control.Monad.Reader import Graphics.Vty (Attr)-import Control.Monad.IO.Class  import Brick.Types.TH import Brick.Types.Internal+import Brick.Types.EventM import Brick.AttrMap (AttrName, attrMapLookup) --- | The type of padding.-data Padding = Pad Int-             -- ^ Pad by the specified number of rows or columns.-             | Max-             -- ^ Pad up to the number of available rows or columns.+-- | Given a state value and an 'EventM' that mutates that state, run+-- the specified action and return resulting modified state.+nestEventM' :: a+           -- ^ The initial state to use in the nested action.+            -> EventM n a b+            -- ^ The action to run.+            -> EventM n s a+nestEventM' s act = fst <$> nestEventM s act --- | A convenience function for handling events intended for values--- that are targets of lenses in your application state. This function--- obtains the target value of the specified lens, invokes 'handleEvent'--- on it, and stores the resulting transformed value back in the state--- using the lens.-handleEventLensed :: a-                  -- ^ The state value.-                  -> Lens' a b-                  -- ^ The lens to use to extract and store the target-                  -- of the event.-                  -> (e -> b -> EventM n b)-                  -- ^ The event handler.-                  -> e-                  -- ^ The event to handle.-                  -> EventM n a-handleEventLensed v target handleEvent ev = do-    newB <- handleEvent ev (v^.target)-    return $ v & target .~ newB+-- | Given a state value and an 'EventM' that mutates that state, run+-- the specified action and return both the resulting modified state and+-- the result of the action itself.+nestEventM :: a+           -- ^ The initial state to use in the nested action.+           -> EventM n a b+           -- ^ The action to run.+           -> EventM n s (a, b)+nestEventM s' act = do+    ro <- EventM ask+    es <- EventM $ lift $ lift get+    vtyCtx <- getVtyContext+    let stInner = ES { nextAction = Continue+                     , esScrollRequests = esScrollRequests es+                     , cacheInvalidateRequests = cacheInvalidateRequests es+                     , requestedVisibleNames = requestedVisibleNames es+                     , vtyContext = vtyCtx+                     }+    ((actResult, newSt), stInnerFinal) <- liftIO $ runStateT (runStateT (runReaderT (runEventM act) ro) s') stInner --- | The monad in which event handlers run. Although it may be tempting--- to dig into the reader value yourself, just use--- 'Brick.Main.lookupViewport'.-newtype EventM n a =-    EventM { runEventM :: ReaderT (EventRO n) (StateT (EventState n) IO) a-           }-           deriving ( Functor, Applicative, Monad, MonadIO-                    , MonadThrow, MonadCatch, MonadMask, MonadFail-                    )+    EventM $ lift $ lift $ modify $+        \st -> st { nextAction = nextAction stInnerFinal+                  , esScrollRequests = esScrollRequests stInnerFinal+                  , cacheInvalidateRequests = cacheInvalidateRequests stInnerFinal+                  , requestedVisibleNames = requestedVisibleNames stInnerFinal+                  , vtyContext = vtyContext stInnerFinal+                  }+    return (newSt, actResult)  -- | The rendering context's current drawing attribute. attrL :: forall r n. Getting r (Context n) Attr
+ src/Brick/Types/EventM.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+module Brick.Types.EventM+  ( EventM(..)+  , getVtyContext+  )+where++import Control.Monad.Catch (MonadThrow, MonadCatch, MonadMask)+#if !MIN_VERSION_base(4,13,0)+import Control.Monad.Fail (MonadFail)+#endif+import Control.Monad.Reader+import Control.Monad.State.Strict+import Lens.Micro.Mtl+import Lens.Micro.Mtl.Internal++import Brick.Types.Internal++-- | The monad in which event handlers run.+newtype EventM n s a =+    EventM { runEventM :: ReaderT (EventRO n) (StateT s (StateT (EventState n) IO)) a+           }+           deriving ( Functor, Applicative, Monad, MonadIO+                    , MonadThrow, MonadCatch, MonadMask+#if !MIN_VERSION_base(4,13,0)+                    , MonadFail+#endif+                    )++instance MonadState s (EventM n s) where+    get = EventM $ lift get+    put = EventM . lift . put++getVtyContext :: EventM n s VtyContext+getVtyContext = EventM $ lift $ lift $ gets vtyContext++type instance Zoomed (EventM n s) = Zoomed (StateT s (StateT (EventState n) IO))++instance Zoom (EventM n s) (EventM n t) s t where+    zoom l (EventM m) = EventM (zoom l m)
src/Brick/Types/Internal.hs view
@@ -45,8 +45,9 @@   , Size(..)    , EventState(..)+  , VtyContext(..)   , EventRO(..)-  , Next(..)+  , NextAction(..)   , Result(..)   , Extent(..)   , Edges(..)@@ -82,9 +83,9 @@   ) where -import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.Reader-import Control.Monad.Trans.State.Lazy+import Control.Concurrent (ThreadId)+import Control.Monad.Reader+import Control.Monad.State.Strict import Lens.Micro (_1, _2, Lens') import Lens.Micro.Mtl (use) import Lens.Micro.TH (makeLenses)@@ -214,37 +215,47 @@  -- | The type of viewports that indicates the direction(s) in which a -- viewport is scrollable.-data ViewportType = Vertical-                  -- ^ Viewports of this type are scrollable only vertically.-                  | Horizontal-                  -- ^ Viewports of this type are scrollable only horizontally.-                  | Both-                  -- ^ Viewports of this type are scrollable vertically and horizontally.-                  deriving (Show, Eq)+data ViewportType =+    Vertical+    -- ^ Viewports of this type are scrollable only vertically.+    | Horizontal+    -- ^ Viewports of this type are scrollable only horizontally.+    | Both+    -- ^ Viewports of this type are scrollable vertically and horizontally.+    deriving (Show, Eq)  data CacheInvalidateRequest n =     InvalidateSingle n     | InvalidateEntire     deriving (Ord, Eq) -data EventState n = ES { esScrollRequests :: [(n, ScrollRequest)]-                       , cacheInvalidateRequests :: S.Set (CacheInvalidateRequest n)-                       , requestedVisibleNames :: S.Set n-                       }+data EventState n =+    ES { esScrollRequests :: ![(n, ScrollRequest)]+       , cacheInvalidateRequests :: !(S.Set (CacheInvalidateRequest n))+       , requestedVisibleNames :: !(S.Set n)+       , nextAction :: !NextAction+       , vtyContext :: VtyContext+       } +data VtyContext =+    VtyContext { vtyContextBuilder :: IO Vty+               , vtyContextHandle :: Vty+               , vtyContextThread :: ThreadId+               , vtyContextPutEvent :: Event -> IO ()+               }+ -- | An extent of a named area: its size, location, and origin.-data Extent n = Extent { extentName      :: n-                       , extentUpperLeft :: Location-                       , extentSize      :: (Int, Int)+data Extent n = Extent { extentName      :: !n+                       , extentUpperLeft :: !Location+                       , extentSize      :: !(Int, Int)                        }                        deriving (Show, Read, Generic, NFData)  -- | The type of actions to take upon completion of an event handler.-data Next a = Continue a-            | ContinueWithoutRedraw a-            | SuspendAndResume (IO a)-            | Halt a-            deriving Functor+data NextAction =+    Continue+    | ContinueWithoutRedraw+    | Halt  -- | Scrolling direction. data Direction = Up@@ -310,15 +321,15 @@ -- result provides the image, cursor positions, and visibility requests -- that resulted from the rendering process. data Result n =-    Result { image :: Image+    Result { image :: !Image            -- ^ The final rendered image for a widget-           , cursors :: [CursorLocation n]+           , cursors :: ![CursorLocation n]            -- ^ The list of reported cursor positions for the            -- application to choose from-           , visibilityRequests :: [VisibilityRequest]+           , visibilityRequests :: ![VisibilityRequest]            -- ^ The list of visibility requests made by widgets rendered            -- while rendering this one (used by viewports)-           , extents :: [Extent n]+           , extents :: ![Extent n]            -- Programmer's note: we don't try to maintain the invariant that            -- the size of the borders closely matches the size of the 'image'            -- field. Most widgets don't need to care about borders, and so they@@ -331,7 +342,7 @@            -- If you're writing a widget, this should make it easier for you to            -- do so; but beware this lack of invariant if you are consuming            -- widgets.-           , borders :: BorderMap DynBorder+           , borders :: !(BorderMap DynBorder)            -- ^ Places where we may rewrite the edge of the image when            -- placing this widget next to another one.            }@@ -362,7 +373,6 @@                     deriving (Show, Eq, Ord)  data EventRO n = EventRO { eventViewportMap :: M.Map n Viewport-                         , eventVtyHandle :: Vty                          , latestExtents :: [Extent n]                          , oldState :: RenderState n                          }
src/Brick/Widgets/Border.hs view
@@ -39,7 +39,7 @@  -- | The top-level border attribute name. borderAttr :: AttrName-borderAttr = "border"+borderAttr = attrName "border"  -- | Draw the specified border element using the active border style -- using 'borderAttr'.
src/Brick/Widgets/Core.hs view
@@ -22,6 +22,7 @@   , hyperlink    -- * Padding+  , Padding(..)   , padLeft   , padRight   , padTop@@ -121,10 +122,8 @@  import Lens.Micro ((^.), (.~), (&), (%~), to, _1, _2, each, to, Lens') import Lens.Micro.Mtl (use, (%=))-import Control.Monad ((>=>),when)-import Control.Monad.Trans.State.Lazy-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Class (lift)+import Control.Monad.State.Strict+import Control.Monad.Reader import qualified Data.Foldable as F import qualified Data.Text as T import qualified Data.DList as DL@@ -377,6 +376,12 @@         let attr = (c^.attrL) `V.withURL` url         withReaderT (ctxAttrMapL %~ setDefaultAttr attr) (render p) +-- | The type of padding.+data Padding = Pad Int+             -- ^ Pad by the specified number of rows or columns.+             | Max+             -- ^ Pad up to the number of available rows or columns.+ -- | Pad the specified widget on the left. If max padding is used, this -- grows greedily horizontally; otherwise it defers to the padded -- widget.@@ -863,13 +868,14 @@         render $ cropToContext p  -- | When drawing the specified widget, set the attribute used for--- drawing to the one with the specified name. Note that the widget--- may use further calls to 'withAttr' to change the active drawing--- attribute, so this only takes effect if nothing in the specified--- widget invokes 'withAttr'. If you want to prevent that, use--- 'forceAttr'. Attributes used this way still get merged hierarchically--- and still fall back to the attribute map's default attribute. If you--- want to change the default attribute, use 'withDefAttr'.+-- drawing to the one with the specified name. Note that the widget may+-- make further changes to the active drawing attribute, so this only+-- takes effect if nothing in the specified widget invokes 'withAttr'+-- or otherwise changes the rendering context's attribute setup. If you+-- want to prevent that, use 'forceAttr'. Attributes used this way still+-- get merged hierarchically and still fall back to the attribute map's+-- default attribute. If you want to change the default attribute, use+-- 'withDefAttr'. -- -- For example: --@@ -1539,17 +1545,17 @@  -- | The base attribute for scroll bars. scrollbarAttr :: AttrName-scrollbarAttr = "scrollbar"+scrollbarAttr = attrName "scrollbar"  -- | The attribute for scroll bar troughs. This attribute is a -- specialization of @scrollbarAttr@. scrollbarTroughAttr :: AttrName-scrollbarTroughAttr = scrollbarAttr <> "trough"+scrollbarTroughAttr = scrollbarAttr <> attrName "trough"  -- | The attribute for scroll bar handles. This attribute is a -- specialization of @scrollbarAttr@. scrollbarHandleAttr :: AttrName-scrollbarHandleAttr = scrollbarAttr <> "handle"+scrollbarHandleAttr = scrollbarAttr <> attrName "handle"  maybeClick :: (Ord n)            => n
src/Brick/Widgets/Dialog.hs view
@@ -72,9 +72,9 @@  suffixLenses ''Dialog -handleDialogEvent :: Event -> Dialog a -> EventM n (Dialog a)-handleDialogEvent ev d =-    return $ case ev of+handleDialogEvent :: Event -> EventM n (Dialog a) ()+handleDialogEvent ev = do+    modify $ \d -> case ev of         EvKey (KChar '\t') [] -> nextButtonBy 1 True d         EvKey KBackTab [] -> nextButtonBy (-1) True d         EvKey KRight [] -> nextButtonBy 1 False d@@ -99,15 +99,15 @@  -- | The default attribute of the dialog dialogAttr :: AttrName-dialogAttr = "dialog"+dialogAttr = attrName "dialog"  -- | The default attribute for all dialog buttons buttonAttr :: AttrName-buttonAttr = "button"+buttonAttr = attrName "button"  -- | The attribute for the selected dialog button (extends 'dialogAttr') buttonSelectedAttr :: AttrName-buttonSelectedAttr = buttonAttr <> "selected"+buttonSelectedAttr = buttonAttr <> attrName "selected"  -- | Render a dialog with the specified body widget. This renders the -- dialog as a layer, which makes this suitable as a top-level layer in
src/Brick/Widgets/Edit.hs view
@@ -62,6 +62,7 @@  -- | Editor state.  Editors support the following events by default: --+-- * Mouse clicks: change cursor position -- * Meta-<: go to beginning of file -- * Meta->: go to end of file -- * Ctrl-a, Home: go to beginning of line@@ -118,11 +119,10 @@  handleEditorEvent :: (Eq n, DecodeUtf8 t, Eq t, Z.GenericTextZipper t)                   => BrickEvent n e-                  -> Editor t n-                  -> EventM n (Editor t n)-handleEditorEvent e ed = return $ applyEdit f ed-    where-        f = case e of+                  -> EventM n (Editor t n) ()+handleEditorEvent e = do+    ed <- get+    let f = case e of               VtyEvent ev ->                   handleVtyEvent ev               MouseDown n _ _ (Location pos) | n == getName ed ->@@ -156,6 +156,7 @@             EvKey (KChar '<') [MMeta] -> Z.gotoBOF             EvKey (KChar '>') [MMeta] -> Z.gotoEOF             _ -> id+    put $ applyEdit f ed  -- | Construct an editor over 'Text' values editorText :: n@@ -192,12 +193,12 @@  -- | The attribute assigned to the editor when it does not have focus. editAttr :: AttrName-editAttr = "edit"+editAttr = attrName "edit"  -- | The attribute assigned to the editor when it has focus. Extends -- 'editAttr'. editFocusedAttr :: AttrName-editFocusedAttr = editAttr <> "focused"+editFocusedAttr = editAttr <> attrName "focused"  -- | Get the contents of the editor. getEditContents :: Monoid t => Editor t n -> [t]
src/Brick/Widgets/FileBrowser.hs view
@@ -155,6 +155,7 @@ import qualified Data.Set as Set import qualified Data.Vector as V import Lens.Micro+import Lens.Micro.Mtl ((%=)) import Lens.Micro.TH (lensRules, generateUpdateableOptics) import qualified Graphics.Vty as Vty import qualified System.Directory as D@@ -164,7 +165,7 @@ import Text.Printf (printf)  import Brick.Types-import Brick.AttrMap (AttrName)+import Brick.AttrMap (AttrName, attrName) import Brick.Widgets.Core import Brick.Widgets.List @@ -356,10 +357,10 @@             Left (_::E.IOException) -> entries             Right parent -> parent : entries -    let b' = setEntries allEntries b-    return $ b' & fileBrowserWorkingDirectoryL .~ path-                & fileBrowserExceptionL .~ exc-                & fileBrowserSelectedFilesL .~ mempty+    return $ (setEntries allEntries b)+                 & fileBrowserWorkingDirectoryL .~ path+                 & fileBrowserExceptionL .~ exc+                 & fileBrowserSelectedFilesL .~ mempty  parentOf :: FilePath -> IO FileInfo parentOf path = getFileInfo ".." $ FP.takeDirectory path@@ -593,126 +594,120 @@ -- * @Esc@, @Ctrl-C@: cancel search mode -- * Text input: update search string -actionFileBrowserBeginSearch :: FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserBeginSearch b =-    return $ updateFileBrowserSearch (const $ Just "") b+actionFileBrowserBeginSearch :: EventM n (FileBrowser n) ()+actionFileBrowserBeginSearch =+    modify $ updateFileBrowserSearch (const $ Just "") -actionFileBrowserSelectEnter :: FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserSelectEnter b =-    maybeSelectCurrentEntry b+actionFileBrowserSelectEnter :: EventM n (FileBrowser n) ()+actionFileBrowserSelectEnter =+    maybeSelectCurrentEntry -actionFileBrowserSelectCurrent :: FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserSelectCurrent b =-    selectCurrentEntry b+actionFileBrowserSelectCurrent :: EventM n (FileBrowser n) ()+actionFileBrowserSelectCurrent =+    selectCurrentEntry -actionFileBrowserListPageUp :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListPageUp b = do-    let old = b ^. fileBrowserEntriesL-    new <- listMovePageUp old-    return $ b & fileBrowserEntriesL .~ new+actionFileBrowserListPageUp :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListPageUp =+    zoom fileBrowserEntriesL listMovePageUp -actionFileBrowserListPageDown :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListPageDown b = do-    let old = b ^. fileBrowserEntriesL-    new <- listMovePageDown old-    return $ b & fileBrowserEntriesL .~ new+actionFileBrowserListPageDown :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListPageDown =+    zoom fileBrowserEntriesL listMovePageDown -actionFileBrowserListHalfPageUp :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListHalfPageUp b = do-    let old = b ^. fileBrowserEntriesL-    new <- listMoveByPages (-0.5::Double) old-    return $ b & fileBrowserEntriesL .~ new+actionFileBrowserListHalfPageUp :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListHalfPageUp =+    zoom fileBrowserEntriesL (listMoveByPages (-0.5::Double)) -actionFileBrowserListHalfPageDown :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListHalfPageDown b = do-    let old = b ^. fileBrowserEntriesL-    new <- listMoveByPages (0.5::Double) old-    return $ b & fileBrowserEntriesL .~ new+actionFileBrowserListHalfPageDown :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListHalfPageDown =+    zoom fileBrowserEntriesL (listMoveByPages (0.5::Double)) -actionFileBrowserListTop :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListTop b =-    return $ b & fileBrowserEntriesL %~ listMoveTo 0+actionFileBrowserListTop :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListTop =+    fileBrowserEntriesL %= listMoveTo 0 -actionFileBrowserListBottom :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListBottom b = do+actionFileBrowserListBottom :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListBottom = do+    b <- get     let sz = length (listElements $ b^.fileBrowserEntriesL)-    return $ b & fileBrowserEntriesL %~ listMoveTo (sz - 1)+    fileBrowserEntriesL %= listMoveTo (sz - 1) -actionFileBrowserListNext :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListNext b =-    return $ b & fileBrowserEntriesL %~ listMoveBy 1+actionFileBrowserListNext :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListNext =+    fileBrowserEntriesL %= listMoveBy 1 -actionFileBrowserListPrev :: Ord n => FileBrowser n -> EventM n (FileBrowser n)-actionFileBrowserListPrev b =-    return $ b & fileBrowserEntriesL %~ listMoveBy (-1)+actionFileBrowserListPrev :: Ord n => EventM n (FileBrowser n) ()+actionFileBrowserListPrev =+    fileBrowserEntriesL %= listMoveBy (-1) -handleFileBrowserEvent :: (Ord n) => Vty.Event -> FileBrowser n -> EventM n (FileBrowser n)-handleFileBrowserEvent e b =+handleFileBrowserEvent :: (Ord n) => Vty.Event -> EventM n (FileBrowser n) ()+handleFileBrowserEvent e = do+    b <- get     if fileBrowserIsSearching b-    then handleFileBrowserEventSearching e b-    else handleFileBrowserEventNormal e b+        then handleFileBrowserEventSearching e+        else handleFileBrowserEventNormal e  safeInit :: T.Text -> T.Text safeInit t | T.length t == 0 = t            | otherwise = T.init t -handleFileBrowserEventSearching :: (Ord n) => Vty.Event -> FileBrowser n -> EventM n (FileBrowser n)-handleFileBrowserEventSearching e b =+handleFileBrowserEventSearching :: (Ord n) => Vty.Event -> EventM n (FileBrowser n) ()+handleFileBrowserEventSearching e =     case e of         Vty.EvKey (Vty.KChar 'c') [Vty.MCtrl] ->-            return $ updateFileBrowserSearch (const Nothing) b+            modify $ updateFileBrowserSearch (const Nothing)         Vty.EvKey Vty.KEsc [] ->-            return $ updateFileBrowserSearch (const Nothing) b+            modify $ updateFileBrowserSearch (const Nothing)         Vty.EvKey Vty.KBS [] ->-            return $ updateFileBrowserSearch (fmap safeInit) b-        Vty.EvKey Vty.KEnter [] ->-            updateFileBrowserSearch (const Nothing) <$>-                maybeSelectCurrentEntry b+            modify $ updateFileBrowserSearch (fmap safeInit)+        Vty.EvKey Vty.KEnter [] -> do+            maybeSelectCurrentEntry+            modify $ updateFileBrowserSearch (const Nothing)         Vty.EvKey (Vty.KChar c) [] ->-            return $ updateFileBrowserSearch (fmap (flip T.snoc c)) b+            modify $ updateFileBrowserSearch (fmap (flip T.snoc c))         _ ->-            handleFileBrowserEventCommon e b+            handleFileBrowserEventCommon e -handleFileBrowserEventNormal :: (Ord n) => Vty.Event -> FileBrowser n -> EventM n (FileBrowser n)-handleFileBrowserEventNormal e b =+handleFileBrowserEventNormal :: (Ord n) => Vty.Event -> EventM n (FileBrowser n) ()+handleFileBrowserEventNormal e =     case e of         Vty.EvKey (Vty.KChar '/') [] ->             -- Begin file search-            actionFileBrowserBeginSearch b+            actionFileBrowserBeginSearch         Vty.EvKey Vty.KEnter [] ->             -- Select file or enter directory-            actionFileBrowserSelectEnter b+            actionFileBrowserSelectEnter         Vty.EvKey (Vty.KChar ' ') [] ->             -- Select entry-            actionFileBrowserSelectCurrent b+            actionFileBrowserSelectCurrent         _ ->-            handleFileBrowserEventCommon e b+            handleFileBrowserEventCommon e -handleFileBrowserEventCommon :: (Ord n) => Vty.Event -> FileBrowser n -> EventM n (FileBrowser n)-handleFileBrowserEventCommon e b =+handleFileBrowserEventCommon :: (Ord n) => Vty.Event -> EventM n (FileBrowser n) ()+handleFileBrowserEventCommon e =     case e of         Vty.EvKey (Vty.KChar 'b') [Vty.MCtrl] ->-            actionFileBrowserListPageUp b+            actionFileBrowserListPageUp         Vty.EvKey (Vty.KChar 'f') [Vty.MCtrl] ->-            actionFileBrowserListPageDown b+            actionFileBrowserListPageDown         Vty.EvKey (Vty.KChar 'd') [Vty.MCtrl] ->-            actionFileBrowserListHalfPageDown b+            actionFileBrowserListHalfPageDown         Vty.EvKey (Vty.KChar 'u') [Vty.MCtrl] ->-            actionFileBrowserListHalfPageUp b+            actionFileBrowserListHalfPageUp         Vty.EvKey (Vty.KChar 'g') [] ->-            actionFileBrowserListTop b+            actionFileBrowserListTop         Vty.EvKey (Vty.KChar 'G') [] ->-            actionFileBrowserListBottom b+            actionFileBrowserListBottom         Vty.EvKey (Vty.KChar 'j') [] ->-            actionFileBrowserListNext b+            actionFileBrowserListNext         Vty.EvKey (Vty.KChar 'k') [] ->-            actionFileBrowserListPrev b+            actionFileBrowserListPrev         Vty.EvKey (Vty.KChar 'n') [Vty.MCtrl] ->-            actionFileBrowserListNext b+            actionFileBrowserListNext         Vty.EvKey (Vty.KChar 'p') [Vty.MCtrl] ->-            actionFileBrowserListPrev b+            actionFileBrowserListPrev         _ ->-            handleEventLensed b fileBrowserEntriesL handleListEvent e+            zoom fileBrowserEntriesL $ handleListEvent e  -- | If the browser's current entry is selectable according to -- @fileBrowserSelectable@, add it to the selection set and return.@@ -720,30 +715,32 @@ -- directory, set the browser's current path to the selected directory. -- -- Otherwise, return the browser state unchanged.-maybeSelectCurrentEntry :: FileBrowser n -> EventM n (FileBrowser n)-maybeSelectCurrentEntry b =+maybeSelectCurrentEntry :: EventM n (FileBrowser n) ()+maybeSelectCurrentEntry = do+    b <- get     case fileBrowserCursor b of-        Nothing -> return b+        Nothing -> return ()         Just entry ->             if fileBrowserSelectable b entry-            then return $ b & fileBrowserSelectedFilesL %~ Set.insert (fileInfoFilename entry)+            then fileBrowserSelectedFilesL %= Set.insert (fileInfoFilename entry)             else case fileInfoFileType entry of                 Just Directory ->-                    liftIO $ setWorkingDirectory (fileInfoFilePath entry) b+                    put =<< (liftIO $ setWorkingDirectory (fileInfoFilePath entry) b)                 Just SymbolicLink ->                     case fileInfoLinkTargetType entry of-                        Just Directory -> do-                            liftIO $ setWorkingDirectory (fileInfoFilePath entry) b+                        Just Directory ->+                            put =<< (liftIO $ setWorkingDirectory (fileInfoFilePath entry) b)                         _ ->-                            return b+                            return ()                 _ ->-                    return b+                    return () -selectCurrentEntry :: FileBrowser n -> EventM n (FileBrowser n)-selectCurrentEntry b =+selectCurrentEntry :: EventM n (FileBrowser n) ()+selectCurrentEntry = do+    b <- get     case fileBrowserCursor b of-        Nothing -> return b-        Just e -> return $ b & fileBrowserSelectedFilesL %~ Set.insert (fileInfoFilename e)+        Nothing -> return ()+        Just e -> fileBrowserSelectedFilesL %= Set.insert (fileInfoFilename e)  -- | Render a file browser. This renders a list of entries in the -- working directory, a cursor to select from among the entries, a@@ -840,49 +837,49 @@  -- | The base attribute for all file browser attributes. fileBrowserAttr :: AttrName-fileBrowserAttr = "fileBrowser"+fileBrowserAttr = attrName "fileBrowser"  -- | The attribute used for the current directory displayed at the top -- of the browser. fileBrowserCurrentDirectoryAttr :: AttrName-fileBrowserCurrentDirectoryAttr = fileBrowserAttr <> "currentDirectory"+fileBrowserCurrentDirectoryAttr = fileBrowserAttr <> attrName "currentDirectory"  -- | The attribute used for the entry information displayed at the -- bottom of the browser. fileBrowserSelectionInfoAttr :: AttrName-fileBrowserSelectionInfoAttr = fileBrowserAttr <> "selectionInfo"+fileBrowserSelectionInfoAttr = fileBrowserAttr <> attrName "selectionInfo"  -- | The attribute used to render directory entries. fileBrowserDirectoryAttr :: AttrName-fileBrowserDirectoryAttr = fileBrowserAttr <> "directory"+fileBrowserDirectoryAttr = fileBrowserAttr <> attrName "directory"  -- | The attribute used to render block device entries. fileBrowserBlockDeviceAttr :: AttrName-fileBrowserBlockDeviceAttr = fileBrowserAttr <> "block"+fileBrowserBlockDeviceAttr = fileBrowserAttr <> attrName "block"  -- | The attribute used to render regular file entries. fileBrowserRegularFileAttr :: AttrName-fileBrowserRegularFileAttr = fileBrowserAttr <> "regular"+fileBrowserRegularFileAttr = fileBrowserAttr <> attrName "regular"  -- | The attribute used to render character device entries. fileBrowserCharacterDeviceAttr :: AttrName-fileBrowserCharacterDeviceAttr = fileBrowserAttr <> "char"+fileBrowserCharacterDeviceAttr = fileBrowserAttr <> attrName "char"  -- | The attribute used to render named pipe entries. fileBrowserNamedPipeAttr :: AttrName-fileBrowserNamedPipeAttr = fileBrowserAttr <> "pipe"+fileBrowserNamedPipeAttr = fileBrowserAttr <> attrName "pipe"  -- | The attribute used to render symbolic link entries. fileBrowserSymbolicLinkAttr :: AttrName-fileBrowserSymbolicLinkAttr = fileBrowserAttr <> "symlink"+fileBrowserSymbolicLinkAttr = fileBrowserAttr <> attrName "symlink"  -- | The attribute used to render Unix socket entries. fileBrowserUnixSocketAttr :: AttrName-fileBrowserUnixSocketAttr = fileBrowserAttr <> "unixSocket"+fileBrowserUnixSocketAttr = fileBrowserAttr <> attrName "unixSocket"  -- | The attribute used for selected entries in the file browser. fileBrowserSelectedAttr :: AttrName-fileBrowserSelectedAttr = fileBrowserAttr <> "selected"+fileBrowserSelectedAttr = fileBrowserAttr <> attrName "selected"  -- | A file type filter for use with 'setFileBrowserEntryFilter'. This -- filter permits entries whose file types are in the specified list.
src/Brick/Widgets/Internal.hs view
@@ -10,9 +10,8 @@  import Lens.Micro ((^.), (&), (%~)) import Lens.Micro.Mtl ((%=))-import Control.Monad (forM_)-import Control.Monad.Trans.State.Lazy-import Control.Monad.Trans.Reader+import Control.Monad.State.Strict+import Control.Monad.Reader import Data.Maybe (fromMaybe) import qualified Data.Map as M import qualified Data.Set as S
src/Brick/Widgets/List.hs view
@@ -37,6 +37,7 @@   , listSelectedL   , listNameL   , listItemHeightL+  , listSelectedElementL    -- * Accessors   , listElements@@ -79,9 +80,9 @@  import Control.Applicative ((<|>)) import Data.Foldable (find, toList)-import Control.Monad.Trans.State (evalState, get, put)+import Control.Monad.State (evalState) -import Lens.Micro ((^.), (^?), (&), (.~), (%~), _2, _head, set)+import Lens.Micro (Traversal', (^.), (^?), (&), (.~), (%~), _2, set) import Data.Functor (($>)) import Data.List.NonEmpty (NonEmpty((:|))) import Data.Maybe (fromMaybe)@@ -193,17 +194,16 @@ -- * Go to last element (End) handleListEvent :: (Foldable t, Splittable t, Ord n)                 => Event-                -> GenericList n t e-                -> EventM n (GenericList n t e)-handleListEvent e theList =+                -> EventM n (GenericList n t e) ()+handleListEvent e =     case e of-        EvKey KUp [] -> return $ listMoveUp theList-        EvKey KDown [] -> return $ listMoveDown theList-        EvKey KHome [] -> return $ listMoveToBeginning theList-        EvKey KEnd [] -> return $ listMoveToEnd theList-        EvKey KPageDown [] -> listMovePageDown theList-        EvKey KPageUp [] -> listMovePageUp theList-        _ -> return theList+        EvKey KUp [] -> modify listMoveUp+        EvKey KDown [] -> modify listMoveDown+        EvKey KHome [] -> modify listMoveToBeginning+        EvKey KEnd [] -> modify listMoveToEnd+        EvKey KPageDown [] -> listMovePageDown+        EvKey KPageUp [] -> listMovePageUp+        _ -> return ()  -- | Enable list movement with the vi keys with a fallback handler if -- none match. Use 'handleListEventVi' 'handleListEvent' in place of@@ -219,23 +219,22 @@ -- * Go to first element (g) -- * Go to last element (G) handleListEventVi :: (Foldable t, Splittable t, Ord n)-                  => (Event -> GenericList n t e -> EventM n (GenericList n t e))+                  => (Event -> EventM n (GenericList n t e) ())                   -- ^ Fallback event handler to use if none of the vi keys                   -- match.                   -> Event-                  -> GenericList n t e-                  -> EventM n (GenericList n t e)-handleListEventVi fallback e theList =+                  -> EventM n (GenericList n t e) ()+handleListEventVi fallback e =     case e of-        EvKey (KChar 'k') [] -> return $ listMoveUp theList-        EvKey (KChar 'j') [] -> return $ listMoveDown theList-        EvKey (KChar 'g') [] -> return $ listMoveToBeginning theList-        EvKey (KChar 'G') [] -> return $ listMoveToEnd theList-        EvKey (KChar 'f') [MCtrl] -> listMovePageDown theList-        EvKey (KChar 'b') [MCtrl] -> listMovePageUp theList-        EvKey (KChar 'd') [MCtrl] -> listMoveByPages (0.5::Double) theList-        EvKey (KChar 'u') [MCtrl] -> listMoveByPages (-0.5::Double) theList-        _ -> fallback e theList+        EvKey (KChar 'k') []      -> modify listMoveUp+        EvKey (KChar 'j') []      -> modify listMoveDown+        EvKey (KChar 'g') []      -> modify listMoveToBeginning+        EvKey (KChar 'G') []      -> modify listMoveToEnd+        EvKey (KChar 'f') [MCtrl] -> listMovePageDown+        EvKey (KChar 'b') [MCtrl] -> listMovePageUp+        EvKey (KChar 'd') [MCtrl] -> listMoveByPages (0.5::Double)+        EvKey (KChar 'u') [MCtrl] -> listMoveByPages (-0.5::Double)+        _                         -> fallback e  -- | Move the list selection to the first element in the list. listMoveToBeginning :: (Foldable t, Splittable t)@@ -251,17 +250,17 @@  -- | The top-level attribute used for the entire list. listAttr :: AttrName-listAttr = "list"+listAttr = attrName "list"  -- | The attribute used only for the currently-selected list item when -- the list does not have focus. Extends 'listAttr'. listSelectedAttr :: AttrName-listSelectedAttr = listAttr <> "selected"+listSelectedAttr = listAttr <> attrName "selected"  -- | The attribute used only for the currently-selected list item when -- the list has focus. Extends 'listSelectedAttr'. listSelectedFocusedAttr :: AttrName-listSelectedFocusedAttr = listSelectedAttr <> "focused"+listSelectedFocusedAttr = listSelectedAttr <> attrName "focused"  -- | Construct a list in terms of container 't' with element type 'e'. list :: (Foldable t)@@ -474,8 +473,7 @@  -- | Move the list selected index up by one page. listMovePageUp :: (Foldable t, Splittable t, Ord n)-               => GenericList n t e-               -> EventM n (GenericList n t e)+               => EventM n (GenericList n t e) () listMovePageUp = listMoveByPages (-1::Double)  -- | Move the list selected index down by one. (Moves the cursor down,@@ -487,23 +485,22 @@  -- | Move the list selected index down by one page. listMovePageDown :: (Foldable t, Splittable t, Ord n)-                 => GenericList n t e-                 -> EventM n (GenericList n t e)+                 => EventM n (GenericList n t e) () listMovePageDown = listMoveByPages (1::Double)  -- | Move the list selected index by some (fractional) number of pages. listMoveByPages :: (Foldable t, Splittable t, Ord n, RealFrac m)                 => m-                -> GenericList n t e-                -> EventM n (GenericList n t e)-listMoveByPages pages theList = do+                -> EventM n (GenericList n t e) ()+listMoveByPages pages = do+    theList <- get     v <- lookupViewport (theList^.listNameL)     case v of-        Nothing -> return theList+        Nothing -> return ()         Just vp -> do             let nElems = round $ pages * fromIntegral (vp^.vpSize._2) /                                  fromIntegral (theList^.listItemHeightL)-            return $ listMoveBy nElems theList+            modify $ listMoveBy nElems  -- | Move the list selected index. --@@ -602,6 +599,28 @@         result = tailResult <|> headResult     in maybe id (set listSelectedL . Just . fst) result l +-- | Traversal that targets the selected element, if any.+--+-- Complexity: depends on usage as well as the list's container type.+--+-- @+-- listSelectedElementL for 'List': O(1) -- preview, fold+--                                O(n) -- set, modify, traverse+-- listSelectedElementL for 'Seq.Seq': O(log(min(i, n - i)))  -- all operations+-- @+--+listSelectedElementL :: (Splittable t, Traversable t, Semigroup (t e))+                     => Traversal' (GenericList n t e) e+listSelectedElementL f l =+    case l ^. listSelectedL of+        Nothing -> pure l+        Just i -> listElementsL go l+            where+                go l' = let (left, rest) = splitAt i l'+                            -- middle contains the target element (if any)+                            (middle, right) = splitAt 1 rest+                        in (\m -> left <> m <> right) <$> (traverse f middle)+ -- | Return a list's selected element, if any. -- -- Only evaluates as much of the container as needed.@@ -612,13 +631,11 @@ -- listSelectedElement for 'List': O(1) -- listSelectedElement for 'Seq.Seq': O(log(min(i, n - i))) -- @-listSelectedElement :: (Splittable t, Foldable t)+listSelectedElement :: (Splittable t, Traversable t, Semigroup (t e))                     => GenericList n t e                     -> Maybe (Int, e)-listSelectedElement l = do-    sel <- l^.listSelectedL-    let (_, xs) = splitAt sel (l ^. listElementsL)-    (sel,) <$> toList xs ^? _head+listSelectedElement l =+    (,) <$> l^.listSelectedL <*> l^?listSelectedElementL  -- | Remove all elements from the list and clear the selection. --@@ -647,11 +664,16 @@ -- -- Complexity: same as 'traverse' for the container type (typically -- /O(n)/).-listModify :: (Traversable t)+--+-- Complexity: same as 'listSelectedElementL' for the list's container type.+--+-- @+-- listModify for 'List': O(n)+-- listModify for 'Seq.Seq': O(log(min(i, n - i)))+-- @+--+listModify :: (Traversable t, Splittable t, Semigroup (t e))            => (e -> e)            -> GenericList n t e            -> GenericList n t e-listModify f l =-    case l ^. listSelectedL of-        Nothing -> l-        Just j -> l & listElementsL %~ imap (\i e -> if i == j then f e else e)+listModify f = listSelectedElementL %~ f
src/Brick/Widgets/ProgressBar.hs view
@@ -22,11 +22,11 @@  -- | The attribute of the completed portion of the progress bar. progressCompleteAttr :: AttrName-progressCompleteAttr = "progressComplete"+progressCompleteAttr = attrName "progressComplete"  -- | The attribute of the incomplete portion of the progress bar. progressIncompleteAttr :: AttrName-progressIncompleteAttr = "progressIncomplete"+progressIncompleteAttr = attrName "progressIncomplete"  -- | Draw a progress bar with the specified (optional) label and -- progress value. This fills available horizontal space and is one row
tests/List.hs view
@@ -1,14 +1,14 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeSynonymInstances #-}- module List-  (-    main-  ) where+  ( main+  )+where  import Prelude hiding (reverse, splitAt) @@ -18,7 +18,9 @@ import Data.Maybe (isNothing) import Data.Monoid (Endo(..)) import Data.Proxy+#if !(MIN_VERSION_base(4,11,0)) import Data.Semigroup (Semigroup((<>)))+#endif  import qualified Data.Sequence as Seq import qualified Data.Vector as V@@ -29,57 +31,56 @@ import Brick.Widgets.List  instance (Arbitrary n, Arbitrary a) => Arbitrary (List n a) where-  arbitrary = list <$> arbitrary <*> (V.fromList <$> arbitrary) <*> pure 1-+    arbitrary = list <$> arbitrary <*> (V.fromList <$> arbitrary) <*> pure 1  -- List move operations that never modify the underlying list-data ListMoveOp a-  = MoveUp-  | MoveDown-  | MoveBy Int-  | MoveTo Int-  | MoveToElement a-  | FindElement a-  deriving (Show)+data ListMoveOp a =+    MoveUp+    | MoveDown+    | MoveBy Int+    | MoveTo Int+    | MoveToElement a+    | FindElement a+    deriving (Show)  instance Arbitrary a => Arbitrary (ListMoveOp a) where-  arbitrary = oneof-    [ pure MoveUp-    , pure MoveDown-    , MoveBy <$> arbitrary-    , MoveTo <$> arbitrary-    , MoveToElement <$> arbitrary-    , FindElement <$> arbitrary-    ]+    arbitrary =+        oneof [ pure MoveUp+              , pure MoveDown+              , MoveBy <$> arbitrary+              , MoveTo <$> arbitrary+              , MoveToElement <$> arbitrary+              , FindElement <$> arbitrary+              ]  -- List operations.  We don't have "page"-based movement operations -- because these depend on render context (i.e. effect in EventM)-data ListOp a-  = Insert Int a-  | Remove Int-  | Replace Int [a]-  | Clear-  | Reverse-  | ListMoveOp (ListMoveOp a)-  deriving (Show)+data ListOp a =+    Insert Int a+    | Remove Int+    | Replace Int [a]+    | Clear+    | Reverse+    | ListMoveOp (ListMoveOp a)+    deriving (Show)  instance Arbitrary a => Arbitrary (ListOp a) where-  arbitrary = frequency-    [ (1, Insert <$> arbitrary <*> arbitrary)-    , (1, Remove <$> arbitrary)-    , (1, Replace <$> arbitrary <*> arbitrary)-    , (1, pure Clear)-    , (1, pure Reverse)-    , (6, arbitrary)-    ]+    arbitrary =+        frequency [ (1, Insert <$> arbitrary <*> arbitrary)+                  , (1, Remove <$> arbitrary)+                  , (1, Replace <$> arbitrary <*> arbitrary)+                  , (1, pure Clear)+                  , (1, pure Reverse)+                  , (6, arbitrary)+                  ]  -- Turn a ListOp into a List endomorphism op :: Eq a => ListOp a -> List n a -> List n a op (Insert i a) = listInsert i a op (Remove i) = listRemove i op (Replace i xs) =-  -- avoid setting index to Nothing-  listReplace (V.fromList xs) (Just i)+    -- avoid setting index to Nothing+    listReplace (V.fromList xs) (Just i) op Clear = listClear op Reverse = listReverse op (ListMoveOp mo) = moveOp mo@@ -93,63 +94,61 @@ moveOp (MoveToElement a) = listMoveToElement a moveOp (FindElement a) = listFindBy (== a) -applyListOps-  :: (Foldable t)-  => (op a -> List n a -> List n a) -> t (op a) -> List n a -> List n a+applyListOps :: (Foldable t)+             => (op a -> List n a -> List n a)+             -> t (op a)+             -> List n a+             -> List n a applyListOps f = appEndo . foldMap (Endo . f) - -- | Initial selection is always 0 (or Nothing for empty list) prop_initialSelection :: [a] -> Bool prop_initialSelection xs =-  list () (V.fromList xs) 1 ^. listSelectedL ==-    if null xs then Nothing else Just 0+    list () (V.fromList xs) 1 ^. listSelectedL ==+        if null xs then Nothing else Just 0  -- list operations keep the selected index in bounds-prop_listOpsMaintainSelectedValid-  :: (Eq a) => [ListOp a] -> List n a -> Bool+prop_listOpsMaintainSelectedValid :: (Eq a)+                                  => [ListOp a]+                                  -> List n a+                                  -> Bool prop_listOpsMaintainSelectedValid ops l =-  let l' = applyListOps op ops l-  in-    case l' ^. listSelectedL of-      -- either there is no selection and list is empty-      Nothing -> null l'-      -- or the selected index is valid-      Just i -> i >= 0 && i < length l'+    let l' = applyListOps op ops l+    in case l' ^. listSelectedL of+        -- either there is no selection and list is empty+        Nothing -> null l'+        -- or the selected index is valid+        Just i -> i >= 0 && i < length l'  -- reversing a list keeps the selected element the same-prop_reverseMaintainsSelectedElement-  :: (Eq a) => [ListOp a] -> List n a -> Bool+prop_reverseMaintainsSelectedElement :: (Eq a)+                                     => [ListOp a]+                                     -> List n a+                                     -> Bool prop_reverseMaintainsSelectedElement ops l =-  let     -- apply some random list ops to (probably) set a selected element-    l' = applyListOps op ops l-    l'' = listReverse l'-  in-    fmap snd (listSelectedElement l') == fmap snd (listSelectedElement l'')+    let l' = applyListOps op ops l+        l'' = listReverse l'+    in fmap snd (listSelectedElement l') == fmap snd (listSelectedElement l'')  -- reversing maintains size of list prop_reverseMaintainsSizeOfList :: List n a -> Bool prop_reverseMaintainsSizeOfList l =-  length l == length (listReverse l)+    length l == length (listReverse l)  -- an inserted element may always be found at the given index -- (when target index is clamped to 0 <= n <= len) prop_insert :: (Eq a) => Int -> a -> List n a -> Bool prop_insert i a l =-  let-    l' = listInsert i a l-    i' = clamp 0 (length l) i-  in-    listSelectedElement (listMoveTo i' l') == Just (i', a)+    let l' = listInsert i a l+        i' = clamp 0 (length l) i+    in listSelectedElement (listMoveTo i' l') == Just (i', a)  -- inserting anywhere always increases size of list by 1 prop_insertSize :: (Eq a) => Int -> a -> List n a -> Bool prop_insertSize i a l =-  let-    l' = listInsert i a l-  in-    length l' == length l + 1+    let l' = listInsert i a l+    in length l' == length l + 1  -- inserting an element and moving to it always succeeds and -- the selected element is the one we inserted.@@ -160,11 +159,9 @@ -- prop_insertMoveTo :: (Eq a) => [ListOp a] -> List n a -> Int -> a -> Bool prop_insertMoveTo ops l i a =-  let-    l' = listInsert i a (applyListOps op ops l)-    sel = listSelectedElement (listMoveToElement a l')-  in-    fmap snd sel == Just a+    let l' = listInsert i a (applyListOps op ops l)+        sel = listSelectedElement (listMoveToElement a l')+    in fmap snd sel == Just a  -- inserting an element and repeatedly seeking it always -- reaches the element we inserted, at the index where we@@ -172,47 +169,38 @@ -- prop_insertFindBy :: (Eq a) => [ListOp a] -> List n a -> Int -> a -> Bool prop_insertFindBy ops l i a =-  let-    l' = applyListOps op ops l-    l'' = set listSelectedL Nothing . listInsert i a $ l'-    seeks = converging ((==) `on` (^. listSelectedL)) (listFindBy (== a)) l''-    i' = clamp 0 (length l') i -- we can't have inserted past len-  in-    (find ((== Just i') . (^. listSelectedL)) seeks >>= listSelectedElement)-    == Just (i', a)+    let l' = applyListOps op ops l+        l'' = set listSelectedL Nothing . listInsert i a $ l'+        seeks = converging ((==) `on` (^. listSelectedL)) (listFindBy (== a)) l''+        i' = clamp 0 (length l') i -- we can't have inserted past len+    in (find ((== Just i') . (^. listSelectedL)) seeks >>= listSelectedElement) == Just (i', a)  -- inserting then deleting always yields a list with the original elems prop_insertRemove :: (Eq a) => Int -> a -> List n a -> Bool prop_insertRemove i a l =-  let-    i' = clamp 0 (length l) i-    l' = listInsert i' a l -- pre-clamped-    l'' = listRemove i' l'-  in-    l'' ^. listElementsL == l ^. listElementsL+    let i' = clamp 0 (length l) i+        l' = listInsert i' a l -- pre-clamped+        l'' = listRemove i' l'+    in l'' ^. listElementsL == l ^. listElementsL  -- deleting in-bounds always reduces size of list by 1 -- deleting out-of-bounds never changes list size prop_remove :: Int -> List n a -> Bool prop_remove i l =-  let-    len = length l-    i' = clamp 0 (len - 1) i-    test-      | len > 0 && i == i' = (== len - 1)  -- i is in bounds-      | otherwise = (== len)               -- i is out of bounds-  in-    test (length (listRemove i l))+    let len = length l+        i' = clamp 0 (len - 1) i+        test+            | len > 0 && i == i' = (== len - 1)  -- i is in bounds+            | otherwise = (== len)               -- i is out of bounds+    in test (length (listRemove i l))  -- deleting an element and re-inserting it at same position -- gives the original list elements prop_removeInsert :: (Eq a) => Int -> List n a -> Bool prop_removeInsert i l =-  let-    sel = listSelectedElement (listMoveTo i l)-    l' = maybe id (\(i', a) -> listInsert i' a . listRemove i') sel l-  in-    l' ^. listElementsL == l ^. listElementsL+    let sel = listSelectedElement (listMoveTo i l)+        l' = maybe id (\(i', a) -> listInsert i' a . listRemove i') sel l+    in l' ^. listElementsL == l ^. listElementsL  -- Apply @f@ until @test a (f a) == True@, then return @a@. converge :: (a -> a -> Bool) -> (a -> a) -> a -> a@@ -222,64 +210,57 @@ -- intermediate and final values as a list. converging :: (a -> a -> Bool) -> (a -> a) -> a -> [a] converging test f a-  | test a (f a) = [a]-  | otherwise = a : converging test f (f a)+    | test a (f a) = [a]+    | otherwise = a : converging test f (f a)  -- listMoveUp always reaches 0 (or list is empty) prop_moveUp :: (Eq a) => [ListOp a] -> List n a -> Bool prop_moveUp ops l =-  let-    l' = applyListOps op ops l-    l'' = converge ((==) `on` (^. listSelectedL)) listMoveUp l'-    len = length l''-  in-    maybe (len == 0) (== 0) (l'' ^. listSelectedL)+    let l' = applyListOps op ops l+        l'' = converge ((==) `on` (^. listSelectedL)) listMoveUp l'+        len = length l''+    in maybe (len == 0) (== 0) (l'' ^. listSelectedL)  -- listMoveDown always reaches end of list (or list is empty) prop_moveDown :: (Eq a) => [ListOp a] -> List n a -> Bool prop_moveDown ops l =-  let-    l' = applyListOps op ops l-    l'' = converge ((==) `on` (^. listSelectedL)) listMoveDown l'-    len = length l''-  in-    maybe (len == 0) (== len - 1) (l'' ^. listSelectedL)+    let l' = applyListOps op ops l+        l'' = converge ((==) `on` (^. listSelectedL)) listMoveDown l'+        len = length l''+    in maybe (len == 0) (== len - 1) (l'' ^. listSelectedL)  -- move ops never change the list prop_moveOpsNeverChangeList :: (Eq a) => [ListMoveOp a] -> List n a -> Bool prop_moveOpsNeverChangeList ops l =-  let-    l' = applyListOps moveOp ops l-  in-    l' ^. listElementsL == l ^. listElementsL+    let l' = applyListOps moveOp ops l+    in l' ^. listElementsL == l ^. listElementsL  -- If the list is empty, empty selection is used. -- Otherwise, if the specified selected index is not in list bounds, -- zero is used instead.-prop_replaceSetIndex-  :: (Eq a)-  => [ListOp a] -> List n a -> [a] -> Int -> Bool+prop_replaceSetIndex :: (Eq a)+                     => [ListOp a]+                     -> List n a+                     -> [a]+                     -> Int+                     -> Bool prop_replaceSetIndex ops l xs i =-  let-    v = V.fromList xs-    l' = applyListOps op ops l-    l'' = listReplace v (Just i) l'-    i' = clamp 0 (length v - 1) i-    inBounds = i == i'-  in-    l'' ^. listSelectedL == case (null v, inBounds) of-      (True, _) -> Nothing-      (False, True) -> Just i-      (False, False) -> Just 0+    let v = V.fromList xs+        l' = applyListOps op ops l+        l'' = listReplace v (Just i) l'+        i' = clamp 0 (length v - 1) i+        inBounds = i == i'+    in l'' ^. listSelectedL == case (null v, inBounds) of+        (True, _) -> Nothing+        (False, True) -> Just i+        (False, False) -> Just 0  -- Replacing with no index always clears the index prop_replaceNoIndex :: (Eq a) => [ListOp a] -> List n a -> [a] -> Bool prop_replaceNoIndex ops l xs =-  let-    v = V.fromList xs-    l' = applyListOps op ops l-  in-    isNothing (listReplace v Nothing l' ^. listSelectedL)+    let v = V.fromList xs+        l' = applyListOps op ops l+    in isNothing (listReplace v Nothing l' ^. listSelectedL)  -- | Move the list selected index. If the index is `Just x`, adjust by the -- specified amount; if it is `Nothing` (i.e. there is no selection) and the@@ -287,26 +268,19 @@ -- `Just (length - 1)` (last element). Subject to validation. prop_moveByWhenNoSelection :: List n a -> Int -> Property prop_moveByWhenNoSelection l amt =-  let-    l' = l & listSelectedL .~ Nothing-    len = length l-    expected = if amt > 0 then 0 else len - 1-  in-    len > 0 ==> listMoveBy amt l' ^. listSelectedL == Just expected-+    let l' = l & listSelectedL .~ Nothing+        len = length l+        expected = if amt > 0 then 0 else len - 1+    in len > 0 ==> listMoveBy amt l' ^. listSelectedL == Just expected  splitAtLength :: (Foldable t, Splittable t) => t a -> Int -> Bool splitAtLength l i =-  let-    len = length l-    (h, t) = splitAt i l-  in-    length h + length t == len-    && length h == clamp 0 len i+    let len = length l+        (h, t) = splitAt i l+    in length h + length t == len && length h == clamp 0 len i -splitAtAppend-  :: (Splittable t, Semigroup (t a), Eq (t a))-  => t a -> Int -> Bool+splitAtAppend :: (Splittable t, Semigroup (t a), Eq (t a))+              => t a -> Int -> Bool splitAtAppend l i = uncurry (<>) (splitAt i l) == l  prop_splitAtLength_Vector :: [a] -> Int -> Bool@@ -322,16 +296,14 @@ prop_splitAtAppend_Seq = splitAtAppend . Seq.fromList  -reverseSingleton-  :: forall t a. (Reversible t, Applicative t, Eq (t a))-  => Proxy t -> a -> Bool+reverseSingleton :: forall t a. (Reversible t, Applicative t, Eq (t a))+                 => Proxy t -> a -> Bool reverseSingleton _ a =-  let l = pure a :: t a-  in reverse l == l+    let l = pure a :: t a+    in reverse l == l -reverseAppend-  :: (Reversible t, Semigroup (t a), Eq (t a))-  => t a -> t a -> Bool+reverseAppend :: (Reversible t, Semigroup (t a), Eq (t a))+              => t a -> t a -> Bool reverseAppend l1 l2 =   reverse (l1 <> l2) == reverse l2 <> reverse l1 @@ -340,49 +312,54 @@  prop_reverseAppend_Vector :: (Eq a) => [a] -> [a] -> Bool prop_reverseAppend_Vector l1 l2 =-  reverseAppend (V.fromList l1) (V.fromList l2)+    reverseAppend (V.fromList l1) (V.fromList l2)  prop_reverseSingleton_Seq :: (Eq a) => a -> Bool prop_reverseSingleton_Seq = reverseSingleton (Proxy :: Proxy Seq.Seq)  prop_reverseAppend_Seq :: (Eq a) => [a] -> [a] -> Bool prop_reverseAppend_Seq l1 l2 =-  reverseAppend (Seq.fromList l1) (Seq.fromList l2)--+    reverseAppend (Seq.fromList l1) (Seq.fromList l2)  -- Laziness tests.  Here we create a custom container type -- that we use to ensure certain operations do not cause the -- whole container to be evaluated. -- newtype L a = L [a]-  deriving (Functor, Foldable, Traversable)+            deriving (Functor, Foldable, Traversable, Semigroup)  instance Splittable L where-  splitAt i (L xs) = over both L (Data.List.splitAt i xs)+    splitAt i (L xs) = over both L (Data.List.splitAt i xs)  -- moveBy positive amount does not evaluate 'length' prop_moveByPosLazy :: Bool prop_moveByPosLazy =-  let-    v = L (1:2:3:4:undefined) :: L Int-    l = list () v 1-    l' = listMoveBy 1 l-  in-    l' ^. listSelectedL == Just 1+    let v = L (1:2:3:4:undefined) :: L Int+        l = list () v 1+        l' = listMoveBy 1 l+    in l' ^. listSelectedL == Just 1  -- listFindBy is lazy prop_findByLazy :: Bool prop_findByLazy =-  let-    v = L (1:2:3:4:undefined) :: L Int-    l = list () v 1 & listSelectedL .~ Nothing-    l' = listFindBy even l-    l'' = listFindBy even l'-  in-    l' ^. listSelectedL == Just 1-    && l'' ^. listSelectedL == Just 3+    let v = L (1:2:3:4:undefined) :: L Int+        l = list () v 1 & listSelectedL .~ Nothing+        l' = listFindBy even l+        l'' = listFindBy even l'+    in l' ^. listSelectedL == Just 1 &&+       l'' ^. listSelectedL == Just 3 +prop_listSelectedElement_lazy :: Bool+prop_listSelectedElement_lazy =+    let v = L (1:2:3:4:undefined) :: L Int+        l = list () v 1 & listSelectedL .~ Just 3+    in listSelectedElement l == Just (3, 4)++prop_listSelectedElementL_lazy :: Bool+prop_listSelectedElementL_lazy =+    let v = L (1:2:3:4:undefined) :: L Int+        l = list () v 1 & listSelectedL .~ Just 3+    in over listSelectedElementL (*2) l ^? listSelectedElementL == Just 8  return []