yi-0.7.0: src/library/Yi/Keymap.hs
{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeSynonymInstances, ExistentialQuantification, MultiParamTypeClasses, FunctionalDependencies, DeriveDataTypeable, StandaloneDeriving, GeneralizedNewtypeDeriving, Rank2Types, TemplateHaskell #-}
-- Copyright (c) Jean-Philippe Bernardy 2007,8.
module Yi.Keymap
( Action(..)
, emptyAction
, Interact
, KeymapM
, Keymap
, KeymapEndo
, KeymapProcess
, KeymapSet(..)
, topKeymapA
, startInsertKeymapA
, insertKeymapA
, startTopKeymapA
, extractTopKeymap
, modelessKeymapSet
, YiM(..)
, withUI
, unsafeWithEditor
, withGivenBuffer
, withBuffer
, readEditor
, catchDynE
, catchJustE
, handleJustE
, shutdown
, YiAction (..)
, Yi(..)
, YiVar(..)
, write
) where
import Prelude ()
import Yi.Prelude
import Control.Concurrent
import Control.Monad.Reader hiding (mapM_)
import Control.Monad.State hiding (mapM_)
import Control.Exception
import Data.Typeable ()
import Yi.Buffer
import Yi.Config
import Yi.Editor (EditorM, Editor, runEditor, MonadEditor(..))
import Yi.Event
import Yi.Monad ()
import Yi.Process (SubprocessInfo, SubprocessId)
import Yi.UI.Common
import qualified Data.Map as M
import qualified Yi.Editor as Editor
import qualified Yi.Interact as I
import Data.Accessor.Template
data Action = forall a. Show a => YiA (YiM a)
| forall a. Show a => EditorA (EditorM a)
| forall a. Show a => BufferA (BufferM a)
deriving Typeable
emptyAction :: Action
emptyAction = BufferA (return ())
instance I.PEq Action where
equiv _ _ = False
instance Show Action where
show (YiA _) = "@Y"
show (EditorA _) = "@E"
show (BufferA _) = "@B"
type Interact ev a = I.I ev Action a
type KeymapM a = Interact Event a
type Keymap = KeymapM ()
type KeymapEndo = Keymap -> Keymap
type KeymapProcess = I.P Event Action
data Yi = Yi {yiUi :: UI,
input :: Event -> IO (), -- ^ input stream
output :: [Action] -> IO (), -- ^ output stream
yiConfig :: Config,
yiVar :: MVar YiVar -- ^ The only mutable state in the program
}
deriving Typeable
data YiVar = YiVar {yiEditor :: !Editor,
threads :: ![ThreadId], -- ^ all our threads
yiSubprocessIdSupply :: !SubprocessId,
yiSubprocesses :: !(M.Map SubprocessId SubprocessInfo)
}
-- | The type of user-bindable functions
newtype YiM a = YiM {runYiM :: ReaderT Yi IO a}
deriving (Monad, MonadReader Yi, MonadIO, Typeable, Functor)
instance MonadState Editor YiM where
get = yiEditor <$> (readRef =<< yiVar <$> ask)
put v = flip modifyRef (\x -> x {yiEditor = v}) =<< yiVar <$> ask
instance MonadEditor YiM where
askCfg = yiConfig <$> ask
withEditor f = do
r <- asks yiVar
cfg <- asks yiConfig
liftIO $ unsafeWithEditor cfg r f
-----------------------
-- Keymap basics
-- | @write a@ returns a keymap that just outputs the action @a@.
write :: (I.MonadInteract m Action ev, YiAction a x, Show x) => a -> m ()
write x = I.write (makeAction x)
--------------------------------
-- Uninteresting glue code
withUI :: (UI -> IO a) -> YiM a
withUI = with yiUi
unsafeWithEditor :: Config -> MVar YiVar -> EditorM a -> IO a
unsafeWithEditor cfg r f = modifyMVar r $ \var -> do
let e = yiEditor var
let (e',a) = runEditor cfg f e
-- Make sure that the result of runEditor is evaluated before
-- replacing the editor state. Otherwise, we might replace e
-- with an exception-producing thunk, which makes it impossible
-- to look at or update the editor state.
-- Maybe this could also be fixed by -fno-state-hack flag?
-- TODO: can we simplify this?
e' `seq` a `seq` return (var {yiEditor = e'}, a)
withGivenBuffer :: MonadEditor m => BufferRef -> BufferM a -> m a
withGivenBuffer b f = withEditor (Editor.withGivenBuffer0 b f)
withBuffer :: MonadEditor m => BufferM a -> m a
withBuffer f = withEditor (Editor.withBuffer0 f)
readEditor :: MonadEditor m => (Editor -> a) -> m a
readEditor f = withEditor (gets f)
catchDynE :: Exception exception => YiM a -> (exception -> YiM a) -> YiM a
catchDynE (YiM inner) handler
= YiM $ ReaderT (\r -> catch (runReaderT inner r) (\e -> runReaderT (runYiM $ handler e) r))
catchJustE :: (Exception e) => (e -> Maybe b) -- ^ Predicate to select exceptions
-> YiM a -- ^ Computation to run
-> (b -> YiM a) -- ^ Handler
-> YiM a
catchJustE p (YiM c) h = YiM $ ReaderT (\r -> catchJust p (runReaderT c r) (\b -> runReaderT (runYiM $ h b) r))
handleJustE :: (Exception e) => (e -> Maybe b) -> (b -> YiM a) -> YiM a -> YiM a
handleJustE p h c = catchJustE p c h
-- | Shut down all of our threads. Should free buffers etc.
shutdown :: YiM ()
shutdown = do ts <- threads <$> readsRef yiVar
liftIO $ mapM_ killThread ts
-- -------------------------------------------
class YiAction a x | a -> x where
makeAction :: Show x => a -> Action
instance YiAction (IO x) x where
makeAction = YiA . io
instance YiAction (YiM x) x where
makeAction = YiA
instance YiAction (EditorM x) x where
makeAction = EditorA
instance YiAction (BufferM x) x where
makeAction = BufferA
instance YiAction Action () where
makeAction = id
instance I.PEq Event where
equiv = (==)
data KeymapSet = KeymapSet
{ topKeymap :: Keymap -- ^ Content of the top-level loop.
, startInsertKeymap :: Keymap -- ^ Startup when entering insert mode
, insertKeymap :: Keymap -- ^ For insertion-only modes
, startTopKeymap :: Keymap -- ^ Startup bit, to execute only once at the beginning.
}
$(nameDeriveAccessors ''KeymapSet $ Just.(++ "A"))
extractTopKeymap :: KeymapSet -> Keymap
extractTopKeymap kms = startTopKeymap kms >> forever (topKeymap kms)
-- Note the use of "forever": this has quite subtle implications, as it means that
-- failures in one iteration can yield to jump to the next iteration seamlessly.
-- eg. in emacs keybinding, failures in incremental search, like <left>, will "exit"
-- incremental search and immediately move to the left.
modelessKeymapSet :: Keymap -> KeymapSet
modelessKeymapSet k = KeymapSet
{ insertKeymap = k
, startInsertKeymap = return ()
, topKeymap = k
, startTopKeymap = return ()
}