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