packages feed

zwirn-0.2.3.1: app/zwirnmill/Keymap.hs

{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

{- HLINT ignore "Use tuple-section" -}

module Keymap where

import Brick.Keybindings
import qualified Brick.Keybindings.KeyEvents as B
import Conferer.Config ((/.))
import Conferer.FromConfig (DefaultConfig (..), FromConfig (..), fetchFromConfig)
import Data.List (groupBy)
import qualified Data.Text as T
import Data.Tuple (swap)
import Data.Yaml (ToJSON (..), Value (..), object, (.=))
import Editor.Keymap (EditorAction (..), editorKeyConfigWithUserConfig, editorKeyEvents)
import qualified Graphics.Vty as V

data GlobalAction
  = Quit
  | Hush
  | Panic
  | CustomAction T.Text
  deriving (Show, Eq, Ord)

data FullKeyConfig = FullKeyConfig
  { kGlobal :: KeyConfig GlobalAction,
    kEditor :: KeyConfig EditorAction
  }
  deriving (Show)

data CustomKeymap = CustomKeymap
  { customGlobal :: [(Binding, GlobalAction)],
    customEditor :: [(Binding, EditorAction)]
  }
  deriving (Show)

customKeyConfig :: CustomKeymap -> FullKeyConfig
customKeyConfig (CustomKeymap gl ed) = FullKeyConfig (globalKeyConfigWithUserConfig gl) (editorKeyConfigWithUserConfig ed)

defaultGlobalBindings :: [(GlobalAction, [Binding])]
defaultGlobalBindings =
  [ (Quit, [ctrl 'q']),
    (Hush, [meta '.']),
    (Panic, [meta ','])
  ]

globalKeyEventsFromUserConfig :: [(Binding, GlobalAction)] -> KeyEvents GlobalAction
globalKeyEventsFromUserConfig conf = B.keyEvents $ map (\(_, ac) -> (ppAction ac, ac)) conf

globalKeyConfigWithUserConfig :: [(Binding, GlobalAction)] -> KeyConfig GlobalAction
globalKeyConfigWithUserConfig conf = newKeyConfig (globalKeyEventsFromUserConfig conf) defaultGlobalBindings conf'
  where
    conf' = map toBinding $ groupBy (\(_, a1) (_, a2) -> a1 == a2) conf
    toBinding [] = error "Error in toBinding"
    toBinding ((b, a) : bs) = (a, BindingList $ b : map fst bs)

instance FromConfig (Binding, GlobalAction) where
  fromConfig key configSource = do
    b <- fetchFromConfig (key /. "binding") configSource :: IO T.Text
    a <- fetchFromConfig (key /. "action") configSource :: IO T.Text
    return (parseShortcut b, interpretAction a)
    where
      parseShortcut :: T.Text -> Binding
      parseShortcut shortcut = case parseBinding (T.strip shortcut) of
        Left err -> error $ "Could not parse keymap file: " <> err
        Right b -> b

instance FromConfig (Binding, EditorAction) where
  fromConfig key configSource = do
    b <- fetchFromConfig (key /. "binding") configSource :: IO T.Text
    a <- fetchFromConfig (key /. "action") configSource :: IO T.Text
    return (parseShortcut b, parseAction a)
    where
      parseAction :: T.Text -> EditorAction
      parseAction x = case lookup x $ keyEventsList editorKeyEvents of
        Just ac -> ac
        Nothing -> error "Could not parse action in editor keymap file."
      parseShortcut :: T.Text -> Binding
      parseShortcut shortcut = case parseBinding (T.strip shortcut) of
        Left err -> error $ "Could not parse keymap file: " <> err
        Right b -> b

instance FromConfig CustomKeymap where
  fromConfig key configSource = do
    gl <- fetchFromConfig (key /. "global") configSource :: IO [(Binding, GlobalAction)]
    ed <- fetchFromConfig (key /. "editor") configSource :: IO [(Binding, EditorAction)]
    return $ CustomKeymap gl ed

interpretAction :: T.Text -> GlobalAction
interpretAction txtVal = case T.toLower (T.strip txtVal) of
  "quit" -> Quit
  "hush" -> Hush
  "panic" -> Panic
  other -> CustomAction other

ppAction :: GlobalAction -> T.Text
ppAction Quit = "quit"
ppAction Hush = "hush"
ppAction Panic = "panic"
ppAction (CustomAction other) = other

instance ToJSON BindingState where
  toJSON (BindingList (b : _)) = toJSON $ ppBinding b
  toJSON _ = Null

instance ToJSON Binding where
  toJSON = toJSON . ppBinding

instance ToJSON GlobalAction where
  toJSON = toJSON . ppAction

instance ToJSON EditorAction where
  toJSON ac = maybe Null toJSON $ lookup ac $ map swap $ keyEventsList editorKeyEvents

instance ToJSON CustomKeymap where
  toJSON (CustomKeymap gl ed) =
    object
      [ "global" .= toJSON (map (\(b, a) -> object ["binding" .= b, "action" .= a]) gl),
        "editor" .= toJSON (map (\(b, a) -> object ["binding" .= b, "action" .= a]) ed)
      ]

instance DefaultConfig CustomKeymap where
  configDef = CustomKeymap [(ctrl 'q', Quit), (meta '.', Hush), (meta ',', Panic)] [(ctrl '@', Hint), (meta V.KEnter, Eval), (ctrl '_', CommentLine)]