Browse Source

vim: plugins: ++committia.vim

Maxim Likhachev 5 years ago
parent
commit
9d3ffffa78
  1. 19
      etc/soft/nvim/+plugins/committia.vim/LICENSE
  2. 121
      etc/soft/nvim/+plugins/committia.vim/README.md
  3. 196
      etc/soft/nvim/+plugins/committia.vim/autoload/committia.vim
  4. 206
      etc/soft/nvim/+plugins/committia.vim/autoload/committia/git.vim
  5. 18
      etc/soft/nvim/+plugins/committia.vim/plugin/committia.vim

19
etc/soft/nvim/+plugins/committia.vim/LICENSE

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
Copyright (c) 2014 rhysd
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.

121
etc/soft/nvim/+plugins/committia.vim/README.md

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
More Pleasant Editing on Commit Message
=======================================
When you type `git commit`, Vim starts and opens a commit buffer. This plugin improves
the commit buffer.
committia.vim splits the buffer into 3 windows; edit window, status window and diff window.
You no longer need to repeat moving to another window, scrolling and backing to the former
position in order to see a long commit diff.
If the width of Vim window is too narrow (160 characters by default), committia.vim falls back
to single column mode, which has 2 windows; edit window and diff window.
For wide window:
![double column mode](https://github.com/rhysd/ss/blob/master/committia.vim/main.jpg?raw=true)
For narrow window:
![single column mode](https://github.com/rhysd/ss/blob/master/committia.vim/narrow.jpg?raw=true)
## Hooks
You can hook on opening the windows.
Available hooks are:
- `edit_open`: When opening a commit message window, this hook is called from the window.
- `diff_open`: When opening a diff window, this hook is called from the window.
- `status_open`: When opening a status window, this hook is called from the window.
Please note that this hook is not called on single-column mode since it does not have a dedicated
window for status.
A vimrc example is below.
```vim
" You can get the information about the windows with first argument as a dictionary.
"
" KEY VALUE AVAILABILITY
"-----------------------------------------------------------------------------------
" vcs : vcs type (e.g. 'git') -> all hooks
" edit_winnr : winnr of edit window -> ditto
" edit_bufnr : bufnr of edit window -> ditto
" diff_winnr : winnr of diff window -> ditto
" diff_bufnr : bufnr of diff window -> ditto
" status_winnr : winnr of status window -> all hooks except for 'diff_open' hook
" status_bufnr : bufnr of status window -> ditto
let g:committia_hooks = {}
function! g:committia_hooks.edit_open(info)
" Additional settings
setlocal spell
" If no commit message, start with insert mode
if a:info.vcs ==# 'git' && getline(1) ==# ''
startinsert
endif
" Scroll the diff window from insert mode
" Map <C-n> and <C-p>
imap <buffer><C-n> <Plug>(committia-scroll-diff-down-half)
imap <buffer><C-p> <Plug>(committia-scroll-diff-up-half)
endfunction
```
## Mappings
Mappings to scroll diff window for insert mode are available.
| Mapping | Description |
|-------------------------------------------|-----------------------------------------------|
| `<Plug>(committia-scroll-diff-down-half)` | Scroll down the diff window by half a screen. |
| `<Plug>(committia-scroll-diff-up-half)` | Scroll up the diff window by half a screen. |
| `<Plug>(committia-scroll-diff-down-page)` | Scroll down the diff window by a screen. |
| `<Plug>(committia-scroll-diff-up-page)` | Scroll up the diff window by a screen. |
## Variables
Some variables are available to control the behavior of committia.vim.
### `g:committia_open_only_vim_starting` (default: `1`)
If the value is `0`, committia.vim always attempts to open committia's buffer when `COMMIT_EDITMSG`
buffer is opened. If you use [vim-fugitive](https://github.com/tpope/vim-fugitive), I recommend to
set this value to `1`.
### `g:committia_use_singlecolumn` (default: `'fallback'`)
If the value is `'always'`, committia.vim always employs single column mode.
### `g:committia_min_window_width` (default: `160`)
If the width of window is narrower than the value, committia.vim employs single column mode.
### `g:committia_status_window_opencmd` (default: `'belowright split'`)
Vim command which opens a status window in multi-columns mode.
### `g:committia_diff_window_opencmd` (default: `'botright vsplit'`)
Vim command which opens a diff window in multi-columns mode.
### `g:committia_singlecolumn_diff_window_opencmd` (default: `'belowright split'`)
Vim command which opens a diff window in single-column mode.
## Future
- Cooperate with [vim-fugitive](https://github.com/tpope/vim-fugitive).
- Add more VCS supports
- Test all features
## Contribution
- [@uasi](https://github.com/uasi) : single column mode
- [@anekos](https://github.com/uasi) : submodule and worktree support
- [and more contributors who sent a patch](https://github.com/rhysd/committia.vim/graphs/contributors)
## License
[Distributed under the MIT license](LICENSE)

196
etc/soft/nvim/+plugins/committia.vim/autoload/committia.vim

@ -0,0 +1,196 @@ @@ -0,0 +1,196 @@
let s:save_cpo = &cpo
set cpo&vim
let g:committia_use_singlecolumn = get(g:, 'committia_use_singlecolumn', 'fallback')
let g:committia_min_window_width = get(g:, 'committia_min_window_width', 160)
let g:committia_diff_window_opencmd = get(g:, 'committia_diff_window_opencmd', 'botright vsplit')
let g:committia_status_window_opencmd = get(g:, 'committia_status_window_opencmd', 'belowright split')
let g:committia_singlecolumn_diff_window_opencmd = get(g:, 'committia_singlecolumn_diff_window_opencmd', 'belowright split')
let g:committia_hooks = get(g:, 'committia_hooks', {})
inoremap <silent> <Plug>(committia-scroll-diff-down-half) <C-o>:call committia#scroll_window('diff', 'C-d')<CR>
inoremap <silent> <Plug>(committia-scroll-diff-up-half) <C-o>:call committia#scroll_window('diff', 'C-u')<CR>
inoremap <silent> <Plug>(committia-scroll-diff-down-page) <C-o>:call committia#scroll_window('diff', 'C-f')<CR>
inoremap <silent> <Plug>(committia-scroll-diff-up-page) <C-o>:call committia#scroll_window('diff', 'C-b')<CR>
inoremap <silent> <Plug>(committia-scroll-diff-down) <C-o>:call committia#scroll_window('diff', 'j')<CR>
inoremap <silent> <Plug>(committia-scroll-diff-up) <C-o>:call committia#scroll_window('diff', 'k')<CR>
nnoremap <silent> <Plug>(committia-scroll-diff-down-half) :<C-u>call committia#scroll_window('diff', 'C-d')<CR>
nnoremap <silent> <Plug>(committia-scroll-diff-up-half) :<C-u>call committia#scroll_window('diff', 'C-u')<CR>
nnoremap <silent> <Plug>(committia-scroll-diff-down-page) :<C-u>call committia#scroll_window('diff', 'C-f')<CR>
nnoremap <silent> <Plug>(committia-scroll-diff-up-page) :<C-u>call committia#scroll_window('diff', 'C-b')<CR>
nnoremap <silent> <Plug>(committia-scroll-diff-down) :<C-u>call committia#scroll_window('diff', 'j')<CR>
nnoremap <silent> <Plug>(committia-scroll-diff-up) :<C-u>call committia#scroll_window('diff', 'k')<CR>
let s:current_info = {}
function! s:open_window(vcs, type, info, ft) abort
let content = call('committia#' . a:vcs . '#' . a:type, [])
let bufname = '__committia_' . a:type . '__'
let coltype = a:info['singlecolumn'] ? 'singlecolumn_' : ''
execute 'silent' g:committia_{coltype}{a:type}_window_opencmd bufname
let a:info[a:type . '_winnr'] = bufwinnr(bufname)
let a:info[a:type . '_bufnr'] = bufnr('%')
call setline(1, content)
execute 0
execute 'setlocal ft=' . a:ft
setlocal nonumber bufhidden=wipe buftype=nofile readonly nolist nobuflisted noswapfile nomodifiable nomodified nofoldenable
endfunction
" Open diff window. If no diff is detected, close the window and return to
" the original window.
" It returns 0 if the window is not open, othewise 1
function! s:open_diff_window(vcs, info) abort
call s:open_window(a:vcs, 'diff', a:info, 'git')
if getline(1, '$') ==# ['']
execute a:info.diff_winnr . 'wincmd c'
wincmd p
return 0
endif
return 1
endfunction
function! s:open_status_window(vcs, info) abort
call s:open_window(a:vcs, 'status', a:info, 'gitcommit')
let status_winheight = winheight(a:info.status_bufnr)
if line('$') < winheight(a:info.status_bufnr)
execute 'resize' line('$')
endif
return 1
endfunction
function! s:execute_hook(name, info) abort
if has_key(g:committia_hooks, a:name)
call call(g:committia_hooks[a:name], [a:info], g:committia_hooks)
endif
endfunction
function! s:remove_all_contents_except_for_commit_message(vcs) abort
1
" Handle squash message
let line = call('committia#' . a:vcs . '#end_of_edit_region_line', [])
if 0 < line && line <= line('$')
execute 'silent' line . ',$delete _'
endif
1
vertical resize 80
endfunction
function! s:callback_on_window_closed() abort
if bufnr('%') == s:current_info.edit_bufnr
for n in ['diff', 'status']
if has_key(s:current_info, n . '_bufnr')
let winnr = bufwinnr(s:current_info[n . '_bufnr'])
if winnr != -1
execute winnr . 'wincmd w'
wincmd c
endif
endif
endfor
let s:current_info = {}
autocmd! plugin-committia-winclosed
endif
endfunction
function! s:callback_on_window_closed_workaround() abort
let edit_winnr = bufwinnr(s:current_info.edit_bufnr)
if edit_winnr == -1
quit!
endif
endfunction
function! s:get_map_of(cmd) abort
return eval('"\<' . a:cmd . '>"')
endfunction
function! committia#scroll_window(type, cmd) abort
let target_winnr = bufwinnr(s:current_info[a:type . '_bufnr'])
if target_winnr == -1
return
endif
noautocmd execute target_winnr . 'wincmd w'
noautocmd execute 'normal!' s:get_map_of(a:cmd)
noautocmd wincmd p
endfunction
function! s:set_callback_on_closed() abort
augroup plugin-committia-winclosed
if exists('##QuitPre')
autocmd QuitPre COMMIT_EDITMSG,MERGE_MSG call s:callback_on_window_closed()
else
autocmd WinEnter __committia_diff__,__committia_status__ nested call s:callback_on_window_closed_workaround()
end
augroup END
endfunction
function! committia#open_multicolumn(vcs) abort
let info = {'vcs' : a:vcs, 'edit_winnr' : winnr(), 'edit_bufnr' : bufnr('%'), 'singlecolumn' : 0}
let diff_window_opened = s:open_diff_window(a:vcs, info)
if !diff_window_opened
return
endif
call s:execute_hook('diff_open', info)
wincmd p
call s:open_status_window(a:vcs, info)
call s:execute_hook('status_open', info)
wincmd p
call s:remove_all_contents_except_for_commit_message(info.vcs)
call s:execute_hook('edit_open', info)
let s:current_info = info
setlocal bufhidden=wipe
let b:committia_opened = 1
call s:set_callback_on_closed()
endfunction
function! committia#open_singlecolumn(vcs) abort
let info = {'vcs' : a:vcs, 'edit_winnr' : winnr(), 'edit_bufnr' : bufnr('%'), 'singlecolumn' : 1}
let diff_window_opened = s:open_diff_window(a:vcs, info)
if !diff_window_opened
return
endif
call s:execute_hook('diff_open', info)
wincmd p
let height = min([line('$') + 3, get(g:, 'committia_singlecolumn_edit_max_winheight', 16)])
execute 'resize' height
call s:execute_hook('edit_open', info)
let s:current_info = info
setlocal bufhidden=wipe
let b:committia_opened = 1
call s:set_callback_on_closed()
endfunction
function! committia#open(vcs) abort
let is_narrow = winwidth(0) < g:committia_min_window_width
let use_singlecolumn
\ = g:committia_use_singlecolumn ==# 'always'
\ || (is_narrow && g:committia_use_singlecolumn ==# 'fallback')
if is_narrow && !use_singlecolumn
call s:execute_hook('edit_open', {'vcs' : a:vcs})
return
endif
" When opening a commit buffer with --amend flag, Vim tries to move the
" cursor to the previous position. Detect it and reset the cursor
" position.
if line('.') != 1
keepjumps call cursor(1, 1)
endif
if use_singlecolumn
call committia#open_singlecolumn(a:vcs)
else
call committia#open_multicolumn(a:vcs)
endif
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

206
etc/soft/nvim/+plugins/committia.vim/autoload/committia/git.vim

@ -0,0 +1,206 @@ @@ -0,0 +1,206 @@
if has('win32') || has('win64')
let s:PATH_SEP = '\'
function! s:is_absolute_path(path) abort
return a:path =~# '^[a-zA-Z]:[/\\]'
endfunction
else
let s:PATH_SEP = '/'
function! s:is_absolute_path(path) abort
return a:path[0] ==# '/'
endfunction
endif
let g:committia#git#cmd = get(g:, 'committia#git#cmd', 'git')
let g:committia#git#diff_cmd = get(g:, 'committia#git#diff_cmd', 'diff -u --cached --no-color --no-ext-diff')
let g:committia#git#status_cmd = get(g:, 'committia#git#status_cmd', '-c color.status=false status -b')
try
silent call vimproc#version()
" Note: vimproc exists
function! s:system(cmd) abort
let out = vimproc#system(a:cmd)
if vimproc#get_last_status()
throw printf("Failed to execute command '%s': %s", a:cmd, out)
endif
return out
endfunction
catch /^Vim\%((\a\+)\)\=:E117/
function! s:system(cmd) abort
let out = system(a:cmd)
if v:shell_error
throw printf("Failed to execute command '%s': %s", a:cmd, out)
endif
return out
endfunction
endtry
if !executable(g:committia#git#cmd)
echoerr g:committia#git#cmd . ' command is not found. Please check g:committia#git#cmd'
endif
function! s:extract_first_line(str) abort
let i = stridx(a:str, "\r")
if i > 0
return a:str[: i - 1]
endif
let i = stridx(a:str, "\n")
if i > 0
return a:str[: i - 1]
endif
return a:str
endfunction
function! s:search_git_dir_and_work_tree() abort
" Use environment variables if set
if !empty($GIT_DIR) && !empty($GIT_WORK_TREE)
if !isdirectory($GIT_WORK_TREE)
throw 'Directory specified with $GIT_WORK_TREE does not exist: ' . $GIT_WORK_TREE
endif
return [$GIT_DIR, $GIT_WORK_TREE]
endif
" '/.git' is unnecessary under submodule directory.
let matched = matchlist(expand('%:p'), '[\\/]\.git[\\/]\%(\(modules\|worktrees\)[\\/].\+[\\/]\)\?\%(COMMIT_EDITMSG\|MERGE_MSG\)$')
if len(matched) > 1
let git_dir = expand('%:p:h')
if matched[1] ==# 'worktrees'
" Note:
" This was added in #31. I'm not sure that the format of gitdir file
" is fixed. Anyway, it works for now.
let work_tree = fnamemodify(readfile(git_dir . '/gitdir')[0], ':h')
return [git_dir, work_tree]
endif
" Avoid executing Git command in git-dir because `git rev-parse --show-toplevel`
" does not return the repository root. To handle work-tree properly,
" set $CWD to the parent of git-dir, which is outside of the
" git-dir. (#39)
let cwd_saved = getcwd()
let cwd = fnamemodify(git_dir, ':h')
if cwd_saved !=# cwd
execute 'lcd' cwd
endif
try
let cmd = printf('%s --git-dir="%s" rev-parse --show-toplevel', g:committia#git#cmd, escape(git_dir, '\'))
let out = s:system(cmd)
finally
if cwd_saved !=# getcwd()
execute 'lcd' cwd_saved
endif
endtry
let work_tree = s:extract_first_line(out)
return [git_dir, work_tree]
endif
if s:is_absolute_path($GIT_DIR) && isdirectory($GIT_DIR)
let git_dir = $GIT_DIR
else
let root = s:extract_first_line(s:system(g:committia#git#cmd . ' rev-parse --show-cdup'))
let git_dir = root . $GIT_DIR
if !isdirectory(git_dir)
throw 'Failed to get git-dir from $GIT_DIR'
endif
endif
return [git_dir, fnamemodify(git_dir, ':h')]
endfunction
function! s:execute_git(cmd) abort
try
let [git_dir, work_tree] = s:search_git_dir_and_work_tree()
catch
throw 'committia: git: Failed to retrieve git-dir or work-tree: ' . v:exception
endtry
if git_dir ==# '' || work_tree ==# ''
throw 'committia: git: Failed to retrieve git-dir or work-tree'
endif
let index_file_was_set = s:ensure_index_file(git_dir)
try
let cmd = printf('%s --git-dir="%s" --work-tree="%s" %s', g:committia#git#cmd, escape(git_dir, '\'), escape(work_tree, '\'), a:cmd)
try
return s:system(cmd)
catch
throw 'committia: git: ' . v:exception
endtry
finally
if index_file_was_set
call s:unset_index_file()
endif
endtry
endfunction
function! s:ensure_index_file(git_dir) abort
if $GIT_INDEX_FILE !=# ''
return 0
endif
let lock_file = s:PATH_SEP . 'index.lock'
if filereadable(lock_file)
let $GIT_INDEX_FILE = lock_file
else
let $GIT_INDEX_FILE = a:git_dir . s:PATH_SEP . 'index'
endif
return 1
endfunction
function! s:unset_index_file() abort
let $GIT_INDEX_FILE = ''
endfunction
function! committia#git#diff() abort
let diff = s:execute_git(g:committia#git#diff_cmd)
if diff !=# ''
return split(diff, '\n')
endif
let line = s:diff_start_line()
if line == 0
return ['']
endif
return getline(line, '$')
endfunction
function! s:diff_start_line() abort
let re_start_diff_line = '# -\+ >8 -\+\n\%(#.*\n\)\+diff --git'
return search(re_start_diff_line, 'cenW')
endfunction
function! committia#git#status() abort
try
let status = s:execute_git(g:committia#git#status_cmd)
catch /^committia: git: Failed to retrieve git-dir or work-tree/
" Leave status window empty when git-dir or work-tree not found
return ''
endtry
return map(split(status, '\n'), 'substitute(v:val, "^", "# ", "g")')
endfunction
function! committia#git#end_of_edit_region_line() abort
let line = s:diff_start_line()
if line == 0
" If diff is not contained, assumes that the buffer ends with comment
" block which was automatically inserted by Git.
" Only the comment block will be removed from edit buffer. (#41)
let line = line('$') + 1
endif
while line > 1
if stridx(getline(line - 1), '#') != 0
break
endif
let line -= 1
endwhile
if line > 1 && empty(getline(line - 1))
" Drop empty line before comment block.
let line -= 1
endif
return line
endfunction

18
etc/soft/nvim/+plugins/committia.vim/plugin/committia.vim

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
if (exists('g:loaded_committia') && g:loaded_committia) || &cp
finish
endif
let g:committia_open_only_vim_starting = get(g:, 'committia_open_only_vim_starting', 1)
function! s:should_open(ft) abort
return &ft ==# a:ft && (!g:committia_open_only_vim_starting || has('vim_starting')) && !exists('b:committia_opened')
endfunction
augroup plugin-committia
autocmd!
autocmd BufReadPost COMMIT_EDITMSG,MERGE_MSG if s:should_open('gitcommit') | call committia#open('git') | endif
" ... Add other VCSs' commit editor filetypes
augroup END
let g:loaded_committia = 1
Loading…
Cancel
Save