Adam Plaice ca866229f6 Fix most remaining broken inter-module docs links
The links were broken due to:

1. Incorrect quotes (' instead of " for module links and occasionally
vice-versa).

2. Changes in the name of the "target" module not reflected in the
"source" docs.

3. Typos to begin with.

4. Use of `<foo>` in the docs is rendered as just `foo` with a link to
`/foo`.

5. Similarly for `"Foo"` if it starts with a capital letter (and hence
could be a module).

6. Markup inside `@` code blocks still being applied.

e.g. `@M-<arrow-keys>@` is rendered as `M-arrow-keys` with a spurious
hyperlink from arrow-keys to `/arrow-keys`, which is confusing.

Three links from XMonad.Util.Run have been removed outright, since
they're no longer examples of the usage of 'runProcessWithInput'.
WmiiActions has been gone since 2008, while XMonad.Prompt.Directory
and XMonad.Layout.WorkspaceDir haven't been using
'runProcessWithInput' since 2020 and 2012, respectively.

In some cases the `<foo>` were surrounded with @, especially in the
case of key definitions, for consistency.  (This wasn't done
everywhere, because it looks ugly in the source.)

MoreManageHelpers has never been in xmonad-contrib.  ManageHelpers
seems to fill the expected role.

In the case of the module description for X.H.ManageDebug the quotes
were simply removed because none of the likely options to make the
link work were successful.
2022-11-01 19:35:55 +01:00

311 lines
10 KiB
Haskell

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
--------------------------------------------------------------------------------
-- |
-- Module : XMonad.Hooks.Modal
-- Description : Implements true modality in xmonad key-bindings.
-- Copyright : (c) 2018 L. S. Leary
-- License : BSD3-style (see LICENSE)
--
-- Author : L. S. Leary
-- Maintainer : Yecine Megdiche <yecine.megdiche@gmail.com>
-- Stability : unstable
-- Portability : unportable
--
-- This module implements modal keybindings for xmonad.
--
--------------------------------------------------------------------------------
-- --< Imports & Exports >-- {{{
module XMonad.Hooks.Modal
(
-- * Usage
-- $Usage
modal
, modeWithExit
, mode
, Mode
, mkKeysEz
, setMode
, exitMode
-- * Provided Modes #ProvidedModes#
-- $ProvidedModes
, noModModeLabel
, noModMode
, floatModeLabel
, floatMode
, overlayedFloatModeLabel
, overlayedFloatMode
, floatMap
, overlay
-- * Logger
, logMode
) where
-- core
import XMonad
-- base
import Data.Bits ( (.&.)
, complement
)
import Data.List
import qualified Data.Map.Strict as M
-- contrib
import XMonad.Actions.FloatKeys ( keysMoveWindow
, keysResizeWindow
)
import XMonad.Prelude
import XMonad.Util.EZConfig ( parseKeyCombo
, mkKeymap
)
import qualified XMonad.Util.ExtensibleConf as XC
import qualified XMonad.Util.ExtensibleState as XS
import XMonad.Util.Grab
import XMonad.Util.Loggers
import XMonad.Util.Parser ( runParser )
-- }}}
-- Original Draft By L.S.Leary : https://gist.github.com/LSLeary/6741b0572d62db3f0cea8e6618141b2f
-- --< Usage >-- {{{
-- $Usage
--
-- This module provides modal keybindings in xmonad. If you're not familiar with
-- modal keybindings from Vim, you can think of modes as submaps from
-- "XMonad.Actions.Submap", but after each action you execute, you land back in
-- the submap until you explicitly exit the submap. To use this module you
-- should apply the 'modal' function to the config, which will setup the list of
-- modes (or rather, @XConfig Layout -> Mode@) you provide:
--
-- >
-- > import XMonad
-- > import XMonad.Hooks.Modal
-- > import XMonad.Util.EZConfig
-- > import qualified Data.Map as M
-- >
-- > main :: IO ()
-- > main =
-- > xmonad
-- > . modal [noModMode, floatMode 10, overlayedFloatMode 10, sayHelloMode]
-- > $ def
-- > `additionalKeysP` [ ("M-S-n", setMode noModModeLabel)
-- > , ("M-S-r", setMode floatModeLabel)
-- > , ("M-S-z", setMode overlayedFloatModeLabel)
-- > , ("M-S-h", setMode "Hello")
-- > ]
-- >
-- > sayHelloMode :: Mode
-- > sayHelloMode = mode "Hello" $ mkKeysEz
-- > [ ("h", xmessage "Hello, World!")
-- > , ("M-g", xmessage "Goodbye, World!")
-- > ]
--
-- Alternatively, one could have defined @sayHelloMode@ as
--
-- > sayHelloMode :: Mode
-- > sayHelloMode = mode "Hello" $ \cfg ->
-- > M.fromList [ ((noModMask, xK_h), xmessage "Hello, World!")
-- > , ((modMask cfg, xK_g), xmessage "Goodbye, World!")
-- > ]
--
-- In short, a 'Mode' has a label describing its purpose, as well as
-- attached keybindings. These are of the form
--
-- - @[(String, X ())]@, or
--
-- - @XConfig Layout -> M.Map (ButtonMask, KeySym) (X ())@.
--
-- The former—accessible via 'mkKeysEz'—is how specifying keys work with
-- "XMonad.Util.EZConfig", while the latter is more geared towards how
-- defining keys works by default in xmonad. Note that, by default,
-- modes are exited with the Escape key. If one wishes to customise
-- this, the 'modeWithExit' function should be used instead of 'mode'
-- when defining a new mode.
--
-- The label of the active mode can be logged with 'logMode' to be
-- displayed in a status bar, for example (For more information check
-- "XMonad.Util.Loggers"). Some examples are included in [the provided
-- modes](#g:ProvidedModes).
-- }}}
-- --< Types >-- {{{
-- | From a list of "XMonad.Util.EZConfig"-style bindings, generate a
-- key representation.
--
-- >>> mkKeysEz [("h", xmessage "Hello, world!")]
mkKeysEz :: [(String, X ())] -> (XConfig Layout -> M.Map (ButtonMask, KeySym) (X ()))
mkKeysEz = flip mkKeymap
-- | The mode type. Use 'mode' or 'modeWithExit' to create modes.
data Mode = Mode
{ label :: !String
, boundKeys :: !(XConfig Layout -> M.Map (ButtonMask, KeySym) (X ()))
}
-- | Newtype for the extensible config.
newtype ModeConfig = MC [Mode] deriving Semigroup
-- | Newtype for the extensible state.
newtype CurrentMode = CurrentMode
{ currentMode :: Maybe Mode
}
instance ExtensionClass CurrentMode where
initialValue = CurrentMode Nothing
-- }}}
-- --< Private >-- {{{
-- | The active keybindings corresponding to the active 'Mode' (or lack
-- thereof).
currentKeys :: X (M.Map (ButtonMask, KeySym) (X ()))
currentKeys = do
cnf <- asks config
XS.gets currentMode >>= \case
Just m -> pure (boundKeys m cnf)
Nothing -> join keys <$> asks config
-- | Grab the keys corresponding to the active 'Mode' (or lack thereof).
regrab :: X ()
regrab = grab . M.keys =<< currentKeys
-- | Called after changing the mode. Grabs the correct keys and runs the
-- 'logHook'.
refreshMode :: X ()
refreshMode = regrab >> asks config >>= logHook
-- | Event hook to control the keybindings.
modalEventHook :: Event -> X All
modalEventHook = customRegrabEvHook regrab <> \case
KeyEvent { ev_event_type = t, ev_state = m, ev_keycode = code }
| t == keyPress -> withDisplay $ \dpy -> do
kp <- (,) <$> cleanMask m <*> io (keycodeToKeysym dpy code 0)
kbs <- currentKeys
userCodeDef () (whenJust (M.lookup kp kbs) id)
pure (All False)
_ -> pure (All True)
-- }}}
-- --< Public >-- {{{
-- | Adds the provided modes to the user's config, and sets up the bells
-- and whistles needed for them to work.
modal :: [Mode] -> XConfig l -> XConfig l
modal modes = XC.once
(\cnf -> cnf { startupHook = startupHook cnf <> initModes
, handleEventHook = handleEventHook cnf <> modalEventHook
}
)
(MC modes)
where initModes = XS.put (CurrentMode Nothing) >> refreshMode
-- | Create a 'Mode' from the given binding to 'exitMode', label and
-- keybindings.
modeWithExit :: String -> String -> (XConfig Layout -> M.Map (KeyMask, KeySym) (X ())) -> Mode
modeWithExit exitKey mlabel keys = Mode mlabel $ \cnf ->
let exit = fromMaybe (0, xK_Escape) $ runParser (parseKeyCombo cnf) exitKey
in M.insert exit exitMode (keys cnf)
-- | Create a 'Mode' from the given label and keybindings. Sets the
-- @escape@ key to 'exitMode'.
mode :: String -> (XConfig Layout -> M.Map (KeyMask, KeySym) (X ())) -> Mode
mode = modeWithExit "<Escape>"
-- | Set the current 'Mode' based on its label.
setMode :: String -> X ()
setMode l = do
XC.with $ \(MC ls) -> case find ((== l) . label) ls of
Nothing -> mempty
Just m -> do
XS.modify $ \cm -> cm { currentMode = Just m }
refreshMode
-- | Exits the current mode.
exitMode :: X ()
exitMode = do
XS.modify $ \m -> m { currentMode = Nothing }
refreshMode
-- | A 'Logger' to display the current mode.
logMode :: Logger
logMode = fmap label <$> XS.gets currentMode
-- Provided modes
noModModeLabel, floatModeLabel, overlayedFloatModeLabel :: String
noModModeLabel = "NoMod"
floatModeLabel = "Float"
overlayedFloatModeLabel = "Overlayed Float"
-- | In this 'Mode', all keybindings are available without the need for pressing
-- the modifier. Pressing @escape@ exits the mode.
noModMode :: Mode
noModMode =
mode noModModeLabel $ \cnf -> stripModifier (modMask cnf) (keys cnf cnf)
-- | Generates the keybindings for 'floatMode' and 'overlayedFloatMode'.
floatMap
:: KeyMask -- ^ Move mask
-> KeyMask -- ^ Enlarge mask
-> KeyMask -- ^ Shrink mask
-> Int -- ^ Step size
-> M.Map (ButtonMask, KeySym) (X ())
floatMap move enlarge shrink s = M.fromList
[ -- move
((move, xK_h) , withFocused (keysMoveWindow (-s, 0)))
, ((move, xK_j) , withFocused (keysMoveWindow (0, s)))
, ((move, xK_k) , withFocused (keysMoveWindow (0, -s)))
, ((move, xK_l) , withFocused (keysMoveWindow (s, 0)))
-- enlarge
, ((enlarge, xK_h), withFocused (keysResizeWindow (s, 0) (1, 0)))
, ((enlarge, xK_j), withFocused (keysResizeWindow (0, s) (0, 0)))
, ((enlarge, xK_k), withFocused (keysResizeWindow (0, s) (0, 1)))
, ((enlarge, xK_l), withFocused (keysResizeWindow (s, 0) (0, 0)))
-- shrink
, ((shrink, xK_h), withFocused (keysResizeWindow (-s, 0) (0, 0)))
, ((shrink, xK_j), withFocused (keysResizeWindow (0, -s) (0, 1)))
, ((shrink, xK_k), withFocused (keysResizeWindow (0, -s) (0, 0)))
, ((shrink, xK_l), withFocused (keysResizeWindow (-s, 0) (1, 0)))
, ((noModMask, xK_Escape), exitMode)
]
-- | A mode to control floating windows with @{hijk}@, @M-{hijk}@ and
-- @M-S-{hijk}@ in order to respectively move, enlarge and
-- shrink windows.
floatMode
:: Int -- ^ Step size
-> Mode
floatMode i = mode floatModeLabel $ \XConfig { modMask } ->
floatMap noModMask modMask (modMask .|. shiftMask) i
-- | Similar to 'resizeMode', but keeps the bindings of the original
-- config active.
overlayedFloatMode
:: Int -- ^ Step size
-> Mode
overlayedFloatMode = overlay overlayedFloatModeLabel . floatMode
-- | Modifies a mode so that the keybindings are merged with those from
-- the config instead of replacing them.
overlay
:: String -- ^ Label for the new mode
-> Mode -- ^ Base mode
-> Mode
overlay label m = Mode label $ \cnf -> boundKeys m cnf <> keys cnf cnf
-- | Strips the modifier key from the provided keybindings.
stripModifier
:: ButtonMask -- ^ Modifier to remove
-> M.Map (ButtonMask, KeySym) (X ()) -- ^ Original keybinding map
-> M.Map (ButtonMask, KeySym) (X ())
stripModifier mask = M.mapKeys $ \(m, k) -> (m .&. complement mask, k)
-- }}}