2022-08-03 05:24:35 +03:00
|
|
|
{-# 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)
|
2022-08-05 05:07:47 +03:00
|
|
|
import qualified Data.Text as Text
|
2022-08-03 05:40:23 +03:00
|
|
|
import qualified Data.Text.IO as Text
|
2022-08-03 05:24:35 +03:00
|
|
|
#if !(MIN_VERSION_base(4,11,0))
|
|
|
|
import Data.Monoid ((<>))
|
|
|
|
#endif
|
|
|
|
import qualified Graphics.Vty as V
|
2022-08-05 05:07:47 +03:00
|
|
|
import System.Environment (getArgs)
|
|
|
|
import System.Exit (exitFailure)
|
2022-08-03 05:24:35 +03:00
|
|
|
|
|
|
|
import qualified Brick.Types as T
|
|
|
|
import Brick.Types (Widget)
|
|
|
|
import qualified Brick.Keybindings as K
|
|
|
|
import Brick.AttrMap
|
|
|
|
import qualified Brick.Main as M
|
|
|
|
import qualified Brick.Widgets.Border as B
|
|
|
|
import qualified Brick.Widgets.Center as C
|
|
|
|
import Brick.Widgets.Core
|
|
|
|
|
2022-08-03 05:42:57 +03:00
|
|
|
-- | The abstract key events for the application.
|
2022-08-03 05:24:35 +03:00
|
|
|
data KeyEvent = QuitEvent
|
|
|
|
| IncrementEvent
|
|
|
|
| DecrementEvent
|
|
|
|
deriving (Ord, Eq, Show)
|
|
|
|
|
2022-08-03 05:42:57 +03:00
|
|
|
-- | The mapping of key events to their configuration field names.
|
2022-08-03 05:24:35 +03:00
|
|
|
allKeyEvents :: K.KeyEvents KeyEvent
|
|
|
|
allKeyEvents =
|
2022-08-03 18:19:43 +03:00
|
|
|
K.keyEvents [ ("quit", QuitEvent)
|
2022-08-03 05:24:35 +03:00
|
|
|
, ("increment", IncrementEvent)
|
|
|
|
, ("decrement", DecrementEvent)
|
|
|
|
]
|
|
|
|
|
2022-08-03 05:42:57 +03:00
|
|
|
-- | Default key bindings for each abstract key event.
|
2022-08-03 05:24:35 +03:00
|
|
|
defaultBindings :: [(KeyEvent, [K.Binding])]
|
|
|
|
defaultBindings =
|
2022-08-03 18:19:43 +03:00
|
|
|
[ (QuitEvent, [K.ctrl 'q', K.bind V.KEsc])
|
2022-08-03 05:24:35 +03:00
|
|
|
, (IncrementEvent, [K.bind '+'])
|
|
|
|
, (DecrementEvent, [K.bind '-'])
|
|
|
|
]
|
|
|
|
|
|
|
|
data St =
|
|
|
|
St { _keyConfig :: K.KeyConfig KeyEvent
|
2022-08-03 05:42:57 +03:00
|
|
|
-- ^ The key config to use.
|
2022-08-03 05:24:35 +03:00
|
|
|
, _lastKey :: Maybe (V.Key, [V.Modifier])
|
2022-08-03 05:42:57 +03:00
|
|
|
-- ^ The last key that was pressed.
|
2022-08-03 05:24:35 +03:00
|
|
|
, _lastKeyHandled :: Bool
|
2022-08-03 05:42:57 +03:00
|
|
|
-- ^ Whether the last key was handled by a handler.
|
2022-08-03 05:24:35 +03:00
|
|
|
, _counter :: Int
|
2022-08-03 05:42:57 +03:00
|
|
|
-- ^ The counter value to manipulate in the handlers.
|
2022-08-05 05:07:47 +03:00
|
|
|
, _loadedCustomBindings :: Maybe FilePath
|
2022-08-05 05:13:13 +03:00
|
|
|
-- ^ Set if the application found custom keybindings in the
|
2022-08-05 05:07:47 +03:00
|
|
|
-- specified file.
|
2022-08-03 05:24:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
makeLenses ''St
|
|
|
|
|
2022-08-03 05:52:14 +03:00
|
|
|
-- | Key event handlers for our application.
|
2022-08-03 05:24:35 +03:00
|
|
|
handlers :: [K.KeyEventHandler KeyEvent (T.EventM () St)]
|
|
|
|
handlers =
|
2022-08-03 06:13:22 +03:00
|
|
|
-- The first three handlers are triggered by keys mapped to abstract
|
|
|
|
-- events, thus decoupling the configured key bindings from these
|
|
|
|
-- handlers.
|
2022-08-03 05:24:35 +03:00
|
|
|
[ 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
|
2022-08-03 05:50:08 +03:00
|
|
|
|
2022-08-03 06:13:01 +03:00
|
|
|
-- These handlers are always triggered by specific keys and thus
|
|
|
|
-- cannot be rebound.
|
2022-08-03 05:48:47 +03:00
|
|
|
, K.onKey (K.bind '\t') "Increment the counter by 10" $
|
|
|
|
counter %= (+ 10)
|
2022-08-03 06:13:01 +03:00
|
|
|
, K.onKey (K.bind V.KBackTab) "Decrement the counter by 10" $
|
|
|
|
counter %= subtract 10
|
2022-08-03 05:24:35 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
appEvent :: T.BrickEvent () e -> T.EventM () St ()
|
|
|
|
appEvent (T.VtyEvent (V.EvKey k mods)) = do
|
2022-08-03 05:42:57 +03:00
|
|
|
-- Dispatch the key to the event handler to which the key is mapped,
|
2022-08-03 05:44:05 +03:00
|
|
|
-- if any. Also record in lastKeyHandled whether the dispatcher
|
|
|
|
-- found a matching handler.
|
2022-08-03 05:24:35 +03:00
|
|
|
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
|
2022-08-03 06:10:33 +03:00
|
|
|
binding = uncurry K.binding <$> st^.lastKey
|
2022-08-03 05:40:23 +03:00
|
|
|
keybindingHelp = K.keybindingHelpWidget (st^.keyConfig) handlers
|
2022-08-03 05:24:35 +03:00
|
|
|
status = hLimit 40 $
|
|
|
|
padRight Max $
|
2022-08-05 05:21:30 +03:00
|
|
|
vBox [ txt $ "Last key: " <> maybe "(none)" K.ppBinding binding
|
2022-08-03 05:24:35 +03:00
|
|
|
, str $ "Last key handled: " <> show (st^.lastKeyHandled)
|
|
|
|
, str $ "Counter: " <> show (st^.counter)
|
|
|
|
]
|
2022-08-05 05:07:47 +03:00
|
|
|
customBindingInfo =
|
|
|
|
B.borderWithLabel (txt "Custom Bindings") $
|
|
|
|
case st^.loadedCustomBindings of
|
|
|
|
Nothing ->
|
|
|
|
hLimit 40 $
|
|
|
|
txtWrap $ "No custom bindings loaded. " <>
|
|
|
|
"Create an INI file with a " <>
|
|
|
|
(Text.pack $ show sectionName) <>
|
2022-08-05 05:10:31 +03:00
|
|
|
" section or use 'programs/custom_keys.ini'. " <>
|
|
|
|
"Pass its path to this " <>
|
2022-08-05 05:07:47 +03:00
|
|
|
"program on the command line."
|
|
|
|
Just f -> str "Loaded custom bindings from:" <=> str (show f)
|
2022-08-03 05:24:35 +03:00
|
|
|
body = C.center $
|
2022-08-05 05:07:47 +03:00
|
|
|
((padRight (Pad 7) $
|
|
|
|
(B.borderWithLabel (txt "Status") status) <=> customBindingInfo)
|
|
|
|
) <+>
|
|
|
|
B.borderWithLabel (txt "Active Keybindings") keybindingHelp
|
2022-08-03 05:24:35 +03:00
|
|
|
|
|
|
|
app :: M.App St e ()
|
|
|
|
app =
|
|
|
|
M.App { M.appDraw = drawUi
|
|
|
|
, M.appStartEvent = return ()
|
|
|
|
, M.appHandleEvent = appEvent
|
|
|
|
, M.appAttrMap = const $ attrMap V.defAttr []
|
|
|
|
, M.appChooseCursor = M.showFirstCursor
|
|
|
|
}
|
|
|
|
|
2022-08-05 05:07:47 +03:00
|
|
|
sectionName :: Text.Text
|
|
|
|
sectionName = "keybindings"
|
|
|
|
|
2022-08-03 05:24:35 +03:00
|
|
|
main :: IO ()
|
|
|
|
main = do
|
2022-08-05 05:07:47 +03:00
|
|
|
args <- getArgs
|
|
|
|
|
2022-08-05 05:16:15 +03:00
|
|
|
-- If the command line specified the path to an INI file with custom
|
|
|
|
-- bindings, attempt to load it.
|
2022-08-05 05:07:47 +03:00
|
|
|
(customBindings, customFile) <- case args of
|
|
|
|
[iniFilePath] -> do
|
|
|
|
result <- K.keybindingsFromFile allKeyEvents sectionName iniFilePath
|
|
|
|
case result of
|
2022-08-05 05:16:15 +03:00
|
|
|
-- A section was found and had zero more bindings.
|
2022-08-05 05:07:47 +03:00
|
|
|
Right (Just bindings) ->
|
|
|
|
return (bindings, Just iniFilePath)
|
2022-08-05 05:16:15 +03:00
|
|
|
|
|
|
|
-- No section was found at all.
|
2022-08-05 05:07:47 +03:00
|
|
|
Right Nothing -> do
|
|
|
|
putStrLn $ "Error: found no section " <> show sectionName <> " in " <> show iniFilePath
|
|
|
|
exitFailure
|
2022-08-05 05:16:15 +03:00
|
|
|
|
|
|
|
-- There was some problem parsing the file as an INI
|
|
|
|
-- file.
|
2022-08-05 05:07:47 +03:00
|
|
|
Left e -> do
|
|
|
|
putStrLn $ "Error reading keybindings file " <> show iniFilePath <> ": " <> e
|
|
|
|
exitFailure
|
2022-08-05 05:16:15 +03:00
|
|
|
|
2022-08-05 05:07:47 +03:00
|
|
|
_ -> return ([], Nothing)
|
|
|
|
|
2022-08-05 05:16:15 +03:00
|
|
|
-- Create a key config that includes the default bindings as well as
|
|
|
|
-- the custom bindings we loaded from the INI file, if any.
|
2022-08-05 05:07:47 +03:00
|
|
|
let kc = K.newKeyConfig allKeyEvents defaultBindings customBindings
|
2022-08-03 05:42:57 +03:00
|
|
|
|
2022-08-03 05:24:35 +03:00
|
|
|
void $ M.defaultMain app $ St { _keyConfig = kc
|
|
|
|
, _lastKey = Nothing
|
|
|
|
, _lastKeyHandled = False
|
|
|
|
, _counter = 0
|
2022-08-05 05:07:47 +03:00
|
|
|
, _loadedCustomBindings = customFile
|
2022-08-03 05:24:35 +03:00
|
|
|
}
|
2022-08-03 05:40:23 +03:00
|
|
|
|
2022-08-03 05:44:44 +03:00
|
|
|
-- Now demonstrate how the library's generated key binding help text
|
2022-08-05 05:16:44 +03:00
|
|
|
-- looks in plain text and Markdown formats. These can be used to
|
|
|
|
-- generate documentation for users.
|
2022-08-03 05:40:23 +03:00
|
|
|
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
|