packages feed

matterhorn-90000.0.1: src/Matterhorn/State/ListWindow.hs

{-# LANGUAGE RankNTypes #-}
module Matterhorn.State.ListWindow
  ( listWindowActivateCurrent
  , listWindowActivate
  , listWindowSearchString
  , listWindowMove
  , exitListWindow
  , enterListWindowMode
  , resetListWindowSearch
  , onEventListWindow
  )
where

import           Prelude ()
import           Matterhorn.Prelude

import           Brick ( BrickEvent(VtyEvent) )
import qualified Brick.Widgets.List as L
import qualified Brick.Widgets.Edit as E
import qualified Data.Text.Zipper as Z
import qualified Data.Vector as Vec
import           Lens.Micro.Platform ( Lens', (%=), (.=) )
import           Network.Mattermost.Types ( Session, TeamId )
import qualified Graphics.Vty as Vty

import           Matterhorn.Types
import           Brick.Keybindings
import           Matterhorn.State.Common
import           Matterhorn.State.Editing ( editingKeybindings )


-- | Activate the specified list window's selected item by invoking the
-- window's configured enter keypress handler function.
listWindowActivateCurrent :: TeamId -> Lens' ChatState (ListWindowState a b) -> MH ()
listWindowActivateCurrent tId which = do
  mItem <- L.listSelectedElement <$> use (which.listWindowSearchResults)
  case mItem of
      Nothing -> return ()
      Just (_, val) -> listWindowActivate tId which val

-- | Activate the specified list window's selected item by invoking the
-- window's configured enter keypress handler function.
listWindowActivate :: TeamId -> Lens' ChatState (ListWindowState a b) -> a -> MH ()
listWindowActivate tId which val = do
    handler <- use (which.listWindowEnterHandler)
    activated <- handler val
    if activated
       then popMode tId
       else return ()

-- | Get the current search string for the specified window.
listWindowSearchString :: Lens' ChatState (ListWindowState a b) -> MH Text
listWindowSearchString which =
    (head . E.getEditContents) <$> use (which.listWindowSearchInput)

-- | Move the list cursor in the specified window.
listWindowMove :: Lens' ChatState (ListWindowState a b)
                -- ^ Which window
                -> (L.List Name a -> L.List Name a)
                -- ^ How to transform the list in the window
                -> MH ()
listWindowMove which how = which.listWindowSearchResults %= how

-- | Clear the state of the specified list window and return to the
-- Main mode.
exitListWindow :: TeamId
                -> Lens' ChatState (ListWindowState a b)
                -- ^ Which window to reset
                -> MH ()
exitListWindow tId which = do
    newList <- use (which.listWindowNewList)
    which.listWindowSearchResults .= newList mempty
    which.listWindowEnterHandler .= (const $ return False)
    popMode tId

-- | Initialize a list window with the specified arguments and switch
-- to the specified mode.
enterListWindowMode :: TeamId
                     -> (Lens' ChatState (ListWindowState a b))
                     -- ^ Which window to initialize
                     -> Mode
                     -- ^ The mode to change to
                     -> b
                     -- ^ The window's initial search scope
                     -> (a -> MH Bool)
                     -- ^ The window's enter keypress handler
                     -> (b -> Session -> Text -> IO (Vec.Vector a))
                     -- ^ The window's results fetcher function
                     -> MH ()
enterListWindowMode tId which mode scope enterHandler fetcher = do
    which.listWindowSearchScope .= scope
    which.listWindowSearchInput.E.editContentsL %= Z.clearZipper
    which.listWindowEnterHandler .= enterHandler
    which.listWindowFetchResults .= fetcher
    which.listWindowSearching .= False
    newList <- use (which.listWindowNewList)
    which.listWindowSearchResults .= newList mempty
    pushMode tId mode
    resetListWindowSearch which

-- | Reset the window's search by initiating a new search request for
-- the string that is currently in the window's editor. This does
-- nothing if a search for this window is already in progress.
resetListWindowSearch :: Lens' ChatState (ListWindowState a b) -> MH ()
resetListWindowSearch which = do
    searchPending <- use (which.listWindowSearching)

    when (not searchPending) $ do
        searchString <- listWindowSearchString which
        which.listWindowSearching .= True
        newList <- use (which.listWindowNewList)
        session <- getSession
        scope <- use (which.listWindowSearchScope)
        fetcher <- use (which.listWindowFetchResults)
        doAsyncWith Preempt $ do
            results <- fetcher scope session searchString
            return $ Just $ Work "resetListWindowSearch" $ do
                which.listWindowSearchResults .= newList results
                which.listWindowSearching .= False

                -- Now that the results are available, check to see if the
                -- search string changed since this request was submitted.
                -- If so, issue another search.
                afterSearchString <- listWindowSearchString which
                when (searchString /= afterSearchString) $ resetListWindowSearch which

-- | Generically handle an event for the list window state targeted
-- by the specified lens. Automatically dispatches new searches in the
-- window's editor if the editor contents change.
onEventListWindow :: Lens' ChatState (ListWindowState a b)
                   -- ^ Which window to dispatch to?
                   -> (KeyConfig KeyEvent -> KeyDispatcher KeyEvent MH)
                   -- ^ The keybinding builder
                   -> Vty.Event
                   -- ^ The event
                   -> MH Bool
onEventListWindow which keybindings =
    handleEventWith [ mhHandleKeyboardEvent keybindings
                    , handleEditorEvent which
                    ]

handleEditorEvent :: Lens' ChatState (ListWindowState a b) -> Vty.Event -> MH Bool
handleEditorEvent which e = do
    -- Get the editor content before the event.
    before <- listWindowSearchString which

    -- First find a matching keybinding in the keybinding list.
    handled <- mhHandleKeyboardEvent (editingKeybindings (which.listWindowSearchInput)) e

    -- If we didn't find a matching binding, just handle the event as a
    -- normal editor input event.
    when (not handled) $
        mhZoom (which.listWindowSearchInput) E.handleEditorEvent (VtyEvent e)

    -- Get the editor content after the event. If the string changed,
    -- start a new search.
    after <- listWindowSearchString which
    when (before /= after) $ resetListWindowSearch which

    return True