diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2016 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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/miv.cabal b/miv.cabal
new file mode 100644
--- /dev/null
+++ b/miv.cabal
@@ -0,0 +1,60 @@
+name:                   miv
+version:                0.3.0
+author:                 itchyny <https://github.com/itchyny>
+maintainer:             itchyny <https://github.com/itchyny>
+license:                MIT
+license-file:           LICENSE
+category:               Compiler
+build-type:             Simple
+cabal-version:          >=1.8
+synopsis:               Manage Vim plugins with command
+description:            The miv command is a cli tool to manage Vim plugins.
+
+executable miv
+  hs-source-dirs:       src
+  main-is:              Main.hs
+  ghc-options:          -threaded -Wall
+  other-modules:        Plugin
+                      , Setting
+                      , Mode
+                      , Command
+                      , Mapping
+                      , ShowText
+                      , ReadText
+                      , VimScript
+                      , Git
+                      , Paths_miv
+  build-depends:        base >= 4.9 && < 5
+                      , ghc-prim
+                      , process
+                      , async
+                      , concurrent-output
+                      , time
+                      , directory
+                      , hashable
+                      , aeson
+                      , yaml
+                      , text
+                      , unordered-containers
+                      , monad-parallel
+
+test-suite spec
+  hs-source-dirs:       test
+  main-is:              Spec.hs
+  type:                 exitcode-stdio-1.0
+  build-depends:        base >= 4.9 && < 5
+                      , ghc-prim
+                      , process
+                      , time
+                      , directory
+                      , hashable
+                      , hspec
+                      , aeson
+                      , yaml
+                      , text
+                      , unordered-containers
+                      , monad-parallel
+
+source-repository head
+  type:     git
+  location: git@github.com:itchyny/miv.git
diff --git a/src/Command.hs b/src/Command.hs
new file mode 100644
--- /dev/null
+++ b/src/Command.hs
@@ -0,0 +1,115 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Command where
+
+import Data.Monoid ((<>))
+import qualified Data.Text as T
+import Data.Text (Text, unwords)
+import Prelude hiding (show, unwords)
+import ShowText
+
+data CmdBang = CmdBang
+             | CmdNoBang
+             deriving Eq
+
+instance ShowText CmdBang where
+  show CmdBang = "-bang"
+  show CmdNoBang = ""
+
+data CmdBar = CmdBar
+            | CmdNoBar
+            deriving Eq
+
+instance ShowText CmdBar where
+  show CmdBar = "-bar"
+  show CmdNoBar = ""
+
+data CmdRegister = CmdRegister
+                 | CmdNoRegister
+                 deriving Eq
+
+instance ShowText CmdRegister where
+  show CmdRegister = "-register"
+  show CmdNoRegister = ""
+
+data CmdBuffer = CmdBuffer
+               | CmdNoBuffer
+               deriving Eq
+
+instance ShowText CmdBuffer where
+  show CmdBuffer = "-buffer"
+  show CmdNoBuffer = ""
+
+data CmdRange = CmdRange
+              | CmdRangeWhole
+              | CmdRangeN Int
+              | CmdRangeCount Int
+              | CmdNoRange
+              deriving Eq
+
+instance ShowText CmdRange where
+  show CmdRange = "-range"
+  show CmdRangeWhole = "-range=%"
+  show (CmdRangeN n) = "-range=" <> show n
+  show (CmdRangeCount n) = "-count=" <> show n
+  show CmdNoRange = ""
+
+data CmdArg = CmdNonNegArg
+            | CmdZeroOneArg
+            | CmdPositiveArg
+            | CmdOneArg
+            | CmdNoArg
+            deriving Eq
+
+instance ShowText CmdArg where
+  show CmdNonNegArg = "-nargs=*"
+  show CmdZeroOneArg = "-nargs=?"
+  show CmdPositiveArg = "-nargs=+"
+  show CmdOneArg = "-nargs=1"
+  show CmdNoArg = "-nargs=0"
+
+data CmdComplete = CmdComplete Text
+                 deriving Eq
+
+instance ShowText CmdComplete where
+  show (CmdComplete "") = ""
+  show (CmdComplete complete) = "-complete=" <> complete
+
+data Command =
+     Command { cmdName     :: Text
+             , cmdRepText  :: Text
+             , cmdBang     :: CmdBang
+             , cmdBar      :: CmdBar
+             , cmdRegister :: CmdRegister
+             , cmdBuffer   :: CmdBuffer
+             , cmdRange    :: CmdRange
+             , cmdArg      :: CmdArg
+             , cmdComplete :: CmdComplete
+     } deriving Eq
+
+instance ShowText Command where
+  show cmd = unwords (filter (not . T.null)
+           [ "command!"
+           , show (cmdBang cmd)
+           , show (cmdBar cmd)
+           , show (cmdRegister cmd)
+           , show (cmdBuffer cmd)
+           , show (cmdRange cmd)
+           , show (cmdArg cmd)
+           , show (cmdComplete cmd)
+           , cmdName cmd
+           , cmdRepText cmd
+           ])
+
+defaultCommand :: Command
+defaultCommand
+ = Command { cmdName     = ""
+           , cmdRepText  = ""
+           , cmdBang     = CmdBang
+           , cmdBar      = CmdNoBar
+           , cmdRegister = CmdNoRegister
+           , cmdBuffer   = CmdNoBuffer
+           , cmdRange    = CmdRange
+           , cmdArg      = CmdNonNegArg
+           , cmdComplete = CmdComplete ""
+ }
+
diff --git a/src/Git.hs b/src/Git.hs
new file mode 100644
--- /dev/null
+++ b/src/Git.hs
@@ -0,0 +1,38 @@
+module Git
+  ( clone
+  , cloneSubmodule
+  , pull
+  , pullSubmodule
+  , lastUpdate
+  , gitStatus
+  , gitUrl
+  )
+  where
+
+import Data.Monoid ((<>))
+import System.Exit (ExitCode(..))
+import System.Process (system, readProcess)
+
+clone :: String -> FilePath -> String
+clone repo path = unwords ["git", "clone", gitUrl repo, singleQuote path]
+
+cloneSubmodule :: String -> FilePath -> String
+cloneSubmodule repo path = unwords ["git", "clone", gitUrl repo, singleQuote path, "&&", "git", "-C", singleQuote path, "submodule", "update", "--init", "--recursive"]
+
+pull :: FilePath -> String
+pull path = unwords ["git", "-C", singleQuote path, "pull", "--rebase", "--prune"]
+
+pullSubmodule :: FilePath -> String
+pullSubmodule path = unwords ["git", "-C", singleQuote path, "pull", "--rebase", "--prune", "&&", "git", "-C", singleQuote path, "submodule", "update", "--init", "--recursive"]
+
+lastUpdate :: FilePath -> IO Integer
+lastUpdate path = read <$> readProcess "sh" ["-c", unwords ["git", "-C", singleQuote path, "show", "-s", "--format=%ct", "2>/dev/null", "||", "echo", "0"]] []
+
+gitStatus :: FilePath -> IO ExitCode
+gitStatus path = system $ unwords ["sh", "-c", "\"git", "-C", singleQuote path, "status", ">/dev/null 2>&1\""]
+
+gitUrl :: String -> String
+gitUrl = ("https://github.com/" <>)
+
+singleQuote :: String -> String
+singleQuote str = "'" <> str <> "'"
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,466 @@
+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
+module Main where
+
+import Control.Concurrent (threadDelay, newEmptyMVar, forkIO, putMVar, takeMVar)
+import Control.Concurrent.Async
+import Control.Exception
+import Control.Monad (filterM, forM_, unless, void, when, guard)
+import qualified Control.Monad.Parallel as P
+import Data.List ((\\), foldl', isPrefixOf, nub, transpose, unfoldr)
+import Data.Maybe (listToMaybe, fromMaybe, isNothing)
+import Data.Monoid ((<>))
+import Data.Text (Text, unlines, pack, unpack)
+import qualified Data.Text as T
+import Data.Text.IO (putStrLn, putStr, writeFile, hGetContents)
+import Data.Time (getZonedTime)
+import Data.Version (showVersion)
+import Data.Yaml (decodeFileEither, prettyPrintParseException)
+import GHC.Conc (getNumProcessors, setNumCapabilities)
+import Prelude hiding (readFile, writeFile, unlines, putStrLn, putStr, show)
+import System.Console.Concurrent ()
+import System.Console.Regions
+import System.Directory
+import System.Environment (getArgs)
+import System.Exit (ExitCode(..))
+import System.Info (os)
+import System.IO (openFile, IOMode(..), hClose, hFlush, stdout, hGetLine)
+import System.IO.Error (isDoesNotExistError, tryIOError, isEOFError)
+import System.Process
+
+import Git
+import Paths_miv (version)
+import Plugin
+import Setting
+import ShowText
+import VimScript
+
+nameversion :: Text
+nameversion = "miv " <> pack (showVersion version)
+
+expandHomeDirectory :: FilePath -> IO FilePath
+expandHomeDirectory ('~':path) = (<>path) <$> getHomeDirectory
+expandHomeDirectory path = return path
+
+getSettingFile :: IO (Maybe FilePath)
+getSettingFile
+  = fmap listToMaybe $ filterM doesFileExist =<< mapM expandHomeDirectory
+       [ "~/.vimrc.yaml"
+       , "~/.vim/.vimrc.yaml"
+       , "~/vimrc.yaml"
+       , "~/.vim/vimrc.yaml"
+       , "~/_vimrc.yaml"
+       , "~/.vim/_vimrc.yaml"
+       , "~/_vim/_vimrc.yaml"
+       , "~/vimfiles/.vimrc.yaml"
+       , "~/vimfiles/vimrc.yaml"
+       , "~/vimfiles/_vimrc.yaml"
+       ]
+
+getSetting :: IO Setting
+getSetting = do
+  maybeFile <- getSettingFile
+  case maybeFile of
+       Just file -> do
+         maybeSetting <- decodeFileEither file
+         case maybeSetting of
+              Right setting -> return setting
+              Left err -> error $ prettyPrintParseException err
+       Nothing -> error "No setting file: ~/.vimrc.yaml"
+
+pluginDirectory :: IO FilePath
+pluginDirectory = do
+  dir <- expandHomeDirectory "~/.vim/miv/"
+  windir <- expandHomeDirectory "~/vimfiles/miv/"
+  exists <- doesDirectoryExist dir
+  return (if exists then dir else (if os `elem` ["windows", "mingw"] then windir else dir))
+
+createPluginDirectory :: IO ()
+createPluginDirectory =
+  createDirectoryIfMissing True =<< pluginDirectory
+
+printUsage :: IO ()
+printUsage = mapM_ putStrLn usage
+
+commandHelp :: IO ()
+commandHelp = mapM_ (putStrLn . show) arguments
+
+data Argument = Argument (Text, Text)
+              deriving (Eq, Ord)
+instance ShowText Argument where
+  show (Argument (x, y)) = x <> T.replicate (10 - T.length x) " " <> y
+
+arguments :: [Argument]
+arguments = map Argument
+          [ ("install" , "Installs the uninstalled plugins.")
+          , ("update"  , "Updates the plugins.")
+          , ("generate", "Generate the miv files.")
+          , ("ftdetect", "Gather ftdetect scripts.")
+          , ("helptags", "Generate the help tags file.")
+          , ("each"    , "Execute command at each plugin directory.")
+          , ("path"    , "Print the paths of each plugins.")
+          , ("list"    , "List the plugins.")
+          , ("clean"   , "Clean up unused plugins.")
+          , ("edit"    , "Edit the configuration file.")
+          , ("command" , "Show the sub-commands.")
+          , ("version" , "Show the version of miv.")
+          , ("help"    , "Show this help.")
+          ]
+
+usage :: [Text]
+usage = [ nameversion
+        , ""
+        , "Usage: miv COMMAND"
+        , ""
+        , "Commands:"
+        ]
+     <> map (T.append "  " . show) arguments
+     <> [ ""
+        , "You can install the plugins by the following command:"
+        , "  miv install"
+        , ""
+        , "You can update the plugins by the following command:"
+        , "  miv update"
+        , ""
+        , "You can specify the name of plugins:"
+        , "  miv update plugin1 plugin2"
+        , ""
+        , "Normally, outdated plugins are ignored but"
+        , "you can update all the plugins with trailing !:"
+        , "  miv update!"
+        , ""
+        , "You can use `miv each' to execute some commands at each plugin directory."
+        , "  miv each pwd"
+        , "  miv each git gc"
+        , "  miv each git diff"
+        , "  miv each 'echo $(du -sh .) $(basename $(pwd))'"
+        , "  miv each 'git diff --quiet || echo ${PWD##*/}'"
+        , ""
+        , "This software is released under the MIT License."
+        , "This software is distributed at https://github.com/itchyny/miv."
+        , "Report a bug of this software at https://github.com/itchyny/miv/issues."
+        , "The author is itchyny <https://github.com/itchyny>."
+        ]
+
+levenshtein :: Eq a => [a] -> [a] -> Int
+levenshtein a b = last $ foldl' f [0..length a] b
+  where
+    f [] _ = []
+    f xs@(x:xs') c = scanl (g c) (x + 1) (zip3 a xs xs')
+    g c z (d, x, y) = minimum [y + 1, z + 1, x + fromEnum (c /= d)]
+
+suggestCommand :: String -> IO ()
+suggestCommand arg = do
+  let distcommands = [(levenshtein arg (unpack x), Argument (x, y)) | (Argument (x, y)) <- arguments]
+      mindist = fst (minimum distcommands)
+      mincommands = [y | (x, y) <- distcommands, x == mindist]
+      prefixcommands = [y | y@(Argument (x, _)) <- arguments, arg `isPrefixOf` unpack x || unpack x `isPrefixOf` arg]
+      containedcommands = [y | y@(Argument (x, _)) <- arguments, length arg > 1 && arg `isContainedIn` unpack x]
+  putStrLn $ "Unknown command: " <> pack arg
+  putStrLn "Probably:"
+  mapM_ (putStrLn . ("  "<>) . show) (if null prefixcommands then nub (containedcommands <> mincommands) else prefixcommands)
+
+isContainedIn :: Eq a => [a] -> [a] -> Bool
+isContainedIn xxs@(x:xs) (y:ys) = x == y && xs `isContainedIn` ys || xxs `isContainedIn` ys
+isContainedIn [] _ = True
+isContainedIn _ [] = False
+
+suggestPlugin :: [Plugin] -> String -> IO ()
+suggestPlugin plugin arg = do
+  let distplugins = [(levenshtein arg (rtpName p), p) | p <- plugin]
+      mindist = fst (minimum distplugins)
+      minplugins = [y | (x, y) <- distplugins, x == mindist]
+  putStrLn $ "Unknown plugin: " <> pack arg
+  putStrLn "Probably:"
+  mapM_ (putStrLn . ("  "<>) . show) minplugins
+
+data Update = Install | Update deriving Eq
+instance ShowText Update where
+  show Install = "installing"
+  show Update = "updating"
+
+data UpdateStatus
+   = UpdateStatus {
+       installed :: [Plugin],
+       updated :: [Plugin],
+       nosync :: [Plugin],
+       outdated :: [Plugin],
+       failed :: [Plugin]
+   }
+
+instance Semigroup UpdateStatus where
+  UpdateStatus i u n o f <> UpdateStatus i' u' n' o' f'
+    = UpdateStatus (i <> i') (u <> u') (n <> n') (o <> o') (f <> f')
+
+instance Monoid UpdateStatus where
+  mempty = UpdateStatus [] [] [] [] []
+
+updatePlugin :: Update -> Maybe [String] -> Setting -> IO ()
+updatePlugin update maybePlugins setting = do
+  setNumCapabilities =<< getNumProcessors
+  let unknownPlugins = filter (`notElem` map rtpName (plugins setting)) (fromMaybe [] maybePlugins)
+  unless (null unknownPlugins) $
+    mapM_ (suggestPlugin (plugins setting)) unknownPlugins
+  createPluginDirectory
+  dir <- pluginDirectory
+  let specified p = rtpName p `elem` fromMaybe [] maybePlugins || maybePlugins == Just []
+  let filterplugin p = isNothing maybePlugins || specified p
+  let ps = filter filterplugin (plugins setting)
+  let count xs = if length xs > 1 then "s (" <> show (length xs) <> ")" else ""
+  time <- maximum <$> mapM' (lastUpdatePlugin dir) ps
+  status <- fmap mconcat <$> displayConsoleRegions $
+    mapConcurrently (fmap mconcat . mapM (\p -> updateOnePlugin time dir update (specified p) p)) $
+      transpose $ takeWhile (not . null) $ unfoldr (Just . splitAt 6) ps
+  putStrLn $ (if null (failed status) then "Success" else "Error occured") <> " in " <> show update <> "."
+  unless (null (installed status)) $ do
+    putStrLn $ "Installed plugin" <> count (installed status) <> ": "
+    mapM_ (putStrLn . ("  "<>) . name) (reverse (installed status))
+  unless (null (updated status)) $ do
+    putStrLn $ "Updated plugin" <> count (updated status) <> ": "
+    mapM_ (putStrLn . ("  "<>) . name) (reverse (updated status))
+  unless (null (failed status)) $ do
+    putStrLn $ "Failed plugin" <> count (failed status) <> ": "
+    mapM_ (putStrLn . ("  "<>) . name) (reverse (failed status))
+  generatePluginCode setting
+  gatherFtdetectScript setting
+  generateHelpTags setting
+
+mapM' :: P.MonadParallel m => (a -> m b) -> [a] -> m [b]
+mapM' f = fmap mconcat . P.mapM (mapM f) . transpose . takeWhile (not . null) . unfoldr (Just . splitAt 32)
+
+cleanAndCreateDirectory :: FilePath -> IO ()
+cleanAndCreateDirectory dir = do
+  createDirectoryIfMissing True dir
+  removeDirectoryRecursive dir
+  createDirectoryIfMissing True dir
+
+generateHelpTags :: Setting -> IO ()
+generateHelpTags setting = do
+  dir <- pluginDirectory
+  let docdir = dir <> "miv/doc/"
+  cleanAndCreateDirectory docdir
+  P.forM_ (map (\p -> dir <> rtpName p <> "/doc/") (plugins setting)) $ \path -> do
+    exists <- doesDirectoryExist path
+    when exists $
+      void $ system $ unwords ["sh", "-c", "\"cd", "'" <> path <> "'", "&& cp *", "'" <> docdir <> "'", "2>/dev/null\""]
+  _ <- system $ "vim -u NONE -i NONE -N -e -s -c 'helptags " <> docdir <> "' -c quit"
+  putStrLn "Success in processing helptags."
+
+lastUpdatePlugin :: FilePath -> Plugin -> IO Integer
+lastUpdatePlugin dir plugin = do
+  let path = dir <> rtpName plugin <> "/.git"
+  exists <- doesDirectoryExist path
+  if exists then lastUpdate path else return 0
+
+updateOnePlugin :: Integer -> FilePath -> Update -> Bool -> Plugin -> IO UpdateStatus
+updateOnePlugin time dir update specified plugin = do
+  let path = dir <> rtpName plugin
+      repo = vimScriptRepo (name plugin)
+      cloneCommand = if submodule plugin then cloneSubmodule else clone
+      pullCommand = if submodule plugin then pullSubmodule else pull
+      putStrLn' = \region -> setConsoleRegion region . ((name plugin <> ": ") <>)
+      finish' = \region -> finishConsoleRegion region . ((name plugin <> ": ") <>)
+  exists <- doesDirectoryExist path
+  gitstatus <- gitStatus path
+  if not exists || (gitstatus /= ExitSuccess && not (sync plugin))
+     then withConsoleRegion Linear $ \region -> do
+       putStrLn' region "Installing"
+       when exists $ removeDirectoryRecursive path
+       cloneStatus <- execCommand (unpack $ name plugin) region $ cloneCommand repo path
+       created <- doesDirectoryExist path
+       if cloneStatus /= ExitSuccess || not created
+          then return mempty { failed = [plugin] }
+          else return mempty { installed = [plugin] }
+     else if update == Install || not (sync plugin)
+             then return mempty { nosync = [plugin] }
+             else withConsoleRegion Linear $ \region -> do
+               lastUpdateTime <- lastUpdate path
+               if lastUpdateTime < time - 60 * 60 * 24 * 30 && not specified
+                  then do
+                    finish' region "Outdated"
+                    return mempty { outdated = [plugin] }
+                  else do
+                    putStrLn' region "Pulling"
+                    pullStatus <- execCommand (unpack $ name plugin) region $ pullCommand path
+                    newUpdateTime <- lastUpdate path
+                    return $ if pullStatus /= ExitSuccess
+                                then mempty { failed = [plugin] }
+                                else if newUpdateTime <= lastUpdateTime
+                                        then mempty
+                                        else mempty { updated = [plugin] }
+
+execCommand :: String -> ConsoleRegion -> String -> IO ExitCode
+execCommand pluginName region command = do
+  (_, Just hout, Just herr, ph) <- createProcess (proc "sh" ["-c", command]) { std_out = CreatePipe, std_err = CreatePipe }
+  outmvar <- newEmptyMVar
+  errmvar <- newEmptyMVar
+  _ <- forkIO $ go herr errmvar ""
+  _ <- forkIO $ go hout outmvar ""
+  errline <- takeMVar errmvar
+  outline <- takeMVar outmvar
+  code <- waitForProcess ph
+  finishConsoleRegion region (pluginName ++ ": " ++ if null outline then errline else outline)
+  return code
+  where go h mvar lastLine = do
+          e <- tryIOError $ hGetLine h
+          case e of
+               Left err -> if isEOFError err then putMVar mvar lastLine else return ()
+               Right line -> do
+                 setConsoleRegion region (pluginName ++ ": " ++ line)
+                 threadDelay 100000
+                 go h mvar line
+
+vimScriptRepo :: Text -> FilePath
+vimScriptRepo pluginname | T.any (=='/') pluginname = unpack pluginname
+                         | otherwise = unpack $ "vim-scripts/" <> pluginname
+
+listPlugin :: Setting -> IO ()
+listPlugin setting = mapM_ putStrLn $ space $ map format $ plugins setting
+  where format p = [show p, name p, pack $ gitUrl (vimScriptRepo (name p))]
+        space xs =
+          let max0 = maximum (map (T.length . (!!0)) xs) + 1
+              max1 = maximum (map (T.length . (!!1)) xs) + 1
+              in map (\(as:bs:cs:_) -> as <> T.replicate (max0 - T.length as) " " <> bs <> T.replicate (max1 - T.length bs) " " <> cs) xs
+
+cleanDirectory :: Setting -> IO ()
+cleanDirectory setting = do
+  createPluginDirectory
+  dir <- pluginDirectory
+  createDirectoryIfMissing True dir
+  cnt <- getDirectoryContents dir
+  let paths = "." : ".." : "miv" : map (unpack . show) (plugins setting)
+      delpath' = [ dir <> d | d <- cnt, d `notElem` paths ]
+  deldir <- filterM doesDirectoryExist delpath'
+  delfile <- filterM doesFileExist delpath'
+  let delpath = deldir <> delfile
+  if not (null delpath)
+     then do putStrLn "Remove:"
+             mapM_ (putStrLn . pack . ("  "<>)) delpath
+             putStr "Really? [y/N] "
+             hFlush stdout
+             c <- getChar
+             when (c == 'y' || c == 'Y') $ do
+               mapM_ removeDirectoryRecursive deldir
+               mapM_ removeFile delfile
+     else putStrLn "Clean."
+
+saveScript :: (FilePath, Place, [Text]) -> IO ()
+saveScript (dir, place, code) = do
+  let relname = show place
+      path = dir <> unpack relname
+      isAllAscii = all (T.all (<='~')) code
+      body = "" : (if isAllAscii then [] else [ "scriptencoding utf-8", "" ])
+         <> [ "let s:save_cpo = &cpo"
+            , "set cpo&vim"
+            , "" ]
+         <> code
+         <> [ ""
+            , "let &cpo = s:save_cpo"
+            , "unlet s:save_cpo" ]
+  time <- getZonedTime
+  contents <- fileContents path
+  when (contents /= Just body) $
+    writeFile path $ unlines $
+              [ "\" Filename: " <> relname
+              , "\" Last Change: " <> show time
+              , "\" Generated by " <> nameversion ] ++ body
+
+fileContents :: String -> IO (Maybe [Text])
+fileContents path = do
+  eitherFile <- tryJust (guard . isDoesNotExistError) (openFile path ReadMode)
+  case eitherFile of
+       Right file -> do
+         contentLines <- T.lines <$> hGetContents file
+         hClose file
+         return $ Just $ dropWhile ("\" " `T.isPrefixOf`) contentLines
+       Left _ -> return Nothing
+
+generatePluginCode :: Setting -> IO ()
+generatePluginCode setting = do
+  dir <- fmap (<>"miv/") pluginDirectory
+  createDirectoryIfMissing True dir
+  -- TODO: remove unused directories and files
+  createDirectoryIfMissing True (dir <> "plugin/")
+  createDirectoryIfMissing True (dir <> "autoload/miv/")
+  createDirectoryIfMissing True (dir <> "ftplugin/")
+  P.mapM_ (saveScript . (\(t, s) -> (dir, t, s)))
+          (vimScriptToList (gatherScript setting))
+  putStrLn "Success in generating Vim scripts of miv."
+
+gatherFtdetectScript :: Setting -> IO ()
+gatherFtdetectScript setting = do
+  dir <- pluginDirectory
+  cleanAndCreateDirectory (dir <> "miv/ftdetect/")
+  forM_ (plugins setting) $ \plugin -> do
+    let path = rtpName plugin
+    exists <- doesDirectoryExist (dir <> path <> "/ftdetect")
+    when exists $ do
+      files <- (\\ [".", ".."]) <$> getDirectoryContents (dir <> path <> "/ftdetect")
+      forM_ files $ \file ->
+        copyFile (dir <> path <> "/ftdetect/" <> file) (dir <> "miv/ftdetect/" <> file)
+  putStrLn "Success in gathering ftdetect scripts."
+
+data EachStatus = EachStatus { failed' :: [Plugin] }
+
+instance Semigroup EachStatus where
+  EachStatus f <> EachStatus f' = EachStatus (f <> f')
+
+instance Monoid EachStatus where
+  mempty = EachStatus []
+
+eachPlugin :: String -> Setting -> IO ()
+eachPlugin command setting = do
+  createPluginDirectory
+  dir <- pluginDirectory
+  status <- mconcat <$> mapM (eachOnePlugin command dir) (plugins setting)
+  unless (null (failed' status)) $ do
+    putStrLn "Error:"
+    mapM_ (putStrLn . ("  "<>) . name) (failed' status)
+
+eachOnePlugin :: String -> FilePath -> Plugin -> IO EachStatus
+eachOnePlugin command dir plugin = do
+  let path = dir <> rtpName plugin
+  exists <- doesDirectoryExist path
+  if not exists
+     then return mempty
+     else do exitCode <- system (unwords ["cd", "'" <> path <> "'", "&&", command])
+             case exitCode of
+                  ExitSuccess -> return mempty
+                  _ -> return $ mempty { failed' = [plugin] }
+
+eachHelp :: IO ()
+eachHelp = mapM_ putStrLn [ "Specify command:", "  miv each [command]" ]
+
+pathPlugin :: [String] -> Setting -> IO ()
+pathPlugin plugins' setting = do
+  let ps = filter (\p -> rtpName p `elem` plugins' || null plugins') (plugins setting)
+  dir <- pluginDirectory
+  forM_ ps (\plugin -> putStrLn (pack dir <> show plugin))
+
+mainProgram :: [String] -> IO ()
+mainProgram [] = printUsage
+mainProgram ['-':arg] = mainProgram [arg]
+mainProgram ["help"] = printUsage
+mainProgram ["version"] = putStrLn nameversion
+mainProgram ["install"] = getSetting >>= updatePlugin Install Nothing
+mainProgram ["update"] = getSetting >>= updatePlugin Update Nothing
+mainProgram ["update!"] = getSetting >>= updatePlugin Update (Just [])
+mainProgram ["update", "!"] = getSetting >>= updatePlugin Update (Just [])
+mainProgram ["each"] = eachHelp
+mainProgram ["list"] = getSetting >>= listPlugin
+mainProgram ["command"] = commandHelp
+mainProgram ["clean"] = getSetting >>= cleanDirectory
+mainProgram ["edit"] = getSettingFile >>= maybe (return ()) (($) void . system . ("vim "<>))
+mainProgram ["generate"] = getSetting >>= generatePluginCode
+mainProgram ["ftdetect"] = getSetting >>= gatherFtdetectScript
+mainProgram ["helptags"] = getSetting >>= generateHelpTags
+mainProgram ["path"] = getSetting >>= pathPlugin []
+mainProgram [arg] = suggestCommand arg
+mainProgram ("install":args) = getSetting >>= updatePlugin Install (Just args)
+mainProgram ("update":args) = getSetting >>= updatePlugin Update (Just args)
+mainProgram ("each":args) = getSetting >>= eachPlugin (unwords args)
+mainProgram ("path":args) = getSetting >>= pathPlugin args
+mainProgram (('-':arg):args) = mainProgram (arg:args)
+mainProgram _ = printUsage
+
+main :: IO ()
+main = getArgs >>= mainProgram
diff --git a/src/Mapping.hs b/src/Mapping.hs
new file mode 100644
--- /dev/null
+++ b/src/Mapping.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Mapping where
+
+import Data.Monoid ((<>))
+import Data.Text (Text, unwords, null)
+import Prelude hiding (show, unwords, null)
+
+import Mode
+import ShowText
+
+data MapUnique = MapUnique | MapNoUnique
+               deriving Eq
+
+instance ShowText MapUnique where
+  show MapUnique = "<unique>"
+  show MapNoUnique = ""
+
+data MapSilent = MapSilent | MapNoSilent
+               deriving Eq
+
+instance ShowText MapSilent where
+  show MapSilent = "<silent>"
+  show MapNoSilent = ""
+
+data Mapping =
+     Mapping { mapName    :: Text
+             , mapRepText :: Text
+             , mapUnique  :: MapUnique
+             , mapSilent  :: MapSilent
+             , mapMode    :: Mode
+     } deriving Eq
+
+instance ShowText Mapping where
+  show m = unwords (filter (not . null)
+          [ show (mapMode m) <> "noremap"
+          , show (mapUnique m)
+         <> show (mapSilent m)
+          , mapName m
+          , mapRepText m
+          ])
+
+defaultMapping :: Mapping
+defaultMapping
+  = Mapping { mapName    = ""
+            , mapRepText = ""
+            , mapUnique  = MapUnique
+            , mapSilent  = MapSilent
+            , mapMode    = NormalMode
+  }
+
diff --git a/src/Mode.hs b/src/Mode.hs
new file mode 100644
--- /dev/null
+++ b/src/Mode.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Mode where
+
+import Data.Text (unpack)
+
+import ReadText
+import ShowText
+
+data Mode = NormalMode
+          | VisualMode
+          | SelectMode
+          | InsertMode
+          | CmdlineMode
+          | ExMode
+          | OperatorPendingMode
+          | ReplaceMode
+          | VirtualReplaceMode
+          | InsertNormalMode
+          | InesrtVisualMode
+          | InsertSelectMode
+          deriving Eq
+
+instance ShowText Mode where
+  show NormalMode = "n"
+  show VisualMode = "v"
+  show SelectMode = "s"
+  show InsertMode = "i"
+  show CmdlineMode = "c"
+  show ExMode = "ex"
+  show OperatorPendingMode = "o"
+  show ReplaceMode = "r"
+  show VirtualReplaceMode = "vr"
+  show InsertNormalMode = "in"
+  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
diff --git a/src/Plugin.hs b/src/Plugin.hs
new file mode 100644
--- /dev/null
+++ b/src/Plugin.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
+module Plugin where
+
+import Control.Applicative ((<|>))
+import Data.Aeson
+import qualified Data.Text as T
+import Data.Text (Text, unpack)
+
+import ShowText
+
+data Plugin =
+     Plugin { name       :: Text
+            , filetypes  :: [Text]
+            , commands   :: [Text]
+            , functions  :: [Text]
+            , mappings   :: [Text]
+            , mapmodes   :: [Text]
+            , insert     :: Bool
+            , enable     :: Text
+            , sync       :: Bool
+            , mapleader  :: Text
+            , script     :: [Text]
+            , after      :: [Text]
+            , before     :: [Text]
+            , dependon   :: [Text]
+            , dependedby :: [Text]
+            , loadafter  :: [Text]
+            , loadbefore :: [Text]
+            , submodule  :: Bool
+     } deriving (Eq, Ord)
+
+instance ShowText Plugin where
+  show plg = subst (name plg)
+    where subst s | T.any (=='/') s = subst (T.tail $ T.dropWhile (/='/') s)
+                  | ".vim" `T.isSuffixOf` s = T.take (T.length s - 4) s
+                  | "-vim" `T.isSuffixOf` s = T.take (T.length s - 4) s
+                  | "vim-" `T.isPrefixOf` s = T.drop 4 s
+                  | otherwise = T.filter (`notElem`("!?;:/<>()[]{}|~'\"" :: String)) s
+
+rtpName :: Plugin -> String
+rtpName = unpack . ShowText.show
+
+instance FromJSON Plugin where
+  parseJSON = withObject "plugin" $ \o -> do
+    name <- o .:? "name" .!= ""
+    filetypes <- o .: "filetype" <|> (fmap return <$> o .:? "filetype") .!= []
+    commands <- o .: "command" <|> (fmap return <$> o .:? "command") .!= []
+    functions <- o .: "function" <|> (fmap return <$> o .:? "function") .!= []
+    mappings <- o .: "mapping" <|> (fmap return <$> o .:? "mapping") .!= []
+    mapmodes <- o .: "mapmode" <|> (fmap return <$> o .:? "mapmode") .!= []
+    mapleader <- o .:? "mapleader" .!= ""
+    insert <- o .:? "insert" .!= False
+    enable <- o .:? "enable" .!= ""
+    sync <- o .:? "sync" .!= True
+    script <- T.lines <$> o .:? "script" .!= ""
+    after <- T.lines <$> o .:? "after" .!= ""
+    before <- T.lines <$> o .:? "before" .!= ""
+    dependon <- o .: "dependon" <|> (fmap return <$> o .:? "dependon") .!= []
+    dependedby <- o .: "dependedby" <|> (fmap return <$> o .:? "dependedby") .!= []
+    loadafter <- o .: "loadafter" <|> (fmap return <$> o .:? "loadafter") .!= []
+    loadbefore <- o .: "loadbefore" <|> (fmap return <$> o .:? "loadbefore") .!= []
+    submodule <- o .:? "submodule" .!= False
+    return Plugin {..}
diff --git a/src/ReadText.hs b/src/ReadText.hs
new file mode 100644
--- /dev/null
+++ b/src/ReadText.hs
@@ -0,0 +1,10 @@
+{-# 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
new file mode 100644
--- /dev/null
+++ b/src/Setting.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
+module Setting where
+
+import Data.Aeson
+import Data.Function (on)
+import qualified Data.HashMap.Lazy as HM
+import Data.List (sortBy)
+import Data.Text (Text, lines)
+import Prelude hiding (lines)
+
+import Plugin
+
+data Setting =
+     Setting { plugins  :: [Plugin]
+             , filetype :: HM.HashMap 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
+    before <- lines <$> o .:? "before" .!= ""
+    after <- lines <$> o .:? "after" .!= ""
+    return Setting {..}
+      where pluginHashMapToList = sortWith name . HM.foldlWithKey' (\a k v -> v { name = k } : a) []
+
+sortWith :: Ord b => (a -> b) -> [a] -> [a]
+sortWith = sortBy . on compare
diff --git a/src/ShowText.hs b/src/ShowText.hs
new file mode 100644
--- /dev/null
+++ b/src/ShowText.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE FlexibleInstances, IncoherentInstances, UndecidableInstances #-}
+module ShowText where
+
+import Data.Text
+
+class ShowText a where
+  show :: a -> Text
+
+instance Show a => ShowText a where
+  show = pack . Prelude.show
diff --git a/src/VimScript.hs b/src/VimScript.hs
new file mode 100644
--- /dev/null
+++ b/src/VimScript.hs
@@ -0,0 +1,351 @@
+{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
+module VimScript where
+
+import Data.Char (isAlpha, isAlphaNum, toLower)
+import Data.Hashable
+import qualified Data.HashMap.Lazy as HM
+import Data.List (foldl')
+import Data.Maybe (mapMaybe)
+import Data.Monoid ((<>))
+import Data.Text (Text, unwords, singleton)
+import qualified Data.Text as T
+import GHC.Generics (Generic)
+import Prelude hiding (show, unwords, read)
+
+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])
+               deriving (Eq)
+
+data Place = Plugin
+           | Autoload Text
+           | Ftplugin 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
+
+instance ShowText Place where
+  show Plugin        = "plugin/miv.vim"
+  show (Autoload "") = "autoload/miv.vim"
+  show (Autoload s)  = "autoload/miv/" <> s <> ".vim"
+  show (Ftplugin s)  = "ftplugin/" <> s <> ".vim"
+
+autoloadSubdirName :: Place -> Maybe Text
+autoloadSubdirName (Autoload "") = Nothing
+autoloadSubdirName (Autoload s) = Just s
+autoloadSubdirName _ = Nothing
+
+isFtplugin :: Place -> Bool
+isFtplugin (Ftplugin _) = True
+isFtplugin _ = False
+
+vimScriptToList :: VimScript -> [(Place, [Text])]
+vimScriptToList (VimScript x) = HM.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)
+    where
+      concat' a [] = a
+      concat' [] b = b
+      concat' a b = a <> [""] <> b
+
+instance Monoid VimScript where
+  mempty = VimScript HM.empty
+
+gatherScript :: S.Setting -> VimScript
+gatherScript setting = addAutoloadNames
+                     $ beforeScript setting
+                    <> gatherBeforeAfterScript plugins
+                    <> gather "dependon" P.dependon plugins
+                    <> gather "dependedby" P.dependedby plugins
+                    <> gather "mappings" P.mappings plugins
+                    <> gather "mapmodes" P.mapmodes plugins
+                    <> gatherFuncUndefined setting
+                    <> pluginLoader
+                    <> mappingLoader
+                    <> commandLoader
+                    <> foldl' (<>) mempty (map pluginConfig plugins)
+                    <> filetypeLoader setting
+                    <> gatherInsertEnter setting
+                    <> filetypeScript (S.filetype setting)
+                    <> afterScript setting
+  where plugins = S.plugins setting
+
+gatherBeforeAfterScript :: [P.Plugin] -> VimScript
+gatherBeforeAfterScript x = insertAuNameMap $ gatherScripts x (mempty, HM.empty)
+  where
+    insertAuNameMap :: (VimScript, HM.HashMap Text Text) -> VimScript
+    insertAuNameMap (vs, hm) = VimScript (HM.singleton (Autoload "") $
+          [ "let s:autoload = {" ]
+       <> [ "      \\ " <> singleQuote k <> ": " <> singleQuote a <> "," | (k, a) <- HM.toList hm ]
+       <> [ "      \\ }" ]) <> 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)
+      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
+        au = Autoload hchar
+        vs' = VimScript $ HM.singleton au $ wrapFunction (funcname "before") (P.before p)
+                                         <> wrapFunction (funcname "after") (P.after p)
+    gatherScripts [] (vs, hm) = (vs, hm)
+
+addAutoloadNames :: VimScript -> VimScript
+addAutoloadNames h@(VimScript hm)
+  = VimScript (HM.singleton (Autoload "")
+      [ "let s:autoloads = { " <> T.intercalate ", " (((<>": 1") . singleQuote)
+                               <$> mapMaybe autoloadSubdirName (HM.keys hm)) <> " }"])
+   <> h
+
+gather :: Text -> (P.Plugin -> [Text]) -> [P.Plugin] -> VimScript
+gather name f plg
+  = VimScript (HM.singleton (Autoload "") $
+      [ "let s:" <> name <> " = {" ]
+   <> [ "      \\ " <> singleQuote (show p)
+                    <> ": [ " <> T.intercalate ", " (map singleQuote (f p))  <> " ],"
+                                                               | p <- plg, not (null (f p)) ]
+   <> [ "      \\ }" ])
+
+pluginConfig :: P.Plugin -> VimScript
+pluginConfig plg
+    = VimScript (HM.singleton Plugin $ wrapInfo $
+        wrapEnable plg $ mapleader <> gatherCommand plg <> gatherMapping plg <> P.script plg <> loadScript plg)
+  where
+    wrapInfo [] = []
+    wrapInfo str = ("\" " <> P.name plg) : str
+    mapleader = (\s -> if T.null s then [] else ["let g:mapleader = " <> singleQuote s]) (P.mapleader plg)
+
+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)
+  = ["call miv#load(" <> singleQuote (show plg) <> ")"]
+  | otherwise = []
+
+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]
+  | 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, VisualMode] else map read (P.mapmodes plg)
+          in concat [map (show . f) modes | f <- genMapping]
+  | otherwise = []
+
+beforeScript :: S.Setting -> VimScript
+beforeScript setting = VimScript (HM.singleton Plugin (S.before setting))
+
+afterScript :: S.Setting -> VimScript
+afterScript setting = VimScript (HM.singleton Plugin (S.after setting))
+
+filetypeLoader :: S.Setting -> VimScript
+filetypeLoader setting
+  = HM.foldrWithKey f mempty (filetypeLoadPlugins (S.plugins setting) HM.empty)
+  where
+    f ft plg val =
+      case getHeadChar ft of
+           Nothing -> val
+           Just c ->
+             let funcname = "miv#" <> singleton c <> "#load_" <> T.filter isAlphaNum (T.toLower ft)
+                 in val
+                  <> VimScript (HM.singleton (Autoload (singleton c))
+                       (("function! " <> funcname <> "() abort")
+                       : "  setl ft="
+                       :  concat [wrapEnable b
+                       [ "  call miv#load(" <> singleQuote (show b) <> ")"] | b <- plg]
+                    <> [ "  autocmd! MivFileTypeLoad" <> ft
+                       , "  setl ft=" <> ft
+                       , "  silent! doautocmd FileType " <> ft
+                       , "endfunction"
+                       ]))
+                  <> VimScript (HM.singleton Plugin
+                       [ "augroup MivFileTypeLoad" <> ft
+                       , "  autocmd!"
+                       , "  autocmd FileType " <> ft <> " call " <> funcname <> "()"
+                       , "augroup END"
+                       ])
+
+filetypeLoadPlugins :: [P.Plugin] -> HM.HashMap Text [P.Plugin] -> HM.HashMap Text [P.Plugin]
+filetypeLoadPlugins (b:plugins) fts
+  | not (null (P.filetypes b))
+  = filetypeLoadPlugins plugins (foldr (flip (HM.insertWith (<>)) [b]) fts (P.filetypes b))
+  | 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! MivInsertEnter"
+               , "  silent! doautocmd InsertEnter"
+               , "endfunction"
+               , ""
+               , "augroup MivInsertEnter"
+               , "  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 MivFuncUndefined"
+               , "  autocmd!"
+               , "  autocmd FuncUndefined * call s:funcUndefined()"
+               , "augroup END"
+               , "" ]
+
+wrapEnable :: P.Plugin -> [Text] -> [Text]
+wrapEnable plg str
+  | null str = []
+  | T.null (P.enable plg) = str
+  | P.enable plg == "0" = []
+  | otherwise = (indent <> "if " <> P.enable plg)
+                           : map ("  "<>) str
+             <> [indent <> "endif"]
+  where indent = T.takeWhile (==' ') (head str)
+
+singleQuote :: Text -> Text
+singleQuote str = "'" <> str <> "'"
+
+filetypeScript :: HM.HashMap Text [Text] -> VimScript
+filetypeScript =
+  HM.foldrWithKey (\ft scr val -> val <> VimScript (HM.singleton (Ftplugin ft) scr)) mempty
+
+wrapFunction :: Text -> [Text] -> [Text]
+wrapFunction funcname script =
+     ["function! " <> funcname <> "() abort"]
+  <> map ("  "<>) script
+  <> ["endfunction"]
+
+getHeadChar :: Text -> Maybe Char
+getHeadChar xs
+  | T.null xs = Nothing
+  | 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 "")
+  [ "function! miv#mapping(name, mapping, mode) abort"
+  , "  call miv#load(a:name)"
+  , "  if a:mode ==# 'v' || a:mode ==# 'x'"
+  , "    call feedkeys('gv', 'n')"
+  , "  elseif a:mode ==# 'o'"
+  , "    call feedkeys(\"\\<Esc>\", 'n')"
+  , "    call feedkeys(v:operator, 'm')"
+  , "  endif"
+  , "  call feedkeys((v:count ? v:count : '') . substitute(a:mapping, '<Plug>', \"\\<Plug>\", 'g'), 'm')"
+  , "  return ''"
+  , "endfunction"
+  ])
+
+commandLoader :: VimScript
+commandLoader = VimScript (HM.singleton (Autoload "")
+  [ "function! miv#command(name, command, bang, args, line1, line2) abort"
+  , "  silent! execute 'delcommand' a:command"
+  , "  call miv#load(a:name)"
+  , "  let range = a:line1 != a:line2 ? a:line1.','.a:line2 : ''"
+  , "  try"
+  , "    exec range.a:command.a:bang a:args"
+  , "  catch /^Vim\\%((\\a\\+)\\)\\=:E481:/"
+  , "    exec a:command.a:bang a:args"
+  , "  endtry"
+  , "endfunction"
+  ])
+
+pluginLoader :: VimScript
+pluginLoader = VimScript (HM.singleton (Autoload "")
+  [ "let s:loaded = {}"
+  , "let s:path = expand('<sfile>:p:h:h')"
+  , "let s:mivpath = expand('<sfile>:p:h:h:h') . '/'"
+  , "function! miv#load(name) abort"
+  , "  if has_key(s:loaded, a:name)"
+  , "    return"
+  , "  endif"
+  , "  let s:loaded[a:name] = 1"
+  , "  for n in get(s:dependon, a:name, [])"
+  , "    call miv#load(n)"
+  , "  endfor"
+  , "  for mode in get(s:mapmodes, a:name, [ 'n', 'v' ])"
+  , "    for mapping in get(s:mappings, a:name, [])"
+  , "      silent! execute mode . 'unmap' mapping"
+  , "    endfor"
+  , "  endfor"
+  , "  let name = substitute(tolower(a:name), '[^a-z0-9]', '', 'g')"
+  , "  let au = has_key(s:autoload, name) && get(s:autoloads, s:autoload[name])"
+  , "  if au"
+  , "    call miv#{s:autoload[name]}#before_{name}()"
+  , "  endif"
+  , "  let newrtp = s:mivpath . a:name . '/'"
+  , "  if !isdirectory(newrtp)"
+  , "    return"
+  , "  endif"
+  , "  let rtps = split(&rtp, ',')"
+  , "  let i = index(map(deepcopy(rtps), 'resolve(v:val)'), s:path)"
+  , "  if i < 0"
+  , "    let mivpath = get(filter(deepcopy(rtps), 'v:val =~# \"miv.\\\\?$\"'), 0, '')"
+  , "    let i = max([0, index(deepcopy(rtps), mivpath)])"
+  , "  endif"
+  , "  let &rtp = join(insert(rtps, newrtp, i), ',')"
+  , "  if isdirectory(newrtp . 'after/')"
+  , "    exec 'set rtp+=' . newrtp . 'after/'"
+  , "  endif"
+  , "  for dir in filter(['plugin', 'after/plugin'], 'isdirectory(newrtp . v:val)')"
+  , "    for file in split(glob(newrtp . dir . '/**/*.vim'), '\\n')"
+  , "      silent! source `=file`"
+  , "    endfor"
+  , "  endfor"
+  , "  if au"
+  , "    call miv#{s:autoload[name]}#after_{name}()"
+  , "  endif"
+  , "  for n in get(s:dependedby, a:name, [])"
+  , "    call miv#load(n)"
+  , "  endfor"
+  , "endfunction"
+  ])
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
