diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2014-2019 itchyny <https://github.com/itchyny>
+Copyright (c) 2014-2021 itchyny <https://github.com/itchyny>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/miv.cabal b/miv.cabal
--- a/miv.cabal
+++ b/miv.cabal
@@ -1,5 +1,5 @@
 name:                   miv
-version:                0.4.6
+version:                0.4.7
 author:                 itchyny <https://github.com/itchyny>
 maintainer:             itchyny <https://github.com/itchyny>
 license:                MIT
@@ -22,7 +22,6 @@
                       , Cmdline
                       , Mapping
                       , ShowText
-                      , ReadText
                       , VimScript
                       , Git
                       , Paths_miv
@@ -34,9 +33,9 @@
                       , SafeSemaphore
                       , time
                       , directory
-                      , hashable
-                      , aeson
-                      , yaml
+                      , containers
+                      , HsYAML
+                      , bytestring
                       , text
                       , unordered-containers
                       , monad-parallel
@@ -55,10 +54,10 @@
                       , process
                       , time
                       , directory
-                      , hashable
+                      , containers
                       , hspec
-                      , aeson
-                      , yaml
+                      , HsYAML
+                      , bytestring
                       , text
                       , unordered-containers
                       , monad-parallel
diff --git a/src/Cmdline.hs b/src/Cmdline.hs
--- a/src/Cmdline.hs
+++ b/src/Cmdline.hs
@@ -1,14 +1,10 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BlockArguments, LambdaCase, OverloadedStrings #-}
 module Cmdline where
 
-import Data.Aeson
-import qualified Data.Aeson as Aeson
-import Data.Hashable
-import Data.Text (Text,unpack)
+import Data.Text (Text, unpack)
+import Data.YAML
 import Prelude hiding (show)
-import qualified Prelude as Prelude
 
-import ReadText
 import ShowText
 
 data Cmdline = CmdlineExCommand
@@ -23,26 +19,13 @@
   show CmdlineBackwardSearch = "?"
   show CmdlineInput          = "@"
 
-instance ReadText Cmdline where
-  read ":" = CmdlineExCommand
-  read "/" = CmdlineForwardSearch
-  read "?" = CmdlineBackwardSearch
-  read "@" = CmdlineInput
-  read m   = error $ "unknown cmdline: " ++ unpack m
-
-instance Hashable Cmdline where
-  hashWithSalt a CmdlineExCommand      = a `hashWithSalt` (0 :: Int)
-  hashWithSalt a CmdlineForwardSearch  = a `hashWithSalt` (1 :: Int)
-  hashWithSalt a CmdlineBackwardSearch = a `hashWithSalt` (2 :: Int)
-  hashWithSalt a CmdlineInput          = a `hashWithSalt` (3 :: Int)
-
-instance FromJSON Cmdline where
-  parseJSON (Aeson.String str)
-    | unpack str == ":" = return CmdlineExCommand
-    | unpack str == "/" = return CmdlineForwardSearch
-    | unpack str == "?" = return CmdlineBackwardSearch
-    | unpack str == "@" = return CmdlineInput
-  parseJSON o = error $ "failed to parse cmdline: " <> Prelude.show o
+instance FromYAML Cmdline where
+  parseYAML = withStr "!!str" \case
+    ":" -> return CmdlineExCommand
+    "/" -> return CmdlineForwardSearch
+    "?" -> return CmdlineBackwardSearch
+    "@" -> return CmdlineInput
+    x   -> fail $ unpack $ "failed to parse cmdline: " <> x
 
 cmdlinePattern :: Cmdline -> Text
 cmdlinePattern CmdlineExCommand      = ":"
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
+{-# LANGUAGE BlockArguments, OverloadedStrings, ScopedTypeVariables #-}
 module Main where
 
 import Control.Applicative
@@ -8,6 +8,7 @@
 import Control.Exception
 import Control.Monad (filterM, forM_, unless, void, when, guard)
 import qualified Control.Monad.Parallel as P
+import qualified Data.ByteString.Lazy as BS
 import Data.Functor ((<&>))
 import Data.List (foldl', isPrefixOf, nub, sort, (\\))
 import Data.Maybe (listToMaybe, fromMaybe, isNothing)
@@ -16,7 +17,7 @@
 import Data.Text.IO (putStrLn, putStr, writeFile, hGetContents)
 import Data.Time (getZonedTime)
 import Data.Version (showVersion)
-import Data.Yaml (decodeFileEither, prettyPrintParseException)
+import qualified Data.YAML as YAML
 import GHC.Conc (getNumProcessors, setNumCapabilities)
 import Prelude hiding (readFile, writeFile, unlines, putStrLn, putStr, show)
 import System.Console.Concurrent ()
@@ -75,10 +76,10 @@
   maybeFile <- getFirstExistingFile $ return candidates
   case maybeFile of
        Just file -> do
-         maybeSetting <- decodeFileEither file
-         case maybeSetting of
+         cnt <- BS.readFile file
+         case YAML.decode1 cnt of
               Right setting -> return setting
-              Left err -> error $ prettyPrintParseException err
+              Left (pos, err) -> error $ YAML.prettyPosWithSource pos cnt err
        Nothing -> error $ unpack $ unlines $
          "No setting file! Tried following locations:" :
          map (\x -> "  - " <> pack x) candidates
@@ -230,13 +231,13 @@
   status <- fmap mconcat <$> displayConsoleRegions $
     mapM' (\p -> updateOnePlugin time dir update (specified p) p) ps
   putStrLn $ (if null (failed status) then "Success" else "Error occured") <> " in " <> show update <> "."
-  unless (null (installed status)) $ do
+  unless (null (installed status)) do
     putStrLn $ "Installed plugin" <> count (installed status) <> ": "
     mapM_ (putStrLn . ("  "<>) . name) (sort (installed status))
-  unless (null (updated status)) $ do
+  unless (null (updated status)) do
     putStrLn $ "Updated plugin" <> count (updated status) <> ": "
     mapM_ (putStrLn . ("  "<>) . name) (sort (updated status))
-  unless (null (failed status)) $ do
+  unless (null (failed status)) do
     putStrLn $ "Failed plugin" <> count (failed status) <> ": "
     mapM_ (putStrLn . ("  "<>) . name) (sort (failed status))
   generatePluginCode setting
@@ -261,13 +262,13 @@
   dir <- pluginDirectory
   let docdir = dir </> "miv" </> "doc"
   cleanAndCreateDirectory docdir
-  P.forM_ (map (\p -> dir </> rtpName p </> "doc") (plugins setting)) $ \path -> do
+  P.forM_ (map (\p -> dir </> rtpName p </> "doc") (plugins setting)) \path -> do
     exists <- doesDirectoryExist path
     when exists $
-      void $ do (_, _, _, ph) <- createProcess (proc "sh" ["-c", "cp -R * " <> singleQuote docdir]) {
-                  cwd = Just path
-                }
-                waitForProcess ph
+      void do (_, _, _, ph) <- createProcess (proc "sh" ["-c", "cp -R * " <> singleQuote docdir]) {
+                cwd = Just path
+              }
+              waitForProcess ph
   _ <- system $ "vim -u NONE -i NONE -N -e -s -c 'helptags " <> docdir <> "' -c quit"
   putStrLn "Success in processing helptags."
 
@@ -288,12 +289,12 @@
   exists <- doesDirectoryExist path
   gitstatus <- gitStatus path
   if not exists || (gitstatus /= ExitSuccess && not (sync plugin))
-     then withConsoleRegion Linear $ \region -> do
+     then withConsoleRegion Linear \region -> do
        putStrLn' region "Installing"
        when exists $ removeDirectoryRecursive path
        cloneStatus <- execCommand (unpack $ name plugin) region dir $ cloneCommand repo path
        created <- doesDirectoryExist path
-       when created $ do
+       when created do
          buildPlugin path region
          changeModifiedTime path =<< lastUpdate path
        if cloneStatus /= ExitSuccess || not created
@@ -301,7 +302,7 @@
           else return mempty { installed = [plugin] }
      else if update == Install || not (sync plugin)
              then return mempty { nosync = [plugin] }
-             else withConsoleRegion Linear $ \region -> do
+             else withConsoleRegion Linear \region -> do
                lastUpdateTime <- lastUpdate path
                if lastUpdateTime < time - 60 * 60 * 24 * 30 && not specified
                   then do
@@ -386,7 +387,7 @@
              putStr "Really? [y/N] "
              hFlush stdout
              c <- getChar
-             when (c == 'y' || c == 'Y') $ do
+             when (c == 'y' || c == 'Y') do
                mapM_ removeDirectoryRecursive deldirs
                mapM_ removeFile $ delfiles
      else putStrLn "Clean."
@@ -440,12 +441,12 @@
 gatherFtdetectScript setting = do
   dir <- pluginDirectory
   cleanAndCreateDirectory (dir </> "miv" </> "ftdetect")
-  forM_ (plugins setting) $ \plugin -> do
+  forM_ (plugins setting) \plugin -> do
     let path = rtpName plugin
     exists <- doesDirectoryExist (dir </> path </> "ftdetect")
-    when exists $ do
+    when exists do
       files <- listDirectory (dir </> path </> "ftdetect")
-      forM_ files $ \file ->
+      forM_ files \file ->
         copyFile (dir </> path </> "ftdetect" </> file) (dir </> "miv" </> "ftdetect" </> file)
   putStrLn "Success in gathering ftdetect scripts."
 
@@ -462,7 +463,7 @@
   createPluginDirectory
   dir <- pluginDirectory
   status <- mconcat <$> mapM (eachOnePlugin command dir) (plugins setting)
-  unless (null (failed' status)) $ do
+  unless (null (failed' status)) do
     putStrLn "Error:"
     mapM_ (putStrLn . ("  "<>) . name) (failed' status)
 
diff --git a/src/Mode.hs b/src/Mode.hs
--- a/src/Mode.hs
+++ b/src/Mode.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BlockArguments, LambdaCase, OverloadedStrings #-}
 module Mode where
 
 import Data.Text (unpack)
+import Data.YAML
 
-import ReadText
 import ShowText
 
 data Mode = NormalMode
@@ -18,7 +18,7 @@
           | InsertNormalMode
           | InesrtVisualMode
           | InsertSelectMode
-          deriving Eq
+          deriving (Eq, Ord)
 
 instance ShowText Mode where
   show NormalMode = "n"
@@ -34,17 +34,18 @@
   show InesrtVisualMode = "iv"
   show InsertSelectMode = "is"
 
-instance ReadText Mode where
-  read "n"  = NormalMode
-  read "v"  = VisualMode
-  read "s"  = SelectMode
-  read "i"  = InsertMode
-  read "c"  = CmdlineMode
-  read "ex" = ExMode
-  read "o"  = OperatorPendingMode
-  read "r"  = ReplaceMode
-  read "vr" = VirtualReplaceMode
-  read "in" = InsertNormalMode
-  read "iv" = InesrtVisualMode
-  read "is" = InsertSelectMode
-  read m    = error $ "unknown mode: " ++ unpack m
+instance FromYAML Mode where
+  parseYAML = withStr "!!str" \case
+    "n"  -> return NormalMode
+    "v"  -> return VisualMode
+    "s"  -> return SelectMode
+    "i"  -> return InsertMode
+    "c"  -> return CmdlineMode
+    "ex" -> return ExMode
+    "o"  -> return OperatorPendingMode
+    "r"  -> return ReplaceMode
+    "vr" -> return VirtualReplaceMode
+    "in" -> return InsertNormalMode
+    "iv" -> return InesrtVisualMode
+    "is" -> return InsertSelectMode
+    x    -> fail $ unpack $ "failed to parse mode: " <> x
diff --git a/src/Plugin.hs b/src/Plugin.hs
--- a/src/Plugin.hs
+++ b/src/Plugin.hs
@@ -1,12 +1,13 @@
-{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
+{-# LANGUAGE BlockArguments, OverloadedStrings, RecordWildCards #-}
 module Plugin where
 
 import Control.Applicative ((<|>))
-import Data.Aeson
 import qualified Data.Text as T
 import Data.Text (Text, unpack)
+import Data.YAML
 
 import Cmdline
+import Mode
 import ShowText
 
 data Plugin =
@@ -15,7 +16,7 @@
             , commands   :: [Text]
             , functions  :: [Text]
             , mappings   :: [Text]
-            , mapmodes   :: [Text]
+            , mapmodes   :: [Mode]
             , cmdlines   :: [Cmdline]
             , insert     :: Bool
             , enable     :: Text
@@ -44,8 +45,8 @@
 rtpName :: Plugin -> String
 rtpName = unpack . ShowText.show
 
-instance FromJSON Plugin where
-  parseJSON = withObject "plugin" $ \o -> do
+instance FromYAML Plugin where
+  parseYAML = withMap "!!map" \o -> do
     name <- o .:? "name" .!= ""
     filetypes <- o .: "filetype" <|> (fmap return <$> o .:? "filetype") .!= []
     commands <- o .: "command" <|> (fmap return <$> o .:? "command") .!= []
diff --git a/src/ReadText.hs b/src/ReadText.hs
deleted file mode 100644
--- a/src/ReadText.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-{-# LANGUAGE FlexibleInstances, IncoherentInstances, UndecidableInstances #-}
-module ReadText where
-
-import Data.Text
-
-class ReadText a where
-  read :: Text -> a
-
-instance Read a => ReadText a where
-  read = Prelude.read . unpack
diff --git a/src/Setting.hs b/src/Setting.hs
--- a/src/Setting.hs
+++ b/src/Setting.hs
@@ -1,32 +1,32 @@
-{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
+{-# LANGUAGE BlockArguments, OverloadedStrings, RecordWildCards #-}
 module Setting where
 
-import Data.Aeson
 import Data.Function (on)
-import qualified Data.HashMap.Lazy as HM
 import Data.List (sortBy)
+import qualified Data.Map.Strict as M
 import Data.Text (Text, lines)
+import Data.YAML
 import Prelude hiding (lines)
 
 import Plugin
 
 data Setting =
      Setting { plugins  :: [Plugin]
-             , filetype :: HM.HashMap Text [Text]
-             , syntax   :: HM.HashMap Text [Text]
+             , filetype :: M.Map Text [Text]
+             , syntax   :: M.Map Text [Text]
              , before   :: [Text]
              , after    :: [Text]
      } deriving Eq
 
-instance FromJSON Setting where
-  parseJSON = withObject "setting" $ \o -> do
-    plugins <- pluginHashMapToList <$> o .:? "plugin" .!= HM.empty
-    filetype <- HM.map lines <$> o .:? "filetype" .!= HM.empty
-    syntax <- HM.map lines <$> o .:? "syntax" .!= HM.empty
+instance FromYAML Setting where
+  parseYAML = withMap "!!map" \o -> do
+    plugins <- pluginMapToList <$> o .:? "plugin" .!= M.empty
+    filetype <- M.map lines <$> o .:? "filetype" .!= M.empty
+    syntax <- M.map lines <$> o .:? "syntax" .!= M.empty
     before <- lines <$> o .:? "before" .!= ""
     after <- lines <$> o .:? "after" .!= ""
     return Setting {..}
-      where pluginHashMapToList = sortWith name . HM.foldlWithKey' (\a k v -> v { name = k } : a) []
+      where pluginMapToList = sortWith name . M.foldlWithKey' (\a k v -> v { name = k } : a) []
 
 sortWith :: Ord b => (a -> b) -> [a] -> [a]
 sortWith = sortBy . on compare
diff --git a/src/VimScript.hs b/src/VimScript.hs
--- a/src/VimScript.hs
+++ b/src/VimScript.hs
@@ -3,37 +3,28 @@
 
 import Data.Char (isAlpha, isAlphaNum, ord, toLower)
 import Data.Function (on)
-import Data.Hashable
-import qualified Data.HashMap.Lazy as HM
 import Data.List (foldl', groupBy, sort, sortBy, nub)
+import qualified Data.Map.Strict as M
 import Data.Text (Text, singleton, unpack, unwords)
 import qualified Data.Text as T
-import GHC.Generics (Generic)
-import Prelude hiding (show, unwords, read)
+import Prelude hiding (show, unwords)
 
 import Cmdline
 import qualified Command as C
 import qualified Mapping as M
 import Mode
 import qualified Plugin as P
-import ReadText
 import qualified Setting as S
 import ShowText
 
-data VimScript = VimScript (HM.HashMap Place [Text])
+data VimScript = VimScript (M.Map Place [Text])
                deriving (Eq)
 
 data Place = Plugin
            | Autoload Text
            | Ftplugin Text
            | Syntax   Text
-           deriving (Eq, Generic)
-
-instance Hashable Place where
-  hashWithSalt a Plugin       = a `hashWithSalt` (0 :: Int) `hashWithSalt` ("" :: Text)
-  hashWithSalt a (Autoload s) = a `hashWithSalt` (1 :: Int) `hashWithSalt` s
-  hashWithSalt a (Ftplugin s) = a `hashWithSalt` (2 :: Int) `hashWithSalt` s
-  hashWithSalt a (Syntax s)   = a `hashWithSalt` (3 :: Int) `hashWithSalt` s
+           deriving (Eq, Ord)
 
 instance ShowText Place where
   show Plugin        = "plugin/miv.vim"
@@ -43,20 +34,20 @@
   show (Syntax s)    = "syntax/" <> s <> ".vim"
 
 vimScriptToList :: VimScript -> [(Place, [Text])]
-vimScriptToList (VimScript x) = HM.toList x
+vimScriptToList (VimScript x) = M.toList x
 
 instance Semigroup VimScript where
   VimScript x <> VimScript y
-    | HM.null x = VimScript y
-    | HM.null y = VimScript x
-    | otherwise = VimScript (HM.unionWith concat' x y)
+    | M.null x = VimScript y
+    | M.null y = VimScript x
+    | otherwise = VimScript (M.unionWith concat' x y)
     where
       concat' a [] = a
       concat' [] b = b
       concat' a b = a <> [""] <> b
 
 instance Monoid VimScript where
-  mempty = VimScript HM.empty
+  mempty = VimScript M.empty
 
 gatherScript :: S.Setting -> VimScript
 gatherScript setting = beforeScript setting
@@ -64,7 +55,7 @@
                     <> gather' "dependon" P.dependon P.loadbefore plugins
                     <> gather' "dependedby" P.dependedby P.loadafter plugins
                     <> gather "mappings" P.mappings plugins
-                    <> gather "mapmodes" P.mapmodes plugins
+                    <> gather "mapmodes" (map show . P.mapmodes) plugins
                     <> gather "functions" P.functions plugins
                     <> pluginLoader
                     <> mappingLoader
@@ -80,30 +71,30 @@
   where plugins = S.plugins setting
 
 gatherBeforeAfterScript :: [P.Plugin] -> VimScript
-gatherBeforeAfterScript x = insertAuNameMap $ gatherScripts x (mempty, HM.empty)
+gatherBeforeAfterScript x = insertAuNameMap $ gatherScripts x (mempty, M.empty)
   where
-    insertAuNameMap :: (VimScript, HM.HashMap Text Text) -> VimScript
-    insertAuNameMap (vs, hm) = VimScript (HM.singleton (Autoload "") $
+    insertAuNameMap :: (VimScript, M.Map Text Text) -> VimScript
+    insertAuNameMap (vs, m) = VimScript (M.singleton (Autoload "") $
           [ "let s:autoload = {" ]
-       <> [ "      \\ " <> singleQuote k <> ": " <> singleQuote a <> "," | (k, a) <- HM.toList hm ]
+       <> [ "      \\ " <> singleQuote k <> ": " <> singleQuote a <> "," | (k, a) <- M.toList m ]
        <> [ "      \\ }" ]) <> vs
-    gatherScripts :: [P.Plugin] -> (VimScript, HM.HashMap Text Text) -> (VimScript, HM.HashMap Text Text)
-    gatherScripts (p:ps) (vs, hm)
-            | null (P.before p) && null (P.after p) = gatherScripts ps (vs, hm)
-            | otherwise = gatherScripts ps (vs <> vs', HM.insert name hchar hm)
+    gatherScripts :: [P.Plugin] -> (VimScript, M.Map Text Text) -> (VimScript, M.Map Text Text)
+    gatherScripts (p:ps) (vs, m)
+            | null (P.before p) && null (P.after p) = gatherScripts ps (vs, m)
+            | otherwise = gatherScripts ps (vs <> vs', M.insert name hchar m)
       where
         name = T.filter isAlphaNum (T.toLower (show p))
         hchar | null (loadScript p) = maybe "_" singleton $ getHeadChar $ show p
               | otherwise = "_"
         funcname str = "miv#" <> hchar <> "#" <> str <> "_" <> name
-        vs' = VimScript $ HM.singleton (Autoload hchar) $
+        vs' = VimScript $ M.singleton (Autoload hchar) $
           wrapFunction (funcname "before") (P.before p) <>
           wrapFunction (funcname "after") (P.after p)
-    gatherScripts [] (vs, hm) = (vs, hm)
+    gatherScripts [] (vs, m) = (vs, m)
 
 gather :: Text -> (P.Plugin -> [Text]) -> [P.Plugin] -> VimScript
 gather name f plg
-  = VimScript (HM.singleton (Autoload "") $
+  = VimScript (M.singleton (Autoload "") $
       [ "let s:" <> name <> " = {" ]
    <> [ "      \\ " <> singleQuote (show p)
                     <> ": [ " <> T.intercalate ", " (map singleQuote (f p))  <> " ],"
@@ -113,7 +104,7 @@
 
 gather' :: Text -> (P.Plugin -> [Text]) -> (P.Plugin -> [Text]) -> [P.Plugin] -> VimScript
 gather' name f g plg
-  = VimScript (HM.singleton (Autoload "") $
+  = VimScript (M.singleton (Autoload "") $
       [ "let s:" <> name <> " = {" ]
    <> [ "      \\ " <> singleQuote p
                     <> ": [ " <> T.intercalate ", " (map singleQuote $ sort $ nub q)  <> " ],"
@@ -129,7 +120,7 @@
 
 pluginConfig :: P.Plugin -> VimScript
 pluginConfig plg
-    = VimScript (HM.singleton Plugin $ wrapInfo $
+    = VimScript (M.singleton Plugin $ wrapInfo $
         wrapEnable (P.enable plg) $ mapleader <> gatherCommand plg <> gatherMapping plg <> P.script plg <> loadScript plg)
   where
     wrapInfo [] = []
@@ -146,37 +137,38 @@
 gatherCommand :: P.Plugin -> [Text]
 gatherCommand plg
   | not (null (P.commands plg))
-    = [show (C.defaultCommand { C.cmdName = c
-        , C.cmdRepText = unwords ["call miv#command(" <> singleQuote (show plg) <> ","
-                               , singleQuote c <> ","
-                               , singleQuote "<bang>" <> ","
-                               , "<q-args>,"
-                               , "expand('<line1>'),"
-                               , "expand('<line2>'))" ] }) | c <- P.commands plg]
+    = [show C.defaultCommand
+          { C.cmdName = c,
+            C.cmdRepText = unwords [
+              "call miv#command(" <> singleQuote (show plg) <> ",",
+              singleQuote c <> ",",
+              singleQuote "<bang>" <> ",",
+              "<q-args>,",
+              "expand('<line1>'),",
+              "expand('<line2>'))"
+          ] } | c <- P.commands plg]
   | otherwise = []
 
 gatherMapping :: P.Plugin -> [Text]
 gatherMapping plg
   | not (null (P.mappings plg))
-    = let genMapping
-            = [\mode ->
-               M.defaultMapping
-                  { M.mapName    = c
-                  , M.mapRepText = escape mode <> ":<C-u>call miv#mapping("
-                        <> singleQuote (show plg) <> ", "
-                        <> singleQuote c <> ", "
-                        <> singleQuote (show mode) <> ")<CR>"
-                  , M.mapMode    = mode } | c <- P.mappings plg]
-          escape m = if m `elem` [ InsertMode, OperatorPendingMode ] then "<ESC>" else ""
-          modes = if null (P.mapmodes plg) then [NormalMode] else map read (P.mapmodes plg)
-          in concat [map (show . f) modes | f <- genMapping]
+    = [show M.defaultMapping
+          { M.mapName = mapping,
+            M.mapRepText = escape mode <> ":<C-u>call miv#mapping("
+                <> singleQuote (show plg) <> ", "
+                <> singleQuote mapping <> ", "
+                <> singleQuote (show mode) <> ")<CR>",
+            M.mapMode = mode
+          } | mapping <- P.mappings plg, mode <- modes]
   | otherwise = []
+    where modes = if null (P.mapmodes plg) then [NormalMode] else P.mapmodes plg
+          escape mode = if mode `elem` [InsertMode, OperatorPendingMode] then "<ESC>" else ""
 
 beforeScript :: S.Setting -> VimScript
-beforeScript setting = VimScript (HM.singleton Plugin (S.before setting))
+beforeScript setting = VimScript (M.singleton Plugin (S.before setting))
 
 afterScript :: S.Setting -> VimScript
-afterScript setting = VimScript (HM.singleton Plugin (S.after setting))
+afterScript setting = VimScript (M.singleton Plugin (S.after setting))
 
 filetypeLoader :: S.Setting -> VimScript
 filetypeLoader setting
@@ -186,12 +178,12 @@
     f :: Text -> [P.Plugin] -> VimScript
     f ft plugins = flip foldMap (getHeadChar ft) $
       \c -> let funcname = "miv#" <> singleton c <> "#load_" <> T.filter isAlphaNum (T.toLower ft)
-                in VimScript (HM.singleton Plugin
+                in VimScript (M.singleton Plugin
                      [ "augroup miv-file-type-" <> ft
                      , "  autocmd!"
                      , "  autocmd FileType " <> ft <> " call " <> funcname <> "()"
                      , "augroup END" ]) <>
-                   VimScript (HM.singleton (Autoload (singleton c)) $
+                   VimScript (M.singleton (Autoload (singleton c)) $
                      ("function! " <> funcname <> "() abort")
                      : "  setlocal filetype="
                      : loadPlugins plugins
@@ -221,10 +213,10 @@
 singleQuote str = "'" <> str <> "'"
 
 filetypeScript :: S.Setting -> VimScript
-filetypeScript = foldMap (\(ft, src) -> VimScript (HM.singleton (Ftplugin ft) src)) . HM.toList . S.filetype
+filetypeScript = foldMap (\(ft, src) -> VimScript (M.singleton (Ftplugin ft) src)) . M.toList . S.filetype
 
 syntaxScript :: S.Setting -> VimScript
-syntaxScript = foldMap (\(ft, src) -> VimScript (HM.singleton (Syntax ft) src)) . HM.toList . S.syntax
+syntaxScript = foldMap (\(ft, src) -> VimScript (M.singleton (Syntax ft) src)) . M.toList . S.syntax
 
 wrapFunction :: Text -> [Text] -> [Text]
 wrapFunction funcname script =
@@ -238,7 +230,7 @@
   | otherwise = let x = T.head xs in if isAlpha x then Just (toLower x) else getHeadChar (T.tail xs)
 
 mappingLoader :: VimScript
-mappingLoader = VimScript (HM.singleton (Autoload "")
+mappingLoader = VimScript (M.singleton (Autoload "")
   [ "function! miv#mapping(name, mapping, mode) abort"
   , "  call miv#load(a:name)"
   , "  if a:mode ==# 'v' || a:mode ==# 'x'"
@@ -252,7 +244,7 @@
   , "endfunction" ])
 
 commandLoader :: VimScript
-commandLoader = VimScript (HM.singleton (Autoload "")
+commandLoader = VimScript (M.singleton (Autoload "")
   [ "function! miv#command(name, command, bang, args, line1, line2) abort"
   , "  silent! execute 'delcommand' a:command"
   , "  call miv#load(a:name)"
@@ -266,13 +258,13 @@
 
 funcUndefinedLoader :: S.Setting -> VimScript
 funcUndefinedLoader setting = if null functions then mempty else
-  VimScript (HM.singleton Plugin
+  VimScript (M.singleton Plugin
   [ "\" FuncUndefined"
   , "augroup miv-func-undefined"
   , "  autocmd!"
   , "  autocmd FuncUndefined * call miv#func_undefined(expand('<amatch>'))"
   , "augroup END" ])
-  <> VimScript (HM.singleton (Autoload "")
+  <> VimScript (M.singleton (Autoload "")
   [ "function! miv#func_undefined(pattern) abort"
   , "  if a:pattern !~# " <> singleQuote (T.intercalate "\\|" functions)
   , "    return"
@@ -299,7 +291,7 @@
       collectSndByFst [(cmdline, p) | p <- S.plugins setting, cmdline <- P.cmdlines p]
   where
     f :: Cmdline -> [P.Plugin] -> VimScript
-    f cmdline plugins = VimScript (HM.singleton Plugin
+    f cmdline plugins = VimScript (M.singleton Plugin
       [ "\" CmdlineEnter " <> (show cmdline)
       , "if exists('#CmdlineEnter')"
       , "  augroup " <> group
@@ -309,7 +301,7 @@
       , "else"
       , "  call miv#cmdline_enter_" <> c <> "()"
       , "endif" ])
-      <> VimScript (HM.singleton (Autoload "") $
+      <> VimScript (M.singleton (Autoload "") $
        ("function! miv#cmdline_enter_" <> c <> "() abort")
       : loadPlugins plugins
       <> [ "  if exists('#CmdlineEnter')"
@@ -322,13 +314,13 @@
 
 insertEnterLoader :: S.Setting -> VimScript
 insertEnterLoader setting = if null plugins then mempty else
-  VimScript (HM.singleton Plugin
+  VimScript (M.singleton Plugin
   [ "\" InsertEnter"
   , "augroup miv-insert-enter"
   , "  autocmd!"
   , "  autocmd InsertEnter * call miv#insert_enter()"
   , "augroup END" ])
-  <> VimScript (HM.singleton (Autoload "")
+  <> VimScript (M.singleton (Autoload "")
   $ "function! miv#insert_enter() abort"
   : loadPlugins plugins
   <> [ "  autocmd! miv-insert-enter"
@@ -338,7 +330,7 @@
   where plugins = filter P.insert (S.plugins setting)
 
 pluginLoader :: VimScript
-pluginLoader = VimScript (HM.singleton (Autoload "")
+pluginLoader = VimScript (M.singleton (Autoload "")
   [ "let s:loaded = {}"
   , "let s:path = expand('<sfile>:p:h:h')"
   , "let s:mivpath = expand('<sfile>:p:h:h:h') . '/'"
