From 9d3ffffa7852e67c12586aed0db544b08eef25e2 Mon Sep 17 00:00:00 2001 From: Maxim Likhachev Date: Tue, 18 Feb 2020 10:41:57 +0300 Subject: [PATCH] vim: plugins: ++committia.vim --- etc/soft/nvim/+plugins/committia.vim/LICENSE | 19 ++ etc/soft/nvim/+plugins/committia.vim/README.md | 121 ++++++++++++ .../+plugins/committia.vim/autoload/committia.vim | 196 ++++++++++++++++++++ .../committia.vim/autoload/committia/git.vim | 206 +++++++++++++++++++++ .../+plugins/committia.vim/plugin/committia.vim | 18 ++ 5 files changed, 560 insertions(+) create mode 100644 etc/soft/nvim/+plugins/committia.vim/LICENSE create mode 100644 etc/soft/nvim/+plugins/committia.vim/README.md create mode 100644 etc/soft/nvim/+plugins/committia.vim/autoload/committia.vim create mode 100644 etc/soft/nvim/+plugins/committia.vim/autoload/committia/git.vim create mode 100644 etc/soft/nvim/+plugins/committia.vim/plugin/committia.vim diff --git a/etc/soft/nvim/+plugins/committia.vim/LICENSE b/etc/soft/nvim/+plugins/committia.vim/LICENSE new file mode 100644 index 0000000..de9e51d --- /dev/null +++ b/etc/soft/nvim/+plugins/committia.vim/LICENSE @@ -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. + diff --git a/etc/soft/nvim/+plugins/committia.vim/README.md b/etc/soft/nvim/+plugins/committia.vim/README.md new file mode 100644 index 0000000..f7d7952 --- /dev/null +++ b/etc/soft/nvim/+plugins/committia.vim/README.md @@ -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 and + imap (committia-scroll-diff-down-half) + imap (committia-scroll-diff-up-half) +endfunction +``` + +## Mappings + +Mappings to scroll diff window for insert mode are available. + +| Mapping | Description | +|-------------------------------------------|-----------------------------------------------| +| `(committia-scroll-diff-down-half)` | Scroll down the diff window by half a screen. | +| `(committia-scroll-diff-up-half)` | Scroll up the diff window by half a screen. | +| `(committia-scroll-diff-down-page)` | Scroll down the diff window by a screen. | +| `(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) diff --git a/etc/soft/nvim/+plugins/committia.vim/autoload/committia.vim b/etc/soft/nvim/+plugins/committia.vim/autoload/committia.vim new file mode 100644 index 0000000..e498183 --- /dev/null +++ b/etc/soft/nvim/+plugins/committia.vim/autoload/committia.vim @@ -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 (committia-scroll-diff-down-half) :call committia#scroll_window('diff', 'C-d') +inoremap (committia-scroll-diff-up-half) :call committia#scroll_window('diff', 'C-u') +inoremap (committia-scroll-diff-down-page) :call committia#scroll_window('diff', 'C-f') +inoremap (committia-scroll-diff-up-page) :call committia#scroll_window('diff', 'C-b') +inoremap (committia-scroll-diff-down) :call committia#scroll_window('diff', 'j') +inoremap (committia-scroll-diff-up) :call committia#scroll_window('diff', 'k') +nnoremap (committia-scroll-diff-down-half) :call committia#scroll_window('diff', 'C-d') +nnoremap (committia-scroll-diff-up-half) :call committia#scroll_window('diff', 'C-u') +nnoremap (committia-scroll-diff-down-page) :call committia#scroll_window('diff', 'C-f') +nnoremap (committia-scroll-diff-up-page) :call committia#scroll_window('diff', 'C-b') +nnoremap (committia-scroll-diff-down) :call committia#scroll_window('diff', 'j') +nnoremap (committia-scroll-diff-up) :call committia#scroll_window('diff', 'k') + +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 diff --git a/etc/soft/nvim/+plugins/committia.vim/autoload/committia/git.vim b/etc/soft/nvim/+plugins/committia.vim/autoload/committia/git.vim new file mode 100644 index 0000000..e96e3e6 --- /dev/null +++ b/etc/soft/nvim/+plugins/committia.vim/autoload/committia/git.vim @@ -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 diff --git a/etc/soft/nvim/+plugins/committia.vim/plugin/committia.vim b/etc/soft/nvim/+plugins/committia.vim/plugin/committia.vim new file mode 100644 index 0000000..4d187d1 --- /dev/null +++ b/etc/soft/nvim/+plugins/committia.vim/plugin/committia.vim @@ -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