1{-# LANGUAGE RankNTypes #-}
2module Matterhorn.State.ListOverlay
3  ( listOverlayActivateCurrent
4  , listOverlayActivate
5  , listOverlaySearchString
6  , listOverlayMove
7  , exitListOverlay
8  , enterListOverlayMode
9  , resetListOverlaySearch
10  , onEventListOverlay
11  )
12where
13
14import           Prelude ()
15import           Matterhorn.Prelude
16
17import qualified Brick.Widgets.List as L
18import qualified Brick.Widgets.Edit as E
19import qualified Data.Text.Zipper as Z
20import qualified Data.Vector as Vec
21import           Lens.Micro.Platform ( Lens', (%=), (.=) )
22import           Network.Mattermost.Types ( Session )
23import qualified Graphics.Vty as Vty
24
25import           Matterhorn.Types
26import           Matterhorn.State.Common
27import           Matterhorn.State.Editing ( editingKeybindings )
28import           Matterhorn.Events.Keybindings ( KeyConfig, KeyHandlerMap, handleKeyboardEvent )
29
30
31-- | Activate the specified list overlay's selected item by invoking the
32-- overlay's configured enter keypress handler function.
33listOverlayActivateCurrent :: Lens' ChatState (ListOverlayState a b) -> MH ()
34listOverlayActivateCurrent which = do
35  mItem <- L.listSelectedElement <$> use (which.listOverlaySearchResults)
36  case mItem of
37      Nothing -> return ()
38      Just (_, val) -> listOverlayActivate which val
39
40-- | Activate the specified list overlay's selected item by invoking the
41-- overlay's configured enter keypress handler function.
42listOverlayActivate :: Lens' ChatState (ListOverlayState a b) -> a -> MH ()
43listOverlayActivate which val = do
44    handler <- use (which.listOverlayEnterHandler)
45    activated <- handler val
46    if activated
47       then setMode Main
48       else return ()
49
50-- | Get the current search string for the specified overlay.
51listOverlaySearchString :: Lens' ChatState (ListOverlayState a b) -> MH Text
52listOverlaySearchString which =
53    (head . E.getEditContents) <$> use (which.listOverlaySearchInput)
54
55-- | Move the list cursor in the specified overlay.
56listOverlayMove :: Lens' ChatState (ListOverlayState a b)
57                -- ^ Which overlay
58                -> (L.List Name a -> L.List Name a)
59                -- ^ How to transform the list in the overlay
60                -> MH ()
61listOverlayMove which how = which.listOverlaySearchResults %= how
62
63-- | Clear the state of the specified list overlay and return to the
64-- Main mode.
65exitListOverlay :: Lens' ChatState (ListOverlayState a b)
66                -- ^ Which overlay to reset
67                -> MH ()
68exitListOverlay which = do
69    st <- use which
70    newList <- use (which.listOverlayNewList)
71    which.listOverlaySearchResults .= newList mempty
72    which.listOverlayEnterHandler .= (const $ return False)
73    setMode (st^.listOverlayReturnMode)
74
75-- | Initialize a list overlay with the specified arguments and switch
76-- to the specified mode.
77enterListOverlayMode :: (Lens' ChatState (ListOverlayState a b))
78                     -- ^ Which overlay to initialize
79                     -> Mode
80                     -- ^ The mode to change to
81                     -> b
82                     -- ^ The overlay's initial search scope
83                     -> (a -> MH Bool)
84                     -- ^ The overlay's enter keypress handler
85                     -> (b -> Session -> Text -> IO (Vec.Vector a))
86                     -- ^ The overlay's results fetcher function
87                     -> MH ()
88enterListOverlayMode which mode scope enterHandler fetcher = do
89    which.listOverlaySearchScope .= scope
90    which.listOverlaySearchInput.E.editContentsL %= Z.clearZipper
91    which.listOverlayEnterHandler .= enterHandler
92    which.listOverlayFetchResults .= fetcher
93    which.listOverlaySearching .= False
94    newList <- use (which.listOverlayNewList)
95    which.listOverlaySearchResults .= newList mempty
96    setMode mode
97    resetListOverlaySearch which
98
99-- | Reset the overlay's search by initiating a new search request for
100-- the string that is currently in the overlay's editor. This does
101-- nothing if a search for this overlay is already in progress.
102resetListOverlaySearch :: Lens' ChatState (ListOverlayState a b) -> MH ()
103resetListOverlaySearch which = do
104    searchPending <- use (which.listOverlaySearching)
105
106    when (not searchPending) $ do
107        searchString <- listOverlaySearchString which
108        which.listOverlaySearching .= True
109        newList <- use (which.listOverlayNewList)
110        session <- getSession
111        scope <- use (which.listOverlaySearchScope)
112        fetcher <- use (which.listOverlayFetchResults)
113        doAsyncWith Preempt $ do
114            results <- fetcher scope session searchString
115            return $ Just $ do
116                which.listOverlaySearchResults .= newList results
117                which.listOverlaySearching .= False
118
119                -- Now that the results are available, check to see if the
120                -- search string changed since this request was submitted.
121                -- If so, issue another search.
122                afterSearchString <- listOverlaySearchString which
123                when (searchString /= afterSearchString) $ resetListOverlaySearch which
124
125-- | Generically handle an event for the list overlay state targeted
126-- by the specified lens. Automatically dispatches new searches in the
127-- overlay's editor if the editor contents change.
128onEventListOverlay :: Lens' ChatState (ListOverlayState a b)
129                   -- ^ Which overlay to dispatch to?
130                   -> (KeyConfig -> KeyHandlerMap)
131                   -- ^ The keybinding builder
132                   -> Vty.Event
133                   -- ^ The event
134                   -> MH Bool
135onEventListOverlay which keybindings =
136    handleKeyboardEvent keybindings $ \e -> do
137        -- Get the editor content before the event.
138        before <- listOverlaySearchString which
139
140        -- First find a matching keybinding in the keybinding list.
141        handled <- handleKeyboardEvent (editingKeybindings (which.listOverlaySearchInput)) (const $ return ()) e
142
143        -- If we didn't find a matching binding, just handle the event
144        -- as a normal editor input event.
145        when (not handled) $
146            mhHandleEventLensed (which.listOverlaySearchInput) E.handleEditorEvent e
147
148        -- Get the editor content after the event. If the string changed,
149        -- start a new search.
150        after <- listOverlaySearchString which
151        when (before /= after) $ resetListOverlaySearch which
152