VimでJavaScript開発環境を作成

JavaScriptの勉強がてら開発環境を整備しました。
チップスは概要のみ掲載しているため、詳細は[.vimrc](https://raw.githubusercontent.com/KazuakiM/dotfiles/master/.vim/vimrc)を参照して下さい。

# Plugin設定
## ディレクトリ作成
``` sh
$ mkdir -p \
    $HOME/.cache/eslint \
    $HOME/.vim/vim-ref/cache \
    $HOME/.vim/tags \
    $HOME/.vim/neosnippet.vim \
    $HOME/.vim/neocomplete.vim
```

## Pluginインストール
Neobundle作者のShougoさんの新しいプラグイン管理プラグイン「<a href="https://github.com/Shougo/dein.vim" target="_blank">dein.vim</a>」を設定します。

``` vim:.vimrc
" Cygwin
if has('win32') || has ('win64')
    set runtimepath^=$HOME\.vim runtimepath+=$HOME\.vim\after
endif
let s:deinDir    = ! exists('s:deinDir') ? $HOME . '/.vim/dein.vim' : s:deinDir
let &runtimepath = &runtimepath . ',' . s:deinDir . '/repos/github.com/Shougo/dein.vim'
" dein.vim {{{
if dein#load_state(s:deinDir)
    call dein#begin(s:deinDir)
    call dein#add('KazuakiM/neosnippet-snippets')
    call dein#add('KazuakiM/vim-qfstatusline')
    call dein#add('mojako/ref-sources.vim')
    call dein#add('pangloss/vim-javascript')
    call dein#add('Shougo/dein.vim')
    call dein#add('Shougo/neocomplete.vim')
    call dein#add('Shougo/neoinclude.vim')
    call dein#add('Shougo/neosnippet.vim')
    call dein#add('Shougo/vimproc.vim', {'build': 'make'})
    call dein#add('thinca/vim-quickrun')
    call dein#add('thinca/vim-ref')
    call dein#add('vim-scripts/taglist.vim')
    call dein#add('osyo-manga/shabadou.vim')
    call dein#add('osyo-manga/vim-watchdogs')
    call dein#add('mustardamus/jqapi', {'lazy':1})
    call dein#add('tokuhirom/jsref',   {'lazy':1})

    call dein#end()
    call dein#save_state()
endif
"}}}
filetype plugin indent on
syntax enable
```

## syntax highlight
[VimでES6のシンタックスハイライト - Qiita](http://qiita.com/QuestionDriven/items/83cba09d0010e8e31315) がイメージもあってよさ気です。綺麗だとその分もっさりするのでお好みで使用するのが良いと思います。

## マニュアル設定
JavaScriptのマニュアルをネットワークを介さずに参照する設定をします。

### .vimrc設定
``` vim:.vimrc
" vim-ref {{{
inoremap <silent><C-k> <C-o>:call<Space>ref#K('normal')<CR><ESC>
nmap <silent>K <Plug>(ref-keyword)
let g:ref_no_default_key_mappings = 1
let g:ref_cache_dir               = $HOME . '/.vim/vim-ref/cache'
let g:ref_detect_filetype         = {
\    'css':        'phpmanual',
\    'html':       ['phpmanual',  'javascript', 'jquery'],
\    'javascript': ['javascript', 'jquery'],
\    'php':        ['phpmanual',  'javascript', 'jquery']
\}
let g:ref_javascript_doc_path = $HOME . '/.vim/dein.vim/repos/github.com/tokuhirom/jsref/htdocs'
let g:ref_jquery_doc_path     = $HOME . '/.vim/dein.vim/repos/github.com/mustardamus/jqapi'
let g:ref_phpmanual_path      = $HOME . '/.vim/vim-ref/php-chunked-xhtml'
let g:ref_use_cache           = 1
let g:ref_use_vimproc         = 1
"}}}
```

## Universal Ctags設定
tagsファイルを適切に生成することでタグジャンプが可能となり、時間短縮が期待できます。

### .ctags設定
正規表現の前にJavaScriptを勉強したてで、ケース漏れが多いかと思われます。
コメント頂けると幸いです。

```:.ctags
--exclude=*min.js
--recurse=yes
--langmap=JavaScript:.js.json.jsx.html
```

### alias設定
``` sh:.bashrc.local
#.bashrc.localみたいな感じでGitHub管理外でよしなに管理する
tagsJsCmd='ctags --languages=js  -f'
tagsVariable=''
tagsVariable="$tagsVariable cd $HOME/sample1; $tagsJsCmd $HOME/.vim/tags/sample1.tags $HOME/sample1;"
tagsVariable="$tagsVariable cd $HOME/sample2; $tagsJsCmd $HOME/.vim/tags/sample2.tags $HOME/sample2;"
tagsVariable="$tagsVariable cd;"
alias TAGS=$tagsVariable
```

### .vimrc設定
``` vim:.vimrc
" taglist.vim {{{
"MEMO:$ ctags --list-maps : ctags supported filetype.
"MEMO:$ ctags --list-kinds: ctags tlist setting.
nnoremap <Leader>t :<C-u>Tlist<CR>
let g:tlist_javascript_settings = 'js;o:object;f:function'
let g:tlist_php_settings        = 'php;c:class;f:function;d:constant'
let g:Tlist_Exit_OnlyWindow     = 1
let g:Tlist_Show_One_File       = 1
let g:Tlist_Use_Right_Window    = 1
let g:Tlist_WinWidth            = 25
"}}}

".vimrc.localみたいな感じでGitHub管理外でよしなに管理する
augroup MyTagsAutoCmd
    autocmd!
    autocmd BufNewFile,BufRead $HOME/sample1/*.{js,json,html} setlocal tags=$HOME/.vim/tags/sample1.tags
    autocmd BufNewFile,BufRead $HOME/sample2/*.{js,json,html} setlocal tags=$HOME/.vim/tags/sample2.tags
augroup END
```

## Syntaxチェック, lintチェック, 自動整形設定
はやりの[ESLint](http://eslint.org/)を導入します。

保存時の動作はESLintによる非同期のSyntaxチェックを実施、
Quickrunした場合には--fixオプションを付与した実行となり、自動整形が行われます。
ESLintのオプションなどはhelpを見て適当に設定したため、改良の余地はあるかと思われます。
(2016-02-24:追記start)
ESLintの設定を作りこむと若干Vimが遅くなったので、".eslintrc.js"と".eslintrc.limit.js"に分けました。
保存時は簡易版チェックで処理を行い、Quickrun時はフル版チェックで処理を行い、
"MyEslintFix()"実行時は--fixオプションを付与した実行となるようにしました。

``` vim:.vimrc
" vim-qfstatusline {{{
function! MyStatuslineSyntax() abort "{{{
    let l:ret = qfstatusline#Update()
    if 0 < len(l:ret)
        if s:lineUpdate is# 0
            highlight StatusLine cterm=NONE gui=NONE ctermfg=Black guifg=Black ctermbg=Magenta guibg=Magenta
            let s:lineUpdate = 1
        endif
    elseif s:lineUpdate is# 1
        highlight StatusLine cterm=NONE gui=NONE ctermfg=Black guifg=Black ctermbg=Grey guibg=Grey
        let s:lineUpdate = 0
    endif
    return l:ret
endfunction "}}}

function! MyStatuslinePaste() abort "{{{
    if &paste is# 1
        return '(paste)'
    endif
    return ''
endfunction "}}}

let g:Qfstatusline#UpdateCmd = function('MyStatuslineSyntax')
set laststatus=2
set cmdheight=1
set statusline=\ %t\ %{MyStatuslinePaste()}\ %m\ %r\ %h\ %w\ %q\ %{MyStatuslineSyntax()}%=%l/%L\ \|\ %Y\ \|\ %{&fileformat}\ \|\ %{&fileencoding}\ 
"}}}

" vim-quickrun {{{
function! EslintFix() abort "{{{
    let l:quickrun_config_backup                  = g:quickrun_config['javascript']
    let g:quickrun_config['javascript']['cmdopt'] = l:quickrun_config_backup['cmdopt'] .' --config '. $HOME .'/.eslintrc.js --fix'
    let g:quickrun_config['javascript']['runner'] = 'system'

    QuickRun

    let g:quickrun_config['javascript'] = l:quickrun_config_backup
endfunction "}}}

nnoremap <Leader>run  :<C-u>QuickRun<CR>
nnoremap <Leader>es   :<C-u>call<Space>EslintFix()<CR>
let s:quickrun_config_javascript = {
\    'command':     'eslint',
\    'cmdopt':      '--cache --cache-location ' . s:envHome . '/.cache/eslint/.eslintcache --format compact --max-warnings 1 --no-color --no-ignore --quiet',
\    'errorformat': '%E%f: line %l\, col %c\, Error - %m,%W%f: line %l\, col %c\, Warning - %m,%-G%.%#',
\    'exec':        '%c %o %s:p'
\}
let g:quickrun_config = {
\    '_': {
\        'hook/close_buffer/enable_empty_data': 1,
\        'hook/close_buffer/enable_failure':    1,
\        'outputter':                           'multi:buffer:quickfix',
\        'outputter/buffer/close_on_empty':     1,
\        'outputter/buffer/split':              ':botright',
\        'runner':                              'vimproc',
\        'runner/vimproc/updatetime':           600
\    },
\    'javascript': {
\        'command':     s:quickrun_config_javascript['command'],
\        'cmdopt':      s:quickrun_config_javascript['cmdopt'] . ' --config ' . s:envHome . '/.eslintrc.js',
\        'errorformat': s:quickrun_config_javascript['errorformat'],
\        'exec':        s:quickrun_config_javascript['exec']
\    },
\    'javascript/watchdogs_checker': {
\        'type': 'watchdogs_checker/javascript'
\    },
\    'watchdogs_checker/_': {
\        'hook/close_quickfix/enable_exit':        1,
\        'hook/back_window/enable_exit':           0,
\        'hook/back_window/priority_exit':         1,
\        'hook/qfstatusline_update/enable_exit':   1,
\        'hook/qfstatusline_update/priority_exit': 2,
\        'outputter/quickfix/open_cmd':            ''
\    },
\    'watchdogs_checker/javascript': {
\        'command':     s:quickrun_config_javascript['command'],
\        'cmdopt':      s:quickrun_config_javascript['cmdopt'] . ' --config ' . s:envHome . '/.eslintrc.limit.js',
\        'errorformat': s:quickrun_config_javascript['errorformat'],
\        'exec':        s:quickrun_config_javascript['exec']
\    }
\}
unlet s:quickrun_config_javascript
"}}}

" shabadou.vim
" vim-watchdogs {{{
let g:watchdogs_check_BufWritePost_enable  = 1
let g:watchdogs_check_BufWritePost_enables = {'vim': 0}
let g:watchdogs_check_CursorHold_enable    = 1
let g:watchdogs_check_CursorHold_enables   = {'vim': 0}
"}}}
```

## 補完機能設定

Vim本体にも補完機能はありますが、ここではリッチな補完機能を活用したチップスの紹介をします。

補完するにあたり最も重要となるのはsnippetsになります。
snippetsはコーディング効率に直結する重要な設定のため、
私はベースとなるsnippetsをforkして自分用に最適化した物を使用しています。

### .vimrc設定
``` vim:.vimrc
" neosnippet-snippets
" neosnippet.vim
" neoinclude.vim
" neocomplete.vim {{{
imap <silent><expr><TAB> pumvisible() ? "\<C-n>" : neosnippet#expandable_or_jumpable() ? "\<Plug>(neosnippet_expand_or_jump)" : "\<TAB>"
" neosnippet.vim
smap <silent><expr><TAB>  neosnippet#expandable_or_jumpable() ? "\<Plug>(neosnippet_expand_or_jump)" : "\<TAB>"
nmap <silent><expr><TAB>  neosnippet#expandable_or_jumpable() ? "\<Plug>(neosnippet_expand_or_jump)" : "\<TAB>"
imap <silent><expr><C-x>  MyNeoCompleteCr()
imap <silent><expr><CR>   MyNeoCompleteCr()
nmap <silent><S-TAB> <ESC>a<C-r>=neosnippet#commands#_clear_markers()<CR>
inoremap <expr><C-h> neocomplete#smart_close_popup()."\<C-h>"
inoremap <expr><BS>  neocomplete#smart_close_popup()."\<C-h>"
"neocomplete.vim
let g:neocomplete#auto_completion_start_length = 3
let g:neocomplete#data_directory               = $HOME .'/.vim/neocomplete.vim'
let g:neocomplete#delimiter_patterns           = {
\    'javascript': ['.'],
\    'php':        ['->', '::', '\'],
\    'ruby':       ['::']
\}
let g:neocomplete#enable_at_startup         = 1
let g:neocomplete#enable_auto_close_preview = 1
let g:neocomplete#enable_auto_delimiter     = 1
let g:neocomplete#enable_auto_select        = 0
let g:neocomplete#enable_fuzzy_completion   = 0
let g:neocomplete#enable_smart_case         = 1
let g:neocomplete#keyword_patterns          = {'_': '\h\w*'}
let g:neocomplete#lock_buffer_name_pattern  = '\.log\|.*quickrun.*\|.jax'
let g:neocomplete#max_keyword_width         = 30
let g:neocomplete#max_list                  = 8
let g:neocomplete#min_keyword_length        = 3
let g:neocomplete#sources                   = {
\    '_':          ['neosnippet', 'file',               'buffer'],
\    'css':        ['neosnippet',         'dictionary', 'buffer'],
\    'html':       ['neosnippet', 'file', 'dictionary', 'buffer'],
\    'javascript': ['neosnippet', 'file', 'dictionary', 'buffer'],
\    'php':        ['neosnippet', 'file', 'dictionary', 'buffer']
\}
let g:neocomplete#sources#buffer#cache_limit_size  = 50000
let g:neocomplete#sources#buffer#disabled_pattern  = '\.log\|\.jax'
let g:neocomplete#sources#buffer#max_keyword_width = 30
let g:neocomplete#sources#dictionary#dictionaries  = {
\    '_':          '',
\    'css':        $HOME . '/.vim/dict/css.dict',
\    'html':       $HOME . '/.vim/dict/html.dict',
\    'javascript': $HOME . '/.vim/dict/javascript.dict',
\    'php':        $HOME . '/.vim/dict/php.dict'
\}
let g:neocomplete#use_vimproc = 1
"neoinclude.vim
let g:neoinclude#exts          = {'php': ['php', 'inc', 'tpl']}
let g:neoinclude#max_processes = 5
"neosnippet.vim
let g:neosnippet#data_directory                = $HOME . '/.vim/neosnippet.vim'
let g:neosnippet#disable_runtime_snippets      = {'_' : 1}
let g:neosnippet#enable_snipmate_compatibility = 1
let g:neosnippet#snippets_directory            = $HOME . '/.vim/dein.vim/repos/github.com/KazuakiM/neosnippet-snippets/neosnippets'
function! MyNeoCompleteCr() abort "{{{
    if pumvisible() is# 0
        return "\<CR>X\<C-h>"
    elseif neosnippet#expandable_or_jumpable()
        return "\<Plug>(neosnippet_expand_or_jump)"
    endif
    return "\<Left>\<Right>"
endfunction "}}}
"}}}
```