DescriptiveKeys-0.0.4: src/XMonad/Config/DescriptiveKeys.hs
-- | Specify your key-bindings with a description and zero or more tags,
-- then add a key-binding to search through them.
module XMonad.Config.DescriptiveKeys
(
-- * Usage
-- $usage
-- * Description
Description(..)
, DescriptiveKey(..)
, defaultDescriptiveKey
-- * `DescriptiveKeys` data structure
, DescriptiveKeys
, descriptiveKeys
, wKeys
, setDescriptiveKeys
-- * Tags
, Tag(..)
, Tags
, allTags
, SearchTags(..)
, defaultSearchTags
, filterTags
-- * Pretty-printing the key description
, DescriptiveKeysPP(..)
, defaultDescriptiveKeysPP
-- * The prompt text when searching
, SearchTextPrompt(..)
, defaultSearchTextPrompt
-- * The action to take to describe the keys
, DescribeKeys(..)
, defaultDescribeKeys
-- * Configuration
, HelpPromptConfig(..)
, helpPrompt
, helpPromptAndSet
, defaultHelpPromptAndSet
) where
import qualified Data.Set as S
import qualified Data.Map as M
import qualified Data.Foldable as F
import XMonad
import XMonad.Prompt
import XMonad.Prompt.Input
import Data.Bits
import Data.List
-- $usage
--
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Config.DescriptiveKeys
--
--
-- Create an instance of `DescriptiveKeys` by annotating key-bindings with a description and list of tags.
--
-- > myDescriptiveKeys = wKeys $ \c -> [(modMask c, xk_z, spawn "xclock"), "opens xclock", ["open", "xclock"]
--
-- Modify your `XConfig` with a configuration combinator. Following is the simplest.
--
-- > myXConfig = defaultHelpPromptAndSet myDescriptiveKeys myXPConfig xConfig
--
-- This will set the keys property of the `XConfig` and a key-binding of mod-F1 to search.
-- | Wraps a string to create a tag.
newtype Tag =
Tag String
deriving (Eq, Ord, Show)
-- | A set of tags.
type Tags =
S.Set Tag
-- | Wraps an optional string to denote a description for a key-binding.
newtype Description =
Description (Maybe String)
deriving (Eq, Ord, Show)
-- | The data structure that denotes an annotated key-binding.
data DescriptiveKey =
DescriptiveKey {
mask :: ButtonMask
, sym :: KeySym
, action :: X ()
, description :: Description
, tags :: Tags
}
-- | A default key-binding that has no description or tags.
defaultDescriptiveKey ::
ButtonMask
-> KeySym
-> X ()
-> DescriptiveKey
defaultDescriptiveKey m s a =
DescriptiveKey m s a (Description Nothing) S.empty
-- | A list of descriptive key-bindings that have access to the `XConfig`.
newtype DescriptiveKeys =
DescriptiveKeys (XConfig Layout -> [DescriptiveKey])
-- | Construct a list of descriptive key-bindings.
descriptiveKeys ::
(XConfig Layout -> [DescriptiveKey])
-> DescriptiveKeys
descriptiveKeys =
DescriptiveKeys
-- | Construct a list of descriptive key-bindings by specifying the description as a string
-- and the tags as a list of strings.
wKeys ::
(XConfig Layout -> [(KeyMask, KeySym, X(), String, [String])])
-> DescriptiveKeys
wKeys z =
descriptiveKeys (fmap (\(m, s, a, d, t) -> DescriptiveKey m s a (Description (Just d)) (S.fromList (fmap Tag t))) . z)
-- | Sets the `keys` property of the given `XConfig` with the given descriptive key-bindings.
setDescriptiveKeys ::
DescriptiveKeys
-> XConfig l
-> XConfig l
setDescriptiveKeys k l =
let rawKeys (DescriptiveKeys d) = F.foldl' (\p (DescriptiveKey m s a _ _) -> M.insert (m, s) a p) M.empty . d
in l { keys = rawKeys k }
-- | Returns all the tags for a given list of key-bindings.
allTags ::
XConfig Layout
-> DescriptiveKeys
-> Tags
allTags l (DescriptiveKeys k) =
S.unions (fmap tags (k l))
-- | How to produce a set of tags from a string, which will likely come from user-input.
newtype SearchTags =
SearchTags {
searchTags :: String -> Tags
}
-- | Splits a string by spaces to produce a set of tags.
defaultSearchTags ::
SearchTags
defaultSearchTags =
SearchTags (S.fromList . fmap Tag . words)
-- | Removes all descriptive key-bindings that are not in the given set of tags.
filterTags ::
Tags
-> DescriptiveKeys
-> DescriptiveKeys
filterTags t z@(DescriptiveKeys k) =
if S.null t
then z
else DescriptiveKeys (\l -> filter (\(DescriptiveKey _ _ _ _ u) -> not (S.null (S.intersection t u))) $ k l)
-- | A pretty-printer for descriptive key-bindings.
data DescriptiveKeysPP =
DescriptiveKeysPP ([DescriptiveKey] -> String)
-- | A plain-text pretty-printer that takes particular care of mod/mask keys and spacing.
defaultDescriptiveKeysPP ::
DescriptiveKeysPP
defaultDescriptiveKeysPP =
DescriptiveKeysPP (unlines . fmap (\(DescriptiveKey m s _ d _) ->
let pick n str = if n .&. complement m == 0 then str else ""
mk = concatMap (++"-") . filter (not . null) . map (uncurry pick) $
[
(mod1Mask, "alt")
, (mod2Mask, "mod")
, (mod3Mask, "mod")
, (mod4Mask, "mod")
, (mod5Mask, "mod")
, (controlMask, "cntrl")
, (shiftMask, "shift")
]
space g = g ++ replicate (16 - length g) ' '
in space (mk ++ keysymToString s) ++ case d of
Description Nothing -> ""
Description (Just e) -> " " ++ e))
-- | The prompt text when searching
newtype SearchTextPrompt =
SearchTextPrompt String
deriving (Eq, Ord, Show)
-- | The default search prompt, @Search key-bindings@
defaultSearchTextPrompt ::
SearchTextPrompt
defaultSearchTextPrompt =
SearchTextPrompt "Search key-bindings"
-- | The action to take to describe key-bindings from a string user-input.
newtype DescribeKeys =
DescribeKeys {
describeKeys :: String -> X ()
}
-- | A default that opens @xmessage@ and uses the default pretty-printer.
defaultDescribeKeys ::
DescriptiveKeys
-> XConfig Layout
-> DescribeKeys
defaultDescribeKeys k l =
let dk (DescriptiveKeys g) = g
pp (DescriptiveKeysPP p) = p
j s = dk (filterTags (searchTags defaultSearchTags s) k) l
in DescribeKeys (\z -> spawn ("xmessage \"" ++ pp defaultDescriptiveKeysPP (j z) ++ "\""))
-- | The attributes required to do the final configuration of the descriptive key-bindings.
data HelpPromptConfig =
HelpPromptConfig {
descriptiveHelp :: DescriptiveKeys -- ^ The descriptive key-bindings.
, xpConfigHelp :: XPConfig -- ^ The `XPConfig` that is used.
, keyHelp :: (ButtonMask, KeySym) -- ^ The key-binding to prompt the user to search.
, searchTextHelp :: SearchTextPrompt -- ^ The search text prompt.
, describeHelp :: DescribeKeys -- ^ The action to take after string user-input.
}
-- | Sets the help prompt on the given `XPConfig`.
helpPrompt ::
(XConfig Layout -> HelpPromptConfig)
-> XConfig l
-> XConfig l
helpPrompt f c =
c {
keys = \d -> let HelpPromptConfig ks xpc ms (SearchTextPrompt stp) (DescribeKeys describek) = f d
compl s = return $ filter (isPrefixOf s) . fmap (\(Tag t) -> t) $ S.toList (allTags d ks)
in M.insert ms (inputPromptWithCompl xpc stp compl ?+ describek) (keys c d)
}
-- | Sets the help prompt on the given `XPConfig` and sets the `keys` attribute.
helpPromptAndSet ::
DescriptiveKeys
-> XPConfig
-> (ButtonMask, KeySym)
-> SearchTextPrompt
-> (XConfig Layout -> DescribeKeys)
-> XConfig l
-> XConfig l
helpPromptAndSet k c m s d =
helpPrompt (\l -> HelpPromptConfig {
descriptiveHelp = k
, xpConfigHelp = c
, keyHelp = m
, searchTextHelp = s
, describeHelp = d l
}) .
setDescriptiveKeys k
-- | Sets the help prompt on the given `XPConfig` and sets the `keys` attribute with a default
-- key-binding of mod-F1, default search text prompt and using @xmessage@ to provide the descriptive response.
defaultHelpPromptAndSet ::
DescriptiveKeys
-> XPConfig
-> XConfig l
-> XConfig l
defaultHelpPromptAndSet k c =
helpPromptAndSet k c (mod4Mask, xK_F1) defaultSearchTextPrompt (defaultDescribeKeys k)