yi-0.8.0: src/library/Yi/Hooks.hs
{- | This module provides assistance in implementing \"hooks\" in Yi. This module provides no major new functionality -- only assistance in using 'YiConfigVariable's more easily to implement hooks.
We consider a simple example. Suppose we have a function
> promptForFile :: Maybe FilePath -> YiM FilePath
which prompts the user to select a file from their file system, starting with the provided directory (if actually provided).
Since this is a frequent task in Yi, it is important for it to be as user-friendly as possible. If opinions vary on the
meaning of \"user-friendly\", then we would really like to provide multiple implementations of @promptForFile@, and allow
users to select which implementation to use in their config files.
A way to achieve this is using hooks, as follows:
> -- create a new type
> newtype FilePrompter = FilePrompter { runFilePrompter :: Maybe FilePath -> YiM FilePath }
> deriving(Typeable)
> $(nameDeriveAccessors ''FilePrompter (n -> Just (n ++ "A")))
>
> -- give some implementations
> filePrompter1, filePrompter2, filePrompter3 :: FilePrompter
> ...
>
> -- declare FilePrompter as a YiConfigVariable (so it can go in the Config)
> instance YiConfigVariable FilePrompter
>
> -- specify the default FilePrompter
> instance Default FilePrompter where
> def = filePrompter1
>
> -- replace the old promptForFile function with a shim
> promptForFile :: Maybe FilePath -> YiM FilePath
> promptForFile = runHook runFilePrompter
>
> -- provide a custom-named Field for Yi.Config.Simple (not strictly necessary, but user-friendly)
> filePrompter :: Field FilePrompter
> filePrompter = customVariable
The user can write
> ...
> filePrompter %= filePrompter2
> ...
in their config file, and calls to @promptForFile@ will now use the different prompter. Library code which
called @promptForFile@ does not need to be changed, but it gets the new @filePrompter2@ behaviour automatically.
See "Yi.Eval" for a real example of hooks.
-}
module Yi.Hooks(
-- * Convenience function 'runHook'
runHook,
HookType,
-- * Re-exports from "Yi.Config.Simple"
customVariable,
Field,
)
where
import Control.Lens
import Yi.Config
import Yi.Dynamic
import Yi.Editor
import Yi.Keymap
import Yi.Config.Simple.Types(customVariable, Field)
{- |
Looks up the configured value for the hook, and runs it. The argument to 'runHook' will typically be a record accessor. See 'HookType' for the valid hook types.
-}
runHook :: (HookType ty, YiConfigVariable var) => (var -> ty) -> ty
runHook = runHookImpl
{- | The class of \"valid hooks\". This class is exported abstractly, but the instances can be phrased quite simply: the functions (of arbitrarily many arguments, including zero) which run in either the 'EditorM' or 'YiM' monads.
A typical example would be something like
@Int -> String -> 'EditorM' String@.
-}
class HookType ty where
runHookImpl :: YiConfigVariable var => (var -> ty) -> ty
instance HookType (EditorM a) where
runHookImpl lookupHook = do
cfg <- askCfg
lookupHook (cfg ^. configVarsA ^. configVariableA)
instance HookType (YiM a) where
runHookImpl lookupHook = do
cfg <- askCfg
lookupHook (cfg ^. configVarsA ^. configVariableA)
instance HookType b => HookType (a -> b) where
runHookImpl lookupHook a = runHookImpl (($a) . lookupHook)