diff --git a/miv.cabal b/miv.cabal
--- a/miv.cabal
+++ b/miv.cabal
@@ -1,5 +1,5 @@
 name:                   miv
-version:                0.4.4
+version:                0.4.5
 author:                 itchyny <https://github.com/itchyny>
 maintainer:             itchyny <https://github.com/itchyny>
 license:                MIT
@@ -19,6 +19,7 @@
                       , Setting
                       , Mode
                       , Command
+                      , Cmdline
                       , Mapping
                       , ShowText
                       , ReadText
diff --git a/src/Cmdline.hs b/src/Cmdline.hs
new file mode 100644
--- /dev/null
+++ b/src/Cmdline.hs
@@ -0,0 +1,51 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Cmdline where
+
+import Data.Aeson
+import qualified Data.Aeson as Aeson
+import Data.Hashable
+import Data.Text (Text,unpack)
+import Prelude hiding (show)
+import qualified Prelude as Prelude
+
+import ReadText
+import ShowText
+
+data Cmdline = CmdlineExCommand
+             | CmdlineForwardSearch
+             | CmdlineBackwardSearch
+             | CmdlineInput
+             deriving (Eq, Ord)
+
+instance ShowText Cmdline where
+  show CmdlineExCommand      = ":"
+  show CmdlineForwardSearch  = "/"
+  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
+
+cmdlinePattern :: Cmdline -> Text
+cmdlinePattern CmdlineExCommand      = ":"
+cmdlinePattern CmdlineForwardSearch  = "/"
+cmdlinePattern CmdlineBackwardSearch = "\\?"
+cmdlinePattern CmdlineInput          = "@"
diff --git a/src/Plugin.hs b/src/Plugin.hs
--- a/src/Plugin.hs
+++ b/src/Plugin.hs
@@ -6,6 +6,7 @@
 import qualified Data.Text as T
 import Data.Text (Text, unpack)
 
+import Cmdline
 import ShowText
 
 data Plugin =
@@ -15,6 +16,7 @@
             , functions  :: [Text]
             , mappings   :: [Text]
             , mapmodes   :: [Text]
+            , cmdlines   :: [Cmdline]
             , insert     :: Bool
             , enable     :: Text
             , sync       :: Bool
@@ -50,6 +52,7 @@
     functions <- o .: "function" <|> (fmap return <$> o .:? "function") .!= []
     mappings <- o .: "mapping" <|> (fmap return <$> o .:? "mapping") .!= []
     mapmodes <- o .: "mapmode" <|> (fmap return <$> o .:? "mapmode") .!= []
+    cmdlines <- o .: "cmdline" <|> (fmap return <$> o .:? "cmdline") .!= []
     mapleader <- o .:? "mapleader" .!= ""
     insert <- o .:? "insert" .!= False
     enable <- o .:? "enable" .!= ""
diff --git a/src/VimScript.hs b/src/VimScript.hs
--- a/src/VimScript.hs
+++ b/src/VimScript.hs
@@ -1,17 +1,18 @@
 {-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
 module VimScript where
 
-import Data.Char (isAlpha, isAlphaNum, toLower)
+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 Data.Maybe (mapMaybe)
-import Data.Text (Text, unwords, singleton)
+import Data.Text (Text, singleton, unpack, unwords)
 import qualified Data.Text as T
 import GHC.Generics (Generic)
 import Prelude hiding (show, unwords, read)
 
+import Cmdline
 import qualified Command as C
 import qualified Mapping as M
 import Mode
@@ -33,7 +34,7 @@
   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` (2 :: Int) `hashWithSalt` s
+  hashWithSalt a (Syntax s)   = a `hashWithSalt` (3 :: Int) `hashWithSalt` s
 
 instance ShowText Place where
   show Plugin        = "plugin/miv.vim"
@@ -71,13 +72,15 @@
                     <> gather' "dependedby" P.dependedby P.loadafter plugins
                     <> gather "mappings" P.mappings plugins
                     <> gather "mapmodes" P.mapmodes plugins
-                    <> gatherFuncUndefined setting
+                    <> gather "functions" P.functions plugins
                     <> pluginLoader
                     <> mappingLoader
                     <> commandLoader
                     <> foldl' (<>) mempty (map pluginConfig plugins)
                     <> filetypeLoader setting
-                    <> gatherInsertEnter setting
+                    <> funcUndefinedLoader setting
+                    <> cmdlineEnterLoader setting
+                    <> insertEnterLoader setting
                     <> filetypeScript (S.filetype setting)
                     <> syntaxScript (S.syntax setting)
                     <> afterScript setting
@@ -150,7 +153,7 @@
 loadScript :: P.Plugin -> [Text]
 loadScript plg
   | all null [ P.commands plg, P.mappings plg, P.functions plg, P.filetypes plg
-             , P.loadafter plg, P.loadbefore plg ] && not (P.insert plg)
+             , P.loadafter plg, P.loadbefore plg ] && null (P.cmdlines plg) && not (P.insert plg)
   = ["call miv#load(" <> singleQuote (show plg) <> ")"]
   | otherwise = []
 
@@ -179,7 +182,7 @@
                         <> 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, VisualMode] else map read (P.mapmodes plg)
+          modes = if null (P.mapmodes plg) then [NormalMode] else map read (P.mapmodes plg)
           in concat [map (show . f) modes | f <- genMapping]
   | otherwise = []
 
@@ -201,11 +204,12 @@
                  in val
                   <> VimScript (HM.singleton (Autoload (singleton c))
                        (("function! " <> funcname <> "() abort")
-                       : "  setl ft="
+                       : "  setlocal filetype="
                        :  concat [wrapEnable b
                        [ "  call miv#load(" <> singleQuote (show b) <> ")"] | b <- plg]
                     <> [ "  autocmd! miv-file-type-" <> ft
-                       , "  setl ft=" <> ft
+                       , "  augroup! miv-file-type-" <> ft
+                       , "  setlocal filetype=" <> ft
                        , "  silent! doautocmd FileType " <> ft
                        , "endfunction"
                        ]))
@@ -223,40 +227,6 @@
   | otherwise = filetypeLoadPlugins plugins fts
 filetypeLoadPlugins [] fts = fts
 
-gatherInsertEnter :: S.Setting -> VimScript
-gatherInsertEnter setting
-  = VimScript (HM.singleton Plugin (f [ p | p <- S.plugins setting, P.insert p ]))
-  where f [] = []
-        f plgs = "\" InsertEnter"
-               : "function! s:insertEnter() abort"
-             : [ "  call miv#load(" <> singleQuote (show p) <> ")" | p <- plgs :: [P.Plugin] ]
-            <> [ "  autocmd! miv-insert-enter"
-               , "  silent! doautocmd InsertEnter"
-               , "endfunction"
-               , ""
-               , "augroup miv-insert-enter"
-               , "  autocmd!"
-               , "  autocmd InsertEnter * call s:insertEnter()"
-               , "augroup END" ]
-
-gatherFuncUndefined :: S.Setting -> VimScript
-gatherFuncUndefined setting
-  = VimScript (HM.singleton Plugin (f [ p | p <- S.plugins setting, not (null (P.functions p))]))
-  where f [] = []
-        f plgs = "\" FuncUndefined"
-               : "function! s:funcUndefined() abort"
-               : "  let f = expand('<amatch>')"
-               : concat [
-               [ "  if f =~# " <> singleQuote q
-               , "    call miv#load(" <> singleQuote (show p) <> ")"
-               , "  endif" ] | (p, q) <- concatMap (\q -> map ((,) q) (P.functions q)) plgs]
-            <> [ "endfunction"
-               , ""
-               , "augroup miv-func-undefined"
-               , "  autocmd!"
-               , "  autocmd FuncUndefined * call s:funcUndefined()"
-               , "augroup END" ]
-
 wrapEnable :: P.Plugin -> [Text] -> [Text]
 wrapEnable plg str
   | null str = []
@@ -318,6 +288,72 @@
   , "endfunction"
   ])
 
+funcUndefinedLoader :: S.Setting -> VimScript
+funcUndefinedLoader setting | all (null . P.functions) (S.plugins setting) = mempty
+funcUndefinedLoader _ = VimScript (HM.singleton Plugin
+  [ "\" FuncUndefined"
+  , "augroup miv-func-undefined"
+  , "  autocmd!"
+  , "  autocmd FuncUndefined * call miv#func_undefined(expand('<amatch>'))"
+  , "augroup END" ])
+  <> VimScript (HM.singleton (Autoload "")
+  [ "function! miv#func_undefined(pattern) abort"
+  , "  if empty(s:functions)"
+  , "    autocmd! miv-func-undefined"
+  , "    augroup! miv-func-undefined"
+  , "    return"
+  , "  endif"
+  , "  for [name, fs] in items(s:functions)"
+  , "    for f in fs"
+  , "      if a:pattern =~# f"
+  , "        call miv#load(name)"
+  , "        return"
+  , "      endif"
+  , "    endfor"
+  , "  endfor"
+  , "endfunction"
+  ])
+
+cmdlineEnterLoader :: S.Setting -> VimScript
+cmdlineEnterLoader setting
+  = HM.foldrWithKey f mempty $
+      foldr (uncurry (HM.insertWith (<>))) HM.empty
+        [ (cmdline, [p]) | p <- S.plugins setting, cmdline <- P.cmdlines p ]
+  where
+    f :: Cmdline -> [P.Plugin] -> VimScript -> VimScript
+    f cmdline plugins val = val <> VimScript (HM.singleton Plugin
+      [ "\" CmdlineEnter " <> (show cmdline)
+      , "augroup " <> group
+      , "  autocmd!"
+      , "  autocmd CmdlineEnter " <> (cmdlinePattern cmdline) <> " call miv#cmdline_enter_" <> c <> "()"
+      , "augroup END" ])
+      <> VimScript (HM.singleton (Autoload "") $
+       ("function! miv#cmdline_enter_" <> c <> "() abort") :
+      [ "  call miv#load(" <> singleQuote (show p) <> ")" | p <- plugins ]
+      <> [ "  autocmd! " <> group
+      , "  augroup! " <> group
+      , "endfunction"
+      ])
+      where c = T.concat $ map (show . ord) (unpack (show cmdline))
+            group = "miv-cmdline-enter-" <> c
+
+insertEnterLoader :: S.Setting -> VimScript
+insertEnterLoader setting = if null plugins then mempty else VimScript (HM.singleton Plugin
+  [ "\" InsertEnter"
+  , "augroup miv-insert-enter"
+  , "  autocmd!"
+  , "  autocmd InsertEnter * call miv#insert_enter()"
+  , "augroup END" ])
+  <> VimScript (HM.singleton (Autoload "")
+  $ "function! miv#insert_enter() abort" :
+  [ "  call miv#load(" <> singleQuote (show p) <> ")" | p <- plugins ]
+  <> [ "  autocmd! miv-insert-enter"
+  , "  augroup! miv-insert-enter"
+  , "  silent! doautocmd InsertEnter"
+  , "endfunction"
+  ])
+  where plugins = filter P.insert (S.plugins setting)
+
 pluginLoader :: VimScript
 pluginLoader = VimScript (HM.singleton (Autoload "")
   [ "let s:loaded = {}"
@@ -328,10 +364,13 @@
   , "    return"
   , "  endif"
   , "  let s:loaded[a:name] = 1"
+  , "  if has_key(s:functions, a:name)"
+  , "    call remove(s:functions, a:name)"
+  , "  endif"
   , "  for n in get(s:dependon, a:name, [])"
   , "    call miv#load(n)"
   , "  endfor"
-  , "  for mode in get(s:mapmodes, a:name, [ 'n', 'v' ])"
+  , "  for mode in get(s:mapmodes, a:name, ['n'])"
   , "    for mapping in get(s:mappings, a:name, [])"
   , "      silent! execute mode . 'unmap' mapping"
   , "    endfor"
