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がない。