YiでEditorM aの状態を変更したい時はMonadEditor使えば
最近はYiコントリビューターになるために、.vimrc
のキーマッピングをyi.hs
に再実装している。
stackageのlts-7.4にあるYiはまだドキュメントが充実していないことがわかってきた。
(ただしHaddockにあるHaskellの型シグネチャと実装読みによってだいたいは補える)
Yi布教を兼ねてメモしていく。
YiでEditorM aの状態を変更したい時は、最悪MonadEditor m => m a使えばいい
YiのVimインターフェース
(まだこれに対する正確な用語を僕は持たないので、
Yi.Keymap.Vim
以下の関数やdefaultVimConfig
などによって提供される
VimライクなYi環境をVimインターフェースと呼称している)
で設定する際に困惑したのが、
- ウィンドウを閉じる
- バッファを破棄する
- バッファの指すファイルを保存する
といった操作を扱う型にYiM a
, EditorM a
という2つの型があったこと。
これは多分…YiM
がYi全体を覆う、状態と操作の、全ての包含であることに対して
EditorM
はVimインターフェースでいう、現在がNormalモードであるとか、バッファをいくつ持っているかどれを持っているか…だとかいう
まさにエディタとしての状態をState
で持っているのかなと思う(そして操作もState
によって提供されているのかな)。
MonadEditorについて
YiM
とEditorM
は両方MonadEditor
インスタンスとして定義されているので、このimplBinding
のようなアドホック多相な関数を作れる。
-- The keymapping implementor for both of V.VimBindingY and V.VimBindingE
implBinding :: MonadEditor m => V.EventString -> m () -> V.EventString -> V.VimState -> V.MatchResult (m V.RepeatToken)
implBinding key context = \key' state ->
case V.vsMode state of
V.Insert _ -> (const $ context >> return V.Continue) <$> key' `V.matchesString` key
_ -> V.NoMatch
これを使って、Vimインターフェースのキーマッピングを表す型であるVimBindingを得る。
inoremapE
とinoremapY
は、EventString
と、YiM ()
またはEditorM ()
を使ってVimBindingを作る関数。
2つの関数に分岐してしまっているのは、VimBinding型の値構築子の定義の関係。
-- Like inoremap of Vim from V.EventString for EditorM
-- for ∀a. V.Insert a
inoremapE :: V.EventString -> EditorM () -> V.VimBinding
inoremapE key x = V.VimBindingE $ implBinding key x
-- Like inoremap of Vim from V.EventString for YiM
-- for ∀a. V.Insert a
inoremapY :: V.EventString -> YiM () -> V.VimBinding
inoremapY key x = V.VimBindingY $ implBinding key x
本題: MonadEditorを使って、問題を解決する
僕は、.vimrcにも定義してある、日常的に使っている
「Insertモードで、カレントバッファの指すファイルを保存した後にNormalモードに戻る」というキーマッピングを定義しようとした。
僕はyiWrite
, switchModeE Normal
という関数を見つけた。
-
viWrite :: YiM ()
- カレントバッファの指すファイルを保存する
-
switchModeE :: VimMode -> EditorM ()
- 現在のVimインターフェースのモード(
VimState
型のvsMode
)を変更する
- 現在のVimインターフェースのモード(
data VimMode = Normal | Insert Char | ...
でもこれらの関数は型が違うので、同時に使用できない。
以下の要点と関数に注目して、switchModeY :: VimMode -> YiM ()
を定義することで解決した。
-
MonadEditor
インスタンスは必ずMonadState
である -
getEditorDyn :: (MonadEditor m, YiVariable a) => m a
(いくつかの定義を省略している) -
putEditorDyn :: (MonadEditor m, YiVariable a) => a -> m ()
(いくつかの定義を省略している) -
instance YiVariable VimState
-
=>
YiM
をMonadState
として使えばVimState
を変更できる
これを書いた。
escVimInsWithSave = inoremapY' "<C-k><CR>" (viWrite >> switchModeY V.Normal) -- Yi interpret <C-j> as <CR>
where
switchModeY :: V.VimMode -> YiM ()
switchModeY mode = getEditorDyn >>= \s -> putEditorDyn s { V.vsMode = mode }
あとはこれをキーマッピングに追加すればいいだけだっ!
解決。
ヨッシャ。
結論
VimState
をMonadEditor
で使えば、Vimインターフェースの状態についてはなんとかなりそう。
余談: EventとEventStringについて
僕がYiの型を巡回して最初に迷ったのは、キーマッピングをされる対象のキーを表す型が、Event
とEventString
の2つあったことで、
これはEventString
がYi.Keymap.EventUtils
以下に定義されていることから、
「EventString
はVimインターフェースのために用いられるものなのかな」と考えている。
以下の関数によって相互に変換ができる
-
Yi.Keymap.EventUtils
stringToEvent :: String -> Event
eventToEventString :: Event -> EventString
-
Yi.Keymap.Vim.Common
_unEv :: EventString -> Text
この記事はYiによって書かれた。
lts-7.4のYiにはまだMarkdownのためのModeがない。