mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-04-13 22:59:15 +08:00
fix(gina): use bundle gina & fix conceal setting
close https://github.com/SpaceVim/SpaceVim/issues/4660
This commit is contained in:
parent
62a15d02a4
commit
1ff70c5873
@ -62,7 +62,7 @@ function! SpaceVim#layers#git#plugins() abort
|
||||
\ ]
|
||||
call add(plugins, ['airblade/vim-gitgutter', { 'merged' : 0}])
|
||||
if s:git_plugin ==# 'gina'
|
||||
call add(plugins, ['lambdalisue/gina.vim', { 'on_cmd' : 'Gina'}])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/gina.vim', { 'merged' : 0}])
|
||||
elseif s:git_plugin ==# 'fugitive'
|
||||
call add(plugins, ['tpope/vim-fugitive', { 'merged' : 0}])
|
||||
call add(plugins, ['tpope/vim-dispatch', { 'merged' : 0}])
|
||||
@ -89,6 +89,11 @@ function! SpaceVim#layers#git#config() abort
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['g', 'b'], 'Gina blame', 'view-git-blame', 1)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['g', 'V'], 'Gina log %', 'git-log-of-current-file', 1)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['g', 'v'], 'Gina log', 'git-log-of-current-repo', 1)
|
||||
augroup spacevim_git_layer_gina
|
||||
autocmd!
|
||||
autocmd FileType gina-log setlocal concealcursor=nvic
|
||||
autocmd FileType gina-log setlocal conceallevel=3
|
||||
augroup END
|
||||
elseif s:git_plugin ==# 'fugitive'
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['g', 's'], 'Git', 'git-status', 1)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['g', 'S'], 'Git add %', 'stage-current-file', 1)
|
||||
|
52
bundle/gina.vim/.github/workflows/neovim.yml
vendored
Normal file
52
bundle/gina.vim/.github/workflows/neovim.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
name: neovim
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
version:
|
||||
- nightly
|
||||
- stable
|
||||
- v0.4.3
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Neovim
|
||||
uses: rhysd/action-setup-vim@v1
|
||||
id: vim
|
||||
with:
|
||||
neovim: true
|
||||
version: ${{ matrix.version }}
|
||||
- name: Download test runner
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: thinca/vim-themis
|
||||
path: vim-themis
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
git config --global user.name "github-action"
|
||||
git config --global user.email "github-action@example.com"
|
||||
- name: Run tests
|
||||
env:
|
||||
CI: 1
|
||||
THEMIS_VIM: ${{ steps.vim.outputs.executable }}
|
||||
# XXX:
|
||||
# Overwrite %TMP% to point a correct temp directory.
|
||||
# Note that %TMP% only affects value of 'tempname()' in Windows.
|
||||
# https://github.community/t5/GitHub-Actions/TEMP-is-broken-on-Windows/m-p/30432#M427
|
||||
TMP: 'C:\Users\runneradmin\AppData\Local\Temp'
|
||||
run: |
|
||||
./vim-themis/bin/themis
|
22
bundle/gina.vim/.github/workflows/reviewdog.yml
vendored
Normal file
22
bundle/gina.vim/.github/workflows/reviewdog.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: reviewdog
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
vimlint:
|
||||
name: runner / vint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: vint
|
||||
uses: reviewdog/action-vint@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
level: error
|
||||
reporter: github-pr-review
|
52
bundle/gina.vim/.github/workflows/vim.yml
vendored
Normal file
52
bundle/gina.vim/.github/workflows/vim.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
name: vim
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
version:
|
||||
- nightly
|
||||
- v8.2.0235
|
||||
- v8.1.2424
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Vim
|
||||
uses: rhysd/action-setup-vim@v1
|
||||
id: vim
|
||||
with:
|
||||
neovim: false
|
||||
version: ${{ matrix.version }}
|
||||
- name: Download test runner
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: thinca/vim-themis
|
||||
path: vim-themis
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
git config --global user.name "github-action"
|
||||
git config --global user.email "github-action@example.com"
|
||||
- name: Run tests
|
||||
env:
|
||||
CI: 1
|
||||
THEMIS_VIM: ${{ steps.vim.outputs.executable }}
|
||||
# XXX:
|
||||
# Overwrite %TMP% to point a correct temp directory.
|
||||
# Note that %TMP% only affects value of 'tempname()' in Windows.
|
||||
# https://github.community/t5/GitHub-Actions/TEMP-is-broken-on-Windows/m-p/30432#M427
|
||||
TMP: 'C:\Users\runneradmin\AppData\Local\Temp'
|
||||
run: |
|
||||
./vim-themis/bin/themis
|
5
bundle/gina.vim/.gitignore
vendored
Normal file
5
bundle/gina.vim/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
doc/tags
|
||||
release
|
||||
coverage.xml
|
||||
.coverage.covimerage
|
||||
.profile
|
20
bundle/gina.vim/LICENSE
Normal file
20
bundle/gina.vim/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright 2016 Alisue, hashnote.net
|
||||
|
||||
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.
|
||||
|
95
bundle/gina.vim/README.md
Normal file
95
bundle/gina.vim/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 👣 gina.vim
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/vim-jp/vital.vim)
|
||||
[](LICENSE)
|
||||
[](doc/gina.txt)
|
||||
[](doc/gina-develop.txt)
|
||||
|
||||
[](https://github.com/lambdalisue/gina.vim/actions?query=workflow%3Areviewdog)
|
||||
[](https://github.com/lambdalisue/gina.vim/actions?query=workflow%3Avim)
|
||||
[](https://github.com/lambdalisue/gina.vim/actions?query=workflow%3Aneovim)
|
||||
|
||||
gina.vim (gina) is a plugin to asynchronously control git repositories.
|
||||
|
||||
## Presentation
|
||||
|
||||
[](https://www.youtube.com/watch?v=zkANQ9l7YDM)
|
||||
|
||||
I've talked about what the gina.vim is in [VimConf2017](http://vimconf.vim-jp.org/2017/) ([Slide](https://lambdalisue.github.io/vimconf2017/assets/player/KeynoteDHTMLPlayer.html)). Check it out if you would like to feel what the gina.vim is.
|
||||
|
||||
## Usage
|
||||
|
||||
The following is a schematic image of general working-flow with gina.
|
||||
|
||||
```
|
||||
┌─────┬──────────┐
|
||||
│ │ │
|
||||
#DIRTY# │ ▼
|
||||
▲ │ :Gina status │ << : stage
|
||||
│ │ │ │ >> : unstage
|
||||
│ │ │ │ -- : toggle
|
||||
:write │ #STAGED# │ == : discard
|
||||
▲ │ │ │ pp : patch
|
||||
│ ├──────────┤ │ dd : diff
|
||||
│ │ ▼
|
||||
#CLEAN# │ :Gina commit │ ! : switch --amend
|
||||
│ │ │ │ :w : save cache
|
||||
│ ▼ │ │ :q : commit changes (confirm)
|
||||
└────────────────┘ │ :wq : commit changes (immediate)
|
||||
```
|
||||
|
||||
So basically user would
|
||||
|
||||
1. Edit contents in a git repository
|
||||
2. Stage changes with `:Gina status`
|
||||
3. Commit changes with `:Gina commit`
|
||||
|
||||
See `:h gina-usage` for advance usage. Gina provides a lot more features.
|
||||
|
||||
## Pros.
|
||||
|
||||
- A git detection is fast and accurate
|
||||
- It does not require `git` process so incredibly fast
|
||||
- Used in [lambdalisue/vim-gita][], for several years
|
||||
- Commands are asynchronously performed
|
||||
- Users don't have to wait `:Gina push` (`git push`)
|
||||
- Asynchronous feature in Neovim is great. `:Gina log` (`git log`) on **Linux** repository won't freeze Neovim
|
||||
- Single command. Users do not need to remember tons of commands
|
||||
- `:Gina {command}` will execute a gina command or a git raw command asynchronously
|
||||
- `:Gina! {command}` will execute a git raw command asynchronously
|
||||
- `:Gina!! {command}` will execute a git raw command in a shell (mainly for `:Gina!! add -p` or `:Gina!! rebase -i`)
|
||||
- Action based. Users do not need to remember tons of mappings
|
||||
- `?` to see the help
|
||||
- `a` to select an action to perform (complete with `<Tab>`)
|
||||
- `.` to repeat previous action
|
||||
- All action can map to an actual keymap
|
||||
- Author tried to follow Vim's flavor
|
||||
- No mapping for `ee` or whatever which conflicts with Vim's native mappings (like vim-gita does...)
|
||||
- Customizable
|
||||
- Users can define action aliases and mappings
|
||||
- Users can define default options and aliases of command
|
||||
- More
|
||||
- Tested on all major platforms
|
||||
- Powered by [vim-jp/vital.vim][], mean that the things are unit tested
|
||||
- Gina add some behaviour test as well
|
||||
|
||||
[lambdalisue/vim-gita]: https://github.com/lambdalisue/vim-gita
|
||||
[vim-jp/vital.vim]: https://github.com/vim-jp/vital.vim
|
||||
|
||||
## Contribution
|
||||
|
||||
Any contribution including documentations are welcome.
|
||||
|
||||
Contributers should install [thinca/vim-themis][] to run tests before sending a PR if they applied some modification to the code.
|
||||
PRs which does not pass tests won't be accepted.
|
||||
|
||||
[thinca/vim-themis]: https://github.com/thinca/vim-themis
|
||||
|
||||
## License
|
||||
|
||||
The code in gina.vim follows MIT license texted in [LICENSE](./LICENSE).
|
||||
Contributors need to agree that any modifications sent in this repository follow the license.
|
27
bundle/gina.vim/autoload/gina.vim
Normal file
27
bundle/gina.vim/autoload/gina.vim
Normal file
@ -0,0 +1,27 @@
|
||||
let s:Config = vital#gina#import('Config')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
|
||||
function! gina#config(sfile, options) abort
|
||||
return s:Config.define(s:translate(a:sfile), a:options)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:translate(sfile) abort
|
||||
let path = s:Path.unixpath(a:sfile)
|
||||
let name = matchstr(path, 'autoload/\zs\%(gina\.vim\|gina/.*\)$')
|
||||
let name = substitute(name, '\.vim$', '', '')
|
||||
let name = substitute(name, '/', '#', 'g')
|
||||
let name = substitute(name, '\%(^#\|#$\)', '', 'g')
|
||||
return 'g:' . name
|
||||
endfunction
|
||||
|
||||
|
||||
" Default variable -----------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'test': 0,
|
||||
\ 'debug': -1,
|
||||
\ 'develop': 1,
|
||||
\ 'complete_threshold': 30,
|
||||
\})
|
116
bundle/gina.vim/autoload/gina/action.vim
Normal file
116
bundle/gina.vim/autoload/gina/action.vim
Normal file
@ -0,0 +1,116 @@
|
||||
scriptencoding utf-8
|
||||
let s:Action = vital#gina#import('Action')
|
||||
let s:Action.name = 'gina'
|
||||
let s:Highlight = vital#gina#import('Vim.Highlight')
|
||||
|
||||
|
||||
|
||||
function! gina#action#get(...) abort
|
||||
return call(s:Action.get, a:000, s:Action)
|
||||
endfunction
|
||||
|
||||
function! gina#action#attach(...) abort
|
||||
return call(s:Action.attach, a:000, s:Action)
|
||||
endfunction
|
||||
|
||||
function! gina#action#include(scheme) abort
|
||||
let binder = s:Action.get()
|
||||
if binder is# v:null
|
||||
" TODO: raise an exception
|
||||
return
|
||||
endif
|
||||
let scheme = substitute(a:scheme, '-', '_', 'g')
|
||||
try
|
||||
return call(
|
||||
\ printf('gina#action#%s#define', scheme),
|
||||
\ [binder]
|
||||
\)
|
||||
catch /^Vim\%((\a\+)\)\=:E117: [^:]\+: gina#action#[^#]\+#define/
|
||||
call gina#core#console#debug(v:exception)
|
||||
call gina#core#console#debug(v:throwpoint)
|
||||
endtry
|
||||
throw gina#core#revelator#error(printf(
|
||||
\ 'No action script "gina/action/%s.vim" is found',
|
||||
\ a:scheme,
|
||||
\))
|
||||
endfunction
|
||||
|
||||
function! gina#action#alias(...) abort
|
||||
let binder = s:Action.get()
|
||||
if binder is# v:null
|
||||
" TODO: raise an exception
|
||||
return
|
||||
endif
|
||||
return gina#core#revelator#call(binder.alias, a:000, binder)
|
||||
endfunction
|
||||
|
||||
function! gina#action#shorten(action_scheme, ...) abort
|
||||
let excludes = get(a:000, 0, [])
|
||||
let binder = s:Action.get()
|
||||
if binder is# v:null
|
||||
" TODO: raise an exception
|
||||
return
|
||||
endif
|
||||
let action_scheme = substitute(a:action_scheme, '-', '_', 'g')
|
||||
let names = filter(
|
||||
\ keys(binder.actions),
|
||||
\ 'v:val =~# ''^'' . action_scheme . '':'''
|
||||
\)
|
||||
for name in filter(names, 'index(excludes, v:val) == -1')
|
||||
call binder.alias(matchstr(name, '^' . action_scheme . ':\zs.*'), name)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! gina#action#call(...) abort
|
||||
let binder = s:Action.get()
|
||||
if binder is# v:null
|
||||
" TODO: raise an exception
|
||||
return
|
||||
endif
|
||||
return gina#core#revelator#call(binder.call, a:000, binder)
|
||||
endfunction
|
||||
|
||||
function! gina#action#candidates(...) abort
|
||||
let binder = s:Action.get()
|
||||
if binder is# v:null
|
||||
" TODO: raise an exception
|
||||
return
|
||||
endif
|
||||
return gina#core#revelator#call(binder._get_candidates, a:000, binder)
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'mark_sign_text': '|',
|
||||
\})
|
||||
|
||||
|
||||
" Highlight ------------------------------------------------------------------
|
||||
function! s:define_highlihghts() abort
|
||||
silent let bg = s:Highlight.get('SignColumn')
|
||||
silent let fg = s:Highlight.get('Title')
|
||||
call s:Highlight.set({
|
||||
\ 'name': 'GinaActionMarkSelected',
|
||||
\ 'attrs': {
|
||||
\ 'cterm': 'bold',
|
||||
\ 'ctermfg': get(fg.attrs, 'ctermfg', '1'),
|
||||
\ 'ctermbg': get(bg.attrs, 'ctermbg', 'NONE'),
|
||||
\ 'gui': 'bold',
|
||||
\ 'guifg': get(fg.attrs, 'guifg', '#ff0000'),
|
||||
\ 'guibg': get(bg.attrs, 'guibg', 'NONE'),
|
||||
\ }
|
||||
\}, {
|
||||
\ 'default': 1,
|
||||
\})
|
||||
highlight link VitalActionMarkSelectedLine Search
|
||||
highlight link VitalActionMarkSelected GinaActionMarkSelected
|
||||
let s:Action.mark_sign_text = g:gina#action#mark_sign_text
|
||||
endfunction
|
||||
|
||||
augroup gina_action_internal
|
||||
autocmd! *
|
||||
autocmd ColorScheme * call s:define_highlihghts()
|
||||
augroup END
|
||||
|
||||
call s:define_highlihghts()
|
154
bundle/gina.vim/autoload/gina/action/blame.vim
Normal file
154
bundle/gina.vim/autoload/gina/action/blame.vim
Normal file
@ -0,0 +1,154 @@
|
||||
function! gina#action#blame#define(binder) abort
|
||||
if gina#core#buffer#param('%', 'scheme') ==# 'blame'
|
||||
call a:binder.define('blame:echo', function('s:on_echo'), {
|
||||
\ 'description': 'Echo a chunk info',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [
|
||||
\ 'summary',
|
||||
\ 'author',
|
||||
\ 'author_time',
|
||||
\ 'author_tz',
|
||||
\ 'revision',
|
||||
\ ],
|
||||
\ 'options': {},
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('blame:open', function('s:on_open'), {
|
||||
\ 'description': 'Blame a content on a commit of a chunk',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': ['rev', 'path'],
|
||||
\ 'options': {},
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('blame:back', function('s:on_back'), {
|
||||
\ 'description': 'Back to a navigational previous blame',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {},
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_echo(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let chunk = a:candidates[0]
|
||||
let timestamper = gina#core#timestamper#new({
|
||||
\ 'months': g:gina#action#blame#timestamp_months,
|
||||
\ 'format1': g:gina#action#blame#timestamp_format1,
|
||||
\ 'format2': g:gina#action#blame#timestamp_format2,
|
||||
\})
|
||||
let timestamp = timestamper.format(
|
||||
\ chunk.author_time,
|
||||
\ chunk.author_tz,
|
||||
\)
|
||||
redraw | echo printf(
|
||||
\ '%s: %s authored on %s [%s]',
|
||||
\ chunk.summary,
|
||||
\ chunk.author,
|
||||
\ timestamp,
|
||||
\ chunk.revision,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_open(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let chunk = a:candidates[0]
|
||||
if !empty(args.params.rev) && chunk.rev =~# '^' . args.params.rev
|
||||
if !has_key(chunk, 'previous')
|
||||
throw gina#core#revelator#info(printf(
|
||||
\ 'No related parent commit exists and "%s" is already shown',
|
||||
\ chunk.rev,
|
||||
\))
|
||||
endif
|
||||
if !gina#core#console#confirm(printf(
|
||||
\ 'A related parent commit "%s" exist. Do you want to move on?',
|
||||
\ chunk.previous,
|
||||
\), 'y')
|
||||
throw gina#core#revelator#info('Cancel')
|
||||
endif
|
||||
let rev = matchstr(chunk.previous, '^\S\+')
|
||||
let path = matchstr(chunk.previous, '^\S\+\s\zs.*')
|
||||
let line = gina#core#tracker#track(
|
||||
\ gina#core#get_or_fail(),
|
||||
\ chunk.path,
|
||||
\ line('.'),
|
||||
\ {
|
||||
\ 'lhs': chunk.rev,
|
||||
\ 'rhs': rev,
|
||||
\ }
|
||||
\)
|
||||
else
|
||||
let rev = chunk.rev
|
||||
let path = chunk.path
|
||||
let line = gina#util#get(chunk, 'line')
|
||||
endif
|
||||
call s:add_history()
|
||||
let treeish = gina#core#treeish#build(rev, path)
|
||||
execute printf(
|
||||
\ '%s Gina blame %s %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(line, '--line='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_back(candidates, options) abort dict
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
let history = s:pop_history()
|
||||
let treeish = gina#core#treeish#build(history.rev, history.path)
|
||||
execute printf(
|
||||
\ '%s Gina blame %s %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(history.line, '--line='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
|
||||
" History --------------------------------------------------------------------
|
||||
function! s:add_history() abort
|
||||
if gina#core#buffer#param('%', 'scheme') !=# 'blame'
|
||||
return
|
||||
endif
|
||||
let w:gina_blame_history = get(w:, 'gina_blame_history', [])
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
call add(w:gina_blame_history, {
|
||||
\ 'rev': empty(args.params.rev) ? ':0' : args.params.rev,
|
||||
\ 'path': args.params.path,
|
||||
\ 'line': line('.'),
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! s:pop_history() abort
|
||||
let w:gina_blame_history = get(w:, 'gina_blame_history', [])
|
||||
if empty(w:gina_blame_history)
|
||||
throw gina#core#revelator#info('No navigational history is found')
|
||||
endif
|
||||
return remove(w:gina_blame_history, -1)
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'timestamp_months': 3,
|
||||
\ 'timestamp_format1': '%d %b',
|
||||
\ 'timestamp_format2': '%d %b, %Y',
|
||||
\})
|
175
bundle/gina.vim/autoload/gina/action/branch.vim
Normal file
175
bundle/gina.vim/autoload/gina/action/branch.vim
Normal file
@ -0,0 +1,175 @@
|
||||
function! gina#action#branch#define(binder) abort
|
||||
call a:binder.define('branch:refresh', function('s:on_refresh'), {
|
||||
\ 'description': 'Refresh remote branches',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {},
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('branch:new', function('s:on_new'), {
|
||||
\ 'description': 'Create a new branch',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('branch:move', function('s:on_move'), {
|
||||
\ 'description': 'Rename a branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('branch:move:force', function('s:on_move'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Rename a branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('branch:delete', function('s:on_delete'), {
|
||||
\ 'description': 'Delete a branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('branch:delete:force', function('s:on_delete'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Delete a branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('branch:set-upstream-to', function('s:on_set_upstream_to'), {
|
||||
\ 'description': 'Set an upstream of a branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('branch:unset-upstream', function('s:on_unset_upstream'), {
|
||||
\ 'description': 'Unset an upstream of a branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['branch', 'remote'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_refresh(candidates, options) abort
|
||||
let options = extend({}, a:options)
|
||||
execute printf(
|
||||
\ '%s Gina remote update --prune',
|
||||
\ options.mods,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_new(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in a:candidates
|
||||
let name = gina#core#console#ask_or_cancel(
|
||||
\ 'Name: ', '',
|
||||
\)
|
||||
let from = gina#core#console#ask_or_cancel(
|
||||
\ 'From: ', candidate.rev,
|
||||
\ function('gina#complete#commit#branch')
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina checkout -b %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(name),
|
||||
\ gina#util#shellescape(from),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_move(candidates, options) abort
|
||||
let candidates = filter(copy(a:candidates), 'empty(v:val.remote)')
|
||||
if empty(candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
for candidate in candidates
|
||||
let name = gina#core#console#ask_or_cancel(
|
||||
\ 'Rename: ',
|
||||
\ candidate.branch,
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina branch --move %s %s %s',
|
||||
\ options.mods,
|
||||
\ options.force ? '--force' : '',
|
||||
\ gina#util#shellescape(candidate.branch),
|
||||
\ gina#util#shellescape(name),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_delete(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let is_remote = !empty(candidate.remote)
|
||||
if is_remote
|
||||
execute printf(
|
||||
\ '%s Gina push --delete %s %s %s',
|
||||
\ options.mods,
|
||||
\ options.force ? '--force' : '',
|
||||
\ gina#util#shellescape(candidate.remote),
|
||||
\ gina#util#shellescape(candidate.branch),
|
||||
\)
|
||||
else
|
||||
execute printf(
|
||||
\ '%s Gina branch --delete %s %s',
|
||||
\ options.mods,
|
||||
\ options.force ? '--force' : '',
|
||||
\ gina#util#shellescape(candidate.branch),
|
||||
\)
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_set_upstream_to(candidates, options) abort
|
||||
let candidates = filter(copy(a:candidates), 'empty(v:val.remote)')
|
||||
if empty(candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in candidates
|
||||
let upstream = gina#core#console#ask_or_cancel(
|
||||
\ 'Upstream: ', candidate.branch,
|
||||
\ function('gina#complete#commit#remote_branch'),
|
||||
\)
|
||||
let upstream = substitute(
|
||||
\ upstream, printf('^%s/', candidate.remote), '', ''
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina branch --set-upstream-to=%s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(upstream),
|
||||
\ gina#util#shellescape(candidate.branch),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_unset_upstream(candidates, options) abort
|
||||
let candidates = filter(copy(a:candidates), 'empty(v:val.remote)')
|
||||
if empty(candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in candidates
|
||||
execute printf(
|
||||
\ '%s Gina branch --unset-upstream %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(candidate.branch),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
60
bundle/gina.vim/autoload/gina/action/browse.vim
Normal file
60
bundle/gina.vim/autoload/gina/action/browse.vim
Normal file
@ -0,0 +1,60 @@
|
||||
function! gina#action#browse#define(binder) abort
|
||||
call a:binder.define('browse', function('s:on_browse'), {
|
||||
\ 'description': 'Open a remote url in a system browser',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {},
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('browse:exact', function('s:on_browse'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Open a remote url in a system browser',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': { 'exact': 1 },
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('browse:yank', function('s:on_browse'), {
|
||||
\ 'description': 'Copy a remote url',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': { 'yank': 1 },
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('browse:yank:exact', function('s:on_browse'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Copy a remote url',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': { 'yank': 1, 'exact': 1 },
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_browse(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'exact': 0,
|
||||
\ 'yank': 0,
|
||||
\}, a:options)
|
||||
let candidate = a:candidates[0]
|
||||
let treeish = gina#core#treeish#build(
|
||||
\ gina#util#get(candidate, 'rev'),
|
||||
\ gina#util#get(candidate, 'path', v:null),
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina browse %s %s %s',
|
||||
\ options.mods,
|
||||
\ options.exact ? '--exact' : '',
|
||||
\ options.yank ? '--yank' : '',
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endfunction
|
110
bundle/gina.vim/autoload/gina/action/changes.vim
Normal file
110
bundle/gina.vim/autoload/gina/action/changes.vim
Normal file
@ -0,0 +1,110 @@
|
||||
function! gina#action#changes#define(binder) abort
|
||||
" changes:of
|
||||
let params = {
|
||||
\ 'description': 'Show changes of a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\}
|
||||
call a:binder.define('changes:of', function('s:on_changes'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('changes:of:split', function('s:on_changes'), extend({
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('changes:of:vsplit', function('s:on_changes'), extend({
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('changes:of:tab', function('s:on_changes'), extend({
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('changes:of:preview', function('s:on_changes'), extend({
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
" changes:between
|
||||
let params = {
|
||||
\ 'description': 'Show changes between a commit and a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\}
|
||||
call a:binder.define('changes:between', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s..' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:between:split', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s..', 'opener': 'new' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:between:vsplit', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s..', 'opener': 'vnew' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:between:tab', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s..', 'opener': 'tabedit' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:between:preview', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s..', 'opener': 'pedit' },
|
||||
\}, params))
|
||||
" changes:from
|
||||
let params = {
|
||||
\ 'description': 'Show changes from a common ancestor of a commit and a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\}
|
||||
call a:binder.define('changes:from', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s...' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:from:split', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s...', 'opener': 'new' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:from:vsplit', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s...', 'opener': 'vnew' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:from:tab', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s...', 'opener': 'tabedit' },
|
||||
\}, params))
|
||||
call a:binder.define('changes:from:preview', function('s:on_changes'), extend({
|
||||
\ 'options': { 'format': '%s...', 'opener': 'pedit' },
|
||||
\}, params))
|
||||
call a:binder.alias('changes:of:above', 'leftabove changes:of:split')
|
||||
call a:binder.alias('changes:of:below', 'belowright changes:of:split')
|
||||
call a:binder.alias('changes:of:left', 'leftabove changes:of:vsplit')
|
||||
call a:binder.alias('changes:of:right', 'belowright changes:of:vsplit')
|
||||
call a:binder.alias('changes:of:top', 'topleft changes:of:split')
|
||||
call a:binder.alias('changes:of:bottom', 'botright changes:of:split')
|
||||
call a:binder.alias('changes:of:leftest', 'topleft changes:of:vsplit')
|
||||
call a:binder.alias('changes:of:rightest', 'botright changes:of:vsplit')
|
||||
call a:binder.alias('changes:between:above', 'leftabove changes:between:split')
|
||||
call a:binder.alias('changes:between:below', 'belowright changes:between:split')
|
||||
call a:binder.alias('changes:between:left', 'leftabove changes:between:vsplit')
|
||||
call a:binder.alias('changes:between:right', 'belowright changes:between:vsplit')
|
||||
call a:binder.alias('changes:between:top', 'topleft changes:between:split')
|
||||
call a:binder.alias('changes:between:bottom', 'botright changes:between:split')
|
||||
call a:binder.alias('changes:between:leftest', 'topleft changes:between:vsplit')
|
||||
call a:binder.alias('changes:between:rightest', 'botright changes:between:vsplit')
|
||||
call a:binder.alias('changes:from:above', 'leftabove changes:from:split')
|
||||
call a:binder.alias('changes:from:below', 'belowright changes:from:split')
|
||||
call a:binder.alias('changes:from:left', 'leftabove changes:from:vsplit')
|
||||
call a:binder.alias('changes:from:right', 'belowright changes:from:vsplit')
|
||||
call a:binder.alias('changes:from:top', 'topleft changes:from:split')
|
||||
call a:binder.alias('changes:from:bottom', 'botright changes:from:split')
|
||||
call a:binder.alias('changes:from:leftest', 'topleft changes:from:vsplit')
|
||||
call a:binder.alias('changes:from:rightest', 'botright changes:from:vsplit')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_changes(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\ 'format': '%s',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina changes %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(printf(options.format, candidate.rev)),
|
||||
\ gina#util#shellescape(gina#util#get(candidate, 'residual')),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
53
bundle/gina.vim/autoload/gina/action/chaperon.vim
Normal file
53
bundle/gina.vim/autoload/gina/action/chaperon.vim
Normal file
@ -0,0 +1,53 @@
|
||||
function! gina#action#chaperon#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Open three buffers to solve conflict',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\}
|
||||
call a:binder.define('chaperon', function('s:on_chaperon'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('chaperon:split', function('s:on_chaperon'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('chaperon:vsplit', function('s:on_chaperon'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('chaperon:tab', function('s:on_chaperon'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('chaperon:above', 'leftabove chaperon:split')
|
||||
call a:binder.alias('chaperon:below', 'belowright chaperon:split')
|
||||
call a:binder.alias('chaperon:left', 'leftabove chaperon:vsplit')
|
||||
call a:binder.alias('chaperon:right', 'belowright chaperon:vsplit')
|
||||
call a:binder.alias('chaperon:top', 'topleft chaperon:split')
|
||||
call a:binder.alias('chaperon:bottom', 'botright chaperon:split')
|
||||
call a:binder.alias('chaperon:leftest', 'topleft chaperon:vsplit')
|
||||
call a:binder.alias('chaperon:rightest', 'botright chaperon:vsplit')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_chaperon(candidates, options) abort
|
||||
let candidates = filter(copy(a:candidates), 'v:val.sign ==# ''UU''')
|
||||
if empty(candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in candidates
|
||||
execute printf(
|
||||
\ '%s Gina chaperon %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(get(candidate, 'line'), '--line='),
|
||||
\ gina#util#shellescape(get(candidate, 'col'), '--col='),
|
||||
\ gina#util#shellescape(candidate.path),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
331
bundle/gina.vim/autoload/gina/action/commit.vim
Normal file
331
bundle/gina.vim/autoload/gina/action/commit.vim
Normal file
@ -0,0 +1,331 @@
|
||||
function! gina#action#commit#define(binder) abort
|
||||
" checkout
|
||||
call a:binder.define('commit:checkout', function('s:on_checkout'), {
|
||||
\ 'description': 'Checkout a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:checkout:track', function('s:on_checkout_track'), {
|
||||
\ 'description': 'Checkout a commit with a tracking branch',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
" reset
|
||||
call a:binder.define('commit:reset', function('s:on_reset'), {
|
||||
\ 'description': 'Reset a HEAD to a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:reset:soft', function('s:on_reset'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Reset a HEAD to a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'mode': 'soft'},
|
||||
\})
|
||||
call a:binder.define('commit:reset:hard', function('s:on_reset'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Reset a HEAD to a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'mode': 'hard'},
|
||||
\})
|
||||
call a:binder.define('commit:reset:merge', function('s:on_reset'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Reset a HEAD to a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'mode': 'merge'},
|
||||
\})
|
||||
call a:binder.define('commit:reset:keep', function('s:on_reset'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Reset a current HEAD to a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'mode': 'keep'},
|
||||
\})
|
||||
" merge
|
||||
call a:binder.define('commit:merge', function('s:on_merge'), {
|
||||
\ 'description': 'Merge a commit into a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:merge:ff-only', function('s:on_merge'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Merge a commit into a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'ff-only': 1 },
|
||||
\})
|
||||
call a:binder.define('commit:merge:no-ff', function('s:on_merge'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Merge a commit into a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'no-ff': 1 },
|
||||
\})
|
||||
call a:binder.define('commit:merge:squash', function('s:on_merge'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Merge a commit into a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'squash': 1 },
|
||||
\})
|
||||
" rebase
|
||||
call a:binder.define('commit:rebase', function('s:on_rebase'), {
|
||||
\ 'description': 'Rebase a HEAD from a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:rebase:merge', function('s:on_rebase'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Rebase a HEAD by merging a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'merge': 1 },
|
||||
\})
|
||||
" revert
|
||||
call a:binder.define('commit:revert', function('s:on_revert'), {
|
||||
\ 'description': 'Revert a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:revert:1', function('s:on_revert'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Revert a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'mainline': '1' },
|
||||
\})
|
||||
call a:binder.define('commit:revert:2', function('s:on_revert'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Revert a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'mainline': '2' },
|
||||
\})
|
||||
" cherry-pick
|
||||
call a:binder.define('commit:cherry-pick', function('s:on_cherry_pick'), {
|
||||
\ 'description': 'Apply changes of a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:cherry-pick:1', function('s:on_cherry_pick'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Apply changes of a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'mainline': '1' },
|
||||
\})
|
||||
call a:binder.define('commit:cherry-pick:2', function('s:on_cherry_pick'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Apply changes of a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': { 'mainline': '2' },
|
||||
\})
|
||||
call a:binder.define('commit:tag:lightweight', function('s:on_tag'), {
|
||||
\ 'description': 'Create a lightweight tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('commit:tag:annotate', function('s:on_tag'), {
|
||||
\ 'description': 'Create an unsigned, annotated tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'annotate': 1},
|
||||
\})
|
||||
call a:binder.define('commit:tag:sign', function('s:on_tag'), {
|
||||
\ 'description': 'Create a GPG-signed tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'sign': 1},
|
||||
\})
|
||||
call a:binder.define('commit:tag:lightweight:force', function('s:on_tag'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Create a lightweight tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'force': 1},
|
||||
\})
|
||||
call a:binder.define('commit:tag:annotate:force', function('s:on_tag'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Create an unsigned, annotated tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'annotate': 1, 'force': 1},
|
||||
\})
|
||||
call a:binder.define('commit:tag:sign:force', function('s:on_tag'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Create a GPG-signed tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'options': {'sign': 1, 'force': 1},
|
||||
\})
|
||||
" Alias
|
||||
call a:binder.alias('commit:tag', 'commit:tag:annotate')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_checkout(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina checkout %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_checkout_track(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in a:candidates
|
||||
if !has_key(candidate, 'branch')
|
||||
let branch = gina#core#console#ask_or_cancel(
|
||||
\ 'A tracking branch name: ',
|
||||
\ printf('%s-tracking', candidate.rev),
|
||||
\)
|
||||
else
|
||||
let branch = candidate.branch
|
||||
endif
|
||||
execute printf(
|
||||
\ '%s Gina checkout -b %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(branch),
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_reset(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'mode': '',
|
||||
\}, a:options)
|
||||
let mode = empty(options.mode)
|
||||
\ ? ''
|
||||
\ : '--' . options.mode
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina reset %s %s',
|
||||
\ options.mods,
|
||||
\ mode,
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_merge(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'no-ff': 0,
|
||||
\ 'ff-only': 0,
|
||||
\ 'squash': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina merge --no-edit %s %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ options['no-ff'] ? '--no-ff' : '',
|
||||
\ options['ff-only'] ? '--ff-only' : '',
|
||||
\ options.squash ? '--squash' : '',
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_rebase(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'merge': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina rebase %s -- %s',
|
||||
\ options.mods,
|
||||
\ options.merge ? '--merge' : '',
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_revert(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'mainline': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina revert %s %s',
|
||||
\ options.mods,
|
||||
\ empty(options.mainline) ? '' : printf('--mainline %s', options.mainline),
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_cherry_pick(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'mainline': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina cherry-pick %s %s',
|
||||
\ options.mods,
|
||||
\ empty(options.mainline) ? '' : printf('--mainline %s', options.mainline),
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_tag(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'annotate': 0,
|
||||
\ 'sign': 0,
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let name = gina#core#console#ask_or_cancel(
|
||||
\ 'Name: ', '',
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina tag %s %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ options.annotate ? '--annotate' : '',
|
||||
\ options.sign ? '--sign' : '',
|
||||
\ options.force ? '--force' : '',
|
||||
\ gina#util#shellescape(name),
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
58
bundle/gina.vim/autoload/gina/action/compare.vim
Normal file
58
bundle/gina.vim/autoload/gina/action/compare.vim
Normal file
@ -0,0 +1,58 @@
|
||||
function! gina#action#compare#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Open two buffers to compare differences',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\}
|
||||
call a:binder.define('compare', function('s:on_compare'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('compare:split', function('s:on_compare'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('compare:vsplit', function('s:on_compare'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('compare:tab', function('s:on_compare'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('compare:above', 'leftabove compare:split')
|
||||
call a:binder.alias('compare:below', 'belowright compare:split')
|
||||
call a:binder.alias('compare:left', 'leftabove compare:vsplit')
|
||||
call a:binder.alias('compare:right', 'belowright compare:vsplit')
|
||||
call a:binder.alias('compare:top', 'topleft compare:split')
|
||||
call a:binder.alias('compare:bottom', 'botright compare:split')
|
||||
call a:binder.alias('compare:leftest', 'topleft compare:vsplit')
|
||||
call a:binder.alias('compare:rightest', 'botright compare:vsplit')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_compare(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let cached = gina#util#get(candidate, 'sign', '!!') !~# '^\%(??\|!!\|.\w\)$'
|
||||
let treeish = gina#core#treeish#build(
|
||||
\ gina#util#get(candidate, 'rev'),
|
||||
\ candidate.path,
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina compare %s %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ cached ? '--cached' : '',
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(get(candidate, 'line'), '--line='),
|
||||
\ gina#util#shellescape(get(candidate, 'col'), '--col='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
63
bundle/gina.vim/autoload/gina/action/diff.vim
Normal file
63
bundle/gina.vim/autoload/gina/action/diff.vim
Normal file
@ -0,0 +1,63 @@
|
||||
function! gina#action#diff#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Open an unified-diff',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': [],
|
||||
\}
|
||||
call a:binder.define('diff', function('s:on_diff'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('diff:split', function('s:on_diff'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('diff:vsplit', function('s:on_diff'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('diff:tab', function('s:on_diff'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('diff:preview', function('s:on_diff'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('diff:above', 'leftabove diff:split')
|
||||
call a:binder.alias('diff:below', 'belowright diff:split')
|
||||
call a:binder.alias('diff:left', 'leftabove diff:vsplit')
|
||||
call a:binder.alias('diff:right', 'belowright diff:vsplit')
|
||||
call a:binder.alias('diff:top', 'topleft diff:split')
|
||||
call a:binder.alias('diff:bottom', 'botright diff:split')
|
||||
call a:binder.alias('diff:leftest', 'topleft diff:vsplit')
|
||||
call a:binder.alias('diff:rightest', 'botright diff:vsplit')
|
||||
call a:binder.alias('diff:preview:top', 'topleft diff:preview')
|
||||
call a:binder.alias('diff:preview:bottom', 'botright diff:preview')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_diff(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let cached = get(candidate, 'sign', '!!') !~# '^\%(??\|!!\|.\w\)$'
|
||||
let treeish = gina#core#treeish#build(
|
||||
\ gina#util#get(candidate, 'rev'),
|
||||
\ gina#util#get(candidate, 'path', v:null),
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina diff %s %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ cached ? '--cached' : '',
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\ gina#util#shellescape(gina#util#get(candidate, 'residual')),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
58
bundle/gina.vim/autoload/gina/action/edit.vim
Normal file
58
bundle/gina.vim/autoload/gina/action/edit.vim
Normal file
@ -0,0 +1,58 @@
|
||||
function! gina#action#edit#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Edit a content in a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\}
|
||||
call a:binder.define('edit', function('s:on_edit'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('edit:split', function('s:on_edit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('edit:vsplit', function('s:on_edit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('edit:tab', function('s:on_edit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('edit:preview', function('s:on_edit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('edit:above', 'leftabove edit:split')
|
||||
call a:binder.alias('edit:below', 'belowright edit:split')
|
||||
call a:binder.alias('edit:left', 'leftabove edit:vsplit')
|
||||
call a:binder.alias('edit:right', 'belowright edit:vsplit')
|
||||
call a:binder.alias('edit:top', 'topleft edit:split')
|
||||
call a:binder.alias('edit:bottom', 'botright edit:split')
|
||||
call a:binder.alias('edit:leftest', 'topleft edit:vsplit')
|
||||
call a:binder.alias('edit:rightest', 'botright edit:vsplit')
|
||||
call a:binder.alias('edit:preview:top', 'topleft edit:preview')
|
||||
call a:binder.alias('edit:preview:bottom', 'botright edit:preview')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_edit(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina edit %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(get(candidate, 'line'), '--line='),
|
||||
\ gina#util#shellescape(get(candidate, 'col'), '--col='),
|
||||
\ gina#util#shellescape(candidate.path),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
49
bundle/gina.vim/autoload/gina/action/export.vim
Normal file
49
bundle/gina.vim/autoload/gina/action/export.vim
Normal file
@ -0,0 +1,49 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
|
||||
function! gina#action#export#define(binder) abort
|
||||
call a:binder.define('export:quickfix', function('s:on_quickfix'), {
|
||||
\ 'description': 'Create a new quickfix list with selected candidates',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'word'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('export:quickfix:add', function('s:on_quickfix'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Add selected candidates to an existing quickfix list',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'word'],
|
||||
\ 'options': { 'action': 'a' },
|
||||
\})
|
||||
call a:binder.define('export:quickfix:replace', function('s:on_quickfix'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Replace an existing quickfix list with selected candidates',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'word'],
|
||||
\ 'options': { 'action': 'r' },
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_quickfix(candidates, options) abort dict
|
||||
let options = extend({
|
||||
\ 'action': ' ',
|
||||
\}, a:options)
|
||||
let git = gina#core#get_or_fail()
|
||||
call setqflist(
|
||||
\ map(copy(a:candidates), 's:to_quickfix(git, v:val)'),
|
||||
\ options.action,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:to_quickfix(git, candidate) abort
|
||||
let abspath = gina#core#repo#abspath(a:git, a:candidate.path)
|
||||
return {
|
||||
\ 'filename': s:Path.realpath(abspath),
|
||||
\ 'lnum': get(a:candidate, 'line', 1),
|
||||
\ 'col': get(a:candidate, 'col', 1),
|
||||
\ 'text': s:String.remove_ansi_sequences(a:candidate.word),
|
||||
\}
|
||||
endfunction
|
338
bundle/gina.vim/autoload/gina/action/index.vim
Normal file
338
bundle/gina.vim/autoload/gina/action/index.vim
Normal file
@ -0,0 +1,338 @@
|
||||
let s:File = vital#gina#import('System.File')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
|
||||
function! gina#action#index#define(binder) abort
|
||||
call a:binder.define('index:add', function('s:on_add'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Add changes to an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:add:force', function('s:on_add'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Add changes to an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('index:add:intent-to-add', function('s:on_add'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Intent to add changes to an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'intent-to-add': 1 },
|
||||
\})
|
||||
call a:binder.define('index:rm', function('s:on_rm'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Remove files from a working tree and from an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:rm:force', function('s:on_rm'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Remove files from a working tree and from an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('index:rm:cached', function('s:on_rm'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Remove files from an index but a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'cached': 1 },
|
||||
\})
|
||||
call a:binder.define('index:reset', function('s:on_reset'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Reset changes on an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:stage', function('s:on_stage'), {
|
||||
\ 'description': 'Stage changes to an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:stage:force', function('s:on_stage'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Stage changes to an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('index:unstage', function('s:on_unstage'), {
|
||||
\ 'description': 'Unstage changes from an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:toggle', function('s:on_toggle'), {
|
||||
\ 'description': 'Toggle stage/unstage of changes in an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:checkout', function('s:on_checkout'), {
|
||||
\ 'description': 'Checkout a content from an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:checkout:force', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('index:checkout:HEAD', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'rev': 'HEAD' },
|
||||
\})
|
||||
call a:binder.define('index:checkout:HEAD:force', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from a HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'rev': 'HEAD', 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('index:checkout:origin', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from an origin/HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'rev': 'origin/HEAD' },
|
||||
\})
|
||||
call a:binder.define('index:checkout:origin:force', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from an origin/HEAD',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'rev': 'origin/HEAD', 'force': 1 },
|
||||
\})
|
||||
call a:binder.define('index:checkout:ours', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from local (ours) during merge',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'ours': 1 },
|
||||
\})
|
||||
call a:binder.define('index:checkout:theirs', function('s:on_checkout'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Checkout a content from remote (theirs) during merge',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'options': { 'theirs': 1 },
|
||||
\})
|
||||
call a:binder.define('index:discard', function('s:on_discard'), {
|
||||
\ 'description': 'Discard changes on a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('index:discard:force', function('s:on_discard'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Discard changes on a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path', 'sign'],
|
||||
\ 'options': { 'force': 1 },
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:on_add(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\ 'intent-to-add': 0,
|
||||
\}, a:options)
|
||||
let pathlist = map(copy(a:candidates), 'v:val.path')
|
||||
execute printf(
|
||||
\ '%s Gina add --ignore-errors %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ options.force ? '--force' : '',
|
||||
\ options['intent-to-add'] ? '--intent-to-add' : '',
|
||||
\ gina#util#shellescape(pathlist),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_rm(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'cached': 0,
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
let pathlist = map(copy(a:candidates), 'v:val.path')
|
||||
execute printf(
|
||||
\ '%s Gina rm --quiet --ignore-unmatch %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ options.force ? '--force' : '',
|
||||
\ options.cached ? '--cached' : '',
|
||||
\ gina#util#shellescape(pathlist),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_reset(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
let pathlist = map(copy(a:candidates), 'v:val.path')
|
||||
execute printf(
|
||||
\ '%s Gina reset --quiet -- %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(pathlist),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_stage(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
let rm_candidates = []
|
||||
let add_candidates = []
|
||||
for candidate in a:candidates
|
||||
if candidate.sign =~# '^.D$'
|
||||
call add(rm_candidates, candidate)
|
||||
elseif candidate.sign !~# '^. $'
|
||||
call add(add_candidates, candidate)
|
||||
endif
|
||||
endfor
|
||||
if options.force
|
||||
call self.call(options.mods . 'index:add:force', add_candidates)
|
||||
if !empty(rm_candidates)
|
||||
call gina#process#wait()
|
||||
endif
|
||||
call self.call(options.mods . 'index:rm:force', rm_candidates)
|
||||
else
|
||||
call self.call(options.mods . 'index:add', add_candidates)
|
||||
if !empty(rm_candidates)
|
||||
call gina#process#wait()
|
||||
endif
|
||||
call self.call(options.mods . 'index:rm', rm_candidates)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_unstage(candidates, options) abort dict
|
||||
let options = extend({}, a:options)
|
||||
call self.call(options.mods . 'index:reset', a:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:on_toggle(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
let stage_candidates = []
|
||||
let unstage_candidates = []
|
||||
for candidate in a:candidates
|
||||
if candidate.sign =~# '^\%(??\|!!\|.\w\)$'
|
||||
call add(stage_candidates, candidate)
|
||||
elseif candidate.sign =~# '^\w.$'
|
||||
call add(unstage_candidates, candidate)
|
||||
endif
|
||||
endfor
|
||||
call self.call(options.mods . 'index:stage', stage_candidates)
|
||||
if !empty(unstage_candidates)
|
||||
call gina#process#wait()
|
||||
endif
|
||||
call self.call(options.mods . 'index:unstage', unstage_candidates)
|
||||
endfunction
|
||||
|
||||
function! s:on_checkout(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\ 'ours': 0,
|
||||
\ 'theirs': 0,
|
||||
\ 'rev': '',
|
||||
\}, a:options)
|
||||
let pathlist = map(copy(a:candidates), 'v:val.path')
|
||||
execute printf(
|
||||
\ '%s Gina! checkout --quiet %s %s %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ options.force ? '--force' : '',
|
||||
\ options.ours ? '--ours' : '',
|
||||
\ options.theirs ? '--theirs' : '',
|
||||
\ gina#util#shellescape(options.rev),
|
||||
\ gina#util#shellescape(pathlist),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:on_discard(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let git = gina#core#get_or_fail()
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
let delete_candidates = []
|
||||
let checkout_candidates = []
|
||||
for candidate in a:candidates
|
||||
if candidate.sign =~# '^\%(??\|!!\)$'
|
||||
call add(delete_candidates, candidate)
|
||||
else
|
||||
call add(checkout_candidates, candidate)
|
||||
endif
|
||||
endfor
|
||||
if !options.force
|
||||
call gina#core#console#warn(join([
|
||||
\ 'A discard action will discard all local changes on the working ',
|
||||
\ 'tree and the operation is irreversible, mean that you have no ',
|
||||
\ 'chance to revert the operation.',
|
||||
\], "\n"))
|
||||
call gina#core#console#info(
|
||||
\ 'This operation will be performed to the following candidates:'
|
||||
\)
|
||||
for candidate in extend(copy(delete_candidates), checkout_candidates)
|
||||
call gina#core#console#echo('- ' . s:Path.realpath(candidate.path))
|
||||
endfor
|
||||
if !gina#core#console#confirm('Are you sure to discard the changes?', 'n')
|
||||
return
|
||||
endif
|
||||
endif
|
||||
" delete untracked files
|
||||
for candidate in delete_candidates
|
||||
let abspath = s:Path.realpath(gina#core#repo#abspath(git, candidate.path))
|
||||
if isdirectory(abspath)
|
||||
if g:gina#action#index#discard_directories
|
||||
call s:File.rmdir(abspath, 'r')
|
||||
else
|
||||
call gina#core#console#info(printf(
|
||||
\ '"%s" is directory. While g:gina#action#index#discard_directories is not set, it is not removed.',
|
||||
\ candidate.path,
|
||||
\))
|
||||
endif
|
||||
elseif filewritable(abspath)
|
||||
call delete(abspath)
|
||||
endif
|
||||
endfor
|
||||
call self.call(options.mods . 'index:checkout:HEAD:force', checkout_candidates)
|
||||
if !empty(delete_candidates) && empty(checkout_candidates)
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'discard_directories': 0,
|
||||
\})
|
55
bundle/gina.vim/autoload/gina/action/ls.vim
Normal file
55
bundle/gina.vim/autoload/gina/action/ls.vim
Normal file
@ -0,0 +1,55 @@
|
||||
function! gina#action#ls#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'List files/directories of the repository on a particular commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\}
|
||||
call a:binder.define('ls', function('s:on_ls'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('ls:split', function('s:on_ls'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('ls:vsplit', function('s:on_ls'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('ls:tab', function('s:on_ls'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('ls:preview', function('s:on_ls'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('ls:above', 'leftabove ls:split')
|
||||
call a:binder.alias('ls:below', 'belowright ls:split')
|
||||
call a:binder.alias('ls:left', 'leftabove ls:vsplit')
|
||||
call a:binder.alias('ls:right', 'belowright ls:vsplit')
|
||||
call a:binder.alias('ls:top', 'topleft ls:split')
|
||||
call a:binder.alias('ls:bottom', 'botright ls:split')
|
||||
call a:binder.alias('ls:leftest', 'topleft ls:vsplit')
|
||||
call a:binder.alias('ls:rightest', 'botright ls:vsplit')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_ls(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina ls %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(candidate.rev),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
77
bundle/gina.vim/autoload/gina/action/patch.vim
Normal file
77
bundle/gina.vim/autoload/gina/action/patch.vim
Normal file
@ -0,0 +1,77 @@
|
||||
function! gina#action#patch#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Open three buffers to patch changes to an index',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\}
|
||||
call a:binder.define('patch', function('s:on_patch'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('patch:split', function('s:on_patch'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('patch:vsplit', function('s:on_patch'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('patch:tab', function('s:on_patch'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('patch:oneside', function('s:on_patch'), extend({
|
||||
\ 'options': {'oneside': 1},
|
||||
\}, params))
|
||||
call a:binder.define('patch:oneside:split', function('s:on_patch'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'oneside': 1, 'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('patch:oneside:vsplit', function('s:on_patch'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'oneside': 1, 'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('patch:oneside:tab', function('s:on_patch'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'oneside': 1, 'opener': 'tabedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('patch:above', 'leftabove patch:split')
|
||||
call a:binder.alias('patch:below', 'belowright patch:split')
|
||||
call a:binder.alias('patch:left', 'leftabove patch:vsplit')
|
||||
call a:binder.alias('patch:right', 'belowright patch:vsplit')
|
||||
call a:binder.alias('patch:top', 'topleft patch:split')
|
||||
call a:binder.alias('patch:bottom', 'botright patch:split')
|
||||
call a:binder.alias('patch:leftest', 'topleft patch:vsplit')
|
||||
call a:binder.alias('patch:rightest', 'botright patch:vsplit')
|
||||
call a:binder.alias('patch:oneside:above', 'leftabove patch:oneside:split')
|
||||
call a:binder.alias('patch:oneside:below', 'belowright patch:oneside:split')
|
||||
call a:binder.alias('patch:oneside:left', 'leftabove patch:oneside:vsplit')
|
||||
call a:binder.alias('patch:oneside:right', 'belowright patch:oneside:vsplit')
|
||||
call a:binder.alias('patch:oneside:top', 'topleft patch:oneside:split')
|
||||
call a:binder.alias('patch:oneside:bottom', 'botright patch:oneside:split')
|
||||
call a:binder.alias('patch:oneside:leftest', 'topleft patch:oneside:vsplit')
|
||||
call a:binder.alias('patch:oneside:rightest', 'botright patch:oneside:vsplit')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_patch(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\ 'oneside': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina patch %s %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ options.oneside ? '--oneside' : '',
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(get(candidate, 'line'), '--line='),
|
||||
\ gina#util#shellescape(get(candidate, 'col'), '--col='),
|
||||
\ gina#util#shellescape(candidate.path),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
120
bundle/gina.vim/autoload/gina/action/show.vim
Normal file
120
bundle/gina.vim/autoload/gina/action/show.vim
Normal file
@ -0,0 +1,120 @@
|
||||
function! gina#action#show#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Show a commit or a content at a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': [],
|
||||
\}
|
||||
call a:binder.define('show', function('s:on_show'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('show:split', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('show:vsplit', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('show:tab', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('show:preview', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
|
||||
let params = {
|
||||
\ 'description': 'Show a commit',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': [],
|
||||
\}
|
||||
call a:binder.define('show:commit', function('s:on_commit'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('show:commit:split', function('s:on_commit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('show:commit:vsplit', function('s:on_commit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('show:commit:tab', function('s:on_commit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('show:commit:preview', function('s:on_commit'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
" Alias
|
||||
call a:binder.alias('show:above', 'leftabove show:split')
|
||||
call a:binder.alias('show:below', 'belowright show:split')
|
||||
call a:binder.alias('show:left', 'leftabove show:vsplit')
|
||||
call a:binder.alias('show:right', 'belowright show:vsplit')
|
||||
call a:binder.alias('show:top', 'topleft show:split')
|
||||
call a:binder.alias('show:bottom', 'botright show:split')
|
||||
call a:binder.alias('show:leftest', 'topleft show:vsplit')
|
||||
call a:binder.alias('show:rightest', 'botright show:vsplit')
|
||||
call a:binder.alias('show:preview:top', 'topleft show:preview')
|
||||
call a:binder.alias('show:preview:bottom', 'botright show:preview')
|
||||
call a:binder.alias('show:commit:above', 'leftabove show:commit:split')
|
||||
call a:binder.alias('show:commit:below', 'belowright show:commit:split')
|
||||
call a:binder.alias('show:commit:left', 'leftabove show:commit:vsplit')
|
||||
call a:binder.alias('show:commit:right', 'belowright show:commit:vsplit')
|
||||
call a:binder.alias('show:commit:top', 'topleft show:commit:split')
|
||||
call a:binder.alias('show:commit:bottom', 'botright show:commit:split')
|
||||
call a:binder.alias('show:commit:leftest', 'topleft show:commit:vsplit')
|
||||
call a:binder.alias('show:commit:rightest', 'botright show:commit:vsplit')
|
||||
call a:binder.alias('show:commit:preview:top', 'topleft show:commit:preview')
|
||||
call a:binder.alias('show:commit:preview:bottom', 'botright show:commit:preview')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_show(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let treeish = gina#core#treeish#build(
|
||||
\ gina#util#get(candidate, 'rev'),
|
||||
\ gina#util#get(candidate, 'path', v:null),
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina show %s %s %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(get(candidate, 'line'), '--line='),
|
||||
\ gina#util#shellescape(get(candidate, 'col'), '--col='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\ gina#util#shellescape(gina#util#get(candidate, 'residual')),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_commit(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let treeish = gina#core#treeish#build(
|
||||
\ gina#util#get(candidate, 'rev'),
|
||||
\ v:null
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina show %s %s -- %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\ gina#util#shellescape(gina#util#get(candidate, 'residual')),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
218
bundle/gina.vim/autoload/gina/action/stash.vim
Normal file
218
bundle/gina.vim/autoload/gina/action/stash.vim
Normal file
@ -0,0 +1,218 @@
|
||||
function! gina#action#stash#define(binder) abort
|
||||
let params = {
|
||||
\ 'description': 'Show changes in a stash',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\}
|
||||
call a:binder.define('stash:show', function('s:on_show'), extend({
|
||||
\ 'options': {},
|
||||
\}, params))
|
||||
call a:binder.define('stash:show:split', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'new'},
|
||||
\}, params))
|
||||
call a:binder.define('stash:show:vsplit', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'vnew'},
|
||||
\}, params))
|
||||
call a:binder.define('stash:show:tab', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'tabedit'},
|
||||
\}, params))
|
||||
call a:binder.define('stash:show:preview', function('s:on_show'), extend({
|
||||
\ 'hidden': 1,
|
||||
\ 'options': {'opener': 'pedit'},
|
||||
\}, params))
|
||||
call a:binder.define('stash:drop', function('s:on_drop'), {
|
||||
\ 'description': 'Remove a stashed state',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('stash:drop:force', function('s:on_drop'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Remove a stashed state',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {'force': 1},
|
||||
\})
|
||||
call a:binder.define('stash:pop', function('s:on_pop'), {
|
||||
\ 'description': 'Remove a stashed state and apply to a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('stash:pop:index', function('s:on_pop'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Remove a stashed state and apply to a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {'index': 1},
|
||||
\})
|
||||
call a:binder.define('stash:apply', function('s:on_apply'), {
|
||||
\ 'description': 'Apply a stashed state to a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('stash:apply:index', function('s:on_apply'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Apply a stashed state to a working tree',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {'index': 1},
|
||||
\})
|
||||
call a:binder.define('stash:branch', function('s:on_branch'), {
|
||||
\ 'description': 'Create a new branch with a stashed state',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['stash'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('stash:clear', function('s:on_clear'), {
|
||||
\ 'description': 'Remove all stashed states',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('stash:clear:force', function('s:on_clear'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Remove all stashed states',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {'force': 1},
|
||||
\})
|
||||
" Alias
|
||||
call a:binder.alias('stash:show:above', 'leftabove stash:show:split')
|
||||
call a:binder.alias('stash:show:below', 'belowright stash:show:split')
|
||||
call a:binder.alias('stash:show:left', 'leftabove stash:show:vsplit')
|
||||
call a:binder.alias('stash:show:right', 'belowright stash:show:vsplit')
|
||||
call a:binder.alias('stash:show:top', 'topleft stash:show:split')
|
||||
call a:binder.alias('stash:show:bottom', 'botright stash:show:split')
|
||||
call a:binder.alias('stash:show:leftest', 'topleft stash:show:vsplit')
|
||||
call a:binder.alias('stash:show:rightest', 'botright stash:show:vsplit')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_show(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'opener': '',
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina stash show %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(options.opener, '--opener='),
|
||||
\ gina#util#shellescape(get(candidate, 'line'), '--line='),
|
||||
\ gina#util#shellescape(get(candidate, 'col'), '--col='),
|
||||
\ gina#util#shellescape(candidate.stash),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_drop(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
if !options.force
|
||||
call gina#core#console#warn(join([
|
||||
\ 'A stash:drop action will drop selected stashes ',
|
||||
\ 'and the operation is irreversible, mean that you have no ',
|
||||
\ 'chance to revert the operation with safety mechanisms.',
|
||||
\], "\n"))
|
||||
call gina#core#console#info(
|
||||
\ 'This operation will be performed to the following candidates:'
|
||||
\)
|
||||
for candidate in a:candidates
|
||||
call gina#core#console#echo('- ' . candidate.stash)
|
||||
endfor
|
||||
if !gina#core#console#confirm('Are you sure to drop stashes?', 'n')
|
||||
return
|
||||
endif
|
||||
endif
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina stash drop --quiet %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(candidate.stash),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_pop(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'index': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina stash pop --quiet %s %s',
|
||||
\ options.mods,
|
||||
\ options.index ? '--index' : '',
|
||||
\ gina#util#shellescape(candidate.stash),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_apply(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'index': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina stash apply --quiet %s %s',
|
||||
\ options.mods,
|
||||
\ options.index ? '--index' : '',
|
||||
\ gina#util#shellescape(candidate.stash),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_branch(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in a:candidates
|
||||
let name = gina#core#console#ask_or_cancel(
|
||||
\ 'Name: ', '',
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina stash branch %s %s',
|
||||
\ options.mods,
|
||||
\ name,
|
||||
\ gina#util#shellescape(candidate.stash),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_clear(candidates, options) abort
|
||||
let options = extend({
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
if !options.force
|
||||
call gina#core#console#warn(join([
|
||||
\ 'A stash:clear action will clear all stashes ',
|
||||
\ 'and the operation is irreversible, mean that you have no ',
|
||||
\ 'chance to revert the operation with safety mechanisms.',
|
||||
\], "\n"))
|
||||
if !gina#core#console#confirm('Are you sure to clear stashes?', 'n')
|
||||
return
|
||||
endif
|
||||
endif
|
||||
execute printf(
|
||||
\ '%s Gina stash clear',
|
||||
\ options.mods,
|
||||
\)
|
||||
endfunction
|
114
bundle/gina.vim/autoload/gina/action/tag.vim
Normal file
114
bundle/gina.vim/autoload/gina/action/tag.vim
Normal file
@ -0,0 +1,114 @@
|
||||
function! gina#action#tag#define(binder) abort
|
||||
call a:binder.define('tag:new:lightweight', function('s:on_new'), {
|
||||
\ 'description': 'Create a lightweight tag',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('tag:new:annotate', function('s:on_new'), {
|
||||
\ 'description': 'Create an unsigned, annotated tag',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {'annotate': 1},
|
||||
\})
|
||||
call a:binder.define('tag:new:sign', function('s:on_new'), {
|
||||
\ 'description': 'Create a GPG-signed tag',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {'sign': 1},
|
||||
\})
|
||||
call a:binder.define('tag:new:lightweight:force', function('s:on_new'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Create a lightweight tag',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {'force': 1},
|
||||
\})
|
||||
call a:binder.define('tag:new:annotate:force', function('s:on_new'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Create an unsigned, annotated tag',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {'annotate': 1, 'force': 1},
|
||||
\})
|
||||
call a:binder.define('tag:new:sign:force', function('s:on_new'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Create a GPG-signed tag',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {'sign': 1, 'force': 1},
|
||||
\})
|
||||
call a:binder.define('tag:delete', function('s:on_delete'), {
|
||||
\ 'description': 'Delete a tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['tag'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
call a:binder.define('tag:verify', function('s:on_verify'), {
|
||||
\ 'description': 'Verify a tag',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['tag'],
|
||||
\ 'options': {},
|
||||
\})
|
||||
" Alias
|
||||
call a:binder.alias('tag:new', 'tag:new:annotate')
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_new(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'annotate': 0,
|
||||
\ 'sign': 0,
|
||||
\ 'force': 0,
|
||||
\}, a:options)
|
||||
for candidate in a:candidates
|
||||
let name = gina#core#console#ask_or_cancel(
|
||||
\ 'Name: ', '',
|
||||
\)
|
||||
let from = gina#core#console#ask_or_cancel(
|
||||
\ 'From: ', 'HEAD',
|
||||
\ function('gina#complete#commit#branch')
|
||||
\)
|
||||
execute printf(
|
||||
\ '%s Gina tag %s %s %s %s %s',
|
||||
\ options.mods,
|
||||
\ options.annotate ? '--annotate' : '',
|
||||
\ options.sign ? '--sign' : '',
|
||||
\ options.force ? '--force' : '',
|
||||
\ gina#util#shellescape(name),
|
||||
\ gina#util#shellescape(from),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_delete(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina tag --delete %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(candidate.tag),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:on_verify(candidates, options) abort
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
for candidate in a:candidates
|
||||
execute printf(
|
||||
\ '%s Gina tag --verify %s',
|
||||
\ options.mods,
|
||||
\ gina#util#shellescape(candidate.tag),
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
81
bundle/gina.vim/autoload/gina/action/yank.vim
Normal file
81
bundle/gina.vim/autoload/gina/action/yank.vim
Normal file
@ -0,0 +1,81 @@
|
||||
function! gina#action#yank#define(binder) abort
|
||||
call a:binder.define('yank:rev', function('s:on_yank_rev'), {
|
||||
\ 'description': 'Yank the revision of a candidate under the cursor',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev'],
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('yank:path', function('s:on_yank_path'), {
|
||||
\ 'description': 'Yank the path of a candidate under the cursor',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['path'],
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call a:binder.define('yank:treeish', function('s:on_yank_treeish'), {
|
||||
\ 'description': 'Yank the treeish (revision and path) of a candidate under the cursor',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['rev', 'path'],
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:on_yank_rev(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
let candidates = map(
|
||||
\ copy(a:candidates),
|
||||
\ 's:get_rev(v:val)'
|
||||
\)
|
||||
call gina#util#yank(join(candidates, "\n"))
|
||||
endfunction
|
||||
|
||||
function! s:on_yank_path(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
let candidates = map(
|
||||
\ copy(a:candidates),
|
||||
\ 's:get_path(v:val)'
|
||||
\)
|
||||
call gina#util#yank(join(candidates, "\n"))
|
||||
endfunction
|
||||
|
||||
function! s:on_yank_treeish(candidates, options) abort dict
|
||||
if empty(a:candidates)
|
||||
return
|
||||
endif
|
||||
let options = extend({}, a:options)
|
||||
let candidates = map(
|
||||
\ copy(a:candidates),
|
||||
\ 's:get_treeish(v:val)'
|
||||
\)
|
||||
call gina#util#yank(join(candidates, "\n"))
|
||||
endfunction
|
||||
|
||||
function! s:get_rev(candidate) abort
|
||||
let rev = gina#util#get(a:candidate, 'rev', v:null)
|
||||
return rev is v:null
|
||||
\ ? gina#core#buffer#param(bufname('%'), 'rev')
|
||||
\ : rev
|
||||
endfunction
|
||||
|
||||
function! s:get_path(candidate) abort
|
||||
let path = gina#util#get(a:candidate, 'path', v:null)
|
||||
return path is v:null
|
||||
\ ? gina#core#buffer#param(bufname('%'), 'path')
|
||||
\ : path
|
||||
endfunction
|
||||
|
||||
function! s:get_treeish(candidate) abort
|
||||
let rev = s:get_rev(a:candidate)
|
||||
let path = s:get_path(a:candidate)
|
||||
return gina#core#treeish#build(rev, path)
|
||||
endfunction
|
65
bundle/gina.vim/autoload/gina/command.vim
Normal file
65
bundle/gina.vim/autoload/gina/command.vim
Normal file
@ -0,0 +1,65 @@
|
||||
function! gina#command#call(bang, range, rargs, mods) abort
|
||||
if a:bang ==# '!' && a:rargs[0] ==# '!'
|
||||
return gina#command#call('', a:range, '_shell ' . a:rargs[1:], a:mods)
|
||||
elseif a:bang ==# '!'
|
||||
return gina#command#call('', a:range, '_raw ' . a:rargs, a:mods)
|
||||
endif
|
||||
let args = gina#core#args#new(a:rargs)
|
||||
if empty(args.params.scheme)
|
||||
" The scheme becomes empty when Gina-xxxxx is given
|
||||
call gina#core#console#error(printf(
|
||||
\ 'The "Gina%s" is not correct gina command. You may want ":Gina %s"',
|
||||
\ a:rargs,
|
||||
\ a:rargs[1:],
|
||||
\))
|
||||
return
|
||||
endif
|
||||
try
|
||||
call gina#core#revelator#call(
|
||||
\ printf('gina#command#%s#call', args.params.scheme),
|
||||
\ [a:range, args, a:mods],
|
||||
\)
|
||||
return
|
||||
catch /^Vim\%((\a\+)\)\=:E117: [^:]\+: gina#command#[^#]\+#call/
|
||||
call gina#core#console#debug(v:exception)
|
||||
call gina#core#console#debug(v:throwpoint)
|
||||
endtry
|
||||
return gina#command#call('', a:range, '_raw ' . a:rargs, a:mods)
|
||||
endfunction
|
||||
|
||||
function! gina#command#complete(arglead, cmdline, cursorpos) abort
|
||||
if a:cmdline =~# '^.\{-}Gina!'
|
||||
return gina#command#complete(
|
||||
\ a:arglead,
|
||||
\ substitute(a:cmdline, '^\(.\{-}\)Gina!', '\1Gina _raw', ''),
|
||||
\ a:cursorpos,
|
||||
\)
|
||||
elseif a:cmdline =~# printf('^.\{-}Gina\s\+%s$', a:arglead)
|
||||
return gina#complete#common#command(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
let cmdline = matchstr(a:cmdline, '^.\{-}Gina\s\+\zs.*')
|
||||
let scheme = matchstr(cmdline, '^\S\+')
|
||||
let scheme = substitute(scheme, '!$', '', '')
|
||||
let scheme = substitute(scheme, '\W', '_', 'g')
|
||||
try
|
||||
return gina#core#revelator#call(
|
||||
\ printf('gina#command#%s#complete', scheme),
|
||||
\ [a:arglead, cmdline, a:cursorpos],
|
||||
\)[:g:gina#complete_threshold]
|
||||
catch /^Vim\%((\a\+)\)\=:E117: [^:]\+: gina#command#[^#]\+#complete/
|
||||
call gina#core#console#debug(v:exception)
|
||||
call gina#core#console#debug(v:throwpoint)
|
||||
endtry
|
||||
return gina#command#complete(
|
||||
\ a:arglead,
|
||||
\ substitute(a:cmdline, '^\(.\{-}\)Gina', '\1Gina _raw', ''),
|
||||
\ a:cursorpos,
|
||||
\)[:g:gina#complete_threshold]
|
||||
endfunction
|
||||
|
||||
function! gina#command#scheme(sfile) abort
|
||||
let name = fnamemodify(a:sfile, ':t')
|
||||
let name = matchstr(name, '.*\ze\.vim')
|
||||
let scheme = substitute(name, '_', '-', 'g')
|
||||
return scheme
|
||||
endfunction
|
154
bundle/gina.vim/autoload/gina/command/_events.vim
Normal file
154
bundle/gina.vim/autoload/gina/command/_events.vim
Normal file
@ -0,0 +1,154 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
let s:current = {
|
||||
\ 'bufnr': 0,
|
||||
\ 'middleware': {},
|
||||
\}
|
||||
|
||||
|
||||
function! gina#command#_events#call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.detail = args.pop('--detail')
|
||||
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal winfixwidth
|
||||
setlocal winfixheight
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=wipe
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
setlocal noautoread
|
||||
setlocal nolist nospell
|
||||
setlocal nowrap nonumber norelativenumber
|
||||
|
||||
augroup gina_internal_command
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer> call s:BufReadCmd()
|
||||
autocmd BufWipeout <buffer> call s:BufWipeout()
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
call gina#core#emitter#remove_middleware(s:current.middleware)
|
||||
setlocal nobuflisted
|
||||
setlocal filetype=gina-_events
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let s:current.bufnr = bufnr('%')
|
||||
let s:current.middleware = copy(s:middleware)
|
||||
let s:current.middleware.detail = args.params.detail
|
||||
call gina#core#emitter#add_middleware(s:current.middleware)
|
||||
endfunction
|
||||
|
||||
function! s:BufWipeout() abort
|
||||
call gina#core#emitter#remove_middleware(s:current.middleware)
|
||||
endfunction
|
||||
|
||||
function! s:print_message(msg) abort
|
||||
let bufnr = s:current.bufnr
|
||||
if !bufnr || bufwinnr(bufnr) < 1
|
||||
call gina#core#emitter#remove_middleware(s:current.middleware)
|
||||
let s:current.bufnr = 0
|
||||
return
|
||||
endif
|
||||
" NOTE:
|
||||
" 'timer_start' is required for prevent E523 Not allowed here raised when
|
||||
" events are emitted from 'statusline' or 'tabline'
|
||||
call timer_start(0, { -> gina#core#writer#replace(bufnr, -1, -1, [a:msg]) })
|
||||
endfunction
|
||||
|
||||
function! s:print_event(prefix, name, attrs) abort
|
||||
let width = gina#util#winwidth(bufwinnr(s:current.bufnr))
|
||||
let head = printf('%-5s: %s: %s',
|
||||
\ a:prefix,
|
||||
\ s:now(),
|
||||
\ a:name,
|
||||
\)
|
||||
let tail = printf('<%s>', bufname('%'))
|
||||
let args = join(map(copy(a:attrs), 'string(v:val)'), ', ')
|
||||
let args = substitute(args, '\r\?\n', '\\n', 'g')
|
||||
let args = substitute(args, '\e', '^[', 'g')
|
||||
let args = s:String.truncate_skipping(
|
||||
\ printf('(%s)', args),
|
||||
\ width - len(head) - len(tail) - 1,
|
||||
\ 3, '...'
|
||||
\)
|
||||
let head = head . args . ' '
|
||||
let message = head . s:String.pad_left(tail, width - len(head))
|
||||
call s:print_message(message)
|
||||
endfunction
|
||||
|
||||
function! s:print_listeners(listeners) abort
|
||||
for [Listener, instance] in a:listeners
|
||||
call s:print_message(printf(
|
||||
\ '| %s [%s]',
|
||||
\ string(Listener),
|
||||
\ string(instance),
|
||||
\))
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
if has('python3')
|
||||
python3 import datetime
|
||||
function! s:now() abort
|
||||
return py3eval('datetime.datetime.now().strftime("%H:%M:%S.%f")')
|
||||
endfunction
|
||||
elseif has('python')
|
||||
python import datetime
|
||||
function! s:now() abort
|
||||
return pyeval('datetime.datetime.now().strftime("%H:%M:%S.%f")')
|
||||
endfunction
|
||||
else
|
||||
function! s:now() abort
|
||||
return strftime('%H:%M:%S.??????')
|
||||
endfunction
|
||||
endif
|
||||
|
||||
|
||||
" Middleware -----------------------------------------------------------------
|
||||
let s:middleware = {'detail': 0}
|
||||
|
||||
function! s:middleware.on_emit_pre(name, listeners, attrs) abort
|
||||
call s:print_event('pre', a:name, a:attrs)
|
||||
if self.detail
|
||||
call s:print_listeners(a:listeners)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:middleware.on_emit_post(name, listeners, attrs) abort
|
||||
call s:print_event('post', a:name, a:attrs)
|
||||
if self.detail
|
||||
call s:print_listeners(a:listeners)
|
||||
endif
|
||||
endfunction
|
113
bundle/gina.vim/autoload/gina/command/_raw.vim
Normal file
113
bundle/gina.vim/autoload/gina/command/_raw.vim
Normal file
@ -0,0 +1,113 @@
|
||||
function! gina#command#_raw#call(range, args, mods) abort
|
||||
let git = gina#core#get()
|
||||
let args = s:build_args(git, a:args)
|
||||
let pipe = a:mods =~# '\<silent\>'
|
||||
\ ? deepcopy(s:pipe_silent)
|
||||
\ : deepcopy(s:pipe)
|
||||
return gina#process#open(git, args, pipe)
|
||||
endfunction
|
||||
|
||||
function! gina#command#_raw#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if empty(args.get(1))
|
||||
return gina#complete#common#raw_command(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
if args.get(1) =~# '^\%(fetch\|pull\|push\|switch\)$'
|
||||
return s:{args.get(1)}_complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
if args.get(0) ==# '_raw'
|
||||
" Remove leading '_raw' if exists
|
||||
call args.pop(0)
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:fetch_complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if empty(args.get(2))
|
||||
return gina#complete#common#remote(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
if empty(args.get(3))
|
||||
" TODO: Return refspecs in remote repository "args.get(2)".
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
function! s:pull_complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if empty(args.get(2))
|
||||
return gina#complete#common#remote(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
if empty(args.get(3))
|
||||
" TODO: Return refspecs in remote repository "args.get(2)".
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
function! s:push_complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if empty(args.get(2))
|
||||
return gina#complete#common#remote(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
if empty(args.get(3))
|
||||
return gina#complete#commit#local_branch(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
function! s:switch_complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if empty(args.get(2))
|
||||
return gina#complete#commit#local_branch(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
|
||||
" Pipe -----------------------------------------------------------------------
|
||||
function! s:_pipe_on_exit(exitval) abort dict
|
||||
call call(s:original_pipe.on_exit, [a:exitval], self)
|
||||
call gina#core#emitter#emit(
|
||||
\ 'command:called:raw',
|
||||
\ self.params.scheme,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:_pipe_silent_on_exit(exitval) abort dict
|
||||
call call(s:original_pipe_silent.on_exit, [a:exitval], self)
|
||||
call gina#core#emitter#emit(
|
||||
\ 'command:called:raw',
|
||||
\ self.params.scheme,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
let s:original_pipe = gina#process#pipe#echo()
|
||||
let s:original_pipe_silent = gina#process#pipe#default()
|
||||
let s:pipe = extend(deepcopy(s:original_pipe), {
|
||||
\ 'on_exit': function('s:_pipe_on_exit'),
|
||||
\})
|
||||
let s:pipe_silent = extend(deepcopy(s:original_pipe_silent), {
|
||||
\ 'on_exit': function('s:_pipe_silent_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Event ----------------------------------------------------------------------
|
||||
function! s:on_command_called_raw(...) abort
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
endfunction
|
||||
|
||||
|
||||
if !exists('s:subscribed')
|
||||
let s:subscribed = 1
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'command:called:raw',
|
||||
\ function('s:on_command_called_raw')
|
||||
\)
|
||||
endif
|
31
bundle/gina.vim/autoload/gina/command/_shell.vim
Normal file
31
bundle/gina.vim/autoload/gina/command/_shell.vim
Normal file
@ -0,0 +1,31 @@
|
||||
function! gina#command#_shell#call(range, args, mods) abort
|
||||
let git = gina#core#get()
|
||||
let args = gina#process#build_raw_args(git, s:build_args(git, a:args))
|
||||
let cmdline = join(map(args, 's:shellescape(v:val)'))
|
||||
if has('nvim')
|
||||
tabnew
|
||||
execute ':terminal' cmdline
|
||||
augroup gina_command__shell_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd TermClose <buffer> call gina#core#emitter#emit('modified:delay')
|
||||
augroup END
|
||||
else
|
||||
execute ':!' cmdline
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
if args.get(0) ==# '_shell'
|
||||
" Remove leading '_shell' if exists
|
||||
call args.pop(0)
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:shellescape(val) abort
|
||||
return a:val =~# '\s' ? shellescape(a:val) : a:val
|
||||
endfunction
|
404
bundle/gina.vim/autoload/gina/command/blame.vim
Normal file
404
bundle/gina.vim/autoload/gina/command/blame.vim
Normal file
@ -0,0 +1,404 @@
|
||||
let s:Dict = vital#gina#import('Data.Dict')
|
||||
let s:Group = vital#gina#import('Vim.Buffer.Group')
|
||||
let s:Opener = vital#gina#import('Vim.Buffer.Opener')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#blame#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#blame#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#common#treeish(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line=',
|
||||
\ 'An initial line number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--col=',
|
||||
\ 'An initial column number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group1=',
|
||||
\ 'A window group name used for a blame body buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group2=',
|
||||
\ 'A window group name used for a blame navigation buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--width=',
|
||||
\ 'A window width used for a blame navigation buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--format=',
|
||||
\ 'Format string used to construct the navi line.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--root',
|
||||
\ 'Do not treat root commits as boundaries.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-L',
|
||||
\ 'Annotate only the given line range. May be specified multiple times.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--reverse=',
|
||||
\ 'Walk history forward instead of backward.',
|
||||
\ function('gina#complete#range#any'),
|
||||
\)
|
||||
call options.define(
|
||||
\ '--encoding=',
|
||||
\ 'Specifies the encoding used to output.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--content=', join([
|
||||
\ 'This flag makes the command pretend as if the working tree copy',
|
||||
\ 'has the contents of the named file.',
|
||||
\ 'Works only when {rev} is not specified.'
|
||||
\ ]),
|
||||
\ function('gina#complete#filename#any'),
|
||||
\)
|
||||
call options.define(
|
||||
\ '-M',
|
||||
\ 'Detect moved or copied lines within a file.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-C',
|
||||
\ 'In addition to -M, detect lines moved or copied from other files.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-w',
|
||||
\ 'Ignore whitespace when comparing.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args, range) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.groups = [
|
||||
\ args.pop('--group1', 'blame-body'),
|
||||
\ args.pop('--group2', 'blame-navi'),
|
||||
\]
|
||||
let args.params.opener = args.pop('--opener', 'tabnew')
|
||||
let args.params.width = args.pop('--width', v:null)
|
||||
let args.params.format = args.pop('--format', v:null)
|
||||
|
||||
" Warn deperecated feature
|
||||
if args.pop('--use-author-instead')
|
||||
call gina#core#console#warn(
|
||||
\ '--use-author-instead option is removed. Use --format instead.'
|
||||
\)
|
||||
endif
|
||||
|
||||
call gina#core#args#extend_treeish(a:git, args, args.pop(1, ':'))
|
||||
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
||||
if empty(args.params.path)
|
||||
throw gina#core#revelator#warning(printf(
|
||||
\ 'No filename is specified. Did you mean "Gina blame %s:"?',
|
||||
\ args.params.rev,
|
||||
\))
|
||||
endif
|
||||
|
||||
if !(a:range[0] == 1 && a:range[1] == line('$'))
|
||||
" Apply visual range
|
||||
call args.set('-L', join(a:range, ','))
|
||||
endif
|
||||
|
||||
call args.pop('--porcelain')
|
||||
call args.pop('--line-porcelain')
|
||||
call args.set('--incremental', 1)
|
||||
call args.set(1, substitute(args.params.rev, '^:0$', '', ''))
|
||||
call args.residual([args.params.path])
|
||||
|
||||
" Check no unknown options are specified
|
||||
call s:validate(args)
|
||||
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:validate(args) abort
|
||||
" Remove all known options
|
||||
let args = a:args.clone()
|
||||
let options = s:get_options()
|
||||
for option in values(options._options)
|
||||
for name in option.names
|
||||
call args.pop(name)
|
||||
endfor
|
||||
endfor
|
||||
call args.pop('--incremental')
|
||||
" Get remaining
|
||||
let unknown = args.get('^-')
|
||||
if !empty(unknown)
|
||||
throw gina#core#revelator#error(printf(
|
||||
\ 'Unknwon options %s has specified',
|
||||
\ string(unknown)
|
||||
\))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args, a:range)
|
||||
let mods = gina#util#contain_direction(a:mods)
|
||||
\ ? 'keepalt ' . a:mods
|
||||
\ : join(['keepalt', 'rightbelow', a:mods])
|
||||
let group = s:Group.new({
|
||||
\ 'on_close_fail': function('s:on_close_fail'),
|
||||
\})
|
||||
|
||||
" Content
|
||||
call s:open(mods, args.params.opener, args.params)
|
||||
call group.add()
|
||||
call gina#util#windo('setlocal noscrollbind')
|
||||
setlocal scrollbind nowrap nofoldenable
|
||||
augroup gina_command_blame_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd WinLeave <buffer> call s:WinLeave()
|
||||
autocmd WinEnter <buffer> call s:WinEnter()
|
||||
augroup END
|
||||
" Navi
|
||||
let bufname = gina#core#buffer#bufname(git, 'blame', {
|
||||
\ 'treeish': args.params.treeish,
|
||||
\ 'noautocmd': 1,
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': 'leftabove',
|
||||
\ 'group': args.params.groups[1],
|
||||
\ 'opener': (args.params.width ? args.params.width : g:gina#command#blame#default_navi_width) . 'vsplit',
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'width': args.params.width,
|
||||
\ 'line': args.params.line,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
call group.add()
|
||||
setlocal scrollbind
|
||||
call gina#util#syncbind()
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
function! s:on_close_fail(winnr, member) abort dict
|
||||
let bufname = bufname(winbufnr(a:winnr))
|
||||
let abspath = gina#core#repo#abspath(
|
||||
\ gina#core#get_or_fail(),
|
||||
\ gina#core#buffer#param(bufname, 'path')
|
||||
\)
|
||||
echomsg abspath
|
||||
if filereadable(abspath)
|
||||
execute printf('edit %s', fnameescape(abspath))
|
||||
else
|
||||
enew
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:open(mods, opener, params) abort
|
||||
if s:Opener.is_preview_opener(a:opener)
|
||||
throw gina#core#revelator#warning(printf(
|
||||
\ 'An opener "%s" is not allowed.',
|
||||
\ a:opener,
|
||||
\))
|
||||
endif
|
||||
let treeish = gina#core#treeish#build(a:params.rev, a:params.path)
|
||||
execute printf(
|
||||
\ '%s Gina show %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[0], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=wipe
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
" While navi must be vertical, set winfixwidth
|
||||
setlocal winfixwidth
|
||||
|
||||
" Attach modules
|
||||
call gina#action#attach(function('s:get_candidates'))
|
||||
|
||||
" Mapping
|
||||
nnoremap <buffer><silent> <Plug>(gina-blame-redraw)
|
||||
\ :<C-u>call <SID>redraw_content()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-blame-C-L)
|
||||
\ :<C-u>call <SID>redraw_content()<CR>:execute "normal! \<C-L>"<CR>
|
||||
|
||||
augroup gina_command_blame_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd WinLeave <buffer> call s:redraw_content_if_necessary()
|
||||
autocmd WinEnter <buffer> call s:redraw_content_if_necessary()
|
||||
autocmd VimResized <buffer> call s:redraw_content_if_necessary()
|
||||
autocmd WinLeave <buffer> call s:WinLeave()
|
||||
autocmd WinEnter <buffer> call s:WinEnter()
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:WinLeave() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let params = gina#core#buffer#parse('%')
|
||||
if params.scheme ==# 'blame'
|
||||
let alternate = gina#core#buffer#bufname(git, 'show', {
|
||||
\ 'params': params.params,
|
||||
\ 'treeish': params.treeish,
|
||||
\})
|
||||
else
|
||||
let alternate = gina#core#buffer#bufname(git, 'blame', {
|
||||
\ 'params': params.params,
|
||||
\ 'treeish': params.treeish,
|
||||
\})
|
||||
endif
|
||||
if bufwinnr(alternate) != -1
|
||||
call setbufvar(alternate, 'gina_syncbind_line', line('.'))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:WinEnter() abort
|
||||
if exists('b:gina_syncbind_line')
|
||||
call setpos('.', [0, b:gina_syncbind_line, col('.'), 0])
|
||||
unlet b:gina_syncbind_line
|
||||
endif
|
||||
syncbind
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#command#blame#pipe#incremental()
|
||||
let job = gina#process#open(git, args, pipe)
|
||||
let status = job.wait()
|
||||
if status
|
||||
throw gina#process#errormsg({
|
||||
\ 'args': job.args,
|
||||
\ 'status': status,
|
||||
\ 'content': pipe._stderr,
|
||||
\})
|
||||
endif
|
||||
call gina#core#meta#set('chunks', job.chunks)
|
||||
call gina#core#meta#set('revisions', job.revisions)
|
||||
call s:redraw_content()
|
||||
call gina#util#syncbind()
|
||||
setlocal filetype=gina-blame
|
||||
endfunction
|
||||
|
||||
function! s:redraw_content() abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let chunks = gina#core#meta#get_or_fail('chunks')
|
||||
let revisions = gina#core#meta#get_or_fail('revisions')
|
||||
let formatter = gina#command#blame#formatter#new(
|
||||
\ gina#util#winwidth(0),
|
||||
\ args.params.rev,
|
||||
\ revisions,
|
||||
\ {
|
||||
\ 'format': args.params.format,
|
||||
\ }
|
||||
\)
|
||||
let content = []
|
||||
call map(copy(chunks), 'extend(content, formatter.format(v:val))')
|
||||
call gina#core#writer#replace('%', 0, -1, content)
|
||||
call gina#util#syncbind()
|
||||
let b:gina_previous_winwidth = winwidth(0)
|
||||
endfunction
|
||||
|
||||
function! s:redraw_content_if_necessary() abort
|
||||
if exists('b:gina_previous_winwidth') && b:gina_previous_winwidth != winwidth(0)
|
||||
call s:redraw_content()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let chunks = gina#core#meta#get_or_fail('chunks')
|
||||
if empty(chunks)
|
||||
return []
|
||||
endif
|
||||
let revisions = gina#core#meta#get_or_fail('revisions')
|
||||
let fidx = s:binary_search(chunks, a:fline, 0, len(chunks) - 1)
|
||||
let lidx = s:binary_search(chunks, a:lline, 0, len(chunks) - 1)
|
||||
let candidates = map(
|
||||
\ range(fidx, lidx),
|
||||
\ 's:translate_candidate(args.params.rev, chunks[v:val], revisions)'
|
||||
\)
|
||||
return candidates
|
||||
endfunction
|
||||
|
||||
function! s:binary_search(chunks, lnum, imin, imax) abort
|
||||
if a:imax < a:imin
|
||||
return v:null
|
||||
endif
|
||||
let imid = a:imin + (a:imax - a:imin) / 2
|
||||
let chunk = a:chunks[imid]
|
||||
if chunk.lnum > a:lnum
|
||||
return s:binary_search(a:chunks, a:lnum, a:imin, imid - 1)
|
||||
elseif (chunk.lnum + chunk.nlines - 1) < a:lnum
|
||||
return s:binary_search(a:chunks, a:lnum, imid + 1, a:imax)
|
||||
else
|
||||
return imid
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:translate_candidate(rev, chunk, revisions) abort
|
||||
let chunk = copy(a:chunk)
|
||||
call extend(chunk, s:Dict.omit(a:revisions[chunk.revision], [
|
||||
\ 'lnum_from', 'lnum', 'nlines',
|
||||
\]))
|
||||
let rev = chunk.revision
|
||||
let path = chunk.filename
|
||||
let line = chunk.lnum_from
|
||||
return extend(chunk, {
|
||||
\ 'rev': rev,
|
||||
\ 'path': path,
|
||||
\ 'line': line,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\ 'writer_threshold': 0,
|
||||
\ 'default_navi_width': 50,
|
||||
\})
|
127
bundle/gina.vim/autoload/gina/command/blame/formatter.vim
Normal file
127
bundle/gina.vim/autoload/gina/command/blame/formatter.vim
Normal file
@ -0,0 +1,127 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:Formatter = vital#gina#import('Data.String.Formatter')
|
||||
|
||||
let s:N_COLORS = 16
|
||||
let s:FORMAT_MAP = {
|
||||
\ 'su': 'summary',
|
||||
\ 'au': 'author',
|
||||
\ 'ma': 'mark',
|
||||
\ 'in': 'index',
|
||||
\ 'ti': 'timestamp',
|
||||
\}
|
||||
|
||||
|
||||
function! gina#command#blame#formatter#new(width, current, revisions, ...) abort
|
||||
let options = extend({
|
||||
\ 'format': v:null,
|
||||
\ 'separator': v:null,
|
||||
\ 'current_mark': v:null,
|
||||
\ 'timestamp_months': v:null,
|
||||
\ 'timestamp_format1': v:null,
|
||||
\ 'timestamp_format2': v:null,
|
||||
\}, get(a:000, 0, {})
|
||||
\)
|
||||
let formatter = deepcopy(s:formatter)
|
||||
let formatter._cache = {}
|
||||
let formatter._width = a:width
|
||||
let formatter._current = empty(a:current) ? 'X' : a:current
|
||||
let formatter._revisions = s:index_revisions(a:revisions)
|
||||
let formatter._previous = 1
|
||||
let formatter._timestamper = gina#core#timestamper#new({
|
||||
\ 'months': empty(options.timestamp_months)
|
||||
\ ? g:gina#command#blame#formatter#timestamp_months
|
||||
\ : options.timestamp_months,
|
||||
\ 'format1': empty(options.timestamp_format1)
|
||||
\ ? g:gina#command#blame#formatter#timestamp_format1
|
||||
\ : options.timestamp_format1,
|
||||
\ 'format2': empty(options.timestamp_format2)
|
||||
\ ? g:gina#command#blame#formatter#timestamp_format2
|
||||
\ : options.timestamp_format2,
|
||||
\})
|
||||
let formatter._current_mark = empty(options.current_mark)
|
||||
\ ? g:gina#command#blame#formatter#current_mark
|
||||
\ : options.current_mark
|
||||
let formatter._separator = empty(options.separator)
|
||||
\ ? g:gina#command#blame#formatter#separator
|
||||
\ : options.separator
|
||||
let formatter._format = empty(options.format)
|
||||
\ ? g:gina#command#blame#formatter#format
|
||||
\ : options.format
|
||||
return formatter
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:index_revisions(revisions) abort
|
||||
let n = s:calc_nindicators(a:revisions)
|
||||
let revisions = deepcopy(a:revisions)
|
||||
let keys = keys(revisions)
|
||||
for index in range(len(revisions))
|
||||
let revisions[keys[index]].index = s:String.pad_left(
|
||||
\ s:String.nr2hex(index), n, '0'
|
||||
\)
|
||||
endfor
|
||||
return revisions
|
||||
endfunction
|
||||
|
||||
function! s:calc_nindicators(revisions) abort
|
||||
let n = len(a:revisions)
|
||||
let x = 1
|
||||
while pow(s:N_COLORS, x) < n
|
||||
let x+= 1
|
||||
endwhile
|
||||
return x
|
||||
endfunction
|
||||
|
||||
|
||||
" Formatter ------------------------------------------------------------------
|
||||
let s:formatter = {}
|
||||
|
||||
function! s:formatter.format(chunk) abort
|
||||
let revision = a:chunk.revision
|
||||
let revinfo = self._revisions[revision]
|
||||
let content = repeat(
|
||||
\ [self._format_line(a:chunk, revision, revinfo)],
|
||||
\ a:chunk.nlines,
|
||||
\)
|
||||
" Fill missing lines from previous
|
||||
let mlines = a:chunk.lnum - self._previous
|
||||
let self._previous = a:chunk.lnum + a:chunk.nlines
|
||||
return repeat([''], mlines) + content
|
||||
endfunction
|
||||
|
||||
function! s:formatter._format_line(chunk, revision, revinfo) abort
|
||||
if has_key(self._cache, a:revision)
|
||||
return self._cache[a:revision]
|
||||
endif
|
||||
let precursor = s:Formatter.format(self._format, s:FORMAT_MAP, {
|
||||
\ 'summary': a:revinfo.summary,
|
||||
\ 'author': a:revinfo.author,
|
||||
\ 'index': a:revinfo.index,
|
||||
\ 'mark': a:revision =~# '^' . self._current
|
||||
\ ? self._current_mark
|
||||
\ : repeat(' ', len(self._current_mark)),
|
||||
\ 'timestamp': self._timestamper.format(
|
||||
\ a:revinfo.author_time,
|
||||
\ a:revinfo.author_tz
|
||||
\ ),
|
||||
\})
|
||||
let [head, tail] = split(precursor . '%=', '%=', 1)[0 : 1]
|
||||
let width = self._width - strwidth(tail) - 1
|
||||
let head = s:String.truncate_skipping(head, width, 3, self._separator)
|
||||
let head = s:String.pad_right(head, width)
|
||||
let self._cache[a:revision] = head . ' ' . tail
|
||||
return self._cache[a:revision]
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'format': '%su%=on %ti %ma%in',
|
||||
\ 'separator': '...',
|
||||
\ 'current_mark': '|',
|
||||
\ 'timestamp_months': 3,
|
||||
\ 'timestamp_format1': '%d %b',
|
||||
\ 'timestamp_format2': '%d %b, %Y',
|
||||
\})
|
107
bundle/gina.vim/autoload/gina/command/blame/pipe.vim
Normal file
107
bundle/gina.vim/autoload/gina/command/blame/pipe.vim
Normal file
@ -0,0 +1,107 @@
|
||||
let s:KEYWORDS = [
|
||||
\ 'author-mail',
|
||||
\ 'author-time',
|
||||
\ 'author-tz',
|
||||
\ 'author',
|
||||
\ 'committer-mail',
|
||||
\ 'committer-time',
|
||||
\ 'committer-tz',
|
||||
\ 'committer',
|
||||
\ 'summary',
|
||||
\ 'previous',
|
||||
\ 'filename',
|
||||
\ 'boundary',
|
||||
\]
|
||||
call map(
|
||||
\ s:KEYWORDS,
|
||||
\ '[v:val, len(v:val), substitute(v:val, ''-'', ''_'', ''g'')]',
|
||||
\)
|
||||
|
||||
function! gina#command#blame#pipe#incremental() abort
|
||||
let parser_pipe = deepcopy(s:parser_pipe)
|
||||
let parser_pipe.revisions = {}
|
||||
let parser_pipe.chunks = []
|
||||
let parser_pipe._stdout = ['']
|
||||
let parser_pipe._stderr = ['']
|
||||
let parser_pipe._chunk = {}
|
||||
return parser_pipe
|
||||
endfunction
|
||||
|
||||
|
||||
" Parser pipe ----------------------------------------------------------------
|
||||
function! s:_parser_pipe_on_stdout(data) abort dict
|
||||
let self._stdout[-1] .= a:data[0]
|
||||
call extend(self._stdout, a:data[1:])
|
||||
endfunction
|
||||
|
||||
function! s:_parser_pipe_on_stderr(data) abort dict
|
||||
let self._stderr[-1] .= a:data[0]
|
||||
call extend(self._stderr, a:data[1:])
|
||||
endfunction
|
||||
|
||||
function! s:_parser_pipe_on_exit(exitval) abort dict
|
||||
call call(s:original_pipe.on_exit, [a:exitval], self)
|
||||
if a:exitval
|
||||
throw gina#process#errormsg({
|
||||
\ 'args': self.args,
|
||||
\ 'content': self._stderr,
|
||||
\})
|
||||
endif
|
||||
" Parse records to create chunks
|
||||
call map(filter(self._stdout, '!empty(v:val)'), 's:parse(self, v:val)')
|
||||
" Sort chunks and assign indices
|
||||
call sort(self.chunks, { a, b -> a.lnum - b.lnum })
|
||||
call map(self.chunks, 'extend(v:val, {''index'': v:key})')
|
||||
endfunction
|
||||
|
||||
let s:original_pipe = gina#process#pipe#default()
|
||||
let s:parser_pipe = extend(deepcopy(s:original_pipe), {
|
||||
\ 'on_stdout': function('s:_parser_pipe_on_stdout'),
|
||||
\ 'on_stderr': function('s:_parser_pipe_on_stderr'),
|
||||
\ 'on_exit': function('s:_parser_pipe_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Private --------------------------------------------------------
|
||||
function! s:parse(pipe, record) abort
|
||||
let chunk = a:pipe._chunk
|
||||
let revisions = a:pipe.revisions
|
||||
call extend(chunk, s:parse_record(a:record))
|
||||
if !has_key(chunk, 'filename')
|
||||
return
|
||||
endif
|
||||
if !has_key(revisions, chunk.revision)
|
||||
let revisions[chunk.revision] = chunk
|
||||
let chunk = {
|
||||
\ 'filename': chunk.filename,
|
||||
\ 'revision': chunk.revision,
|
||||
\ 'lnum_from': chunk.lnum_from,
|
||||
\ 'lnum': chunk.lnum,
|
||||
\ 'nlines': chunk.nlines,
|
||||
\}
|
||||
endif
|
||||
call add(a:pipe.chunks, chunk)
|
||||
let a:pipe._chunk = {}
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record) abort
|
||||
for [prefix, length, vname] in s:KEYWORDS
|
||||
if a:record[:length-1] ==# prefix
|
||||
return {vname : a:record[length+1:]}
|
||||
endif
|
||||
endfor
|
||||
let terms = split(a:record)
|
||||
let nterms = len(terms)
|
||||
if nterms >= 3
|
||||
return {
|
||||
\ 'revision': terms[0],
|
||||
\ 'lnum_from': terms[1] + 0,
|
||||
\ 'lnum': terms[2] + 0,
|
||||
\ 'nlines': nterms == 3 ? 1 : (terms[3] + 0),
|
||||
\}
|
||||
endif
|
||||
throw gina#core#revelator#critical(printf(
|
||||
\ 'Failed to parse a record "%s"',
|
||||
\ a:record,
|
||||
\))
|
||||
endfunction
|
249
bundle/gina.vim/autoload/gina/command/branch.vim
Normal file
249
bundle/gina.vim/autoload/gina/command/branch.vim
Normal file
@ -0,0 +1,249 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#branch#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
|
||||
if s:is_raw_command(a:args)
|
||||
" Remove non git options
|
||||
let args = a:args.clone()
|
||||
call args.pop('--group')
|
||||
call args.pop('--opener')
|
||||
" Call raw git command
|
||||
return gina#command#_raw#call(a:range, args, a:mods)
|
||||
endif
|
||||
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#branch#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#commit#branch(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-d|--delete',
|
||||
\ 'Delete a branch.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-D',
|
||||
\ 'Shortcut for --delete --force.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-l|--create-reflog',
|
||||
\ 'Create the branch''s reflog.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-f|--force',
|
||||
\ 'Operate forcedly.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-m|--move',
|
||||
\ 'Move/rename a branch and the corresponding reflog.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-M',
|
||||
\ 'Shortcut for --move --force.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-i|--ignore-case',
|
||||
\ 'Sorting and filtering branches are case insensitive.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-r|--remotes',
|
||||
\ 'List or delete (if used with -d) the remote-tracking branches.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--all',
|
||||
\ 'List both remote-tracking branches and local branches.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--list',
|
||||
\ 'Activate the list mode. Mainly for <pattern> match.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-v|--verbose',
|
||||
\ 'Show sha1 and commit subject line for each head.'
|
||||
\)
|
||||
call options.define(
|
||||
\ '-q|--quiet',
|
||||
\ 'Be more quiet when creating or deleting a branch.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-t|--track',
|
||||
\ 'Set up a branch.<name>.remote and branch.<name>.merge config.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-track',
|
||||
\ 'Do not set up "upstream" config.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--set-upstream',
|
||||
\ 'Set up a "upstream" as like --track for non existing branch.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-u|--set-upstream-to=',
|
||||
\ 'Set up "upstream" to <upstream>.',
|
||||
\ function('gina#complete#commit#branch'),
|
||||
\)
|
||||
call options.define(
|
||||
\ '--unset-upstream',
|
||||
\ 'Remove the upstream information.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--contains=',
|
||||
\ 'Only list branches which contain the specified commit.',
|
||||
\ function('gina#complete#commit#any')
|
||||
\)
|
||||
call options.define(
|
||||
\ '--merged=',
|
||||
\ 'Only list branches whose tips are reachable from the specified commit.',
|
||||
\ function('gina#complete#commit#any')
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-merged=',
|
||||
\ 'Only list branches whose tips are not reachable from the specified commit.',
|
||||
\ function('gina#complete#commit#any')
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
|
||||
call args.set('--color', 'always')
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:is_raw_command(args) abort
|
||||
if a:args.get('--list')
|
||||
return 0
|
||||
elseif !empty(a:args.get('-u|--set-upstream-to', ''))
|
||||
return 1
|
||||
elseif a:args.get('--unset-upstream')
|
||||
return 1
|
||||
elseif a:args.get('-m|--move') || a:args.get('-M')
|
||||
return 1
|
||||
elseif a:args.get('-d|--delete') || a:args.get('-D')
|
||||
return 1
|
||||
endif
|
||||
return !empty(a:args.get(1))
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
setlocal autoread
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'), {
|
||||
\ 'markable': 1,
|
||||
\})
|
||||
|
||||
augroup gina_command_branch_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-branch
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record) abort
|
||||
let record = s:String.remove_ansi_sequences(a:record)
|
||||
let m = matchlist(record, '\(\*\|\s\) \([^ ]\+\)\%( -> \([^ ]\+\)\)\?')
|
||||
let remote = matchstr(m[2], '^remotes/\zs[^ /]\+')
|
||||
let rev = matchstr(m[2], '^\%(remotes/\)\?\zs[^ ]\+')
|
||||
let branch = matchstr(rev, printf('^\%%(%s/\)\?\zs[^ ]\+', remote))
|
||||
return {
|
||||
\ 'word': record,
|
||||
\ 'abbr': a:record,
|
||||
\ 'sign': m[1],
|
||||
\ 'alias': m[3],
|
||||
\ 'remote': remote,
|
||||
\ 'rev': rev,
|
||||
\ 'branch': branch,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
217
bundle/gina.vim/autoload/gina/command/browse.vim
Normal file
217
bundle/gina.vim/autoload/gina/command/browse.vim
Normal file
@ -0,0 +1,217 @@
|
||||
let s:Formatter = vital#gina#import('Data.String.Formatter')
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
let s:FORMAT_MAP = {
|
||||
\ 'pt': 'path',
|
||||
\ 'ls': 'line_start',
|
||||
\ 'le': 'line_end',
|
||||
\ 'c0': 'commit0',
|
||||
\ 'c1': 'commit1',
|
||||
\ 'c2': 'commit2',
|
||||
\ 'h0': 'hash0',
|
||||
\ 'h1': 'hash1',
|
||||
\ 'h2': 'hash2',
|
||||
\ 'r0': 'rev0',
|
||||
\ 'r1': 'rev1',
|
||||
\ 'r2': 'rev2',
|
||||
\}
|
||||
|
||||
|
||||
function! gina#command#browse#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#browse#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#common#treeish(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--scheme=',
|
||||
\ 'Specify a URL scheme to open.',
|
||||
\ ['_', 'root', 'blame', 'compare'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--exact',
|
||||
\ 'Use a sha1 instead of a branch name.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--yank',
|
||||
\ 'Yank a URL instead of opening.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args, range) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.yank = args.pop('--yank')
|
||||
let args.params.exact = args.pop('--exact')
|
||||
let args.params.range = a:range == [1, line('$')] ? [] : a:range
|
||||
let args.params.scheme = args.pop('--scheme', v:null)
|
||||
call gina#core#args#extend_treeish(a:git, args, args.pop(1))
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args, a:range)
|
||||
let rev = gina#util#get(args.params, 'rev')
|
||||
let path = gina#util#get(args.params, 'path')
|
||||
let revinfo = s:parse_rev(git, rev)
|
||||
let base_url = s:build_base_url(
|
||||
\ s:get_remote_url(git, revinfo.commit1, revinfo.commit2),
|
||||
\ args.params.scheme is# v:null
|
||||
\ ? empty(path) ? 'root' : '_'
|
||||
\ : args.params.scheme,
|
||||
\)
|
||||
let url = s:Formatter.format(base_url, s:FORMAT_MAP, {
|
||||
\ 'path': substitute(path, ' ', '%20', 'g'),
|
||||
\ 'line_start': get(args.params.range, 0, ''),
|
||||
\ 'line_end': get(args.params.range, 1, ''),
|
||||
\ 'commit0': revinfo.commit0,
|
||||
\ 'commit1': revinfo.commit1,
|
||||
\ 'commit2': revinfo.commit2,
|
||||
\ 'hash0': revinfo.hash0,
|
||||
\ 'hash1': revinfo.hash1,
|
||||
\ 'hash2': revinfo.hash2,
|
||||
\ 'rev0': args.params.exact ? revinfo.hash0 : revinfo.commit0,
|
||||
\ 'rev1': args.params.exact ? revinfo.hash1 : revinfo.commit1,
|
||||
\ 'rev2': args.params.exact ? revinfo.hash2 : revinfo.commit2,
|
||||
\})
|
||||
if empty(url)
|
||||
throw gina#core#revelator#warning(printf(
|
||||
\ 'No url translation pattern for "%s" is found.',
|
||||
\ rev,
|
||||
\))
|
||||
endif
|
||||
if args.params.yank
|
||||
call gina#util#yank(url)
|
||||
else
|
||||
call gina#util#open(url)
|
||||
endif
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
function! s:parse_rev(git, rev) abort
|
||||
let [commit1, commit2] = gina#core#treeish#split(a:rev)
|
||||
let commit0 = empty(a:rev) ? 'HEAD' : a:rev
|
||||
let commit1 = empty(commit1) ? 'HEAD' : commit1
|
||||
let commit2 = empty(commit2) ? 'HEAD' : commit2
|
||||
let hash0 = gina#core#treeish#sha1(a:git, commit0)
|
||||
let hash1 = gina#core#treeish#sha1(a:git, commit1)
|
||||
let hash2 = gina#core#treeish#sha1(a:git, commit2)
|
||||
return {
|
||||
\ 'commit0': gina#core#treeish#resolve(a:git, commit0, 1),
|
||||
\ 'commit1': gina#core#treeish#resolve(a:git, commit1, 1),
|
||||
\ 'commit2': gina#core#treeish#resolve(a:git, commit2, 1),
|
||||
\ 'hash0': hash0,
|
||||
\ 'hash1': hash1,
|
||||
\ 'hash2': hash2,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:get_remote_url(git, commit1, commit2) abort
|
||||
let config = gina#core#repo#config(a:git)
|
||||
" Find a corresponding 'remote'
|
||||
let candidates = [a:commit1, a:commit2, 'master']
|
||||
for candidate in candidates
|
||||
let remote_name = get(config, printf('branch.%s.remote', candidate), '')
|
||||
if !empty(remote_name) && remote_name !=# '.'
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
let remote_name = empty(remote_name) ? 'origin' : remote_name
|
||||
let result = gina#process#call(a:git, ['remote', 'get-url', remote_name])
|
||||
if result.status
|
||||
throw gina#process#errormsg(result)
|
||||
endif
|
||||
return result.content[0]
|
||||
endfunction
|
||||
|
||||
function! s:build_base_url(remote_url, scheme) abort
|
||||
for [domain, info] in items(g:gina#command#browse#translation_patterns)
|
||||
for pattern in info[0]
|
||||
let pattern = substitute(pattern, '\C' . '%domain', domain, 'g')
|
||||
if a:remote_url =~# pattern
|
||||
let repl = get(info[1], a:scheme, a:remote_url)
|
||||
let repl = escape(repl, '&')
|
||||
return substitute(a:remote_url, '\C' . pattern, repl, 'g')
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'translation_patterns': {
|
||||
\ 'github\.com': [
|
||||
\ [
|
||||
\ '\vhttps?://(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vgit://(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vgit\@(%domain):(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vssh://git\@(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ ], {
|
||||
\ '_': 'https://\1/\2/\3/blob/%r0/%pt%{#L|}ls%{-L|}le',
|
||||
\ 'root': 'https://\1/\2/\3/tree/%r0/',
|
||||
\ 'blame': 'https://\1/\2/\3/blame/%r0/%pt%{#L|}ls%{-L|}le',
|
||||
\ 'compare': 'https://\1/\2/\3/compare/%h1...%h2',
|
||||
\ },
|
||||
\ ],
|
||||
\ 'gitlab\.com': [
|
||||
\ [
|
||||
\ '\vhttps?://(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vgit://(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vgit\@(%domain):(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vssh://git\@(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ ], {
|
||||
\ '_': 'https://\1/\2/\3/blob/%r0/%pt%{#L|}ls%{-L|}le',
|
||||
\ 'root': 'https://\1/\2/\3/tree/%r0/',
|
||||
\ 'blame': 'https://\1/\2/\3/blame/%r0/%pt%{#L|}ls%{-L|}le',
|
||||
\ 'compare': 'https://\1/\2/\3/compare/%h1...%h2',
|
||||
\ },
|
||||
\ ],
|
||||
\ 'bitbucket\.org': [
|
||||
\ [
|
||||
\ '\vhttps?://(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vgit://(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vgit\@(%domain):(.{-})/(.{-})%(\.git)?$',
|
||||
\ '\vssh://git\@(%domain)/(.{-})/(.{-})%(\.git)?$',
|
||||
\ ], {
|
||||
\ '_': 'https://\1/\2/\3/src/%r0/%pt%{#cl-|}ls',
|
||||
\ 'root': 'https://\1/\2/\3/commits/%r0',
|
||||
\ 'blame': 'https://\1/\2/\3/annotate/%r0/%pt',
|
||||
\ 'compare': 'https://\1/\2/\3/diff/%pt?diff1=%h1&diff2=%h2',
|
||||
\ },
|
||||
\ ],
|
||||
\ '.*\.visualstudio\.com': [
|
||||
\ [
|
||||
\ '\vhttps?://(%domain)/(.{-})/_git/(.{-})$',
|
||||
\ ], {
|
||||
\ '_': 'https://\1/\2/_git/\3/?path=%pt&version=GB%r0%{&line=|}ls%{&lineEnd=|}le',
|
||||
\ 'root': 'https://\1/\2/_git/\3/?version=GB%r0',
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\})
|
56
bundle/gina.vim/autoload/gina/command/cd.vim
Normal file
56
bundle/gina.vim/autoload/gina/command/cd.vim
Normal file
@ -0,0 +1,56 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#cd#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#cd#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#directory(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--local',
|
||||
\ 'Use "lcd" command instead of "cd" command.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.local = args.get('--local')
|
||||
call gina#core#args#extend_path(a:git, args, args.pop(1, v:null))
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let path = gina#util#get(args.params, 'path', '')
|
||||
let abspath = gina#core#repo#abspath(git, path)
|
||||
let command = args.params.local ? 'lcd' : 'cd'
|
||||
execute command gina#util#fnameescape(s:Path.realpath(abspath))
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
166
bundle/gina.vim/autoload/gina/command/changes.vim
Normal file
166
bundle/gina.vim/autoload/gina/command/changes.vim
Normal file
@ -0,0 +1,166 @@
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#changes#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'rev': args.params.rev,
|
||||
\ 'params': [
|
||||
\ args.params.cached ? 'cached' : '',
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#changes#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:cmdline =~# '\s--\s'
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#range#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for a buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cached',
|
||||
\ 'Compare changes to the index instead of the working tree.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.cached = args.get('--cached')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
let args.params.rev = args.get(1, gina#core#buffer#param('%', 'rev'))
|
||||
|
||||
" Use {rev}^..{rev} instead
|
||||
" https://github.com/lambdalisue/gina.vim/issues/147
|
||||
if !empty(args.params.rev) && args.params.rev !~# '^.\{-}\.\.\.\?.*$'
|
||||
let args.params.rev = printf(
|
||||
\ '%s^..%s',
|
||||
\ args.params.rev,
|
||||
\ args.params.rev,
|
||||
\)
|
||||
endif
|
||||
|
||||
" NOTE:
|
||||
" --stat is more human friendly but --stat with long filename truncate the
|
||||
" filename so could not be used.
|
||||
call args.set('--numstat', 1)
|
||||
call args.set(0, 'diff')
|
||||
call args.set(1, args.params.rev)
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'))
|
||||
|
||||
augroup gina_command_changes_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-changes
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let rev = args.params.rev
|
||||
let residual = args.residual()
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val, rev, residual)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record, rev, residual) abort
|
||||
let m = matchlist(
|
||||
\ a:record,
|
||||
\ '^\(\d\+\)\s\+\(\d\+\)\s\+\(.\+\)$'
|
||||
\)
|
||||
return empty(m) ? {} : {
|
||||
\ 'word': a:record,
|
||||
\ 'added': m[1],
|
||||
\ 'removed': m[2],
|
||||
\ 'path': m[3],
|
||||
\ 'rev': a:rev,
|
||||
\ 'residual': a:residual,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
196
bundle/gina.vim/autoload/gina/command/chaperon.vim
Normal file
196
bundle/gina.vim/autoload/gina/command/chaperon.vim
Normal file
@ -0,0 +1,196 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
let s:WORKTREE = '@@'
|
||||
let s:REGION_PATTERN = printf('%s.\{-}%s\r\?\n\?',
|
||||
\ printf('%s[^\n]\{-}\%%(\n\|$\)', repeat('<', 7)),
|
||||
\ printf('%s[^\n]\{-}\%%(\n\|$\)', repeat('>', 7))
|
||||
\)
|
||||
|
||||
|
||||
function! gina#command#chaperon#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#chaperon#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#conflicted(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group1=',
|
||||
\ 'A window group name used for a LOCAL (our) buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group2=',
|
||||
\ 'A window group name used for a MERGE (working tree) buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group3=',
|
||||
\ 'A window group name used for a REMOTE (theirs) buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line=',
|
||||
\ 'An initial line number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--col=',
|
||||
\ 'An initial column number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--diffoff',
|
||||
\ 'Call diffoff! prior to open buffers.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.groups = [
|
||||
\ args.pop('--group1', ''),
|
||||
\ args.pop('--group2', ''),
|
||||
\ args.pop('--group3', ''),
|
||||
\]
|
||||
let args.params.opener = args.pop('--opener', 'tabnew')
|
||||
let args.params.diffoff = args.pop('--diffoff')
|
||||
call gina#core#args#extend_path(a:git, args, args.pop(1))
|
||||
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
||||
call gina#core#args#extend_col(a:git, args, args.pop('--col'))
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let mods = gina#util#contain_direction(a:mods)
|
||||
\ ? 'keepalt ' . a:mods
|
||||
\ : join(['keepalt', 'rightbelow', a:mods])
|
||||
let opener1 = args.params.opener
|
||||
let opener2 = empty(matchstr(&diffopt, 'vertical'))
|
||||
\ ? 'split'
|
||||
\ : 'vsplit'
|
||||
|
||||
if !empty(args.params.diffoff)
|
||||
diffoff!
|
||||
endif
|
||||
|
||||
call s:open(0, mods, opener1, ':2', args.params)
|
||||
let bufnr1 = bufnr('%')
|
||||
|
||||
call s:open(1, mods, opener2, s:WORKTREE, args.params)
|
||||
let bufnr2 = bufnr('%')
|
||||
|
||||
call s:open(2, mods, opener2, ':3', args.params)
|
||||
let bufnr3 = bufnr('%')
|
||||
|
||||
" :3 Theirs (REMOTE)
|
||||
call gina#util#diffthis()
|
||||
call s:define_plug_mapping('diffput', bufnr2)
|
||||
if g:gina#command#chaperon#use_default_mappings
|
||||
nmap dp <Plug>(gina-diffput)
|
||||
endif
|
||||
|
||||
" :2 Ours (Local)
|
||||
execute printf('%dwincmd w', bufwinnr(bufnr1))
|
||||
call gina#util#diffthis()
|
||||
call s:define_plug_mapping('diffput', bufnr2)
|
||||
if g:gina#command#chaperon#use_default_mappings
|
||||
nmap dp <Plug>(gina-diffput)
|
||||
endif
|
||||
|
||||
" WORKTREE
|
||||
execute printf('%dwincmd w', bufwinnr(bufnr2))
|
||||
call gina#util#diffthis()
|
||||
call s:define_plug_mapping('diffget', bufnr1, '-l')
|
||||
call s:define_plug_mapping('diffget', bufnr3, '-r')
|
||||
if g:gina#command#chaperon#use_default_mappings
|
||||
nmap dol <Plug>(gina-diffget-l)
|
||||
nmap dor <Plug>(gina-diffget-r)
|
||||
endif
|
||||
|
||||
if !&l:modified
|
||||
let content = s:strip_conflict(getline(1, '$'))
|
||||
silent keepjumps %delete _
|
||||
call setline(1, content)
|
||||
setlocal modified
|
||||
endif
|
||||
|
||||
call gina#util#diffupdate()
|
||||
normal! zm
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
function! s:define_plug_mapping(command, bufnr, ...) abort
|
||||
let suffix = a:0 ? a:1 : ''
|
||||
let lhs = printf('<Plug>(gina-%s%s)', a:command, suffix)
|
||||
let rhs = printf(':<C-u>%s %d<CR>:diffupdate<CR>', a:command, a:bufnr)
|
||||
call gina#util#map(lhs, rhs, {
|
||||
\ 'mode': 'n',
|
||||
\ 'noremap': 1,
|
||||
\ 'silent': 1,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! s:open(n, mods, opener, commit, params) abort
|
||||
if a:commit ==# s:WORKTREE
|
||||
execute printf(
|
||||
\ '%s Gina edit %s %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(a:params.col, '--col='),
|
||||
\ gina#util#shellescape(a:params.path),
|
||||
\)
|
||||
else
|
||||
let treeish = gina#core#treeish#build(a:commit, a:params.path)
|
||||
execute printf(
|
||||
\ '%s Gina show %s %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(a:params.col, '--col='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:strip_conflict(content) abort
|
||||
let ff = &fileformat
|
||||
let newline = ff ==# 'unix' ? "\n" : ff ==# 'dos' ? "\r\n" : "\r"
|
||||
let text = s:String.join_posix_lines(a:content, "\n")
|
||||
let text = substitute(text, s:REGION_PATTERN, '', 'g')
|
||||
return s:String.split_posix_text(text, newline)
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
524
bundle/gina.vim/autoload/gina/command/commit.vim
Normal file
524
bundle/gina.vim/autoload/gina/command/commit.vim
Normal file
@ -0,0 +1,524 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:Git = vital#gina#import('Git')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
let s:messages = {}
|
||||
|
||||
|
||||
function! gina#command#commit#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
|
||||
if s:is_raw_command(a:args)
|
||||
" Remove non git options
|
||||
let args = a:args.clone()
|
||||
call args.pop('--group')
|
||||
call args.pop('--opener')
|
||||
" Call raw git command
|
||||
return gina#command#_raw#call(a:range, args, a:mods)
|
||||
endif
|
||||
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#commit#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead =~# '^\%(-C\|--reuse-message=\|-c\|--reedit-message=\|--fixup=\|--squash=\)'
|
||||
let leading = matchstr(
|
||||
\ a:arglead,
|
||||
\ '^\%(-C\|--reuse-message=\|-c\|--reedit-message\|--fixup=\|--squash=\)'
|
||||
\)
|
||||
let candidates = gina#complete#commit#any(
|
||||
\ matchstr(a:arglead, '^' . leading . '\zs.*'),
|
||||
\ a:cmdline, a:cursorpos,
|
||||
\)
|
||||
return map(candidates, 'leading . v:val')
|
||||
elseif a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif a:cmdline !~# '\s--\s'
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#tracked(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for a buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--restore',
|
||||
\ 'Restore the previous buffer when the window is closed.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--all',
|
||||
\ 'Commit all changed files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-C|--reuse-message=',
|
||||
\ 'Reuse message from specified commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-c|--reedit-message=',
|
||||
\ 'Reuse and edit message from specified commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--fixup=',
|
||||
\ 'Use autosquash formatted message to fixup specified commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--squash=',
|
||||
\ 'Use autosquash formatted message to squash specified commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--reset-author',
|
||||
\ 'The commit is authored by me now (used with -C/-c/--amend)',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-F|--file=',
|
||||
\ 'Read message from file',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--author=',
|
||||
\ 'Override the commit author',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--date=',
|
||||
\ 'Override the author date used in the commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-m|--message=',
|
||||
\ 'Use the given message as the commit message',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-t|--template=',
|
||||
\ 'Read message from file and use it as a template',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-s|--signoff',
|
||||
\ 'Add Signed-off-by:',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--allow-empty',
|
||||
\ 'Allow empty commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--allow-empty-message',
|
||||
\ 'Allow empty commit message',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cleanup=',
|
||||
\ 'How to strip spaces and #comments from message',
|
||||
\ ['strip', 'whitespace', 'verbatim', 'scissors', 'default'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '-e|--edit',
|
||||
\ 'Force edit of commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-edit',
|
||||
\ 'Use the selected commit message without editing',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--amend',
|
||||
\ 'Replace the tip of the current branch by creating a new commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-i|--include',
|
||||
\ 'Add specified files to index for commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-o|--only',
|
||||
\ 'Commit only specified files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-u|--untracked-files=',
|
||||
\ 'Show untracked files, optional modes: all, normal, no (Default: all)',
|
||||
\ ['no', 'normal', 'all'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '-v|--verbose',
|
||||
\ 'Show unified diff between the HEAD commit and what would be committed',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-q|--quiet',
|
||||
\ 'Suppress commit summary message',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--dry-run',
|
||||
\ 'Do not create a commit, but show a list of paths that are to be committed',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--status',
|
||||
\ 'Include status in commit message template',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-status',
|
||||
\ 'Do not include status in commit message template',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-S|--gpg-sign',
|
||||
\ 'GPG sign commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-gpg-sign',
|
||||
\ 'Do not GPG sign commit',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.restore = args.pop(
|
||||
\ '--restore',
|
||||
\ empty(args.params.opener) || args.params.opener ==# 'edit',
|
||||
\)
|
||||
let args.params.amend = args.get('--amend')
|
||||
call gina#core#args#extend_diff(a:git, args, '')
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:is_raw_command(args) abort
|
||||
if a:args.get('-e|--edit')
|
||||
return 0
|
||||
elseif a:args.get('--no-edit')
|
||||
return 1
|
||||
elseif a:args.get('--dry-run')
|
||||
return 1
|
||||
elseif !empty(a:args.get('-C|--reuse-message', ''))
|
||||
return 1
|
||||
elseif !empty(a:args.get('-c|--reedit-message', ''))
|
||||
return 0
|
||||
elseif a:args.get('-F|--file')
|
||||
return 1
|
||||
elseif !empty(a:args.get('-m|--message', ''))
|
||||
return 1
|
||||
elseif a:args.get('-t|--template')
|
||||
return 0
|
||||
elseif !empty(a:args.get('--fixup', ''))
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
silent! unlet b:gina_QuitPre
|
||||
silent! unlet b:gina_BufWriteCmd
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal nobuflisted
|
||||
setlocal buftype=acwrite
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal modifiable
|
||||
|
||||
augroup gina_command_commit_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer> call s:BufReadCmd()
|
||||
autocmd BufWriteCmd <buffer> call s:BufWriteCmd()
|
||||
autocmd QuitPre <buffer> call s:QuitPre()
|
||||
autocmd WinLeave <buffer> call s:WinLeave()
|
||||
autocmd WinEnter <buffer> silent! unlet! b:gina_QuitPre
|
||||
augroup END
|
||||
|
||||
nnoremap <silent><buffer> <Plug>(gina-commit-amend)
|
||||
\ :<C-u>call <SID>toggle_amend()<CR>
|
||||
|
||||
if a:args.get('-v|--verbose')
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump)
|
||||
\ :<C-u>call gina#core#diffjump#jump()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump-split)
|
||||
\ :<C-u>call gina#core#diffjump#jump('split')<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump-vsplit)
|
||||
\ :<C-u>call gina#core#diffjump#jump('vsplit')<CR>
|
||||
if g:gina#command#commit#use_default_mappings
|
||||
nmap <buffer> <CR> <Plug>(gina-diff-jump)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
if v:cmdbang
|
||||
let content = s:get_commitmsg_template(git, args)
|
||||
else
|
||||
let content = s:get_commitmsg(git, args)
|
||||
endif
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#core#writer#replace('%', 0, -1, content)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
setlocal filetype=gina-commit
|
||||
endfunction
|
||||
|
||||
function! s:BufWriteCmd() abort
|
||||
let b:gina_BufWriteCmd = 1
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
call gina#core#revelator#call(
|
||||
\ function('s:set_commitmsg'),
|
||||
\ [git, args, getline(1, '$')]
|
||||
\)
|
||||
setlocal nomodified
|
||||
endfunction
|
||||
|
||||
function! s:QuitPre() abort
|
||||
" Restore the previous buffer if 'restore' is specified
|
||||
let args = gina#core#meta#get('args', v:null)
|
||||
if args isnot# v:null && get(args.params, 'restore')
|
||||
let win_id = win_getid()
|
||||
if !empty(bufname('#')) && bufnr('#') isnot# -1
|
||||
silent keepalt keepjumps 1split #
|
||||
else
|
||||
silent keepalt keepjumps 1new
|
||||
endif
|
||||
call win_gotoid(win_id)
|
||||
endif
|
||||
" Do not perform commit when user hit :q!
|
||||
if histget('cmd', -1) !~# '^q\%[uit]!'
|
||||
let b:gina_QuitPre = 1
|
||||
" If this is a last window, open a new window to prevent quit
|
||||
if tabpagenr('$') == 1 && winnr('$') == 1
|
||||
let win_id = win_getid()
|
||||
silent keepalt keepjumps 1new
|
||||
call win_gotoid(win_id)
|
||||
endif
|
||||
endif
|
||||
" Clear the flag set by :w but keep it when user hit :wq
|
||||
if histget('cmd', -1) !~# '^\(wq\|x\%[it]\|exi\%[t]\)'
|
||||
silent! unlet b:gina_BufWriteCmd
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" NOTE:
|
||||
" :w -- BufWriteCmd
|
||||
" <C-w>p -- WinLeave
|
||||
" :wq -- QuitPre -> BufWriteCmd -> WinLeave (-8.2.2899)
|
||||
" BufWriteCmd -> QuitPre -> WinLeave (8.2.2900-)
|
||||
" :q -- QuitPre -> WinLeave
|
||||
function! s:WinLeave() abort
|
||||
if exists('b:gina_QuitPre')
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
if exists('b:gina_BufWriteCmd')
|
||||
" User execute 'wq' so do not confirm
|
||||
call gina#core#revelator#call(
|
||||
\ function('s:commit_commitmsg'),
|
||||
\ [git, args]
|
||||
\)
|
||||
else
|
||||
" User execute 'q' so confirm if commit message is written
|
||||
if !empty(s:get_cached_commitmsg(git, args))
|
||||
call gina#core#revelator#call(
|
||||
\ function('s:commit_commitmsg_confirm'),
|
||||
\ [git, args]
|
||||
\)
|
||||
else
|
||||
redraw | echo ''
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:toggle_amend() abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let args = args.clone()
|
||||
if args.get('--amend')
|
||||
call args.pop('--amend')
|
||||
else
|
||||
call args.set('--amend', 1)
|
||||
endif
|
||||
call gina#core#meta#set('args', args)
|
||||
edit
|
||||
endfunction
|
||||
|
||||
function! s:get_cleanup(git, args) abort
|
||||
let config = gina#core#repo#config(a:git)
|
||||
if a:args.get('--cleanup')
|
||||
return a:args.get('--cleanup')
|
||||
endif
|
||||
return get(config, 'commit.cleanup', 'strip')
|
||||
endfunction
|
||||
|
||||
function! s:get_commitmsg(git, args) abort
|
||||
let content = s:get_cached_commitmsg(a:git, a:args)
|
||||
if empty(content)
|
||||
return s:get_commitmsg_template(a:git, a:args)
|
||||
else
|
||||
call gina#core#console#debug('Use a cached commit message:')
|
||||
return s:get_commitmsg_cleanedup(a:git, a:args, content)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_commitmsg_template(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let filename = s:Git.resolve(a:git, 'COMMIT_EDITMSG')
|
||||
let previous_content = filereadable(filename)
|
||||
\ ? readfile(filename)
|
||||
\ : []
|
||||
try
|
||||
" Build a new commit message template
|
||||
call args.pop('--no-edit')
|
||||
call args.set('-e|--edit', 1)
|
||||
let result = gina#process#call(a:git, args)
|
||||
if !result.status || (
|
||||
\ match(result.stderr, 'error: unable to start editor') is# -1 &&
|
||||
\ match(result.stderr, 'error: There was a problem with the editor') is# -1
|
||||
\)
|
||||
" While git is executed with '-c core.editor=false', the command above
|
||||
" should fail after that create a COMMIT_EDITMSG for the current
|
||||
" situation
|
||||
throw gina#process#errormsg(result)
|
||||
endif
|
||||
" Get a built commitmsg template
|
||||
return readfile(filename)
|
||||
finally
|
||||
" Restore the content
|
||||
call writefile(previous_content, filename)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
" Note:
|
||||
" Commit the cached messate temporary to build a correct COMMIT_EDITMSG
|
||||
" This hacky implementation is required due to the lack of cleanup command.
|
||||
" https://github.com/lambdalisue/gina.vim/issues/37#issuecomment-281661605
|
||||
" Note:
|
||||
" It is not possible to remove diff content when user does
|
||||
" 1. Gina commit --verbose
|
||||
" 2. Save content
|
||||
" 3. Gina commit
|
||||
" 4. The diff part is cached so shown and no chance to remove that
|
||||
" This is a bit anoyying but I don't have any way to remove that so I just
|
||||
" ended up. PRs for this issue is welcome.
|
||||
" https://github.com/lambdalisue/gina.vim/issues/37#issuecomment-281687325
|
||||
function! s:get_commitmsg_cleanedup(git, args, content) abort
|
||||
let args = a:args.clone()
|
||||
let filename = s:Git.resolve(a:git, 'COMMIT_EDITMSG')
|
||||
let previous_content = readfile(filename)
|
||||
let tempfile = tempname()
|
||||
try
|
||||
call writefile(a:content, tempfile)
|
||||
call args.set('--cleanup', s:get_cleanup(a:git, args))
|
||||
call args.set('-F|--file', tempfile)
|
||||
call args.set('--no-edit', 1)
|
||||
call args.set('--allow-empty', 1)
|
||||
call args.set('--allow-empty-message', 1)
|
||||
call args.pop('-C|--reuse-message')
|
||||
call args.pop('-m|--message')
|
||||
call args.pop('-e|--edit')
|
||||
call gina#process#call_or_fail(a:git, args)
|
||||
" Reset the temporary commit and remove all logs
|
||||
call gina#process#call_or_fail(a:git, ['reset', '--soft', 'HEAD@{1}'])
|
||||
call gina#process#call_or_fail(a:git, ['reflog', 'delete', 'HEAD@{0}'])
|
||||
call gina#process#call_or_fail(a:git, ['reflog', 'delete', 'HEAD@{0}'])
|
||||
" Get entire content of commitmsg
|
||||
return readfile(filename)
|
||||
finally
|
||||
call delete(tempfile)
|
||||
call writefile(previous_content, filename)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:set_commitmsg(git, args, content) abort
|
||||
call s:set_cached_commitmsg(a:git, a:args, a:content)
|
||||
endfunction
|
||||
|
||||
function! s:commit_commitmsg(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let content = s:get_cached_commitmsg(a:git, args)
|
||||
let tempfile = tempname()
|
||||
try
|
||||
call writefile(content, tempfile)
|
||||
call args.set('--no-edit', 1)
|
||||
call args.set('--cleanup', s:get_cleanup(a:git, args))
|
||||
call args.set('-F|--file', tempfile)
|
||||
call args.pop('-C|--reuse-message')
|
||||
call args.pop('-m|--message')
|
||||
call args.pop('-e|--edit')
|
||||
let result = gina#process#call(a:git, args)
|
||||
call gina#process#inform(result)
|
||||
call s:remove_cached_commitmsg(a:git)
|
||||
call gina#core#emitter#emit('command:called:commit')
|
||||
bwipeout
|
||||
finally
|
||||
call delete(tempfile)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:commit_commitmsg_confirm(git, args) abort
|
||||
if gina#core#console#confirm('Do you want to commit changes?', 'y')
|
||||
call s:commit_commitmsg(a:git, a:args)
|
||||
else
|
||||
redraw | echo ''
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_cached_commitmsg(git, args) abort
|
||||
let wname = a:git.worktree
|
||||
let cname = a:args.get('--amend') ? 'amend' : '_'
|
||||
let s:messages[wname] = get(s:messages, wname, {})
|
||||
return get(s:messages[wname], cname, [])
|
||||
endfunction
|
||||
|
||||
function! s:set_cached_commitmsg(git, args, commitmsg) abort
|
||||
let wname = a:git.worktree
|
||||
let cname = a:args.get('--amend') ? 'amend' : '_'
|
||||
let s:messages[wname] = get(s:messages, wname, {})
|
||||
let s:messages[wname][cname] = a:commitmsg
|
||||
endfunction
|
||||
|
||||
function! s:remove_cached_commitmsg(git) abort
|
||||
let cname = a:git.worktree
|
||||
let s:messages[cname] = {}
|
||||
endfunction
|
||||
|
||||
|
||||
" Event ----------------------------------------------------------------------
|
||||
function! s:on_command_called_commit(...) abort
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
endfunction
|
||||
|
||||
if !exists('s:subscribed')
|
||||
let s:subscribed = 1
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'command:called:commit',
|
||||
\ function('s:on_command_called_commit')
|
||||
\)
|
||||
endif
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
150
bundle/gina.vim/autoload/gina/command/compare.vim
Normal file
150
bundle/gina.vim/autoload/gina/command/compare.vim
Normal file
@ -0,0 +1,150 @@
|
||||
let s:Opener = vital#gina#import('Vim.Buffer.Opener')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#compare#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#compare#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-'
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#common#treeish(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group1=',
|
||||
\ 'A window group name used for the 1st buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group2=',
|
||||
\ 'A window group name used for the 2nd buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line',
|
||||
\ 'An initial line number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--col',
|
||||
\ 'An initial column number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cached',
|
||||
\ 'Compare to the index rather than the working tree',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-R',
|
||||
\ 'Reverse the buffer order',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--diffoff',
|
||||
\ 'Call diffoff! prior to open buffers.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.groups = [
|
||||
\ args.pop('--group1', ''),
|
||||
\ args.pop('--group2', ''),
|
||||
\]
|
||||
let args.params.opener = args.pop('--opener', 'tabnew')
|
||||
let args.params.cached = args.get('--cached')
|
||||
let args.params.R = args.get('-R')
|
||||
let args.params.diffoff = args.pop('--diffoff')
|
||||
|
||||
call gina#core#args#extend_treeish(a:git, args, args.pop(1, ':'))
|
||||
call gina#core#args#extend_diff(a:git, args, args.params.rev)
|
||||
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
||||
call gina#core#args#extend_col(a:git, args, args.pop('--col'))
|
||||
if empty(args.params.path)
|
||||
throw gina#core#revelator#warning(printf(
|
||||
\ 'No filename is specified. Did you mean "Gina compare %s:"?',
|
||||
\ args.params.rev,
|
||||
\))
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let mods = gina#util#contain_direction(a:mods)
|
||||
\ ? 'keepalt ' . a:mods
|
||||
\ : join(['keepalt', 'rightbelow', a:mods])
|
||||
|
||||
if !empty(args.params.diffoff)
|
||||
diffoff!
|
||||
endif
|
||||
|
||||
let opener1 = args.params.opener
|
||||
let opener2 = empty(matchstr(&diffopt, 'vertical'))
|
||||
\ ? 'split'
|
||||
\ : 'vsplit'
|
||||
call s:open(0, mods, opener1, args.params.rev1, args.params)
|
||||
call gina#util#diffthis()
|
||||
|
||||
call s:open(1, mods, opener2, args.params.rev2, args.params)
|
||||
call gina#util#diffthis()
|
||||
|
||||
call gina#util#diffupdate()
|
||||
normal! zm
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
function! s:open(n, mods, opener, rev, params) abort
|
||||
if s:Opener.is_preview_opener(a:opener)
|
||||
throw gina#core#revelator#warning(printf(
|
||||
\ 'An opener "%s" is not allowed.',
|
||||
\ a:opener,
|
||||
\))
|
||||
endif
|
||||
if gina#core#args#is_worktree(a:rev)
|
||||
execute printf(
|
||||
\ '%s Gina edit %s %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(a:params.col, '--col='),
|
||||
\ gina#util#shellescape(a:params.path),
|
||||
\)
|
||||
else
|
||||
let treeish = gina#core#treeish#build(a:rev, a:params.path)
|
||||
execute printf(
|
||||
\ '%s Gina show %s %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(a:params.col, '--col='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endif
|
||||
endfunction
|
407
bundle/gina.vim/autoload/gina/command/diff.vim
Normal file
407
bundle/gina.vim/autoload/gina/command/diff.vim
Normal file
@ -0,0 +1,407 @@
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#diff#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'treeish': args.params.treeish,
|
||||
\ 'params': [
|
||||
\ args.params.cached ? 'cached' : '',
|
||||
\ args.params.R ? 'R' : '',
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\ 'noautocmd': !empty(args.params.path),
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#diff#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead =~# '^--opener='
|
||||
return gina#complete#common#opener(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif a:arglead =~# '^\%(--diff-algorithm=\)'
|
||||
let leading = matchstr(a:arglead, '^--diff-algorithm=')
|
||||
return gina#util#filter(a:arglead, map(
|
||||
\ ['patience', 'minimal', 'histogram', 'myers'],
|
||||
\ 'leading . v:val'
|
||||
\))
|
||||
elseif a:arglead =~# '^\%(--dirstat=\)'
|
||||
let leading = matchstr(a:arglead, '^--dirstat=')
|
||||
let dirstat = matchstr(a:arglead, '^--dirstat=\zs\%([^,]\+,\)*[^,]*')
|
||||
let candidates = filter(
|
||||
\ ['changes', 'lines', 'files', 'cumulative'],
|
||||
\ 'dirstat !~# ''\<'' . v:val . ''\>''',
|
||||
\)
|
||||
return gina#util#filter(a:arglead, map(
|
||||
\ candidates, 'leading . dirstat . v:val'
|
||||
\))
|
||||
elseif a:arglead =~# '^\%(--submodule=\)'
|
||||
let leading = matchstr(a:arglead, '^--submodule=')
|
||||
return gina#util#filter(a:arglead, map(
|
||||
\ ['short', 'log', 'diff'],
|
||||
\ 'leading . v:val'
|
||||
\))
|
||||
elseif a:arglead =~# '^\%(--diff-filter=\)'
|
||||
let leading = matchstr(a:arglead, '^--diff-filter=[ACDMRTUXB]*')
|
||||
return gina#util#filter(a:arglead, map(
|
||||
\ split('ACDMRTUXB', '\zs'),
|
||||
\ 'leading . v:val'
|
||||
\))
|
||||
elseif a:arglead =~# '^\%(--ignore-submodules=\)'
|
||||
let leading = matchstr(a:arglead, '^--ignore-submodules=')
|
||||
return gina#util#filter(a:arglead, map(
|
||||
\ ['none', 'untracked', 'dirty', 'all'],
|
||||
\ 'leading . v:val'
|
||||
\))
|
||||
elseif a:cmdline =~# '\s--\s'
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif a:arglead[0] ==# '-'
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#common#treeish(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cached',
|
||||
\ 'Compare to the index rather than the working tree',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-U|--unified=',
|
||||
\ 'Generate diffs with <n> lines of context',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--raw',
|
||||
\ 'Generate the diff in raw format',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--patch-with-raw',
|
||||
\ 'Synonym for -p --raw',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--minimal',
|
||||
\ 'Spend extra time to make sure the smallest possible diff is produced',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--patience',
|
||||
\ 'Generate a diff using the "patience diff" algorithm',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--histogram',
|
||||
\ 'Generate a diff using the "histogram diff" algorithm',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--diff-algorithm',
|
||||
\ 'Choose a diff algorithm',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--stat',
|
||||
\ 'Generate a diffstat',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--numstat',
|
||||
\ 'Similar to --stat, but with more machine friendly output',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--shortstat',
|
||||
\ 'Output only the last line of the --stat format',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--dirstat',
|
||||
\ 'Output the dirstat numbers',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--summary',
|
||||
\ 'Output a condensed summary of extended header information',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--patch-with-stat',
|
||||
\ 'Synonym for -p --stat',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--name-only',
|
||||
\ 'Show only names of changed files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--name-status',
|
||||
\ 'Show only names and status of changed files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--submodule',
|
||||
\ 'Specify how differences in submodules are shown',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--submodule',
|
||||
\ 'Specify how differences in submodules are shown',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-renames',
|
||||
\ 'Turn off rename detection',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--check',
|
||||
\ 'Warn if changes introduce conflict markers or whitespace errors',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--full-index',
|
||||
\ 'Instead of the first handful of characters,show the full blob',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--binary',
|
||||
\ 'In addition to --full-index, output a binary diff',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--abbrev',
|
||||
\ 'Show only a particular prefix hexadecimal object name',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-B|--break-rewrites',
|
||||
\ 'Break complete rewrite changes into pair of delete and create',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-M|--find-renames',
|
||||
\ 'Detect renames. -M<n> to specify the threshold.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-C|--find-copies',
|
||||
\ 'Detect copies as well as renames. -C<n> to specify the threshold',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--find-copies-harder',
|
||||
\ 'Detect copies more harder than -C/--find-copies',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-D|--irreversible-delete',
|
||||
\ 'Omit the preimage for deletes',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-l',
|
||||
\ 'Specify the threshold of -M or -C',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--diff-filter=', join([
|
||||
\ 'Specify the Select only files that are Added(A), Copied(C), ',
|
||||
\ 'Deleted (D), Modified (M), Renamed (R), ',
|
||||
\ 'have their type changed (T), are Unmerged (U), ',
|
||||
\ 'are Unknown (X), or have had their pairing Broken (B)',
|
||||
\ ])
|
||||
\)
|
||||
call options.define(
|
||||
\ '-S', join([
|
||||
\ 'Look for differences that change the number of occurrences of ',
|
||||
\ 'the specified string in a file.'
|
||||
\ ])
|
||||
\)
|
||||
call options.define(
|
||||
\ '-G', join([
|
||||
\ 'Look for differences whose patch text contains added/removed ',
|
||||
\ 'line that match <regex>'
|
||||
\ ])
|
||||
\)
|
||||
call options.define(
|
||||
\ '--pickaxe-all', join([
|
||||
\ 'Wnen -S or -G finds a change, show all the changes in that ',
|
||||
\ 'changeset, not just the files and contain the change'
|
||||
\ ])
|
||||
\)
|
||||
call options.define(
|
||||
\ '--pickaxe-regex', join([
|
||||
\ 'Treat the <string> given to -S as an extended POSIX regular ',
|
||||
\ 'expression to match'
|
||||
\ ])
|
||||
\)
|
||||
call options.define(
|
||||
\ '-O',
|
||||
\ 'Output the patch in the order specified in the <orderfile>',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-R',
|
||||
\ 'Swap two inputs',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--relative',
|
||||
\ 'Show pathnames relative to',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--text',
|
||||
\ 'Treat all files as text',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--ignore-space-at-eol',
|
||||
\ 'Ignore changes in whitespace at EOL',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-b|--ignore-space-change',
|
||||
\ 'Ignore changes in amount of whitespace',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-w|--ignore-all-space',
|
||||
\ 'Ignore whitespace when comparing lines',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--ignore-blank-lines',
|
||||
\ 'Ignore changes whose line are all blank',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--inter-hunk-context=',
|
||||
\ 'Show the context between diff hunks',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-W|--function-context',
|
||||
\ 'Show whole surrounding functions of changes',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--ext-diff',
|
||||
\ 'Allow an external diff helper to be executed',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-ext-diff',
|
||||
\ 'Disallow external diff drivers',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--textconv',
|
||||
\ 'Allow an external text conversion filters',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-textconv',
|
||||
\ 'Disallow an external text conversion filters',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--ignore-submodules',
|
||||
\ 'Ignore changes to submodules in the diff generation',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--src-prefix=',
|
||||
\ 'Show the given source prefix instead of "a/"',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--dst-prefix=',
|
||||
\ 'Show the given destination prefix instead of "a/"',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-prefix',
|
||||
\ 'Do not show any source or destination prefix',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line-prefix=',
|
||||
\ 'Prepend an additional prefix to every line of output',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.cached = args.get('--cached')
|
||||
let args.params.R = args.get('-R')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
|
||||
" Remove unsupported options
|
||||
call args.pop('-z')
|
||||
call args.pop('--color')
|
||||
call args.pop('--word-diff')
|
||||
call args.pop('--word-diff-regex')
|
||||
call args.pop('--color-words')
|
||||
call args.pop('--ws-error-highlight')
|
||||
|
||||
" Force --no-color
|
||||
call args.set('--no-color', 1)
|
||||
|
||||
call gina#core#args#extend_treeish(a:git, args, args.pop(1))
|
||||
call gina#core#args#extend_diff(a:git, args, args.params.rev)
|
||||
call args.set(1, args.params.rev)
|
||||
if args.params.path isnot# v:null
|
||||
call args.residual([args.params.path] + args.residual())
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal nomodeline
|
||||
setlocal buftype=nowrite
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
if a:args.params.partial
|
||||
setlocal bufhidden=wipe
|
||||
else
|
||||
setlocal bufhidden&
|
||||
endif
|
||||
|
||||
augroup gina_command_diff_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
autocmd BufWinEnter <buffer> call setbufvar(str2nr(expand('<abuf>')), '&buflisted', 1)
|
||||
autocmd BufWinLeave <buffer> call setbufvar(str2nr(expand('<abuf>')), '&buflisted', 0)
|
||||
augroup END
|
||||
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump)
|
||||
\ :<C-u>call gina#core#diffjump#jump()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump-split)
|
||||
\ :<C-u>call gina#core#diffjump#jump('split')<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump-vsplit)
|
||||
\ :<C-u>call gina#core#diffjump#jump('vsplit')<CR>
|
||||
if g:gina#command#diff#use_default_mappings
|
||||
nmap <buffer> <CR> <Plug>(gina-diff-jump)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=diff
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
77
bundle/gina.vim/autoload/gina/command/edit.vim
Normal file
77
bundle/gina.vim/autoload/gina/command/edit.vim
Normal file
@ -0,0 +1,77 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#edit#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#edit#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-'
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line',
|
||||
\ 'An initial line number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--col',
|
||||
\ 'An initial column number.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
call gina#core#args#extend_path(a:git, args, args.pop(1))
|
||||
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
||||
call gina#core#args#extend_col(a:git, args, args.pop('--col'))
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get()
|
||||
let args = s:build_args(git, a:args)
|
||||
let abspath = gina#core#repo#abspath(git, args.params.path)
|
||||
let bufname = s:Path.realpath(abspath)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'line': args.params.line,
|
||||
\ 'col': args.params.col,
|
||||
\})
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
321
bundle/gina.vim/autoload/gina/command/grep.vim
Normal file
321
bundle/gina.vim/autoload/gina/command/grep.vim
Normal file
@ -0,0 +1,321 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#grep#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'params': [
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#grep#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:cmdline =~# '\s--\s'
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif a:arglead[0] ==# '-' || !empty(args.get(2))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
function! gina#command#grep#parse_record(...) abort
|
||||
return call('s:parse_record', a:000)
|
||||
endfunction
|
||||
|
||||
function! gina#command#grep#_is_column_supported(version) abort
|
||||
return s:is_column_supported(a:version)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:is_column_supported(version) abort
|
||||
" https://github.com/git/git/blob/master/Documentation/RelNotes/2.19.0.txt#L18-L19
|
||||
return a:version =~# '\%(^[^012]\|^2\.[^01]\|^2\.19\)'
|
||||
endfunction
|
||||
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cached',
|
||||
\ 'Search in index instead of in the work tree',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-index',
|
||||
\ 'Find in contents not managed by git',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--untracked',
|
||||
\ 'Search in both tracked and untracked files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--exclude-standard',
|
||||
\ 'Ignore files specified via .gitignore',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-v|--invert-match',
|
||||
\ 'Show non-matching lines',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-i|--ignore-case',
|
||||
\ 'Case insensitive matching',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-w|--word-regexp',
|
||||
\ 'Match patterns only at word boundaries',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--text',
|
||||
\ 'Process binary files as text',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-I',
|
||||
\ 'Don''t match patterns in binary files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--textconv',
|
||||
\ 'Process binary files with textconv filters',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--max-depth=',
|
||||
\ 'Descend at most <depth> levels',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-E|--extended-regexp',
|
||||
\ 'Use extended POSIC regular expression',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-G|--basic-regexp',
|
||||
\ 'Use basic POSIX regular expression',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-F|--fixed-string',
|
||||
\ 'Interpret patterns as fixed strings',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-P|--perl-regexp',
|
||||
\ 'Use Perl-compatible regular expression',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--break',
|
||||
\ 'Print empty line between matches from different files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-C|--context=',
|
||||
\ 'Show <n> context lines before and after matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-B|--before-context=',
|
||||
\ 'Show <n> context lines before matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-A|--after-context=',
|
||||
\ 'Show <n> context lines after matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--threads=',
|
||||
\ 'Use <n> worker threads',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-p|--show-function',
|
||||
\ 'Show a line with the function name before matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-W|--function-context',
|
||||
\ 'Show the surrounding function',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-f',
|
||||
\ 'Read patterns from file',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-e',
|
||||
\ 'Match <pattern>',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--and|--or|--not',
|
||||
\ 'Combine patterns specified with -e',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--all-match',
|
||||
\ 'Show only matches from files that match all patterns',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
|
||||
" Ask pattern if no option has specified.
|
||||
if !s:is_pattern_given(args)
|
||||
let pattern = gina#core#console#ask('Pattern: ')
|
||||
if empty(pattern)
|
||||
throw gina#core#revelator#info('Cancel')
|
||||
endif
|
||||
call args.set('-e', pattern)
|
||||
endif
|
||||
|
||||
" Remove unsupported options
|
||||
call args.pop('-h')
|
||||
call args.pop('-H')
|
||||
call args.pop('-l|--files-with-matches')
|
||||
call args.pop('--name-only')
|
||||
call args.pop('-L|--files-without-match')
|
||||
call args.pop('-z|--null')
|
||||
call args.pop('-c|--count')
|
||||
call args.pop('--heading')
|
||||
|
||||
" Force required options
|
||||
if !args.has('--no-column') && s:is_column_supported(gina#core#git_version())
|
||||
call insert(args.raw, '--no-column', 1)
|
||||
endif
|
||||
if !args.has('--line-number')
|
||||
call insert(args.raw, '--line-number', 1)
|
||||
endif
|
||||
if !args.has('--full-name')
|
||||
call insert(args.raw, '--full-name', 1)
|
||||
endif
|
||||
if !args.has('--color')
|
||||
call insert(args.raw, '--color=always', 1)
|
||||
else
|
||||
call args.set('--color', 'always')
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'), {
|
||||
\ 'markable': 1,
|
||||
\})
|
||||
|
||||
augroup gina_command_grep_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-grep
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let residual = args.residual()
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val, residual)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record, residual) abort
|
||||
let record = s:String.remove_ansi_sequences(a:record)
|
||||
let m = matchlist(record, '^\([^:]\+:\)\?\(.*\):\(\d\+\):\(.*\)$')
|
||||
if empty(m)
|
||||
return {}
|
||||
endif
|
||||
let matched = matchstr(a:record, '\e\[1;31m\zs.\{-}\ze\e\[m')
|
||||
let line = str2nr(m[3])
|
||||
let col = stridx(m[4], matched) + 1
|
||||
let candidate = {
|
||||
\ 'word': m[4],
|
||||
\ 'abbr': a:record,
|
||||
\ 'line': line,
|
||||
\ 'col': col,
|
||||
\ 'path': m[2],
|
||||
\ 'rev': m[1],
|
||||
\ 'residual': a:residual,
|
||||
\}
|
||||
return candidate
|
||||
endfunction
|
||||
|
||||
function! s:is_pattern_given(args) abort
|
||||
let cmdline = join(a:args.raw[1:])
|
||||
let value_options = [
|
||||
\ '--max-depth',
|
||||
\ '-C', '--context',
|
||||
\ '-A', '--after-context',
|
||||
\ '-B', '--before-context',
|
||||
\ '--threads',
|
||||
\]
|
||||
for value_option in value_options
|
||||
let cmdline = substitute(
|
||||
\ cmdline,
|
||||
\ value_option . '\s\+\d\+',
|
||||
\ '', 'g',
|
||||
\)
|
||||
endfor
|
||||
return cmdline =~# '\<\%(-e\|-f\|[^-].\{-}\)\>'
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'send_to_quickfix': 1,
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
16
bundle/gina.vim/autoload/gina/command/lcd.vim
Normal file
16
bundle/gina.vim/autoload/gina/command/lcd.vim
Normal file
@ -0,0 +1,16 @@
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#lcd#call(range, args, mods) abort
|
||||
let args = a:args.clone()
|
||||
call args.set('--local', 1)
|
||||
call gina#command#cd#call(a:range, args, a:mods)
|
||||
endfunction
|
||||
|
||||
function! gina#command#lcd#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if empty(args.get(1))
|
||||
return gina#complete#filename#directory(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
152
bundle/gina.vim/autoload/gina/command/log.vim
Normal file
152
bundle/gina.vim/autoload/gina/command/log.vim
Normal file
@ -0,0 +1,152 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#log#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'path': args.params.path,
|
||||
\ 'params': [
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\ 'noautocmd': !empty(args.params.path),
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#log#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--follow',
|
||||
\ 'Continue listing the history of a file beyond renames',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
|
||||
call args.set('--color', 'always')
|
||||
call args.set('--pretty', "format:\e[32m%h\e[m %s \e[33;1m%cr\e[m \e[35;1m<%an>\e[m\e[36;1m%d\e[m")
|
||||
call gina#core#args#extend_treeish(a:git, args, args.pop(1, v:null))
|
||||
if args.params.path isnot# v:null
|
||||
call args.residual([args.params.path] + args.residual())
|
||||
elseif args.params.rev isnot# v:null
|
||||
call args.set(1, args.params.rev)
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'))
|
||||
|
||||
augroup gina_command_log_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-log
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let path = args.params.path
|
||||
let residual = args.residual()
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val, path, residual)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record, path, residual) abort
|
||||
let record = s:String.remove_ansi_sequences(a:record)
|
||||
let rev = matchstr(record, '^[|/\* ]*\s*\zs[a-z0-9]\+')
|
||||
return empty(rev) ? {} : {
|
||||
\ 'word': record,
|
||||
\ 'abbr': a:record,
|
||||
\ 'path': a:path,
|
||||
\ 'rev': rev,
|
||||
\ 'residual': a:residual,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
146
bundle/gina.vim/autoload/gina/command/ls.vim
Normal file
146
bundle/gina.vim/autoload/gina/command/ls.vim
Normal file
@ -0,0 +1,146 @@
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#ls#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'rev': args.params.rev,
|
||||
\ 'params': [
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#ls#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
let args.params.rev = args.get(1, gina#core#buffer#param('%', 'rev'))
|
||||
|
||||
if empty(args.params.rev)
|
||||
call args.set(0, 'ls-files')
|
||||
call args.set('--full-name', 1)
|
||||
else
|
||||
call args.set('--full-name', 1)
|
||||
call args.set('--full-tree', 1)
|
||||
call args.set('--name-only', 1)
|
||||
call args.set('-r', 1)
|
||||
call args.set(0, 'ls-tree')
|
||||
call args.set(1, args.params.rev)
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'))
|
||||
|
||||
augroup gina_command_ls_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-ls
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let rev = args.params.rev
|
||||
let residual = args.residual()
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val, rev, residual)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record, rev, residual) abort
|
||||
return {
|
||||
\ 'word': a:record,
|
||||
\ 'path': a:record,
|
||||
\ 'rev': a:rev,
|
||||
\ 'residual': a:residual,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
352
bundle/gina.vim/autoload/gina/command/patch.vim
Normal file
352
bundle/gina.vim/autoload/gina/command/patch.vim
Normal file
@ -0,0 +1,352 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
let s:WORKTREE = '@@'
|
||||
let s:is_windows = has('win32') || has('win64')
|
||||
|
||||
|
||||
function! gina#command#patch#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
call gina#process#register(s:SCHEME, 1)
|
||||
try
|
||||
call s:call(a:range, a:args, a:mods)
|
||||
finally
|
||||
call gina#process#unregister(s:SCHEME, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#command#patch#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#tracked(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group1=',
|
||||
\ 'A window group name used for the 1st buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group2=',
|
||||
\ 'A window group name used for the 2nd buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group3=',
|
||||
\ 'A window group name used for the 3rd buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line=',
|
||||
\ 'An initial line number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--col=',
|
||||
\ 'An initial column number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--oneside',
|
||||
\ 'Use two buffers instead of three buffers.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--diffoff',
|
||||
\ 'Call diffoff! prior to open buffers.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.groups = [
|
||||
\ args.pop('--group1', ''),
|
||||
\ args.pop('--group2', ''),
|
||||
\ args.pop('--group3', ''),
|
||||
\]
|
||||
let args.params.no_group = args.pop('--no-group', 0)
|
||||
let args.params.opener = args.pop('--opener', 'tabnew')
|
||||
let args.params.oneside = args.pop('--oneside', 0)
|
||||
let args.params.diffoff = args.pop('--diffoff')
|
||||
call gina#core#args#extend_path(a:git, args, args.pop(1))
|
||||
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
||||
call gina#core#args#extend_col(a:git, args, args.pop('--col'))
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:open(n, mods, opener, rev, params) abort
|
||||
if a:rev ==# s:WORKTREE
|
||||
execute printf(
|
||||
\ '%s Gina edit %s %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(a:params.col, '--col='),
|
||||
\ gina#util#shellescape(a:params.path),
|
||||
\)
|
||||
else
|
||||
let treeish = gina#core#treeish#build(a:rev, a:params.path)
|
||||
execute printf(
|
||||
\ '%s Gina show %s %s %s %s %s %s',
|
||||
\ a:mods,
|
||||
\ a:params.cmdarg,
|
||||
\ gina#util#shellescape(a:opener, '--opener='),
|
||||
\ gina#util#shellescape(a:params.groups[a:n], '--group='),
|
||||
\ gina#util#shellescape(a:params.line, '--line='),
|
||||
\ gina#util#shellescape(a:params.col, '--col='),
|
||||
\ gina#util#shellescape(treeish),
|
||||
\)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let mods = gina#util#contain_direction(a:mods)
|
||||
\ ? 'keepalt ' . a:mods
|
||||
\ : join(['keepalt', 'rightbelow', a:mods])
|
||||
|
||||
if !empty(args.params.diffoff)
|
||||
diffoff!
|
||||
endif
|
||||
|
||||
let opener1 = args.params.opener
|
||||
let opener2 = empty(matchstr(&diffopt, 'vertical'))
|
||||
\ ? 'split'
|
||||
\ : 'vsplit'
|
||||
|
||||
" Validate if all requirements exist
|
||||
call gina#core#treeish#validate(git, ':0', args.params.path, printf(join([
|
||||
\ 'The "%s" does not have an index content.',
|
||||
\ 'Use "chaperon" instead if you would like to patch on conflicted file',
|
||||
\], "\n"), args.params.path))
|
||||
|
||||
if args.params.oneside
|
||||
call s:open(1, mods, opener1, ':0', args.params)
|
||||
let bufnr2 = bufnr('%')
|
||||
|
||||
call s:open(2, mods, opener2, s:WORKTREE, args.params)
|
||||
let bufnr3 = bufnr('%')
|
||||
else
|
||||
call s:open(0, mods, opener1, 'HEAD', args.params)
|
||||
let bufnr1 = bufnr('%')
|
||||
|
||||
call s:open(1, mods, opener2, ':0', args.params)
|
||||
let bufnr2 = bufnr('%')
|
||||
|
||||
call s:open(2, mods, opener2, s:WORKTREE, args.params)
|
||||
let bufnr3 = bufnr('%')
|
||||
endif
|
||||
|
||||
" WORKTREE
|
||||
call gina#util#diffthis()
|
||||
call s:define_plug_mapping('diffput', bufnr2)
|
||||
call s:define_plug_mapping('diffget', bufnr2)
|
||||
if g:gina#command#patch#use_default_mappings
|
||||
nmap <buffer> dp <Plug>(gina-diffput)
|
||||
nmap <buffer> do <Plug>(gina-diffget)
|
||||
endif
|
||||
|
||||
" HEAD
|
||||
if !args.params.oneside
|
||||
execute printf('%dwincmd w', bufwinnr(bufnr1))
|
||||
call gina#util#diffthis()
|
||||
call s:define_plug_mapping('diffput', bufnr2)
|
||||
if g:gina#command#patch#use_default_mappings
|
||||
nmap <buffer> dp <Plug>(gina-diffput)
|
||||
endif
|
||||
endif
|
||||
|
||||
" INDEX
|
||||
execute printf('%dwincmd w', bufwinnr(bufnr2))
|
||||
call gina#util#diffthis()
|
||||
call s:define_plug_mapping('diffput', bufnr3)
|
||||
if !args.params.oneside
|
||||
call s:define_plug_mapping('diffget', bufnr1, '-l')
|
||||
endif
|
||||
call s:define_plug_mapping('diffget', bufnr3, '-r')
|
||||
if g:gina#command#patch#use_default_mappings
|
||||
nmap <buffer> dp <Plug>(gina-diffput)
|
||||
if !args.params.oneside
|
||||
nmap <buffer> dol <Plug>(gina-diffget-l)
|
||||
endif
|
||||
nmap <buffer> dor <Plug>(gina-diffget-r)
|
||||
endif
|
||||
|
||||
setlocal buftype=acwrite
|
||||
setlocal modifiable
|
||||
augroup gina_command_patch_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufWriteCmd <buffer> call s:BufWriteCmd()
|
||||
augroup END
|
||||
|
||||
call gina#util#diffupdate()
|
||||
normal! zm
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
function! s:define_plug_mapping(command, bufnr, ...) abort
|
||||
let suffix = a:0 ? a:1 : ''
|
||||
let lhs = printf('<Plug>(gina-%s%s)', a:command, suffix)
|
||||
let rhs = printf(':<C-u>%s %d<CR>:diffupdate<CR>', a:command, a:bufnr)
|
||||
call gina#util#map(lhs, rhs, {
|
||||
\ 'mode': 'n',
|
||||
\ 'noremap': 1,
|
||||
\ 'silent': 1,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! s:patch(git) abort
|
||||
let abspath = gina#core#path#expand('%')
|
||||
let path = gina#core#repo#relpath(a:git, abspath)
|
||||
call gina#process#call_or_fail(a:git, [
|
||||
\ 'add',
|
||||
\ '--intent-to-add',
|
||||
\ '--',
|
||||
\ s:Path.realpath(abspath),
|
||||
\])
|
||||
let tempfile = tempname()
|
||||
let tempfile1 = tempfile . '.index'
|
||||
let tempfile2 = tempfile . '.buffer'
|
||||
try
|
||||
let diff = s:diff(a:git, path, getline(1, '$'), tempfile1, tempfile2)
|
||||
let result = s:apply(a:git, diff)
|
||||
finally
|
||||
silent! call delete(tempfile1)
|
||||
silent! call delete(tempfile2)
|
||||
endtry
|
||||
return result
|
||||
endfunction
|
||||
|
||||
function! s:diff(git, path, buffer, tempfile1, tempfile2) abort
|
||||
if writefile(s:index(a:git, a:path), a:tempfile1) == -1
|
||||
return ''
|
||||
endif
|
||||
if writefile(a:buffer, a:tempfile2) == -1
|
||||
return ''
|
||||
endif
|
||||
" NOTE:
|
||||
" --no-index force --exit-code option.
|
||||
" --exit-code mean that the program exits with 1 if there were differences
|
||||
" and 0 means no differences
|
||||
let result = gina#process#call(a:git, [
|
||||
\ 'diff',
|
||||
\ '--no-index',
|
||||
\ '--unified=1',
|
||||
\ '--',
|
||||
\ a:tempfile1,
|
||||
\ a:tempfile2,
|
||||
\])
|
||||
if !result.status
|
||||
throw gina#core#revelator#info(
|
||||
\ 'No difference between index and buffer'
|
||||
\)
|
||||
endif
|
||||
return s:replace_filenames_in_diff(
|
||||
\ result.stdout,
|
||||
\ a:tempfile1,
|
||||
\ a:tempfile2,
|
||||
\ a:path,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:index(git, path) abort
|
||||
let result = gina#process#call(a:git, ['show', ':' . a:path])
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
return result.stdout
|
||||
endfunction
|
||||
|
||||
function! s:replace_filenames_in_diff(content, filename1, filename2, repl) abort
|
||||
" replace tempfile1/tempfile2 in the header to a:filename
|
||||
"
|
||||
" diff --git a/<tempfile1> b/<tempfile2>
|
||||
" index XXXXXXX..XXXXXXX XXXXXX
|
||||
" --- a/<tempfile1>
|
||||
" +++ b/<tempfile2>
|
||||
"
|
||||
let src1 = s:String.escape_pattern(a:filename1)
|
||||
let src2 = s:String.escape_pattern(a:filename2)
|
||||
if s:is_windows
|
||||
" NOTE:
|
||||
" '\' in {content} from 'git diff' are escaped so double escape is required
|
||||
" to substitute such path
|
||||
" NOTE:
|
||||
" escape(src1, '\') cannot be used while other characters such as '.' are
|
||||
" already escaped as well
|
||||
let src1 = substitute(src1, '\\\\', '\\\\\\\\', 'g')
|
||||
let src2 = substitute(src2, '\\\\', '\\\\\\\\', 'g')
|
||||
endif
|
||||
let repl = (a:filename1 =~# '^/' ? '/' : '') . a:repl
|
||||
let content = copy(a:content)
|
||||
let content[0] = substitute(content[0], src1, repl, '')
|
||||
let content[0] = substitute(content[0], src2, repl, '')
|
||||
let content[2] = substitute(content[2], src1, repl, '')
|
||||
let content[3] = substitute(content[3], src2, repl, '')
|
||||
return content
|
||||
endfunction
|
||||
|
||||
function! s:apply(git, content) abort
|
||||
let tempfile = tempname()
|
||||
try
|
||||
if writefile(a:content, tempfile) == -1
|
||||
return
|
||||
endif
|
||||
let result = gina#process#call_or_fail(a:git, [
|
||||
\ 'apply',
|
||||
\ '--verbose',
|
||||
\ '--cached',
|
||||
\ '--',
|
||||
\ tempfile,
|
||||
\])
|
||||
call gina#core#emitter#emit('command:called:patch')
|
||||
return result
|
||||
finally
|
||||
silent! call delete(tempfile)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:BufWriteCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let result = gina#core#revelator#call(function('s:patch'), [git])
|
||||
if !empty(result)
|
||||
setlocal nomodified
|
||||
endif
|
||||
call gina#util#diffupdate()
|
||||
endfunction
|
||||
|
||||
|
||||
" Event ----------------------------------------------------------------------
|
||||
function! s:on_command_called_patch(...) abort
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
endfunction
|
||||
|
||||
if !exists('s:subscribed')
|
||||
let s:subscribed = 1
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'command:called:patch',
|
||||
\ function('s:on_command_called_patch')
|
||||
\)
|
||||
endif
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
223
bundle/gina.vim/autoload/gina/command/qrep.vim
Normal file
223
bundle/gina.vim/autoload/gina/command/qrep.vim
Normal file
@ -0,0 +1,223 @@
|
||||
let s:Guard = vital#gina#import('Vim.Guard')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#qrep#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
|
||||
call gina#util#doautocmd('QuickfixCmdPre')
|
||||
let result = gina#process#call(git, args)
|
||||
let guard = s:Guard.store(['&more'])
|
||||
try
|
||||
set nomore
|
||||
call gina#process#inform(result)
|
||||
|
||||
" XXX: Support rev
|
||||
" 1. Globally enable BufReadCmd for gina://xxx:show/...
|
||||
" 2. Use gina://xxx:show/... to open a content in a rev
|
||||
let rev = ''
|
||||
let residual = args.residual()
|
||||
|
||||
let items = map(
|
||||
\ copy(result.stdout),
|
||||
\ 's:parse_record(git, v:val, rev, residual)',
|
||||
\)
|
||||
call setqflist(
|
||||
\ filter(items, '!empty(v:val)'),
|
||||
\ args.params.action,
|
||||
\)
|
||||
finally
|
||||
call guard.restore()
|
||||
endtry
|
||||
call gina#util#doautocmd('QuickfixCmdPost')
|
||||
if !args.params.bang
|
||||
cc
|
||||
endif
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
function! gina#command#qrep#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:cmdline =~# '\s--\s'
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif a:arglead[0] ==# '-' || !empty(args.get(2))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cached',
|
||||
\ 'Search in index instead of in the work tree',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-index',
|
||||
\ 'Find in contents not managed by git',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--untracked',
|
||||
\ 'Search in both tracked and untracked files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--exclude-standard',
|
||||
\ 'Ignore files specified via .gitignore',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-v|--invert-match',
|
||||
\ 'Show non-matching lines',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-i|--ignore-case',
|
||||
\ 'Case insensitive matching',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-w|--word-regexp',
|
||||
\ 'Match patterns only at word boundaries',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--text',
|
||||
\ 'Process binary files as text',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-I',
|
||||
\ 'Don''t match patterns in binary files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--textconv',
|
||||
\ 'Process binary files with textconv filters',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--max-depth=',
|
||||
\ 'Descend at most <depth> levels',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-E|--extended-regexp',
|
||||
\ 'Use extended POSIC regular expression',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-G|--basic-regexp',
|
||||
\ 'Use basic POSIX regular expression',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-F|--fixed-string',
|
||||
\ 'Interpret patterns as fixed strings',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-P|--perl-regexp',
|
||||
\ 'Use Perl-compatible regular expression',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--break',
|
||||
\ 'Print empty line between matches from different files',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-C|--context=',
|
||||
\ 'Show <n> context lines before and after matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-B|--before-context=',
|
||||
\ 'Show <n> context lines before matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-A|--after-context=',
|
||||
\ 'Show <n> context lines after matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--threads=',
|
||||
\ 'Use <n> worker threads',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-p|--show-function',
|
||||
\ 'Show a line with the function name before matches',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-W|--function-context',
|
||||
\ 'Show the surrounding function',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-f',
|
||||
\ 'Read patterns from file',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-e',
|
||||
\ 'Match <pattern>',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--and|--or|--not',
|
||||
\ 'Combine patterns specified with -e',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--all-match',
|
||||
\ 'Show only matches from files that match all patterns',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--action',
|
||||
\ 'An action which is specified to setqflist()',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.bang = args.get(0) =~# '!$'
|
||||
let args.params.action = args.pop('--action', ' ')
|
||||
let args.params.pattern = args.pop(1, '')
|
||||
|
||||
" Check if available grep patterns has specified and ask if not
|
||||
if empty(args.params.pattern) && !(args.has('-e') || args.has('-f'))
|
||||
let pattern = gina#core#console#ask('Pattern: ')
|
||||
if empty(pattern)
|
||||
throw gina#core#revelator#info('Cancel')
|
||||
endif
|
||||
let args.params.pattern = pattern
|
||||
endif
|
||||
|
||||
if gina#command#grep#_is_column_supported(gina#core#git_version())
|
||||
call args.set('--no-column', 1)
|
||||
endif
|
||||
call args.set('--line-number', 1)
|
||||
call args.set('--color', 'always')
|
||||
call args.set(0, 'grep')
|
||||
call args.set(1, args.params.pattern)
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(git, record, rev, residual) abort
|
||||
" Parse record to make a gina candidate and translate it to a quickfix item
|
||||
let candidate = gina#command#grep#parse_record(
|
||||
\ a:record, a:residual,
|
||||
\)
|
||||
if empty(candidate)
|
||||
return {}
|
||||
endif
|
||||
return {
|
||||
\ 'filename': s:Path.realpath(
|
||||
\ gina#core#repo#abspath(a:git, candidate.path)
|
||||
\ ),
|
||||
\ 'text': candidate.word,
|
||||
\ 'lnum': candidate.line,
|
||||
\ 'col': candidate.col,
|
||||
\}
|
||||
endfunction
|
241
bundle/gina.vim/autoload/gina/command/reflog.vim
Normal file
241
bundle/gina.vim/autoload/gina/command/reflog.vim
Normal file
@ -0,0 +1,241 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#reflog#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options_show())
|
||||
|
||||
if s:is_raw_command(a:args)
|
||||
" Remove non git options
|
||||
let args = a:args.clone()
|
||||
call args.pop('--group')
|
||||
call args.pop('--opener')
|
||||
" Call raw git command
|
||||
return gina#command#_raw#call(a:range, args, a:mods)
|
||||
endif
|
||||
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#reflog#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if args.get(1) =~# '^\%(show\|\)$'
|
||||
if a:arglead =~# '^-'
|
||||
let options = s:get_options_show()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
elseif args.get(1) ==# 'expire'
|
||||
if a:arglead =~# '^-'
|
||||
let options = s:get_options_expire()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
else
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
elseif args.get(1) ==# 'delete'
|
||||
if a:arglead =~# '^-'
|
||||
let options = s:get_options_delete()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
else
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
elseif args.get(1) ==# 'exists'
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#util#filter(a:arglead, [
|
||||
\ 'show',
|
||||
\ 'expire',
|
||||
\ 'delete',
|
||||
\ 'exists',
|
||||
\])
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options_show() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--follow',
|
||||
\ 'Continue listing the history of a file beyond renames',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:get_options_expire() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--expire=',
|
||||
\ 'Prune entries older than the specified time.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--expire-unreachable=',
|
||||
\ 'Prune entries older than the specified time that are not reachable.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--rewrite',
|
||||
\ 'Adjust old SHA-1 to be equal to the new SHA-1',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--updateref',
|
||||
\ 'Update the reference to the value of the top reflog entry',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--stale-fix',
|
||||
\ 'Prune broken commit reflog entries',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-n|--dry-run',
|
||||
\ 'Do not actually prune any entries',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--verbose',
|
||||
\ 'Print extra information',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:get_options_delete() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--rewrite',
|
||||
\ 'Adjust old SHA-1 to be equal to the new SHA-1',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--updateref',
|
||||
\ 'Update the reference to the value of the top reflog entry',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-n|--dry-run',
|
||||
\ 'Do not actually prune any entries',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--verbose',
|
||||
\ 'Print extra information',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
|
||||
call args.set('--color', 'always')
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:is_raw_command(args) abort
|
||||
if a:args.get(1) =~# '^\%(show\|\)$'
|
||||
return 0
|
||||
elseif a:args.get(1) ==# 'expire'
|
||||
return 1
|
||||
elseif a:args.get(1) ==# 'delete'
|
||||
return 1
|
||||
elseif a:args.get(1) ==# 'exists'
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'))
|
||||
|
||||
augroup gina_command_reflog_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-reflog
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record) abort
|
||||
let record = s:String.remove_ansi_sequences(a:record)
|
||||
let rev = matchstr(record, '^[a-z0-9]\+')
|
||||
return {
|
||||
\ 'word': record,
|
||||
\ 'abbr': a:record,
|
||||
\ 'rev': rev,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
158
bundle/gina.vim/autoload/gina/command/show.vim
Normal file
158
bundle/gina.vim/autoload/gina/command/show.vim
Normal file
@ -0,0 +1,158 @@
|
||||
let s:Buffer = vital#gina#import('Vim.Buffer')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#show#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'treeish': args.params.treeish,
|
||||
\ 'params': [
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'line': args.params.line,
|
||||
\ 'col': args.params.col,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#show#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead =~# '^-'
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#common#treeish(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--line',
|
||||
\ 'An initial line number.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--col',
|
||||
\ 'An initial column number.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
let args.params.cached = 0
|
||||
let args.params.R = args.get('-R')
|
||||
|
||||
call gina#core#args#extend_treeish(a:git, args, args.pop(1))
|
||||
call gina#core#args#extend_diff(a:git, args, args.params.rev)
|
||||
|
||||
" Enable --line/--col only when a path has specified
|
||||
if args.params.path isnot# v:null
|
||||
call gina#core#args#extend_line(a:git, args, args.pop('--line'))
|
||||
call gina#core#args#extend_col(a:git, args, args.pop('--col'))
|
||||
else
|
||||
call args.pop('--line')
|
||||
call args.pop('--col')
|
||||
let args.params.line = v:null
|
||||
let args.params.col = v:null
|
||||
endif
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nowrite
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
if a:args.params.partial
|
||||
setlocal bufhidden=wipe
|
||||
else
|
||||
setlocal bufhidden&
|
||||
endif
|
||||
|
||||
augroup gina_command_show_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
autocmd BufWinEnter <buffer> call setbufvar(str2nr(expand('<abuf>')), '&buflisted', 1)
|
||||
autocmd BufWinLeave <buffer> call setbufvar(str2nr(expand('<abuf>')), '&buflisted', 0)
|
||||
augroup END
|
||||
|
||||
if a:args.params.path is# v:null
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump)
|
||||
\ :<C-u>call gina#core#diffjump#jump()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump-split)
|
||||
\ :<C-u>call gina#core#diffjump#jump('split')<CR>
|
||||
nnoremap <buffer><silent> <Plug>(gina-diff-jump-vsplit)
|
||||
\ :<C-u>call gina#core#diffjump#jump('vsplit')<CR>
|
||||
if g:gina#command#show#use_default_mappings
|
||||
nmap <buffer> <CR> <Plug>(gina-diff-jump)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:reassign_rev(git, args) abort
|
||||
let rev = gina#core#treeish#resolve(a:git, a:args.params.rev)
|
||||
let treeish = gina#core#treeish#build(rev, a:args.params.path)
|
||||
call a:args.set(1, substitute(treeish, '^:0', '', ''))
|
||||
return a:args
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let args = s:reassign_rev(git, args.clone())
|
||||
let result = gina#process#call_or_fail(git, args)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#core#writer#replace('%', 0, -1, result.stdout)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
if args.params.path is# v:null
|
||||
setlocal nomodeline
|
||||
setfiletype git
|
||||
else
|
||||
call gina#util#doautocmd('BufRead')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
94
bundle/gina.vim/autoload/gina/command/stash.vim
Normal file
94
bundle/gina.vim/autoload/gina/command/stash.vim
Normal file
@ -0,0 +1,94 @@
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#stash#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options_save())
|
||||
|
||||
let git = gina#core#get_or_fail()
|
||||
let command = a:args.get(1, 'save')
|
||||
if command ==# 'show'
|
||||
return gina#command#stash#show#call(a:range, a:args, a:mods)
|
||||
elseif command ==# 'list'
|
||||
return gina#command#stash#list#call(a:range, a:args, a:mods)
|
||||
endif
|
||||
return gina#command#_raw#call(a:range, a:args, a:mods)
|
||||
endfunction
|
||||
|
||||
function! gina#command#stash#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if args.get(1) ==# 'list'
|
||||
return gina#command#stash#list#complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif args.get(1) ==# 'show'
|
||||
return gina#command#stash#show#complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif args.get(1) =~# '^\%(save\|\)$'
|
||||
if a:arglead =~# '^-'
|
||||
let options = s:get_options_save()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
elseif args.get(1) ==# 'drop'
|
||||
return gina#complete#stash#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif args.get(1) =~# '^\%(pop\|apply\)$'
|
||||
if a:arglead =~# '^-' || !empty(args.get(2))
|
||||
let options = s:get_options_pop()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#stash#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif args.get(1) ==# 'branch'
|
||||
if empty(args.get(2))
|
||||
return gina#complete#commit#branch(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#stash#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#util#filter(a:arglead, [
|
||||
\ 'list',
|
||||
\ 'show',
|
||||
\ 'drop',
|
||||
\ 'pop',
|
||||
\ 'apply',
|
||||
\ 'branch',
|
||||
\ 'save',
|
||||
\ 'clear',
|
||||
\ 'create',
|
||||
\ 'store',
|
||||
\])
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options_save() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-k|--keep-index',
|
||||
\ 'Left intact all changes already added to the index',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-keep-index',
|
||||
\ 'Do not left intact all changes already added to the index',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--all',
|
||||
\ 'The ignored files and untracked files are stashed and cleaned',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--include-untracked',
|
||||
\ 'The untracked files are stashed and cleaned',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:get_options_pop() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--index',
|
||||
\ 'Tries to reinstate not only the working tree but also index',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
133
bundle/gina.vim/autoload/gina/command/stash/list.vim
Normal file
133
bundle/gina.vim/autoload/gina/command/stash/list.vim
Normal file
@ -0,0 +1,133 @@
|
||||
let s:SCHEME = 'stash'
|
||||
|
||||
|
||||
function! gina#command#stash#list#call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#stash#list#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead =~# '^-'
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--follow',
|
||||
\ 'Continue listing the history of a file beyond renames',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
setlocal autoread
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'), {
|
||||
\ 'markable': 1,
|
||||
\})
|
||||
|
||||
augroup gina_command_stash_list_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-stash-list
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record) abort
|
||||
let stash = matchstr(a:record, '^[^:]\+')
|
||||
return {
|
||||
\ 'word': a:record,
|
||||
\ 'rev': stash,
|
||||
\ 'stash': stash,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
||||
|
139
bundle/gina.vim/autoload/gina/command/stash/show.vim
Normal file
139
bundle/gina.vim/autoload/gina/command/stash/show.vim
Normal file
@ -0,0 +1,139 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = 'stash'
|
||||
|
||||
|
||||
function! gina#command#stash#show#call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'rev': args.params.rev,
|
||||
\ 'params': ['show'],
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#stash#show#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead =~# '^-' || !empty(args.get(2))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#stash#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.rev = args.get(2, 'stash@{0}')
|
||||
call args.set('--numstat', 1)
|
||||
call args.set(1, 'show')
|
||||
call args.set(2, args.params.rev)
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'))
|
||||
|
||||
augroup gina_command_stash_show_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-stash-show
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let rev = args.params.rev
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val, rev)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record, rev) abort
|
||||
let m = matchlist(
|
||||
\ a:record,
|
||||
\ '^\(\d\+\)\s\+\(\d\+\)\s\+\(.\+\)$'
|
||||
\)
|
||||
return empty(m) ? {} : {
|
||||
\ 'word': a:record,
|
||||
\ 'added': m[1],
|
||||
\ 'removed': m[2],
|
||||
\ 'path': m[3],
|
||||
\ 'rev': a:rev,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
244
bundle/gina.vim/autoload/gina/command/status.vim
Normal file
244
bundle/gina.vim/autoload/gina/command/status.vim
Normal file
@ -0,0 +1,244 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#status#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'params': [
|
||||
\ args.params.partial ? '--' : '',
|
||||
\ ],
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#status#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-' || !empty(args.get(1))
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.partial = !empty(args.residual())
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
setlocal autoread
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'), {
|
||||
\ 'markable': 1,
|
||||
\})
|
||||
|
||||
augroup gina_command_status_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-status
|
||||
endfunction
|
||||
|
||||
function! s:compare_record(a, b) abort
|
||||
let a = matchstr(a:a, '...\zs.*')
|
||||
let b = matchstr(a:b, '...\zs.*')
|
||||
return a ==# b ? 0 : a > b ? 1 : -1
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let conf = gina#core#repo#config(git)
|
||||
let residual = args.residual()
|
||||
if args.get('-s|--short') || get(conf, 'status.short', 'false') ==? 'true'
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record_short(v:val, residual)'
|
||||
\)
|
||||
else
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record_normal(v:val, residual)'
|
||||
\)
|
||||
endif
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record_short(record, residual) abort
|
||||
let record = s:String.remove_ansi_sequences(a:record)
|
||||
let m = matchlist(
|
||||
\ record,
|
||||
\ '^\(..\) \("[^"]\{-}"\|.\{-}\)\%( -> \("[^"]\{-}"\|[^ ]\+\)\)\?$'
|
||||
\)
|
||||
if empty(m) || m[1] ==# '##'
|
||||
return {}
|
||||
endif
|
||||
let candidate = {
|
||||
\ 'word': record,
|
||||
\ 'abbr': a:record,
|
||||
\ 'sign': m[1],
|
||||
\ 'residual': a:residual,
|
||||
\}
|
||||
if len(m) && !empty(m[3])
|
||||
return extend(candidate, {
|
||||
\ 'path': s:strip_quotes(m[3]),
|
||||
\ 'path1': s:strip_quotes(m[2]),
|
||||
\ 'path2': s:strip_quotes(m[3]),
|
||||
\})
|
||||
else
|
||||
return extend(candidate, {
|
||||
\ 'path': s:strip_quotes(m[2]),
|
||||
\ 'path1': s:strip_quotes(m[2]),
|
||||
\ 'path2': '',
|
||||
\})
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:parse_record_normal(record, residual) abort
|
||||
let signs = {
|
||||
\ 'modified': 'M',
|
||||
\ 'new file': 'A',
|
||||
\ 'deleted': 'D',
|
||||
\ 'renamed': 'R',
|
||||
\ 'copied': 'C',
|
||||
\ 'both added': 'AA',
|
||||
\ 'both deleted': 'DD',
|
||||
\ 'both modified': 'UU',
|
||||
\ 'added by us': 'AU',
|
||||
\ 'added by them': 'UA',
|
||||
\ 'deleted by us': 'DU',
|
||||
\ 'deleted by them': 'UD',
|
||||
\}
|
||||
let record = s:String.remove_ansi_sequences(a:record)
|
||||
if record !~# '^.\?\t'
|
||||
return {}
|
||||
endif
|
||||
let m = matchlist(record, printf(
|
||||
\ '^.\?\s\+\(%s\):\s\+\("[^"]\{-}"\|.\{-}\)\%%( -> \("[^"]\{-}"\|[^ ]\+\)\)\?$',
|
||||
\ join(keys(signs), '\|')
|
||||
\))
|
||||
if empty(m)
|
||||
" Untracked files
|
||||
let path = s:strip_quotes(substitute(record, '^\t', '', ''))
|
||||
return {
|
||||
\ 'word': record,
|
||||
\ 'abbr': a:record,
|
||||
\ 'sign': '??',
|
||||
\ 'residual': a:residual,
|
||||
\ 'path': path,
|
||||
\ 'path1': path,
|
||||
\ 'path2': '',
|
||||
\}
|
||||
endif
|
||||
if search('^Unmerged paths:', 'bnW') != 0
|
||||
" Conflict
|
||||
let sign = signs[m[1]]
|
||||
elseif search('^Changes not staged for commit:', 'bnW') != 0
|
||||
" Unstaged
|
||||
let sign = ' ' . signs[m[1]]
|
||||
else
|
||||
" Staged
|
||||
let sign = signs[m[1]] . ' '
|
||||
endif
|
||||
let candidate = {
|
||||
\ 'word': record,
|
||||
\ 'abbr': a:record,
|
||||
\ 'sign': sign,
|
||||
\ 'residual': a:residual,
|
||||
\}
|
||||
if len(m) && !empty(m[3])
|
||||
return extend(candidate, {
|
||||
\ 'path': s:strip_quotes(m[3]),
|
||||
\ 'path1': s:strip_quotes(m[2]),
|
||||
\ 'path2': s:strip_quotes(m[3]),
|
||||
\})
|
||||
else
|
||||
return extend(candidate, {
|
||||
\ 'path': s:strip_quotes(m[2]),
|
||||
\ 'path1': s:strip_quotes(m[2]),
|
||||
\ 'path2': '',
|
||||
\})
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:strip_quotes(str) abort
|
||||
return a:str =~# '^\%(".*"\|''.*''\)$' ? a:str[1:-2] : a:str
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
246
bundle/gina.vim/autoload/gina/command/tag.vim
Normal file
246
bundle/gina.vim/autoload/gina/command/tag.vim
Normal file
@ -0,0 +1,246 @@
|
||||
let s:SCHEME = gina#command#scheme(expand('<sfile>'))
|
||||
|
||||
|
||||
function! gina#command#tag#call(range, args, mods) abort
|
||||
call gina#core#options#help_if_necessary(a:args, s:get_options())
|
||||
|
||||
if s:is_edit_command(a:args)
|
||||
return gina#command#tag#edit#call(a:range, a:args, a:mods)
|
||||
elseif s:is_raw_command(a:args)
|
||||
" Remove non git options
|
||||
let args = a:args.clone()
|
||||
call args.pop('--group')
|
||||
call args.pop('--opener')
|
||||
call args.pop('--restore')
|
||||
" Call raw git command
|
||||
return gina#command#_raw#call(a:range, args, a:mods)
|
||||
endif
|
||||
|
||||
" list
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(git, a:args)
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME)
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#command#tag#complete(arglead, cmdline, cursorpos) abort
|
||||
let args = gina#core#args#new(matchstr(a:cmdline, '^.*\ze .*'))
|
||||
if a:arglead[0] ==# '-'
|
||||
let options = s:get_options()
|
||||
return options.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif s:is_edit_command(args)
|
||||
return gina#complete#commit#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
elseif s:is_raw_command(args)
|
||||
return gina#complete#tag#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_options() abort
|
||||
let options = gina#core#options#new()
|
||||
call options.define(
|
||||
\ '-h|--help',
|
||||
\ 'Show this help.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--opener=',
|
||||
\ 'A Vim command to open a new buffer.',
|
||||
\ ['edit', 'split', 'vsplit', 'tabedit', 'pedit'],
|
||||
\)
|
||||
call options.define(
|
||||
\ '--group=',
|
||||
\ 'A window group name used for the buffer.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--restore',
|
||||
\ 'Restore the previous buffer when the window is closed.',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-a|--annotate',
|
||||
\ 'Make an unsigned, annotated tag object',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-s|--sign',
|
||||
\ 'Make a GPG-signed tag, using the default e-mail address key',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-U|--local-user=',
|
||||
\ 'Make a GPG-signed tag, using the given key',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-f|--force',
|
||||
\ 'Replace an existing tag with the given name',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-d|--delete',
|
||||
\ 'Delete existing tags with the given name',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-n',
|
||||
\ 'Print <n> lines from the annotation when using -l',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-l|--list=',
|
||||
\ 'List tags with names that match the given pattern',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--sort=',
|
||||
\ 'Sort based on the key given',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--contains=',
|
||||
\ 'Only listtags which contains the specified commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--points-at=',
|
||||
\ 'Only list tags of the given object',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-m|--message=',
|
||||
\ 'Use the given tag message',
|
||||
\)
|
||||
call options.define(
|
||||
\ '-F|--file=',
|
||||
\ 'Take thetag message from the given file',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--cleanup=',
|
||||
\ 'Set how thetag message is cleaned up',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--create-reflog',
|
||||
\ 'Createa reflog for the tag',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--merged=',
|
||||
\ 'Only list tags whose tips are reachable from the given commit',
|
||||
\)
|
||||
call options.define(
|
||||
\ '--no-merged=',
|
||||
\ 'Only list tags whose tips are not reachable from the given commit',
|
||||
\)
|
||||
return options
|
||||
endfunction
|
||||
|
||||
function! s:is_edit_command(args) abort
|
||||
if a:args.get('-a|--annotate')
|
||||
return 1
|
||||
elseif a:args.get('-s|--sign')
|
||||
return 1
|
||||
elseif !empty(a:args.get('-u|--local-user'))
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:is_raw_command(args) abort
|
||||
if a:args.get('-l|--list')
|
||||
return 0
|
||||
elseif a:args.get('-d|--delete')
|
||||
return 1
|
||||
elseif a:args.get('-v|--verify')
|
||||
return 1
|
||||
elseif a:args.get('-m|--message')
|
||||
" -a/--annotate is implied
|
||||
return 1
|
||||
elseif a:args.get('-f|--file')
|
||||
" -a/--annotate is implied
|
||||
return 1
|
||||
elseif !empty(a:args.get(1))
|
||||
" lightweight tag
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:build_args(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
" Remove unused option
|
||||
call args.pop('--restore')
|
||||
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal buftype=nofile
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal nomodifiable
|
||||
setlocal autoread
|
||||
|
||||
" Attach modules
|
||||
call gina#core#locator#attach()
|
||||
call gina#action#attach(function('s:get_candidates'), {
|
||||
\ 'markable': 1,
|
||||
\})
|
||||
|
||||
augroup gina_command_tag_list_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer>
|
||||
\ call gina#core#revelator#call(function('s:BufReadCmd'), [])
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let pipe = gina#process#pipe#stream(s:writer)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#process#open(git, args, pipe)
|
||||
setlocal filetype=gina-tag
|
||||
endfunction
|
||||
|
||||
function! s:get_candidates(fline, lline) abort
|
||||
let candidates = map(
|
||||
\ getline(a:fline, a:lline),
|
||||
\ 's:parse_record(v:val)'
|
||||
\)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:parse_record(record) abort
|
||||
return {
|
||||
\ 'word': a:record,
|
||||
\ 'branch': a:record,
|
||||
\ 'rev': a:record,
|
||||
\ 'tag': a:record,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
|
||||
" Writer ---------------------------------------------------------------------
|
||||
function! s:_writer_on_exit() abort dict
|
||||
call call(s:original_writer.on_exit, [], self)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
endfunction
|
||||
|
||||
let s:original_writer = gina#process#pipe#stream_writer()
|
||||
let s:writer = extend(deepcopy(s:original_writer), {
|
||||
\ 'on_exit': function('s:_writer_on_exit'),
|
||||
\})
|
||||
|
||||
" Config ---------------------------------------------------------------------
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'use_default_aliases': 1,
|
||||
\ 'use_default_mappings': 1,
|
||||
\})
|
203
bundle/gina.vim/autoload/gina/command/tag/edit.vim
Normal file
203
bundle/gina.vim/autoload/gina/command/tag/edit.vim
Normal file
@ -0,0 +1,203 @@
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:Git = vital#gina#import('Git')
|
||||
|
||||
let s:SCHEME = 'tag'
|
||||
|
||||
|
||||
function! gina#command#tag#edit#call(range, args, mods) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = s:build_args(a:args)
|
||||
|
||||
let bufname = gina#core#buffer#bufname(git, s:SCHEME, {
|
||||
\ 'params': ['edit']
|
||||
\})
|
||||
call gina#core#buffer#open(bufname, {
|
||||
\ 'mods': a:mods,
|
||||
\ 'group': args.params.group,
|
||||
\ 'opener': args.params.opener,
|
||||
\ 'cmdarg': args.params.cmdarg,
|
||||
\ 'callback': {
|
||||
\ 'fn': function('s:init'),
|
||||
\ 'args': [args],
|
||||
\ }
|
||||
\})
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:build_args(args) abort
|
||||
let args = a:args.clone()
|
||||
let args.params.group = args.pop('--group', '')
|
||||
let args.params.opener = args.pop('--opener', '')
|
||||
let args.params.restore = args.pop(
|
||||
\ '--restore',
|
||||
\ empty(args.params.opener) || args.params.opener ==# 'edit',
|
||||
\)
|
||||
return args.lock()
|
||||
endfunction
|
||||
|
||||
function! s:init(args) abort
|
||||
call gina#core#meta#set('args', a:args)
|
||||
silent! unlet b:gina_QuitPre
|
||||
silent! unlet b:gina_BufWriteCmd
|
||||
|
||||
if exists('b:gina_initialized')
|
||||
return
|
||||
endif
|
||||
let b:gina_initialized = 1
|
||||
|
||||
setlocal nobuflisted
|
||||
setlocal buftype=acwrite
|
||||
setlocal bufhidden=hide
|
||||
setlocal noswapfile
|
||||
setlocal modifiable
|
||||
|
||||
augroup gina_command_tag_edit_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadCmd <buffer> call s:BufReadCmd()
|
||||
autocmd BufWriteCmd <buffer> call s:BufWriteCmd()
|
||||
autocmd QuitPre <buffer> call s:QuitPre()
|
||||
autocmd WinLeave <buffer> call s:WinLeave()
|
||||
autocmd WinEnter <buffer> silent! unlet! b:gina_QuitPre
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:BufReadCmd() abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let content = gina#core#revelator#call(
|
||||
\ function('s:get_tagmsg_template'),
|
||||
\ [git, args]
|
||||
\)
|
||||
call gina#core#buffer#assign_cmdarg()
|
||||
call gina#core#writer#replace('%', 0, -1, content)
|
||||
call gina#core#emitter#emit('command:called', s:SCHEME)
|
||||
setlocal filetype=conf
|
||||
endfunction
|
||||
|
||||
function! s:BufWriteCmd() abort
|
||||
let b:gina_BufWriteCmd = 1
|
||||
setlocal nomodified
|
||||
endfunction
|
||||
|
||||
function! s:QuitPre() abort
|
||||
" Restore the previous buffer if 'restore' is specified
|
||||
let args = gina#core#meta#get('args', v:null)
|
||||
if args isnot# v:null && get(args.params, 'restore')
|
||||
let win_id = win_getid()
|
||||
if bufnr('#') == -1
|
||||
silent keepalt keepjumps 1new
|
||||
else
|
||||
silent keepalt keepjumps 1split #
|
||||
endif
|
||||
call win_gotoid(win_id)
|
||||
endif
|
||||
" Do not perform commit when user hit :q!
|
||||
if histget('cmd', -1) !~# '^q\%[uit]!'
|
||||
let b:gina_QuitPre = 1
|
||||
" If this is a last window, open a new window to prevent quit
|
||||
if tabpagenr('$') == 1 && winnr('$') == 1
|
||||
let win_id = win_getid()
|
||||
silent keepalt keepjumps 1new
|
||||
call win_gotoid(win_id)
|
||||
endif
|
||||
endif
|
||||
" Clear the flag set by :w but keep it when user hit :wq
|
||||
if histget('cmd', -1) !~# '^\(wq\|x\%[it]\|exi\%[t]\)'
|
||||
silent! unlet b:gina_BufWriteCmd
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" NOTE:
|
||||
" :w -- BufWriteCmd
|
||||
" <C-w>p -- WinLeave
|
||||
" :wq -- QuitPre -> BufWriteCmd -> WinLeave (-8.2.2899)
|
||||
" BufWriteCmd -> QuitPre -> WinLeave (8.2.2900-)
|
||||
" :q -- QuitPre -> WinLeave
|
||||
function! s:WinLeave() abort
|
||||
if exists('b:gina_QuitPre')
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
if exists('b:gina_BufWriteCmd')
|
||||
" User execute 'wq' so do not confirm
|
||||
call gina#core#revelator#call(
|
||||
\ function('s:apply_tagmsg'),
|
||||
\ [git, args]
|
||||
\)
|
||||
else
|
||||
" User execute 'q' so confirm
|
||||
call gina#core#revelator#call(
|
||||
\ function('s:apply_tagmsg_confirm'),
|
||||
\ [git, args]
|
||||
\)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_tagmsg_template(git, args) abort
|
||||
let config = gina#core#repo#config(a:git)
|
||||
let comment = get(config, 'core.commentchar', '#')
|
||||
let tagname = a:args.get(1)
|
||||
if a:args.get('--cleanup', 'strip') ==# 'strip'
|
||||
let template = [
|
||||
\ '',
|
||||
\ 'Write a message for tag:',
|
||||
\ ' ' . tagname,
|
||||
\ printf(
|
||||
\ 'Lines starting with ''%s'' will be ignored.',
|
||||
\ comment,
|
||||
\ ),
|
||||
\]
|
||||
else
|
||||
let template = [
|
||||
\ '',
|
||||
\ 'Write a message for tag:',
|
||||
\ ' ' . tagname,
|
||||
\ printf(
|
||||
\ 'Lines starting with ''%s'' will be kept; you may remove them yourself if you want to.',
|
||||
\ comment,
|
||||
\ ),
|
||||
\]
|
||||
endif
|
||||
call map(template, 'comment . '' '' . v:val')
|
||||
return [''] + template
|
||||
endfunction
|
||||
|
||||
function! s:apply_tagmsg(git, args) abort
|
||||
let args = a:args.clone()
|
||||
let content = getline(1, '$')
|
||||
let tempfile = s:Git.resolve(a:git, 'TAG_EDITMSG')
|
||||
try
|
||||
call writefile(content, tempfile)
|
||||
call args.set('--cleanup', args.get('--cleanup', 'strip'))
|
||||
call args.set('-F|--file', tempfile)
|
||||
call args.pop('-m|--message')
|
||||
let result = gina#process#call(a:git, args)
|
||||
call gina#process#inform(result)
|
||||
call gina#core#emitter#emit('command:called:tag')
|
||||
finally
|
||||
call delete(tempfile)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:apply_tagmsg_confirm(git, args) abort
|
||||
if gina#core#console#confirm('Do you want to create a tag?', 'y')
|
||||
call s:apply_tagmsg(a:git, a:args)
|
||||
else
|
||||
redraw | echo ''
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Event ----------------------------------------------------------------------
|
||||
function! s:on_command_called_tag(...) abort
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
endfunction
|
||||
|
||||
if !exists('s:subscribed')
|
||||
let s:subscribed = 1
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'command:called:tag',
|
||||
\ function('s:on_command_called_tag')
|
||||
\)
|
||||
endif
|
88
bundle/gina.vim/autoload/gina/complete/commit.vim
Normal file
88
bundle/gina.vim/autoload/gina/complete/commit.vim
Normal file
@ -0,0 +1,88 @@
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
|
||||
function! gina#complete#commit#any(arglead, cmdline, cursorpos) abort
|
||||
let candidates = ['']
|
||||
let candidates += gina#complete#commit#branch(a:arglead, a:cmdline, a:cursorpos)
|
||||
if !empty(a:arglead)
|
||||
let candidates += gina#complete#commit#hashref(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#commit#branch(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of(s:Git.resolve(git, 'config'))
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_branches(git, ['--all'])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#commit#local_branch(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of(s:Git.resolve(git, 'config'))
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_branches(git, [])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#commit#remote_branch(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of(s:Git.resolve(git, 'config'))
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_branches(git, ['--remotes'])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#commit#hashref(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of(s:Git.resolve(git, 'config'))
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_commits(git, [])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
|
||||
" Public ---------------------------------------------------------------------
|
||||
function! s:filter(arglead, candidates) abort
|
||||
return gina#util#filter(a:arglead, a:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:get_available_commits(git, args) abort
|
||||
let args = ['log', '--pretty=%h'] + a:args
|
||||
let result = gina#process#call(a:git, args)
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
return result.stdout
|
||||
endfunction
|
||||
|
||||
function! s:get_available_branches(git, args) abort
|
||||
let args = ['branch', '--no-color', '--list'] + a:args
|
||||
let result = gina#process#call(a:git, args)
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
let candidates = filter(copy(result.stdout), 'v:val !~# ''^.* -> .*$''')
|
||||
call map(candidates, 'matchstr(v:val, ''^..\zs.*$'')')
|
||||
call map(candidates, 'substitute(v:val, ''^remotes/'', '''', '''')')
|
||||
return ['HEAD'] + filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
101
bundle/gina.vim/autoload/gina/complete/common.vim
Normal file
101
bundle/gina.vim/autoload/gina/complete/common.vim
Normal file
@ -0,0 +1,101 @@
|
||||
let s:Cache = vital#gina#import('System.Cache.Memory')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
|
||||
function! gina#complete#common#opener(arglead, cmdline, cursorpos) abort
|
||||
if a:arglead !~# '^--opener='
|
||||
return []
|
||||
endif
|
||||
let candidates = [
|
||||
\ 'split',
|
||||
\ 'vsplit',
|
||||
\ 'tabedit',
|
||||
\ 'pedit',
|
||||
\]
|
||||
let prefix = '--opener='
|
||||
return gina#util#filter(a:arglead, map(candidates, 'prefix . v:val'))
|
||||
endfunction
|
||||
|
||||
function! gina#complete#common#treeish(arglead, cmdline, cursorpos) abort
|
||||
if a:arglead =~# '^[^:]*:'
|
||||
let revision = matchstr(a:arglead, '^[^:]*\ze:')
|
||||
let candidates = gina#complete#filename#tracked(
|
||||
\ matchstr(a:arglead, '^[^:]*:\zs.*'),
|
||||
\ a:cmdline,
|
||||
\ a:cursorpos,
|
||||
\ revision,
|
||||
\)
|
||||
return map(candidates, 'revision . '':'' . v:val')
|
||||
else
|
||||
let candidates = gina#complete#range#any(a:arglead, a:cmdline, a:cursorpos)
|
||||
return map(candidates, 'v:val . '':''')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#complete#common#command(arglead, cmdline, cursorpos) abort
|
||||
let cache = s:get_cache()
|
||||
if !cache.has('command_names')
|
||||
call cache.set('command_names', s:get_command_names())
|
||||
endif
|
||||
let command_names = cache.get('command_names')
|
||||
return gina#util#filter(a:arglead, command_names, '^_')
|
||||
endfunction
|
||||
|
||||
function! gina#complete#common#raw_command(arglead, cmdline, cursorpos) abort
|
||||
return gina#util#filter(a:arglead, [
|
||||
\ 'add',
|
||||
\ 'bisect',
|
||||
\ 'branch',
|
||||
\ 'checkout',
|
||||
\ 'clone',
|
||||
\ 'commit',
|
||||
\ 'diff',
|
||||
\ 'fetch',
|
||||
\ 'grep',
|
||||
\ 'init',
|
||||
\ 'log',
|
||||
\ 'merge',
|
||||
\ 'mv',
|
||||
\ 'pull',
|
||||
\ 'push',
|
||||
\ 'rebase',
|
||||
\ 'reset',
|
||||
\ 'restore',
|
||||
\ 'rm',
|
||||
\ 'show',
|
||||
\ 'status',
|
||||
\ 'switch',
|
||||
\ 'tag',
|
||||
\])
|
||||
endfunction
|
||||
|
||||
function! gina#complete#common#remote(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let result = gina#process#call(git, ['remote'])
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
return gina#util#filter(a:arglead, result.stdout)
|
||||
endfunction
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_cache() abort
|
||||
if exists('s:cache')
|
||||
return s:cache
|
||||
endif
|
||||
let s:cache = s:Cache.new()
|
||||
return s:cache
|
||||
endfunction
|
||||
|
||||
function! s:get_command_names() abort
|
||||
let suffix = s:Path.realpath('autoload/gina/command/*.vim')
|
||||
let command_names = []
|
||||
for runtimepath in split(&runtimepath, ',')
|
||||
let names = map(
|
||||
\ glob(s:Path.join(runtimepath, suffix), 0, 1),
|
||||
\ 'matchstr(fnamemodify(v:val, '':t''), ''^.\+\ze\.vim$'')',
|
||||
\)
|
||||
call extend(command_names, names)
|
||||
endfor
|
||||
return sort(command_names)
|
||||
endfunction
|
164
bundle/gina.vim/autoload/gina/complete/filename.vim
Normal file
164
bundle/gina.vim/autoload/gina/complete/filename.vim
Normal file
@ -0,0 +1,164 @@
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
|
||||
function! gina#complete#filename#directory(arglead, cmdline, cursorpos) abort
|
||||
let separator = s:Path.separator()
|
||||
return filter(
|
||||
\ gina#complete#filename#any(a:arglead, a:cmdline, a:cursorpos),
|
||||
\ 'v:val =~# separator . ''$'''
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#any(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let candidates = s:get_available_filenames(git, [
|
||||
\ '--cached', '--others', '--', a:arglead . '*',
|
||||
\])
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#tracked(arglead, cmdline, cursorpos, ...) abort
|
||||
let rev = a:0 ? a:1 : ''
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr()) . rev
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_filenames(git, [], rev)
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#cached(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_filenames(git, ['--cached'])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#deleted(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_filenames(git, ['--deleted'])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#modified(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_filenames(git, ['--modified'])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#others(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let candidates = s:get_available_filenames(git, [
|
||||
\ '--others', '--', a:arglead . '*',
|
||||
\])
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#unstaged(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let candidates = s:get_available_filenames(git, [
|
||||
\ '--others',
|
||||
\ '--modified',
|
||||
\ '--',
|
||||
\ a:arglead . '*',
|
||||
\])
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#filename#conflicted(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let result = gina#process#call(git, [
|
||||
\ 'status',
|
||||
\ '--porcelain',
|
||||
\ '--ignore-submodules',
|
||||
\])
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
call filter(
|
||||
\ candidates,
|
||||
\ 'v:val[:1] =~# ''^\%(DD\|AU\|UD\|UA\|DU\|AA\|UU\)'''
|
||||
\)
|
||||
call map(candidates, 'matchstr(v:val, ''.. \zs.*'')')
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:filter(arglead, candidates) abort
|
||||
let pattern = s:String.escape_pattern(a:arglead)
|
||||
let separator = s:Path.separator()
|
||||
let candidates = gina#util#filter(a:arglead, a:candidates, '^\.')
|
||||
call map(
|
||||
\ candidates,
|
||||
\ printf(
|
||||
\ 'matchstr(v:val, ''^%s[^%s]*%s\?\ze'')',
|
||||
\ pattern, separator, separator
|
||||
\ ),
|
||||
\)
|
||||
return uniq(candidates)
|
||||
endfunction
|
||||
|
||||
function! s:get_available_filenames(git, args, ...) abort
|
||||
let rev = a:0 ? a:1 : ''
|
||||
if empty(rev)
|
||||
let args = ['ls-files', '--full-name']
|
||||
else
|
||||
let args = [
|
||||
\ 'ls-tree',
|
||||
\ '--full-name',
|
||||
\ '--full-tree',
|
||||
\ '--name-only',
|
||||
\ '-r',
|
||||
\ rev,
|
||||
\]
|
||||
endif
|
||||
let result = gina#process#call(a:git, args + a:args)
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
return map(result.stdout, 'gina#core#repo#relpath(a:git, v:val)')
|
||||
endfunction
|
48
bundle/gina.vim/autoload/gina/complete/range.vim
Normal file
48
bundle/gina.vim/autoload/gina/complete/range.vim
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
function! gina#complete#range#any(arglead, cmdline, cursorpos) abort
|
||||
return s:complete(
|
||||
\ function('gina#complete#commit#any'),
|
||||
\ a:arglead, a:cmdline, a:cursorpos,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#range#branch(arglead, cmdline, cursorpos) abort
|
||||
return s:complete(
|
||||
\ function('gina#complete#commit#branch'),
|
||||
\ a:arglead, a:cmdline, a:cursorpos,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#range#local_branch(arglead, cmdline, cursorpos) abort
|
||||
return s:complete(
|
||||
\ function('gina#complete#commit#branch'),
|
||||
\ a:arglead, a:cmdline, a:cursorpos,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#range#remote_branch(arglead, cmdline, cursorpos) abort
|
||||
return s:complete(
|
||||
\ function('gina#complete#commit#branch'),
|
||||
\ a:arglead, a:cmdline, a:cursorpos,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#complete#range#hashref(arglead, cmdline, cursorpos) abort
|
||||
return s:complete(
|
||||
\ function('gina#complete#commit#branch'),
|
||||
\ a:arglead, a:cmdline, a:cursorpos,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:complete(fn, arglead, cmdline, cursorpos) abort
|
||||
if a:arglead =~# '^[^.]*\.\.\.\?'
|
||||
let lhs = matchstr(a:arglead, '^[^.]*\.\.\.\?')
|
||||
let rhs = matchstr(a:arglead, '^[^.]*\.\.\.\?\zs.*')
|
||||
let candidates = a:fn(rhs, a:cmdline, a:cursorpos)
|
||||
return map(candidates, 'lhs . v:val')
|
||||
else
|
||||
return a:fn(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
endfunction
|
33
bundle/gina.vim/autoload/gina/complete/stash.vim
Normal file
33
bundle/gina.vim/autoload/gina/complete/stash.vim
Normal file
@ -0,0 +1,33 @@
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
|
||||
function! gina#complete#stash#any(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_stashes(git, [])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
" Public ---------------------------------------------------------------------
|
||||
function! s:filter(arglead, candidates) abort
|
||||
return gina#util#filter(a:arglead, a:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:get_available_stashes(git, args) abort
|
||||
let args = ['stash', 'list', '--format=%gd'] + a:args
|
||||
let result = gina#process#call(a:git, args)
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
let candidates = copy(result.stdout)
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
34
bundle/gina.vim/autoload/gina/complete/tag.vim
Normal file
34
bundle/gina.vim/autoload/gina/complete/tag.vim
Normal file
@ -0,0 +1,34 @@
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
|
||||
function! gina#complete#tag#any(arglead, cmdline, cursorpos) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\])
|
||||
let candidates = store.get(slug, [])
|
||||
if empty(candidates)
|
||||
let candidates = s:get_available_tags(git, [])
|
||||
call store.set(slug, candidates)
|
||||
endif
|
||||
return s:filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
" Public ---------------------------------------------------------------------
|
||||
function! s:filter(arglead, candidates) abort
|
||||
return gina#util#filter(a:arglead, a:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:get_available_tags(git, args) abort
|
||||
let args = ['tag', '--list', '--format=%(refname:strip=2)'] + a:args
|
||||
let result = gina#process#call(a:git, args)
|
||||
if result.status
|
||||
return []
|
||||
endif
|
||||
let candidates = result.stdout
|
||||
return filter(candidates, '!empty(v:val)')
|
||||
endfunction
|
||||
|
||||
|
115
bundle/gina.vim/autoload/gina/component/repo.vim
Normal file
115
bundle/gina.vim/autoload/gina/component/repo.vim
Normal file
@ -0,0 +1,115 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
|
||||
function! gina#component#repo#name() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
return fnamemodify(git.worktree, ':t')
|
||||
endfunction
|
||||
|
||||
function! gina#component#repo#branch() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'config'),
|
||||
\])
|
||||
let branch = store.get(slug, '')
|
||||
if !empty(branch)
|
||||
return branch
|
||||
endif
|
||||
let content = get(readfile(s:Git.resolve(git, 'HEAD')), 0, '')
|
||||
if content =~# '^ref:\s\+refs/heads'
|
||||
let branch = matchstr(content, '^ref:\s\+refs/heads/\zs.\+')
|
||||
elseif content =~# '^ref:'
|
||||
let branch = matchstr(content, '^ref:\s\+refs/\zs.\+')
|
||||
elseif g:gina#component#repo#commit_length > 0
|
||||
let branch = content[:(g:gina#component#repo#commit_length - 1)]
|
||||
else
|
||||
let branch = content
|
||||
endif
|
||||
call store.set(slug, branch)
|
||||
return branch
|
||||
endfunction
|
||||
|
||||
function! gina#component#repo#track() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'HEAD'),
|
||||
\ s:Git.resolve(git, 'config'),
|
||||
\])
|
||||
let branch = store.get(slug, '')
|
||||
if !empty(branch)
|
||||
return branch
|
||||
endif
|
||||
if !exists('s:track_job')
|
||||
let pipe = gina#process#pipe#store()
|
||||
let pipe.__on_exit = pipe.on_exit
|
||||
let pipe.on_exit = funcref('s:track_on_exit', [store, slug])
|
||||
let s:track_job = gina#process#open(git, [
|
||||
\ 'rev-parse',
|
||||
\ '--abbrev-ref',
|
||||
\ '--symbolic-full-name',
|
||||
\ '@{upstream}',
|
||||
\], pipe)
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! gina#component#repo#preset(...) abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let kind = get(a:000, 0, 'ascii')
|
||||
return call('s:preset_' . kind, [])
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:track_on_exit(store, slug, exitval) abort dict
|
||||
call self.__on_exit(a:exitval)
|
||||
silent! unlet! s:track_job
|
||||
if a:exitval
|
||||
return
|
||||
endif
|
||||
call a:store.set(a:slug, get(self.stdout, 0))
|
||||
endfunction
|
||||
|
||||
function! s:preset_ascii() abort
|
||||
let name = gina#component#repo#name()
|
||||
let branch = gina#component#repo#branch()
|
||||
let track = gina#component#repo#track()
|
||||
if empty(track)
|
||||
return printf('%s [%s]', name, branch)
|
||||
endif
|
||||
return printf('%s [%s -> %s]', name, branch, track)
|
||||
endfunction
|
||||
|
||||
function! s:preset_fancy() abort
|
||||
let name = gina#component#repo#name()
|
||||
let branch = gina#component#repo#branch()
|
||||
let track = gina#component#repo#track()
|
||||
if empty(track)
|
||||
return printf('%s [%s]', name, branch)
|
||||
endif
|
||||
return printf('%s [%s → %s]', name, branch, track)
|
||||
endfunction
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'commit_length': 0,
|
||||
\})
|
147
bundle/gina.vim/autoload/gina/component/status.vim
Normal file
147
bundle/gina.vim/autoload/gina/component/status.vim
Normal file
@ -0,0 +1,147 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
let s:SLUG = eval(s:Store.get_slug_expr())
|
||||
|
||||
|
||||
function! gina#component#status#staged() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let c = get(s:status_count(git), 'staged', 0)
|
||||
return c == 0 ? '' : (c . '')
|
||||
endfunction
|
||||
|
||||
function! gina#component#status#unstaged() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let c = get(s:status_count(git), 'unstaged', 0)
|
||||
return c == 0 ? '' : (c . '')
|
||||
endfunction
|
||||
|
||||
function! gina#component#status#conflicted() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let c = get(s:status_count(git), 'conflicted', 0)
|
||||
return c == 0 ? '' : (c . '')
|
||||
endfunction
|
||||
|
||||
function! gina#component#status#preset(...) abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let kind = get(a:000, 0, 'ascii')
|
||||
return call('s:preset_' . kind, [])
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:get_store(git) abort
|
||||
let ref = get(s:Git.ref(a:git, 'HEAD'), 'path', 'HEAD')
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(a:git, 'index'),
|
||||
\ s:Git.resolve(a:git, ref),
|
||||
\])
|
||||
return store
|
||||
endfunction
|
||||
|
||||
function! s:status_count(git) abort
|
||||
let store = s:get_store(a:git)
|
||||
let status_count = store.get(s:SLUG, {})
|
||||
if !empty(status_count)
|
||||
return status_count
|
||||
endif
|
||||
if !exists('s:status_job')
|
||||
let pipe = gina#process#pipe#store()
|
||||
let pipe.__on_exit = pipe.on_exit
|
||||
let pipe.on_exit = funcref('s:status_on_exit', [store])
|
||||
let s:status_job = gina#process#open(a:git, [
|
||||
\ 'status',
|
||||
\ '--porcelain',
|
||||
\ '--ignore-submodules',
|
||||
\], pipe)
|
||||
endif
|
||||
return {
|
||||
\ 'staged': 0,
|
||||
\ 'unstaged': 0,
|
||||
\ 'conflicted': 0,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:status_on_exit(store, exitval) abort dict
|
||||
call self.__on_exit(a:exitval)
|
||||
silent! unlet! s:status_job
|
||||
if a:exitval
|
||||
return
|
||||
endif
|
||||
let status_count = {
|
||||
\ 'staged': 0,
|
||||
\ 'unstaged': 0,
|
||||
\ 'conflicted': 0,
|
||||
\}
|
||||
for record in self.stdout
|
||||
let sign = record[:1]
|
||||
if sign =~# '^\%(DD\|AU\|UD\|UA\|DU\|AA\|UU\)$'
|
||||
let status_count.conflicted += 1
|
||||
elseif sign ==# '??' || sign ==# '!!'
|
||||
continue
|
||||
else
|
||||
if sign =~# '^\S.$'
|
||||
let status_count.staged += 1
|
||||
endif
|
||||
if sign =~# '^.\S$'
|
||||
let status_count.unstaged += 1
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
call a:store.set(s:SLUG, status_count)
|
||||
endfunction
|
||||
|
||||
function! s:preset_ascii() abort
|
||||
let staged = gina#component#status#staged()
|
||||
let unstaged = gina#component#status#unstaged()
|
||||
let conflicted = gina#component#status#conflicted()
|
||||
let staged = empty(staged) ? '' : ('<' . staged)
|
||||
let unstaged = empty(unstaged) ? '' : ('>' . unstaged)
|
||||
let conflicted = empty(conflicted) ? '' : ('x' . conflicted)
|
||||
return join(filter([staged, unstaged, conflicted], '!empty(v:val)'))
|
||||
endfunction
|
||||
|
||||
function! s:preset_fancy() abort
|
||||
let staged = gina#component#status#staged()
|
||||
let unstaged = gina#component#status#unstaged()
|
||||
let conflicted = gina#component#status#conflicted()
|
||||
let staged = empty(staged) ? '' : ('«' . staged)
|
||||
let unstaged = empty(unstaged) ? '' : ('»' . unstaged)
|
||||
let conflicted = empty(conflicted) ? '' : ('×' . conflicted)
|
||||
return join(filter([staged, unstaged, conflicted], '!empty(v:val)'))
|
||||
endfunction
|
||||
|
||||
" NOTE:
|
||||
" Tracked files might be changed (unstaged) so remove cache when 'modified'
|
||||
" event has called.
|
||||
function! s:on_modified(...) abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return
|
||||
endif
|
||||
let store = s:get_store(git)
|
||||
call store.remove(s:SLUG)
|
||||
endfunction
|
||||
|
||||
if !exists('s:subscribed')
|
||||
let s:subscribed = 1
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'modified',
|
||||
\ function('s:on_modified')
|
||||
\)
|
||||
endif
|
144
bundle/gina.vim/autoload/gina/component/traffic.vim
Normal file
144
bundle/gina.vim/autoload/gina/component/traffic.vim
Normal file
@ -0,0 +1,144 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
|
||||
function! gina#component#traffic#ahead() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let ref = get(s:Git.ref(git, 'HEAD'), 'path', 'HEAD')
|
||||
let track = s:get_tracking_ref(git)
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\ s:Git.resolve(git, ref),
|
||||
\ s:Git.resolve(git, track),
|
||||
\])
|
||||
let ahead = store.get(slug, '')
|
||||
if !empty(ahead)
|
||||
return str2nr(ahead)
|
||||
endif
|
||||
if !exists('s:ahead_job')
|
||||
let pipe = gina#process#pipe#store()
|
||||
let pipe.__on_exit = pipe.on_exit
|
||||
let pipe.on_exit = funcref('s:ahead_on_exit', [store, slug])
|
||||
let s:ahead_job = gina#process#open(git, [
|
||||
\ 'log',
|
||||
\ '--oneline',
|
||||
\ '@{upstream}..',
|
||||
\], pipe)
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! gina#component#traffic#behind() abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let ref = get(s:Git.ref(git, 'HEAD'), 'path', 'HEAD')
|
||||
let track = s:get_tracking_ref(git)
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(git, 'index'),
|
||||
\ s:Git.resolve(git, ref),
|
||||
\ s:Git.resolve(git, track),
|
||||
\])
|
||||
let behind = store.get(slug, '')
|
||||
if !empty(behind)
|
||||
return str2nr(behind)
|
||||
endif
|
||||
if !exists('s:behind_job')
|
||||
let pipe = gina#process#pipe#store()
|
||||
let pipe.__on_exit = pipe.on_exit
|
||||
let pipe.on_exit = funcref('s:behind_on_exit', [store, slug])
|
||||
let s:behind_job = gina#process#open(git, [
|
||||
\ 'log',
|
||||
\ '--oneline',
|
||||
\ '..@{upstream}',
|
||||
\], pipe)
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! gina#component#traffic#preset(...) abort
|
||||
let git = gina#core#get()
|
||||
if empty(git)
|
||||
return ''
|
||||
endif
|
||||
let kind = get(a:000, 0, 'ascii')
|
||||
return call('s:preset_' . kind, [])
|
||||
endfunction
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:ahead_on_exit(store, slug, exitval) abort dict
|
||||
call self.__on_exit(a:exitval)
|
||||
silent! unlet! s:ahead_job
|
||||
if a:exitval
|
||||
return
|
||||
endif
|
||||
let ahead = len(filter(copy(self.stdout), '!empty(v:val)')) . ''
|
||||
call a:store.set(a:slug, ahead)
|
||||
endfunction
|
||||
|
||||
function! s:behind_on_exit(store, slug, exitval) abort dict
|
||||
call self.__on_exit(a:exitval)
|
||||
silent! unlet! s:behind_job
|
||||
if a:exitval
|
||||
return
|
||||
endif
|
||||
let behind = len(filter(copy(self.stdout), '!empty(v:val)')) . ''
|
||||
call a:store.set(a:slug, behind)
|
||||
endfunction
|
||||
|
||||
function! s:get_tracking_ref(git) abort
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of([
|
||||
\ s:Git.resolve(a:git, 'HEAD'),
|
||||
\ s:Git.resolve(a:git, 'config'),
|
||||
\])
|
||||
let ref = store.get(slug, '')
|
||||
if !empty(ref)
|
||||
return ref
|
||||
endif
|
||||
if !exists('s:tracking_ref_job')
|
||||
let pipe = gina#process#pipe#store()
|
||||
let pipe.__on_exit = pipe.on_exit
|
||||
let pipe.on_exit = funcref('s:tracking_ref_on_exit', [store, slug])
|
||||
let s:tracking_ref_job = gina#process#open(a:git, [
|
||||
\ 'rev-parse',
|
||||
\ '--symbolic-full-name',
|
||||
\ '@{upstream}',
|
||||
\], pipe)
|
||||
endif
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
function! s:tracking_ref_on_exit(store, slug, exitval) abort dict
|
||||
call self.__on_exit(a:exitval)
|
||||
silent! unlet! s:tracking_ref_job
|
||||
if a:exitval
|
||||
return
|
||||
endif
|
||||
call a:store.set(a:slug, get(self.stdout, 0))
|
||||
endfunction
|
||||
|
||||
function! s:preset_ascii() abort
|
||||
let ahead = gina#component#traffic#ahead()
|
||||
let behind = gina#component#traffic#behind()
|
||||
let ahead = empty(ahead) ? '' : ('^' . ahead)
|
||||
let behind = empty(behind) ? '' : ('v' . behind)
|
||||
return join(filter([ahead, behind], '!empty(v:val)'))
|
||||
endfunction
|
||||
|
||||
function! s:preset_fancy() abort
|
||||
let ahead = gina#component#traffic#ahead()
|
||||
let behind = gina#component#traffic#behind()
|
||||
let ahead = empty(ahead) ? '' : ('↑' . ahead)
|
||||
let behind = empty(behind) ? '' : ('↓' . behind)
|
||||
return join(filter([ahead, behind], '!empty(v:val)'))
|
||||
endfunction
|
175
bundle/gina.vim/autoload/gina/core.vim
Normal file
175
bundle/gina.vim/autoload/gina/core.vim
Normal file
@ -0,0 +1,175 @@
|
||||
let s:Cache = vital#gina#import('System.Cache.Memory')
|
||||
let s:Git = vital#gina#import('Git')
|
||||
|
||||
let s:registry = s:Cache.new()
|
||||
let s:reference = s:Cache.new()
|
||||
|
||||
let s:CACHE_NEVER = 'never' " No cache. Search .git always.
|
||||
let s:CACHE_TRUTH = 'truth' " Use a cache when a git repository is found.
|
||||
let s:CACHE_ALWAYS = 'always' " Use a cache always.
|
||||
|
||||
|
||||
function! gina#core#get_or_fail(...) abort
|
||||
let options = extend({
|
||||
\ 'expr': '%',
|
||||
\ 'cache': s:CACHE_TRUTH,
|
||||
\}, get(a:000, 0, {})
|
||||
\)
|
||||
let git = gina#core#get(options)
|
||||
if !empty(git)
|
||||
return git
|
||||
endif
|
||||
throw gina#core#revelator#warning(printf(
|
||||
\ 'No git repository for a buffer "%s" is found.',
|
||||
\ expand(options.expr)
|
||||
\))
|
||||
endfunction
|
||||
|
||||
function! gina#core#get(...) abort
|
||||
let options = extend({
|
||||
\ 'expr': '%',
|
||||
\ 'cache': s:CACHE_ALWAYS,
|
||||
\}, get(a:000, 0, {})
|
||||
\)
|
||||
if options.cache !=# s:CACHE_NEVER
|
||||
let cached = s:get_cached_instance(options.expr)
|
||||
if !empty(cached)
|
||||
call gina#core#console#debug(printf(
|
||||
\ 'A cached git instanse "%s" is used for "%s"',
|
||||
\ get(cached, 'refname', ''),
|
||||
\ expand(options.expr),
|
||||
\))
|
||||
return cached
|
||||
elseif options.cache ==# s:CACHE_TRUTH && cached isnot# v:null
|
||||
call gina#core#console#debug(printf(
|
||||
\ 'An empty cached git instanse is used for "%s"',
|
||||
\ expand(options.expr),
|
||||
\))
|
||||
return cached
|
||||
endif
|
||||
endif
|
||||
|
||||
let params = gina#core#buffer#parse(options.expr)
|
||||
if empty(params)
|
||||
let git = {}
|
||||
let params.path = expand(options.expr)
|
||||
else
|
||||
let git = s:get_from_cache(params.repo)
|
||||
endif
|
||||
if empty(git)
|
||||
if s:is_file_buffer(options.expr)
|
||||
let git = s:get_from_bufname(params.path)
|
||||
else
|
||||
let git = s:get_from_cwd(bufnr(options.expr))
|
||||
endif
|
||||
endif
|
||||
call s:set_cached_instance(options.expr, git)
|
||||
return git
|
||||
endfunction
|
||||
|
||||
function! gina#core#git_version() abort
|
||||
if exists('s:git_version')
|
||||
return s:git_version
|
||||
endif
|
||||
let r = gina#process#call(gina#core#get(), ['--version'])
|
||||
let s:git_version = matchstr(join(r.stdout, "\n"), '\%(\d\+\.\)\+\d')
|
||||
return s:git_version
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:is_file_buffer(expr) abort
|
||||
return getbufvar(a:expr, '&buftype', '') =~# '^\%(\|nowrite\|acwrite\)$'
|
||||
endfunction
|
||||
|
||||
function! s:get_cached_instance(expr) abort
|
||||
let refinfo = getbufvar(a:expr, 'gina', {})
|
||||
if empty(refinfo)
|
||||
return v:null
|
||||
endif
|
||||
" Check if the refinfo is fresh enough
|
||||
if refinfo.bufname !=# simplify(bufname(a:expr))
|
||||
return v:null
|
||||
elseif refinfo.buftype !=# getbufvar(a:expr, '&buftype', '')
|
||||
return v:null
|
||||
elseif refinfo.cwd !=# simplify(getcwd())
|
||||
return v:null
|
||||
endif
|
||||
" refinfo is fresh enough, use a cached git instance
|
||||
return s:get_from_cache(refinfo.refname)
|
||||
endfunction
|
||||
|
||||
function! s:set_cached_instance(expr, git) abort
|
||||
if bufexists(bufnr(a:expr))
|
||||
call setbufvar(a:expr, 'gina', {
|
||||
\ 'refname': get(a:git, 'refname', ''),
|
||||
\ 'bufname': simplify(bufname(a:expr)),
|
||||
\ 'buftype': getbufvar(a:expr, '&buftype', ''),
|
||||
\ 'cwd': simplify(getcwd()),
|
||||
\})
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_available_refname(refname, git) abort
|
||||
let refname = a:refname
|
||||
let ref = s:reference.get(refname, '')
|
||||
let index = 1
|
||||
while !empty(ref) && ref !=# a:git.worktree
|
||||
let refname = a:refname . '~' . index
|
||||
let ref = s:reference.get(refname, '')
|
||||
let index += 1
|
||||
endwhile
|
||||
return refname
|
||||
endfunction
|
||||
|
||||
function! s:get_from_cache(reference) abort
|
||||
if s:registry.has(a:reference)
|
||||
return s:registry.get(a:reference)
|
||||
elseif s:reference.has(a:reference)
|
||||
return s:registry.get(s:reference.get(a:reference), {})
|
||||
endif
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
function! s:get_from_path(path) abort
|
||||
let path = simplify(fnamemodify(a:path, ':p'))
|
||||
let git = s:Git.new(path)
|
||||
if empty(git)
|
||||
return {}
|
||||
endif
|
||||
let git.refname = s:get_available_refname(
|
||||
\ fnamemodify(git.worktree, ':t'),
|
||||
\ git,
|
||||
\)
|
||||
call s:registry.set(git.worktree, git)
|
||||
call s:reference.set(path, git.worktree)
|
||||
call s:reference.set(git.refname, git.worktree)
|
||||
return git
|
||||
endfunction
|
||||
|
||||
function! s:get_from_bufname(path) abort
|
||||
let git = s:get_from_path(a:path)
|
||||
if !empty(git)
|
||||
return git
|
||||
endif
|
||||
|
||||
" Resolve symbol link
|
||||
let sympath = simplify(resolve(a:path))
|
||||
if sympath !=# a:path
|
||||
let git = s:get_from_path(sympath)
|
||||
if !empty(git)
|
||||
return git
|
||||
endif
|
||||
endif
|
||||
|
||||
" Not found
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
function! s:get_from_cwd(bufnr) abort
|
||||
let winnr = bufwinnr(a:bufnr)
|
||||
let cwdpath = winnr == -1
|
||||
\ ? simplify(getcwd())
|
||||
\ : simplify(getcwd(winnr))
|
||||
return s:get_from_path(cwdpath)
|
||||
endfunction
|
162
bundle/gina.vim/autoload/gina/core/args.vim
Normal file
162
bundle/gina.vim/autoload/gina/core/args.vim
Normal file
@ -0,0 +1,162 @@
|
||||
let s:Argument = vital#gina#import('Argument')
|
||||
let s:WORKTREE = '@@'
|
||||
|
||||
function! gina#core#args#is_worktree(rev) abort
|
||||
return s:WORKTREE ==# a:rev
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#raw(rargs) abort
|
||||
return s:Argument.new(a:rargs)
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#new(rargs) abort
|
||||
let args = gina#core#args#raw(a:rargs)
|
||||
for preference in gina#custom#preferences(args.get(0))
|
||||
" Assign default options
|
||||
for [query, value, remover] in preference.command.options
|
||||
if !empty(remover) && args.has(remover)
|
||||
call args.pop(remover)
|
||||
call args.pop(query)
|
||||
elseif !args.has(query)
|
||||
call args.set(query, value)
|
||||
endif
|
||||
endfor
|
||||
" Assign alias
|
||||
if preference.command.raw
|
||||
call args.set(0, ['_raw', preference.command.origin])
|
||||
else
|
||||
call args.set(0, preference.command.origin)
|
||||
endif
|
||||
endfor
|
||||
" Expand residuals to allow '%'
|
||||
let pathlist = args.residual()
|
||||
if !empty(pathlist)
|
||||
call args.residual(map(pathlist, 'gina#core#path#expand(v:val)'))
|
||||
endif
|
||||
" Assig global params
|
||||
let args.params = {}
|
||||
let args.params.scheme = substitute(args.get(0, ''), '!$', '', '')
|
||||
let cmd = args.pop('^+')
|
||||
let cmdarg = []
|
||||
while !empty(cmd)
|
||||
call add(cmdarg, cmd)
|
||||
let cmd = args.pop('^+')
|
||||
endwhile
|
||||
let args.params.cmdarg = empty(cmdarg) ? '' : (join(cmdarg) . ' ')
|
||||
return args
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#extend_path(git, args, path) abort
|
||||
if a:path is# v:null
|
||||
let a:args.params.path = v:null
|
||||
else
|
||||
let path = empty(a:path)
|
||||
\ ? gina#core#path#expand('%')
|
||||
\ : gina#core#path#expand(a:path)
|
||||
let path = gina#core#repo#relpath(a:git, path)
|
||||
let a:args.params.path = path
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#extend_diff(git, args, treeish) abort
|
||||
let [rev1, rev2] = gina#core#treeish#split(a:treeish)
|
||||
if get(a:args.params, 'cached')
|
||||
let rev1 = empty(rev1) ? 'HEAD' : rev1
|
||||
let rev2 = empty(rev2) ? ':0' : rev2
|
||||
else
|
||||
let rev1 = empty(rev1) ? ':0' : rev1
|
||||
let rev2 = empty(rev2) ? s:WORKTREE : rev2
|
||||
endif
|
||||
if get(a:args.params, 'R')
|
||||
let [rev2, rev1] = [rev1, rev2]
|
||||
endif
|
||||
|
||||
" Validate if all requirements exist
|
||||
" if !empty(get(a:args.params, 'path'))
|
||||
" if rev1 != s:WORKTREE
|
||||
" call gina#core#treeish#validate(a:git, rev1, a:args.params.path)
|
||||
" endif
|
||||
" if rev2 != s:WORKTREE
|
||||
" call gina#core#treeish#validate(a:git, rev2, a:args.params.path)
|
||||
" endif
|
||||
" endif
|
||||
|
||||
call extend(a:args.params, {
|
||||
\ 'rev1': rev1,
|
||||
\ 'rev2': rev2,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#extend_treeish(git, args, treeish) abort
|
||||
if a:treeish is# v:null
|
||||
let rev = v:null
|
||||
let path = v:null
|
||||
else
|
||||
let [rev, path] = gina#core#treeish#parse(a:treeish)
|
||||
" Guess a revision from the current buffer name if necessary
|
||||
if empty(rev)
|
||||
let rev = gina#core#buffer#param('%', 'rev')
|
||||
endif
|
||||
" Guess a path from the current buffer name if necessary
|
||||
" and make sure that the path is a relative path from rep root
|
||||
if path isnot# v:null
|
||||
let path = empty(path) ? gina#core#path#expand('%:p') : path
|
||||
let path = gina#core#repo#relpath(a:git, path)
|
||||
endif
|
||||
endif
|
||||
call extend(a:args.params, {
|
||||
\ 'rev': rev,
|
||||
\ 'path': path,
|
||||
\ 'treeish': gina#core#treeish#build(rev, path),
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#extend_line(git, args, line) abort
|
||||
if a:line is# v:null || !empty(a:line)
|
||||
let a:args.params.line = a:line
|
||||
return
|
||||
endif
|
||||
let scheme = gina#core#buffer#param('%', 'scheme')
|
||||
if scheme !~# '^\%(\|show\|blame\)$'
|
||||
let a:args.params.line = v:null
|
||||
return
|
||||
elseif empty(scheme)
|
||||
let treeish1 = gina#core#repo#relpath(a:git, expand('%:p'))
|
||||
let treeish1 = ':' . treeish1
|
||||
else
|
||||
let treeish1 = gina#core#buffer#param('%', 'treeish')
|
||||
let treeish1 = substitute(treeish1, '^:0', '', '')
|
||||
endif
|
||||
let treeish2 = gina#core#treeish#build(
|
||||
\ gina#util#get(a:args.params, 'rev', v:null),
|
||||
\ gina#util#get(a:args.params, 'path', v:null),
|
||||
\)
|
||||
let a:args.params.line = treeish1 ==# treeish2
|
||||
\ ? line('.')
|
||||
\ : v:null
|
||||
endfunction
|
||||
|
||||
function! gina#core#args#extend_col(git, args, col) abort
|
||||
if a:col is# v:null || !empty(a:col)
|
||||
let a:args.params.col = a:col
|
||||
return
|
||||
endif
|
||||
let scheme = gina#core#buffer#param('%', 'scheme')
|
||||
if scheme !~# '^\%(\|show\|blame\)$'
|
||||
let a:args.params.col = v:null
|
||||
return
|
||||
elseif empty(scheme)
|
||||
let treeish1 = gina#core#repo#relpath(a:git, expand('%'))
|
||||
let treeish1 = ':' . treeish1
|
||||
else
|
||||
let treeish1 = gina#core#buffer#param('%', 'treeish')
|
||||
let treeish1 = substitute(treeish1, '^:0', '', '')
|
||||
endif
|
||||
let treeish2 = gina#core#treeish#build(
|
||||
\ gina#util#get(a:args.params, 'rev', v:null),
|
||||
\ gina#util#get(a:args.params, 'path', v:null),
|
||||
\)
|
||||
let a:args.params.col = treeish1 ==# treeish2
|
||||
\ ? col('.')
|
||||
\ : v:null
|
||||
endfunction
|
70
bundle/gina.vim/autoload/gina/core/askpass.vim
Normal file
70
bundle/gina.vim/autoload/gina/core/askpass.vim
Normal file
@ -0,0 +1,70 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
|
||||
let s:is_windows = has('win32') || has('win64')
|
||||
let s:is_darwin = has('mac') || has('macunix')
|
||||
let s:repository_root = expand('<sfile>:p:h:h:h:h')
|
||||
let s:askpass_script = s:Path.join(s:repository_root, 'scripts', 'askpass')
|
||||
|
||||
if s:is_windows
|
||||
" Using an official credential helper 'wincred' helps.
|
||||
" See https://github.com/lambdalisue/gina.vim/pull/11#issuecomment-279541140
|
||||
let s:askpass_script = ''
|
||||
elseif s:is_darwin
|
||||
" AFAI, no usable GUI ssh-askpass exist
|
||||
let s:askpass_script .= '.mac'
|
||||
else
|
||||
if executable('zenity')
|
||||
let s:askpass_script .= '.zenity'
|
||||
else
|
||||
" ssh-askpass-gnome would help in this case
|
||||
let s:askpass_script = ''
|
||||
endif
|
||||
endif
|
||||
|
||||
if s:is_windows
|
||||
" While Windows has an official credential which raise a GUI prompt, gina
|
||||
" won't touch askpass for Windows
|
||||
function! gina#core#askpass#wrap(git, args) abort
|
||||
return a:args
|
||||
endfunction
|
||||
else
|
||||
function! gina#core#askpass#wrap(git, args) abort
|
||||
if empty(a:git)
|
||||
return a:args
|
||||
endif
|
||||
let prefix = ['env', 'GIT_TERMINAL_PROMPT=0']
|
||||
let askpass = s:askpass(a:git)
|
||||
if !empty(askpass)
|
||||
" NOTE:
|
||||
" '$GIT_ASKPASS' has a higest priority so use this instead of
|
||||
" '-c core.askpass=...' in Mac/Linux environment while 'env'
|
||||
" is available.
|
||||
let prefix += ['GIT_ASKPASS=' . askpass]
|
||||
endif
|
||||
return extend(a:args, prefix, 0)
|
||||
endfunction
|
||||
endif
|
||||
|
||||
|
||||
function! s:askpass(git) abort
|
||||
let config = gina#core#repo#config(a:git)
|
||||
let askpass = get(config, 'core.askpass', '')
|
||||
if !empty(g:gina#core#askpass#askpass_program)
|
||||
return g:gina#core#askpas#askpass_program
|
||||
elseif g:gina#core#askpass#force_internal
|
||||
return s:askpass_script
|
||||
elseif exists('$GIT_ASKPASS')
|
||||
return $GIT_ASKPASS
|
||||
elseif !empty(askpass)
|
||||
return askpass
|
||||
elseif exists('$SSH_ASKPASS')
|
||||
return $SSH_ASKPASS
|
||||
endif
|
||||
return s:askpass_script
|
||||
endfunction
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'askpass_program': '',
|
||||
\ 'force_internal': 0,
|
||||
\})
|
223
bundle/gina.vim/autoload/gina/core/buffer.vim
Normal file
223
bundle/gina.vim/autoload/gina/core/buffer.vim
Normal file
@ -0,0 +1,223 @@
|
||||
let s:Buffer = vital#gina#import('Vim.Buffer')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:Guard = vital#gina#import('Vim.Guard')
|
||||
let s:Opener = vital#gina#import('Vim.Buffer.Opener')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Window = vital#gina#import('Vim.Window')
|
||||
|
||||
let s:NOAUTOCMD_SUFFIX = ':$'
|
||||
let s:DEFAULT_PARAMS_ATTRIBUTES = {
|
||||
\ 'repo': '',
|
||||
\ 'scheme': '',
|
||||
\ 'params': [],
|
||||
\ 'rev': '',
|
||||
\ 'path': '',
|
||||
\ 'treeish': '',
|
||||
\}
|
||||
|
||||
|
||||
function! gina#core#buffer#bufname(git, scheme, ...) abort
|
||||
let options = get(a:000, 0, {})
|
||||
let params = filter(gina#util#get(options, 'params', []), '!empty(v:val)')
|
||||
let treeish = gina#util#get(options, 'treeish', printf('%s:%s',
|
||||
\ gina#util#get(options, 'rev'),
|
||||
\ gina#util#get(options, 'path'),
|
||||
\))
|
||||
return s:normalize_bufname(printf(
|
||||
\ 'gina://%s:%s%s/%s%s',
|
||||
\ a:git.refname,
|
||||
\ a:scheme,
|
||||
\ empty(params) ? '' : ':' . join(params, ':'),
|
||||
\ s:Path.unixpath(substitute(treeish, '^:0', '', '')),
|
||||
\ gina#util#get(options, 'noautocmd', 0) ? s:NOAUTOCMD_SUFFIX : '',
|
||||
\))
|
||||
endfunction
|
||||
|
||||
function! gina#core#buffer#parse(expr) abort
|
||||
let path = expand(a:expr)
|
||||
let m = matchlist(path, printf(
|
||||
\ '\v^gina://([^:]+):([^:\/]+)([^\/]*)[\/]?(%%(:[0-3]|[^:]*)%%(:[^:]*)?)%%(\m%s\v)?$',
|
||||
\ s:String.escape_pattern(s:NOAUTOCMD_SUFFIX),
|
||||
\))
|
||||
if empty(m)
|
||||
return {}
|
||||
endif
|
||||
let treeish = m[4]
|
||||
let [rev, path] = gina#core#treeish#parse(treeish)
|
||||
let params = {
|
||||
\ 'repo': m[1],
|
||||
\ 'scheme': m[2],
|
||||
\ 'params': filter(split(m[3], ':'), '!empty(v:val)'),
|
||||
\ 'rev': rev,
|
||||
\ 'treeish': treeish,
|
||||
\}
|
||||
if path isnot# v:null
|
||||
let params.path = substitute(
|
||||
\ path,
|
||||
\ s:String.escape_pattern(s:NOAUTOCMD_SUFFIX) . '$',
|
||||
\ '',
|
||||
\ '',
|
||||
\)
|
||||
endif
|
||||
return params
|
||||
endfunction
|
||||
|
||||
function! gina#core#buffer#param(expr, attr, ...) abort
|
||||
if !has_key(s:DEFAULT_PARAMS_ATTRIBUTES, a:attr)
|
||||
throw gina#core#revelator#critical(printf(
|
||||
\ 'Unknown attribute "%s" has specified',
|
||||
\ a:attr,
|
||||
\))
|
||||
endif
|
||||
let default = get(a:000, 0, s:DEFAULT_PARAMS_ATTRIBUTES[a:attr])
|
||||
let params = gina#core#buffer#parse(a:expr)
|
||||
return gina#util#get(params, a:attr, default)
|
||||
endfunction
|
||||
|
||||
function! gina#core#buffer#open(bufname, ...) abort
|
||||
let options = extend({
|
||||
\ 'mods': '',
|
||||
\ 'group': '',
|
||||
\ 'opener': '',
|
||||
\ 'origin': v:true,
|
||||
\ 'range': 'tabpage',
|
||||
\ 'cmdarg': '',
|
||||
\ 'width': v:null,
|
||||
\ 'height': v:null,
|
||||
\ 'line': v:null,
|
||||
\ 'col': v:null,
|
||||
\ 'callback': v:null,
|
||||
\}, get(a:000, 0, {}),
|
||||
\)
|
||||
let bufname = s:normalize_bufname(a:bufname)
|
||||
" Move focus to an origin window if necessary
|
||||
if options.origin isnot# v:null && s:is_locator_available(options.opener)
|
||||
let origin = options.origin is# v:true ? winnr() : options.origin
|
||||
call gina#core#locator#focus(origin)
|
||||
endif
|
||||
" Open a buffer
|
||||
if options.callback is# v:null
|
||||
let context = s:open_without_callback(bufname, options)
|
||||
else
|
||||
let context = s:open_with_callback(bufname, options)
|
||||
endif
|
||||
" Resize width/height if necessary
|
||||
if options.width && options.width != winwidth(0)
|
||||
execute printf('vertical resize %d', options.width)
|
||||
endif
|
||||
if options.height && options.height != winheight(0)
|
||||
execute printf('resize %d', options.height)
|
||||
endif
|
||||
" Move cursor if necessary
|
||||
call setpos('.', [
|
||||
\ 0,
|
||||
\ options.line is# v:null ? line('.') : options.line,
|
||||
\ options.col is# v:null ? col('.') : options.col,
|
||||
\ 0,
|
||||
\])
|
||||
normal! zvzz
|
||||
" Finalize
|
||||
call context.end()
|
||||
return context
|
||||
endfunction
|
||||
|
||||
function! gina#core#buffer#focus(expr) abort
|
||||
return s:Window.focus_buffer(a:expr)
|
||||
endfunction
|
||||
|
||||
function! gina#core#buffer#assign_cmdarg(...) abort
|
||||
let guard = s:Guard.store(['&l:modifiable'])
|
||||
try
|
||||
setlocal modifiable
|
||||
let cmdarg = a:0 == 0 ? v:cmdarg : a:1
|
||||
let fileencoding = matchstr(cmdarg, '++enc=\zs\S\+')
|
||||
if !empty(fileencoding)
|
||||
let &l:fileencoding = fileencoding
|
||||
endif
|
||||
let fileformat = matchstr(cmdarg, '++ff=\zs\S\+')
|
||||
if !empty(fileformat)
|
||||
let &l:fileformat = fileformat
|
||||
endif
|
||||
finally
|
||||
call guard.restore()
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:normalize_bufname(bufname) abort
|
||||
" The {bufname}
|
||||
" 1. Could not be started/ended with whitespaces
|
||||
" 2. Could not ends with ':' in Windows
|
||||
" 3. Should not ends with '/' in Vim 8 (opening a buffer fail randomly)
|
||||
let oldname = ''
|
||||
let newname = a:bufname
|
||||
while oldname !=# newname
|
||||
let oldname = newname
|
||||
let newname = substitute(newname, '\%(^\s\+\|\s\+$\)', '', 'g')
|
||||
let newname = substitute(newname, '\:\+$', '', '')
|
||||
let newname = substitute(newname, '[\\/]$', '', '')
|
||||
endwhile
|
||||
return newname
|
||||
endfunction
|
||||
|
||||
function! s:open_without_callback(bufname, options) abort
|
||||
let context = s:Opener.open(a:bufname, {
|
||||
\ 'mods': a:options.mods,
|
||||
\ 'cmdarg': a:options.cmdarg,
|
||||
\ 'group': a:options.group,
|
||||
\ 'opener': a:options.opener,
|
||||
\ 'range': a:options.range,
|
||||
\})
|
||||
return context
|
||||
endfunction
|
||||
|
||||
function! s:open_with_callback(bufname, options) abort
|
||||
" Open a buffer without BufReadCmd
|
||||
let guard = s:Guard.store(['&eventignore'])
|
||||
try
|
||||
set eventignore+=BufReadCmd
|
||||
let context = s:Opener.open(a:bufname, {
|
||||
\ 'mods': a:options.mods,
|
||||
\ 'group': a:options.group,
|
||||
\ 'opener': a:options.opener,
|
||||
\ 'range': a:options.range,
|
||||
\})
|
||||
finally
|
||||
call guard.restore()
|
||||
endtry
|
||||
" NOTE:
|
||||
" The content of the buffer MUST NOT be modified by callback while 'edit'
|
||||
" command will be called to override the content later.
|
||||
let content = getline(1, '$')
|
||||
call call(
|
||||
\ a:options.callback.fn,
|
||||
\ get(a:options.callback, 'args', []),
|
||||
\ a:options.callback
|
||||
\)
|
||||
if content != getline(1, '$')
|
||||
throw gina#core#revelator#critical(
|
||||
\ 'A buffer content could not be modified by callback'
|
||||
\)
|
||||
endif
|
||||
" Update content
|
||||
if !&modified
|
||||
execute 'keepjumps edit' a:options.cmdarg
|
||||
endif
|
||||
return context
|
||||
endfunction
|
||||
|
||||
function! s:is_locator_available(opener) abort
|
||||
if a:opener =~# '\<p\%[tag]!\?\>'
|
||||
return 0
|
||||
elseif a:opener =~# '\<ped\%[it]!\?\>'
|
||||
return 0
|
||||
elseif a:opener =~# '\<ps\%[earch]!\?\>'
|
||||
return 0
|
||||
elseif a:opener =~# '\<\%(tabe\%[dit]\|tabnew\)\>'
|
||||
return 0
|
||||
elseif a:opener =~# '\<tabf\%[ind]\>'
|
||||
return 0
|
||||
endif
|
||||
return 1
|
||||
endfunction
|
89
bundle/gina.vim/autoload/gina/core/console.vim
Normal file
89
bundle/gina.vim/autoload/gina/core/console.vim
Normal file
@ -0,0 +1,89 @@
|
||||
let s:Console = vital#gina#import('Vim.Console')
|
||||
let s:Console.prefix = '[gina] '
|
||||
|
||||
|
||||
if has('nvim')
|
||||
function! gina#core#console#message(msg) abort
|
||||
return s:message(a:msg)
|
||||
endfunction
|
||||
else
|
||||
" NOTE:
|
||||
" Vim 8.0.0329 will not echo entire message which was invoked in timer/job.
|
||||
" While echo pipe is used to inform the result of the process to a user, it
|
||||
" is kind critical so use autocmd to forcedly invoke message.
|
||||
let s:message_queue = []
|
||||
function! gina#core#console#message(msg) abort
|
||||
augroup gina_core_console_message_internal
|
||||
autocmd! *
|
||||
autocmd CursorMoved * call s:message_callback()
|
||||
autocmd CursorHold * call s:message_callback()
|
||||
autocmd InsertEnter * call s:message_callback()
|
||||
augroup END
|
||||
call add(s:message_queue, a:msg)
|
||||
endfunction
|
||||
|
||||
function! s:message_callback() abort
|
||||
while !empty(s:message_queue)
|
||||
let msg = remove(s:message_queue, 0)
|
||||
call s:message(msg)
|
||||
endwhile
|
||||
augroup gina_core_console_message_internal
|
||||
autocmd! *
|
||||
augroup END
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! gina#core#console#echo(...) abort
|
||||
return call(s:Console.echo, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#echon(...) abort
|
||||
return call(s:Console.echon, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#echomsg(...) abort
|
||||
return call(s:Console.echomsg, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#debug(...) abort
|
||||
return call(s:Console.debug, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#info(...) abort
|
||||
return call(s:Console.info, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#warn(...) abort
|
||||
return call(s:Console.warn, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#error(...) abort
|
||||
return call(s:Console.error, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#ask(...) abort
|
||||
return call(s:Console.ask, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#confirm(...) abort
|
||||
return call(s:Console.confirm, a:000, s:Console)
|
||||
endfunction
|
||||
|
||||
function! gina#core#console#ask_or_cancel(...) abort
|
||||
let result = call(s:Console.ask, a:000, s:Console)
|
||||
if empty(result)
|
||||
throw gina#core#revelator#info('Canceled')
|
||||
endif
|
||||
return result
|
||||
endfunction
|
||||
|
||||
function! s:message(msg) abort
|
||||
if g:gina#core#console#enable_message_history
|
||||
return gina#core#console#echomsg(a:msg)
|
||||
endif
|
||||
return gina#core#console#echo(a:msg)
|
||||
endfunction
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'enable_message_history': 0,
|
||||
\})
|
117
bundle/gina.vim/autoload/gina/core/diffjump.vim
Normal file
117
bundle/gina.vim/autoload/gina/core/diffjump.vim
Normal file
@ -0,0 +1,117 @@
|
||||
function! s:find_path_and_lnum(git, src_prefix, dst_prefix) abort
|
||||
if getline('.') =~# '^-'
|
||||
return s:find_path_and_lnum_lhs(a:git, a:src_prefix)
|
||||
elseif getline('.') =~# '^[ +]'
|
||||
return s:find_path_and_lnum_rhs(a:git, a:dst_prefix)
|
||||
else
|
||||
return v:null
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:find_path_and_lnum_lhs(git, prefix) abort
|
||||
let lnum = search('^@@', 'bnW')
|
||||
let m = matchlist(
|
||||
\ getline(lnum),
|
||||
\ '^@@ -\(\d\+\)\%(,\(\d\+\)\)\? +\(\d\+\),\(\d\+\) @@\(.*\)$'
|
||||
\)
|
||||
if empty(m)
|
||||
return v:null
|
||||
endif
|
||||
let path = matchstr(
|
||||
\ getline(search('^--- ', 'bnW')),
|
||||
\ printf('^--- %s\zs.*$', a:prefix),
|
||||
\)
|
||||
if empty(path)
|
||||
return v:null
|
||||
endif
|
||||
let n = len(filter(
|
||||
\ map(range(lnum, line('.')), { _, v -> getline(v) }),
|
||||
\ { _, v -> v !~# '^+' }
|
||||
\))
|
||||
return {'path': path, 'lnum': m[1] + n - 2, 'side': 0}
|
||||
endfunction
|
||||
|
||||
function! s:find_path_and_lnum_rhs(git, prefix) abort
|
||||
if getline('.') !~# '^[ -+]'
|
||||
return v:null
|
||||
endif
|
||||
let lnum = search('^@@', 'bnW')
|
||||
let m = matchlist(
|
||||
\ getline(lnum),
|
||||
\ '^@@ -\(\d\+\)\%(,\(\d\+\)\)\? +\(\d\+\),\(\d\+\) @@\(.*\)$'
|
||||
\)
|
||||
if empty(m)
|
||||
return v:null
|
||||
endif
|
||||
let path = matchstr(
|
||||
\ getline(search('^+++ ', 'bnW')),
|
||||
\ printf('^+++ %s\zs.*$', a:prefix)
|
||||
\)
|
||||
if empty(path)
|
||||
return v:null
|
||||
endif
|
||||
let n = len(filter(
|
||||
\ map(range(lnum, line('.')), { _, v -> getline(v) }),
|
||||
\ { _, v -> v !~# '^-' }
|
||||
\))
|
||||
return {'path': path, 'lnum': m[3] + n - 2, 'side': 1}
|
||||
endfunction
|
||||
|
||||
function! s:jump(opener) abort
|
||||
let git = gina#core#get_or_fail()
|
||||
let args = gina#core#meta#get_or_fail('args')
|
||||
let config = gina#core#repo#config(git)
|
||||
|
||||
if get(config, 'diff.noprefix', '') =~? 'true' || args.get('--no-prefix')
|
||||
let src_prefix = ''
|
||||
let dst_prefix = ''
|
||||
else
|
||||
let src_prefix = args.get('--src-prefix', '[aic]/')
|
||||
let dst_prefix = args.get('--dst-prefix', '[bwi]/')
|
||||
endif
|
||||
|
||||
let pathinfo = s:find_path_and_lnum(git, src_prefix, dst_prefix)
|
||||
if pathinfo is v:null
|
||||
return 1
|
||||
endif
|
||||
|
||||
let rev = pathinfo.side ? args.params.rev2 : args.params.rev1
|
||||
if gina#core#args#is_worktree(rev) && pathinfo.side == 1
|
||||
call gina#core#console#debug(printf(
|
||||
\ 'Gina edit --line=%d --opener=%s %s',
|
||||
\ pathinfo.lnum,
|
||||
\ a:opener,
|
||||
\ pathinfo.path,
|
||||
\))
|
||||
execute printf(
|
||||
\ 'Gina edit --line=%d --opener=%s %s',
|
||||
\ pathinfo.lnum,
|
||||
\ a:opener,
|
||||
\ pathinfo.path,
|
||||
\)
|
||||
else
|
||||
call gina#core#console#debug(printf(
|
||||
\ 'Gina show --line=%d --opener=%s %s:%s',
|
||||
\ pathinfo.lnum,
|
||||
\ a:opener,
|
||||
\ rev,
|
||||
\ pathinfo.path,
|
||||
\))
|
||||
execute printf(
|
||||
\ 'Gina show --line=%d --opener=%s %s:%s',
|
||||
\ pathinfo.lnum,
|
||||
\ a:opener,
|
||||
\ rev,
|
||||
\ pathinfo.path,
|
||||
\)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#core#diffjump#jump(...) abort
|
||||
let opener = a:0 ? a:1 : ''
|
||||
let opener = empty(opener) ? 'edit' : opener
|
||||
return gina#core#revelator#call(
|
||||
\ function('s:jump'),
|
||||
\ [opener],
|
||||
\)
|
||||
endfunction
|
77
bundle/gina.vim/autoload/gina/core/emitter.vim
Normal file
77
bundle/gina.vim/autoload/gina/core/emitter.vim
Normal file
@ -0,0 +1,77 @@
|
||||
let s:Emitter = vital#gina#import('App.Emitter')
|
||||
let s:modified_timer = v:null
|
||||
|
||||
|
||||
function! gina#core#emitter#emit(name, ...) abort
|
||||
call call(s:Emitter.emit, [a:name] + a:000, s:Emitter)
|
||||
endfunction
|
||||
|
||||
function! gina#core#emitter#subscribe(name, listener, ...) abort
|
||||
call call(s:Emitter.subscribe, [a:name, a:listener] + a:000, s:Emitter)
|
||||
endfunction
|
||||
|
||||
function! gina#core#emitter#unsubscribe(name, listener, ...) abort
|
||||
call call(s:Emitter.unsubscribe, [a:name, a:listener] + a:000, s:Emitter)
|
||||
endfunction
|
||||
|
||||
function! gina#core#emitter#add_middleware(middleware) abort
|
||||
call call(s:Emitter.add_middleware, [a:middleware] + a:000, s:Emitter)
|
||||
endfunction
|
||||
|
||||
function! gina#core#emitter#remove_middleware(...) abort
|
||||
call call(s:Emitter.remove_middleware, a:000, s:Emitter)
|
||||
endfunction
|
||||
|
||||
|
||||
" Subscribe ------------------------------------------------------------------
|
||||
function! s:on_modified(...) abort
|
||||
if !empty(gina#process#runnings())
|
||||
" DO NOT update if there are some running process
|
||||
call gina#core#emitter#emit('modified:delay')
|
||||
return
|
||||
endif
|
||||
let winid_saved = win_getid()
|
||||
for winnr in range(1, winnr('$'))
|
||||
let bufnr = winbufnr(winnr)
|
||||
if !getbufvar(bufnr, '&modified')
|
||||
\ && getbufvar(bufnr, '&autoread')
|
||||
\ && bufname(bufnr) =~# '^gina://'
|
||||
call win_gotoid(bufwinid(bufnr))
|
||||
edit
|
||||
endif
|
||||
endfor
|
||||
call win_gotoid(winid_saved)
|
||||
endfunction
|
||||
|
||||
function! s:on_modified_delay() abort
|
||||
if s:modified_timer isnot# v:null
|
||||
" Do not emit 'modified' for previous 'modified:delay'
|
||||
silent! call timer_stop(s:modified_timer)
|
||||
endif
|
||||
let s:modified_timer = timer_start(
|
||||
\ g:gina#core#emitter#modified_delay,
|
||||
\ function('s:emit_modified')
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:emit_modified(...) abort
|
||||
call gina#core#emitter#emit('modified')
|
||||
endfunction
|
||||
|
||||
if !exists('s:subscribed')
|
||||
let s:subscribed = 1
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'modified',
|
||||
\ function('s:on_modified')
|
||||
\)
|
||||
|
||||
call gina#core#emitter#subscribe(
|
||||
\ 'modified:delay',
|
||||
\ function('s:on_modified_delay')
|
||||
\)
|
||||
endif
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'modified_delay': 10,
|
||||
\})
|
70
bundle/gina.vim/autoload/gina/core/locator.vim
Normal file
70
bundle/gina.vim/autoload/gina/core/locator.vim
Normal file
@ -0,0 +1,70 @@
|
||||
function! gina#core#locator#find(origin) abort
|
||||
let nwinnr = winnr('$')
|
||||
if nwinnr == 1
|
||||
return 1
|
||||
endif
|
||||
let origin = a:origin == 0 ? winnr() : a:origin
|
||||
let former = range(origin, winnr('$'))
|
||||
let latter = reverse(range(1, origin - 1))
|
||||
for winnr in (former + latter)
|
||||
if gina#core#locator#is_suitable(winnr)
|
||||
return winnr
|
||||
endif
|
||||
endfor
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! gina#core#locator#focus(origin) abort
|
||||
let winnr = gina#core#locator#find(a:origin)
|
||||
if winnr == 0 || winnr == winnr()
|
||||
return 1
|
||||
endif
|
||||
call win_gotoid(win_getid(winnr))
|
||||
endfunction
|
||||
|
||||
function! gina#core#locator#attach() abort
|
||||
augroup gina_core_locator_local_internal
|
||||
autocmd! * <buffer>
|
||||
autocmd WinLeave <buffer> call s:on_WinLeave()
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! gina#core#locator#detach() abort
|
||||
augroup gina_core_locator_local_internal
|
||||
autocmd! * <buffer>
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! gina#core#locator#is_suitable(winnr) abort
|
||||
if getbufvar(winbufnr(a:winnr), '&previewwindow')
|
||||
\ || winwidth(a:winnr) < g:gina#core#locator#winwidth_threshold
|
||||
\ || winheight(a:winnr) < g:gina#core#locator#winheight_threshold
|
||||
return 0
|
||||
endif
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:on_WinLeave() abort
|
||||
let s:info = {
|
||||
\ 'nwin': winnr('$'),
|
||||
\ 'previous': win_getid(winnr('#'))
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:on_WinEnter() abort
|
||||
if exists('s:info') && winnr('$') < s:info.nwin
|
||||
call gina#core#locator#focus(win_id2win(s:info.previous) || winnr())
|
||||
endif
|
||||
silent! unlet! s:info
|
||||
endfunction
|
||||
|
||||
augroup gina_core_locator_internal
|
||||
autocmd! *
|
||||
autocmd WinEnter * nested call s:on_WinEnter()
|
||||
augroup END
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'winwidth_threshold': &columns / 4,
|
||||
\ 'winheight_threshold': &lines / 3,
|
||||
\})
|
55
bundle/gina.vim/autoload/gina/core/meta.vim
Normal file
55
bundle/gina.vim/autoload/gina/core/meta.vim
Normal file
@ -0,0 +1,55 @@
|
||||
let s:Cache = vital#gina#import('System.Cache.Memory')
|
||||
|
||||
|
||||
function! gina#core#meta#get(...) abort
|
||||
let meta = s:meta('%')
|
||||
return call(meta.get, a:000, meta)
|
||||
endfunction
|
||||
|
||||
function! gina#core#meta#set(...) abort
|
||||
let meta = s:meta('%')
|
||||
return call(meta.set, a:000, meta)
|
||||
endfunction
|
||||
|
||||
function! gina#core#meta#has(...) abort
|
||||
let meta = s:meta('%')
|
||||
return call(meta.has, a:000, meta)
|
||||
endfunction
|
||||
|
||||
function! gina#core#meta#remove(...) abort
|
||||
let meta = s:meta('%')
|
||||
return call(meta.remove, a:000, meta)
|
||||
endfunction
|
||||
|
||||
function! gina#core#meta#clear(...) abort
|
||||
let meta = s:meta('%')
|
||||
return call(meta.clear, a:000, meta)
|
||||
endfunction
|
||||
|
||||
function! gina#core#meta#get_or_fail(name) abort
|
||||
let meta = s:meta('%')
|
||||
if !meta.has(a:name)
|
||||
throw gina#core#revelator#critical(printf(
|
||||
\ 'A required meta value "%s" does not exist on "%s"',
|
||||
\ a:name,
|
||||
\ bufname('%'),
|
||||
\))
|
||||
endif
|
||||
return meta.get(a:name)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:meta(expr) abort
|
||||
let bufnr = bufnr(a:expr)
|
||||
if !bufexists(bufnr)
|
||||
" Always return a fresh cache instance
|
||||
return s:Cache.new()
|
||||
endif
|
||||
let meta = getbufvar(bufnr, 'gina_meta', {})
|
||||
if empty(meta)
|
||||
let meta = s:Cache.new()
|
||||
call setbufvar(bufnr, 'gina_meta', meta)
|
||||
endif
|
||||
return meta
|
||||
endfunction
|
12
bundle/gina.vim/autoload/gina/core/options.vim
Normal file
12
bundle/gina.vim/autoload/gina/core/options.vim
Normal file
@ -0,0 +1,12 @@
|
||||
let s:Options = vital#gina#import('Options')
|
||||
|
||||
function! gina#core#options#new() abort
|
||||
return s:Options.new()
|
||||
endfunction
|
||||
|
||||
function! gina#core#options#help_if_necessary(args, options) abort
|
||||
if a:args.get('-h|--help')
|
||||
call a:options.help()
|
||||
throw gina#core#revelator#cancel()
|
||||
endif
|
||||
endfunction
|
76
bundle/gina.vim/autoload/gina/core/path.vim
Normal file
76
bundle/gina.vim/autoload/gina/core/path.vim
Normal file
@ -0,0 +1,76 @@
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:is_windows = has('win32') || has('win64')
|
||||
|
||||
|
||||
if s:is_windows
|
||||
function! gina#core#path#expand(expr) abort
|
||||
return s:Path.unixpath(s:expand(s:realpath(a:expr)))
|
||||
endfunction
|
||||
|
||||
function! gina#core#path#abspath(path, ...) abort
|
||||
let path = s:realpath(a:path)
|
||||
return s:Path.unixpath(call('s:abspath', [path] + a:000))
|
||||
endfunction
|
||||
|
||||
function! gina#core#path#relpath(path, ...) abort
|
||||
let path = s:realpath(a:path)
|
||||
return s:Path.unixpath(call('s:relpath', [path] + a:000))
|
||||
endfunction
|
||||
|
||||
function! s:realpath(path) abort
|
||||
if a:path =~# '^\w\+://'
|
||||
return s:Path.unixpath(a:path)
|
||||
endif
|
||||
return s:Path.realpath(a:path)
|
||||
endfunction
|
||||
else
|
||||
function! gina#core#path#expand(expr) abort
|
||||
return s:expand(a:expr)
|
||||
endfunction
|
||||
|
||||
function! gina#core#path#abspath(path, ...) abort
|
||||
return call('s:abspath', [a:path] + a:000)
|
||||
endfunction
|
||||
|
||||
function! gina#core#path#relpath(path, ...) abort
|
||||
return call('s:relpath', [a:path] + a:000)
|
||||
endfunction
|
||||
endif
|
||||
|
||||
|
||||
function! s:expand(expr) abort
|
||||
if empty(a:expr) || a:expr[0] ==# ':'
|
||||
return a:expr
|
||||
elseif a:expr[:6] ==# 'gina://'
|
||||
let git = gina#core#get_or_fail({'expr': a:expr})
|
||||
let path = gina#core#buffer#param(a:expr, 'path')
|
||||
return empty(path) ? '' : s:Path.join(git.worktree, path)
|
||||
elseif a:expr[0] =~# '[%#<]'
|
||||
let m = matchlist(a:expr, '^\([%#]\|<\w\+>\)\(.*\)')
|
||||
let expr = expand(m[1])
|
||||
let modifiers = m[2]
|
||||
return fnamemodify(s:expand(expr), modifiers)
|
||||
endif
|
||||
return a:expr
|
||||
endfunction
|
||||
|
||||
function! s:abspath(path, ...) abort
|
||||
if s:Path.is_absolute(a:path) || a:path[0] ==# ':'
|
||||
return a:path
|
||||
endif
|
||||
let root = s:Path.remove_last_separator(a:0 == 0 ? getcwd() : a:1)
|
||||
return s:Path.join(root, a:path)
|
||||
endfunction
|
||||
|
||||
function! s:relpath(path, ...) abort
|
||||
if s:Path.is_relative(a:path) || a:path[0] ==# ':'
|
||||
return a:path
|
||||
endif
|
||||
let root = s:Path.remove_last_separator(a:0 == 0 ? getcwd() : a:1)
|
||||
let pattern = s:String.escape_pattern(root . s:Path.separator())
|
||||
return a:path =~# '^' . pattern
|
||||
\ ? matchstr(a:path, '^' . pattern . '\zs.*')
|
||||
\ : a:path
|
||||
endfunction
|
49
bundle/gina.vim/autoload/gina/core/repo.vim
Normal file
49
bundle/gina.vim/autoload/gina/core/repo.vim
Normal file
@ -0,0 +1,49 @@
|
||||
let s:Git = vital#gina#import('Git')
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Store = vital#gina#import('System.Store')
|
||||
|
||||
|
||||
function! gina#core#repo#abspath(git, expr) abort
|
||||
return gina#core#path#abspath(a:expr, a:git.worktree)
|
||||
endfunction
|
||||
|
||||
function! gina#core#repo#relpath(git, expr) abort
|
||||
let path = gina#core#path#expand(a:expr)
|
||||
if s:Path.is_relative(s:Path.realpath(path))
|
||||
return path
|
||||
endif
|
||||
let path = gina#core#path#relpath(path, a:git.worktree)
|
||||
if path ==# path && path !=# resolve(path)
|
||||
return gina#core#path#relpath(resolve(path), a:git.worktree)
|
||||
endif
|
||||
return path
|
||||
endfunction
|
||||
|
||||
function! gina#core#repo#config(git) abort
|
||||
let slug = eval(s:Store.get_slug_expr())
|
||||
let store = s:Store.of(s:Git.resolve(a:git, 'config'))
|
||||
let config = store.get(slug, {})
|
||||
if !empty(config)
|
||||
return config
|
||||
endif
|
||||
let result = gina#process#call(a:git, ['config', '--list'])
|
||||
if result.status
|
||||
throw gina#process#errormsg(result)
|
||||
endif
|
||||
let config = {}
|
||||
for record in filter(copy(result.stdout), '!empty(v:val)')
|
||||
call s:extend_config(config, record)
|
||||
endfor
|
||||
call store.set(slug, config)
|
||||
return config
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:extend_config(config, record) abort
|
||||
let m = matchlist(a:record, '^\(.\+\)=\(.*\)$')
|
||||
if empty(m)
|
||||
return
|
||||
endif
|
||||
let a:config[tolower(m[1])] = m[2]
|
||||
endfunction
|
57
bundle/gina.vim/autoload/gina/core/revelator.vim
Normal file
57
bundle/gina.vim/autoload/gina/core/revelator.vim
Normal file
@ -0,0 +1,57 @@
|
||||
let s:Revelator = vital#gina#import('App.Revelator')
|
||||
|
||||
|
||||
function! gina#core#revelator#cancel() abort
|
||||
return call(s:Revelator.message, ['CANCEL', ''], s:Revelator)
|
||||
endfunction
|
||||
|
||||
function! gina#core#revelator#info(msg) abort
|
||||
return call(s:Revelator.info, [a:msg], s:Revelator)
|
||||
endfunction
|
||||
|
||||
function! gina#core#revelator#warning(msg) abort
|
||||
return call(s:Revelator.warning, [a:msg], s:Revelator)
|
||||
endfunction
|
||||
|
||||
function! gina#core#revelator#error(msg) abort
|
||||
return call(s:Revelator.error, [a:msg], s:Revelator)
|
||||
endfunction
|
||||
|
||||
function! gina#core#revelator#critical(msg) abort
|
||||
return call(s:Revelator.critical, [a:msg], s:Revelator)
|
||||
endfunction
|
||||
|
||||
function! gina#core#revelator#call(funcref, args, ...) abort
|
||||
return call(s:Revelator.call, [a:funcref, a:args] + a:000, s:Revelator)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:receiver(revelation) abort
|
||||
if a:revelation.category ==# 'CANCEL'
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'INFO'
|
||||
redraw
|
||||
call gina#core#console#info(a:revelation.message)
|
||||
call gina#core#console#debug(a:revelation.throwpoint)
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'WARNING'
|
||||
redraw
|
||||
call gina#core#console#warn(a:revelation.message)
|
||||
call gina#core#console#debug(a:revelation.throwpoint)
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'ERROR'
|
||||
redraw
|
||||
call gina#core#console#error(a:revelation.message)
|
||||
call gina#core#console#debug(a:revelation.throwpoint)
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'CRITICAL'
|
||||
redraw
|
||||
call gina#core#console#error(a:revelation.message)
|
||||
call gina#core#console#error(a:revelation.throwpoint)
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
call s:Revelator.unregister(s:Revelator.get_default_receiver())
|
||||
call s:Revelator.register(function('s:receiver'))
|
108
bundle/gina.vim/autoload/gina/core/spinner.vim
Normal file
108
bundle/gina.vim/autoload/gina/core/spinner.vim
Normal file
@ -0,0 +1,108 @@
|
||||
scriptencoding utf-8
|
||||
|
||||
if $LANG ==# 'C'
|
||||
let s:frames = ['-', '\', '|', '/']
|
||||
else
|
||||
let s:frames = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷']
|
||||
endif
|
||||
|
||||
function! gina#core#spinner#new(expr, ...) abort
|
||||
let options = extend({
|
||||
\ 'frames': g:gina#core#spinner#frames,
|
||||
\ 'message': g:gina#core#spinner#message,
|
||||
\ 'delaytime': g:gina#core#spinner#delaytime,
|
||||
\ 'updatetime': g:gina#core#spinner#updatetime,
|
||||
\}, a:0 ? a:1 : {})
|
||||
let spinner = deepcopy(s:spinner)
|
||||
let spinner._bufnr = bufnr(a:expr)
|
||||
let spinner._frames = options.frames
|
||||
let spinner._message = options.message
|
||||
let spinner._delaytime = options.delaytime
|
||||
let spinner._updatetime = options.updatetime
|
||||
return spinner
|
||||
endfunction
|
||||
|
||||
function! gina#core#spinner#start(expr, ...) abort
|
||||
let spinner = gina#core#spinner#new(a:expr, a:0 ? a:1 : {})
|
||||
call spinner.start_delay()
|
||||
return spinner
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
function! s:_spinner_next() abort dict
|
||||
let index = self._index + 1
|
||||
let self._index = index >= len(self._frames) ? 0 : index
|
||||
return self._index
|
||||
endfunction
|
||||
|
||||
function! s:_spinner_text() abort dict
|
||||
let face = self._frames[self._index]
|
||||
return ' ' . face . ' ' . self._message
|
||||
endfunction
|
||||
|
||||
function! s:_spinner_start_delay() abort dict
|
||||
if self._timer isnot# v:null || self._timer_delay isnot# v:null || self._delaytime < 0
|
||||
return
|
||||
endif
|
||||
let self._timer_delay = timer_start(
|
||||
\ self._delaytime,
|
||||
\ self.start,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:_spinner_start(...) abort dict
|
||||
if self._timer isnot# v:null || self._delaytime < 0
|
||||
return
|
||||
endif
|
||||
silent! call timer_stop(self._timer_delay)
|
||||
let self._statusline = getbufvar(self._bufnr, '&statusline')
|
||||
let self._timer = timer_start(
|
||||
\ self._updatetime,
|
||||
\ function('s:update_spinner', [self]),
|
||||
\ { 'repeat': -1 }
|
||||
\)
|
||||
let self._timer_delay = v:null
|
||||
endfunction
|
||||
|
||||
function! s:_spinner_stop() abort dict
|
||||
if self._timer_delay isnot# v:null
|
||||
call timer_stop(self._timer_delay)
|
||||
let self._timer_delay = v:null
|
||||
endif
|
||||
if self._timer is# v:null
|
||||
return
|
||||
endif
|
||||
call timer_stop(self._timer)
|
||||
call setbufvar(self._bufnr, '&statusline', self._statusline)
|
||||
let self._timer = v:null
|
||||
endfunction
|
||||
|
||||
function! s:update_spinner(spinner, timer) abort
|
||||
if !bufexists(a:spinner._bufnr)
|
||||
call a:spinner.stop()
|
||||
elseif bufwinnr(a:spinner._bufnr) >= 0
|
||||
call a:spinner.next()
|
||||
call setbufvar(a:spinner._bufnr, '&statusline', a:spinner.text())
|
||||
endif
|
||||
endfunction
|
||||
|
||||
let s:spinner = {
|
||||
\ '_timer': v:null,
|
||||
\ '_timer_delay': v:null,
|
||||
\ '_bufnr': 0,
|
||||
\ '_index': 0,
|
||||
\ 'next': function('s:_spinner_next'),
|
||||
\ 'text': function('s:_spinner_text'),
|
||||
\ 'start': function('s:_spinner_start'),
|
||||
\ 'start_delay': function('s:_spinner_start_delay'),
|
||||
\ 'stop': function('s:_spinner_stop'),
|
||||
\}
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'frames': s:frames,
|
||||
\ 'message': 'Loading ...',
|
||||
\ 'delaytime': 500,
|
||||
\ 'updatetime': 100,
|
||||
\})
|
59
bundle/gina.vim/autoload/gina/core/timestamper.vim
Normal file
59
bundle/gina.vim/autoload/gina/core/timestamper.vim
Normal file
@ -0,0 +1,59 @@
|
||||
let s:DateTime = vital#gina#import('DateTime')
|
||||
|
||||
|
||||
function! gina#core#timestamper#new(...) abort
|
||||
let timestamper = extend({
|
||||
\ 'now': s:DateTime.now(),
|
||||
\ 'months': 3,
|
||||
\ 'format1': '%d %b',
|
||||
\ 'format2': '%d %b, %Y',
|
||||
\}, get(a:000, 0, {})
|
||||
\)
|
||||
let timestamper = extend(timestamper, s:timestamper, 'keep')
|
||||
let timestamper._cache_timezone = {}
|
||||
let timestamper._cache_datetime = {}
|
||||
let timestamper._cache_timestamp = {}
|
||||
return timestamper
|
||||
endfunction
|
||||
|
||||
|
||||
" Timestamper ----------------------------------------------------------------
|
||||
let s:timestamper = {}
|
||||
|
||||
function! s:timestamper.timezone(timezone) abort
|
||||
if has_key(self._cache_timezone, a:timezone)
|
||||
return self._cache_timezone[a:timezone]
|
||||
endif
|
||||
let timezone = s:DateTime.timezone(a:timezone)
|
||||
let self._cache_timezone[a:timezone] = timezone
|
||||
return timezone
|
||||
endfunction
|
||||
|
||||
function! s:timestamper.datetime(epoch, timezone) abort
|
||||
let cname = a:epoch . a:timezone
|
||||
if has_key(self._cache_datetime, cname)
|
||||
return self._cache_datetime[cname]
|
||||
endif
|
||||
let timezone = self.timezone(a:timezone)
|
||||
let datetime = s:DateTime.from_unix_time(a:epoch, timezone)
|
||||
let self._cache_datetime[cname] = datetime
|
||||
return datetime
|
||||
endfunction
|
||||
|
||||
function! s:timestamper.format(epoch, timezone) abort
|
||||
let cname = a:epoch . a:timezone
|
||||
if has_key(self._cache_timestamp, cname)
|
||||
return self._cache_timestamp[cname]
|
||||
endif
|
||||
let datetime = self.datetime(a:epoch, a:timezone)
|
||||
let timedelta = datetime.delta(self.now)
|
||||
if timedelta.duration().months() < self.months
|
||||
let timestamp = timedelta.about()
|
||||
elseif datetime.year() == self.now.year()
|
||||
let timestamp = datetime.strftime(self.format1)
|
||||
else
|
||||
let timestamp = datetime.strftime(self.format2)
|
||||
endif
|
||||
let self._cache_timestamp[cname] = timestamp
|
||||
return timestamp
|
||||
endfunction
|
66
bundle/gina.vim/autoload/gina/core/tracker.vim
Normal file
66
bundle/gina.vim/autoload/gina/core/tracker.vim
Normal file
@ -0,0 +1,66 @@
|
||||
function! gina#core#tracker#track(git, path, lnum, ...) abort
|
||||
let options = extend({
|
||||
\ 'lhs': '',
|
||||
\ 'rhs': 'HEAD',
|
||||
\ 'cache': 0,
|
||||
\}, a:0 ? a:1 : {}
|
||||
\)
|
||||
let result = s:investigate(
|
||||
\ a:git, a:path,
|
||||
\ options.lhs,
|
||||
\ options.rhs,
|
||||
\ options.cache
|
||||
\)
|
||||
let offset = 0
|
||||
for ref in result.refs
|
||||
if a:lnum > ref.ll && a:lnum < (ref.ll + ref.ls)
|
||||
let offset = a:lnum - ref.ll
|
||||
return offset <= ref.rs ? ref.rl + offset : ref.rl + ref.rs
|
||||
elseif a:lnum > ref.ll
|
||||
let offset += (ref.rs - ref.ls)
|
||||
else
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
return a:lnum + offset
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:investigate(git, path, lhs, rhs, cache) abort
|
||||
let result = gina#process#call_or_fail(a:git, filter([
|
||||
\ 'diff',
|
||||
\ '--unified=0',
|
||||
\ '--dst-prefix=',
|
||||
\ '--find-renames',
|
||||
\ '--inter-hunk-context=0',
|
||||
\ a:cache ? '--cache' : '',
|
||||
\ printf('%s..%s', a:lhs, a:rhs),
|
||||
\ '--',
|
||||
\ a:path,
|
||||
\], '!empty(v:val)'))
|
||||
let path = get(filter(copy(result.stdout), 'v:val =~# ''^+++'''), 0, a:path)
|
||||
let path = substitute(path, '^+++ ', '', '')
|
||||
let refs = map(
|
||||
\ filter(copy(result.stdout), 'v:val =~# ''^@@'''),
|
||||
\ 's:extract_ranges(v:val)'
|
||||
\)
|
||||
return {
|
||||
\ 'path': path,
|
||||
\ 'refs': refs,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:extract_ranges(record) abort
|
||||
let m = matchlist(
|
||||
\ a:record,
|
||||
\ '^@@ -\(\d\+\%(,\d\+\)\?\) +\(\d\+\%(,\d\+\)\?\) @@',
|
||||
\)
|
||||
let lhs = split(m[1], ',')
|
||||
let rhs = split(m[2], ',')
|
||||
return {
|
||||
\ 'll': abs(0 + lhs[0]),
|
||||
\ 'ls': 0 + get(lhs, 1, 1),
|
||||
\ 'rl': abs(0 + rhs[0]),
|
||||
\ 'rs': 0 + get(rhs, 1, 1),
|
||||
\}
|
||||
endfunction
|
89
bundle/gina.vim/autoload/gina/core/treeish.vim
Normal file
89
bundle/gina.vim/autoload/gina/core/treeish.vim
Normal file
@ -0,0 +1,89 @@
|
||||
let s:Path = vital#gina#import('System.Filepath')
|
||||
let s:Git = vital#gina#import('Git')
|
||||
|
||||
|
||||
function! gina#core#treeish#parse(treeish) abort
|
||||
" Ref: https://git-scm.com/docs/gitrevisions
|
||||
if a:treeish =~# '^:/' || a:treeish =~# '^[^:]*^{/' || a:treeish !~# ':'
|
||||
return [a:treeish, v:null]
|
||||
endif
|
||||
let m = matchlist(a:treeish, '^\(:[0-3]\|[^:]*\)\%(:\(.*\)\)\?$')
|
||||
return [m[1], m[2]]
|
||||
endfunction
|
||||
|
||||
function! gina#core#treeish#build(rev, path) abort
|
||||
let rev = a:rev is# v:null ? ':0' : a:rev
|
||||
if a:path is# v:null
|
||||
return rev
|
||||
endif
|
||||
return printf('%s:%s', rev, s:Path.unixpath(a:path))
|
||||
endfunction
|
||||
|
||||
function! gina#core#treeish#split(rev) abort
|
||||
if a:rev =~# '^.\{-}\.\.\..*$'
|
||||
let [lhs, rhs] = matchlist(a:rev, '^\(.\{-}\)\.\.\.\(.*\)$')[1 : 2]
|
||||
let lhs = empty(lhs) ? 'HEAD' : lhs
|
||||
let rhs = empty(rhs) ? 'HEAD' : rhs
|
||||
return [lhs . '...' . rhs, rhs]
|
||||
elseif a:rev =~# '^.\{-}\.\..*$'
|
||||
let [lhs, rhs] = matchlist(a:rev, '^\(.\{-}\)\.\.\(.*\)$')[1 : 2]
|
||||
let lhs = empty(lhs) ? 'HEAD' : lhs
|
||||
let rhs = empty(rhs) ? 'HEAD' : rhs
|
||||
return [lhs, rhs]
|
||||
else
|
||||
return [a:rev, '']
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#core#treeish#sha1(git, rev) abort
|
||||
let ref = s:Git.ref(a:git, a:rev)
|
||||
if !empty(ref)
|
||||
return ref.hash
|
||||
endif
|
||||
" Fallback to rev-parse (e.g. HEAD@{2.days.ago})
|
||||
let result = gina#process#call_or_fail(a:git, ['rev-parse', a:rev])
|
||||
return get(result.stdout, 0, '')
|
||||
endfunction
|
||||
|
||||
function! gina#core#treeish#resolve(git, rev, ...) abort
|
||||
let aggressive = a:0 ? a:1 : 0
|
||||
if a:rev =~# '^.\{-}\.\.\..*$'
|
||||
let [lhs, rhs] = matchlist(a:rev, '^\(.\{-}\)\.\.\.\(.*\)$')[1 : 2]
|
||||
let lhs = empty(lhs) ? 'HEAD' : lhs
|
||||
let rhs = empty(rhs) ? 'HEAD' : rhs
|
||||
return s:find_common_ancestor(a:git, lhs, rhs)
|
||||
elseif a:rev =~# '^.\{-}\.\..*$'
|
||||
let [lhs, _] = matchlist(a:rev, '^\(.\{-}\)\.\.\(.*\)$')[1 : 2]
|
||||
let lhs = empty(lhs) ? 'HEAD' : lhs
|
||||
if aggressive
|
||||
let ref = s:Git.ref(a:git, lhs)
|
||||
return get(ref, 'name', lhs)
|
||||
else
|
||||
return lhs
|
||||
endif
|
||||
elseif aggressive
|
||||
let ref = s:Git.ref(a:git, a:rev)
|
||||
return get(ref, 'name', a:rev)
|
||||
else
|
||||
return a:rev
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#core#treeish#validate(git, rev, path, ...) abort
|
||||
let treeish = gina#core#treeish#build(a:rev, a:path)
|
||||
let result = gina#process#call(a:git, ['rev-parse', treeish])
|
||||
if result.status
|
||||
throw gina#core#revelator#warning(a:0 ? a:1 : join(result.stderr, "\n"))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:find_common_ancestor(git, rev1, rev2) abort
|
||||
let lhs = empty(a:rev1) ? 'HEAD' : a:rev1
|
||||
let rhs = empty(a:rev2) ? 'HEAD' : a:rev2
|
||||
let result = gina#process#call_or_fail(a:git, [
|
||||
\ 'merge-base', lhs, rhs
|
||||
\])
|
||||
return get(result.stdout, 0, '')
|
||||
endfunction
|
18
bundle/gina.vim/autoload/gina/core/writer.vim
Normal file
18
bundle/gina.vim/autoload/gina/core/writer.vim
Normal file
@ -0,0 +1,18 @@
|
||||
let s:Writer = vital#gina#import('Vim.Buffer.Writer')
|
||||
|
||||
function! gina#core#writer#new(...) abort
|
||||
let options = extend({
|
||||
\ 'updatetime': g:gina#core#writer#updatetime,
|
||||
\}, a:0 ? a:1 : {},
|
||||
\)
|
||||
return s:Writer.new(options)
|
||||
endfunction
|
||||
|
||||
function! gina#core#writer#replace(...) abort
|
||||
return call(s:Writer.replace, a:000)
|
||||
endfunction
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'updatetime': 100,
|
||||
\})
|
76
bundle/gina.vim/autoload/gina/custom.vim
Normal file
76
bundle/gina.vim/autoload/gina/custom.vim
Normal file
@ -0,0 +1,76 @@
|
||||
let s:preferences = {}
|
||||
let s:pattern_preferences = {}
|
||||
|
||||
function! gina#custom#preference(scheme, ...) abort
|
||||
let readonly = a:0 ? a:1 : 1
|
||||
let preferences = a:scheme =~# '^/'
|
||||
\ ? s:pattern_preferences
|
||||
\ : s:preferences
|
||||
let preferences[a:scheme] = get(preferences, a:scheme, {})
|
||||
let preference = extend(preferences[a:scheme], {
|
||||
\ 'action': {
|
||||
\ 'aliases': [],
|
||||
\ 'shortens': [],
|
||||
\ },
|
||||
\ 'mapping': {
|
||||
\ 'mappings': [],
|
||||
\ },
|
||||
\ 'command': {
|
||||
\ 'options': [],
|
||||
\ 'origin': a:scheme,
|
||||
\ 'raw': 0,
|
||||
\ },
|
||||
\ 'executes': [],
|
||||
\}, 'keep'
|
||||
\)
|
||||
return readonly ? deepcopy(preference) : preference
|
||||
endfunction
|
||||
|
||||
function! gina#custom#preferences(scheme) abort
|
||||
let preferences = []
|
||||
for [pattern, preference] in items(s:pattern_preferences)
|
||||
if a:scheme =~# pattern[1:]
|
||||
call add(preferences, preference)
|
||||
endif
|
||||
endfor
|
||||
return extend(
|
||||
\ deepcopy(preferences),
|
||||
\ [gina#custom#preference(a:scheme)]
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#custom#clear() abort
|
||||
let s:preferences = {}
|
||||
let s:pattern_preferences = {}
|
||||
endfunction
|
||||
|
||||
function! gina#custom#execute(scheme, expr) abort
|
||||
let value = get(a:000, 0, 1)
|
||||
let preference = gina#custom#preference(a:scheme, 0)
|
||||
call add(preference.executes, [a:expr])
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:apply_preference(preference) abort
|
||||
for [expr] in a:preference.executes
|
||||
execute expr
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:FileType() abort
|
||||
let scheme = gina#core#buffer#param('%', 'scheme')
|
||||
if empty(scheme)
|
||||
return
|
||||
endif
|
||||
for preference in gina#custom#preferences(scheme)
|
||||
call s:apply_preference(preference)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
" Autocmd --------------------------------------------------------------------
|
||||
augroup gina_custom_internal
|
||||
autocmd! *
|
||||
autocmd FileType * call s:FileType()
|
||||
augroup END
|
38
bundle/gina.vim/autoload/gina/custom/action.vim
Normal file
38
bundle/gina.vim/autoload/gina/custom/action.vim
Normal file
@ -0,0 +1,38 @@
|
||||
function! gina#custom#action#alias(scheme, alias, origin) abort
|
||||
let preference = gina#custom#preference(a:scheme, 0)
|
||||
call add(preference.action.aliases, [a:alias, a:origin])
|
||||
endfunction
|
||||
|
||||
function! gina#custom#action#shorten(scheme, action_scheme, ...) abort
|
||||
let excludes = get(a:000, 0, [])
|
||||
let preference = gina#custom#preference(a:scheme, 0)
|
||||
call add(preference.action.shortens, [a:action_scheme, excludes])
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:apply_preference(preference) abort
|
||||
for [alias, origin] in a:preference.action.aliases
|
||||
call gina#action#alias(alias, origin)
|
||||
endfor
|
||||
for [action_scheme, excludes] in a:preference.action.shortens
|
||||
call gina#action#shorten(action_scheme, excludes)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:FileType() abort
|
||||
let scheme = gina#core#buffer#param('%', 'scheme')
|
||||
if empty(scheme)
|
||||
return
|
||||
endif
|
||||
for preference in gina#custom#preferences(scheme)
|
||||
call s:apply_preference(preference)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
" Autocmd --------------------------------------------------------------------
|
||||
augroup gina_custom_action_internal
|
||||
autocmd! *
|
||||
autocmd FileType * call s:FileType()
|
||||
augroup END
|
36
bundle/gina.vim/autoload/gina/custom/command.vim
Normal file
36
bundle/gina.vim/autoload/gina/custom/command.vim
Normal file
@ -0,0 +1,36 @@
|
||||
let s:t_number = type(0)
|
||||
|
||||
function! gina#custom#command#option(scheme, query, ...) abort
|
||||
if a:query !~# '^--\?\S\+\%(|--\?\S\+\)*$'
|
||||
throw gina#core#revelator#error(
|
||||
\ 'Invalid query. See :h gina#custom#command#option'
|
||||
\)
|
||||
endif
|
||||
let value = get(a:000, 0, 1)
|
||||
let remover = type(value) == s:t_number ? s:build_remover(a:query) : ''
|
||||
let preference = gina#custom#preference(a:scheme, 0)
|
||||
call add(preference.command.options, [a:query, value, remover])
|
||||
endfunction
|
||||
|
||||
function! gina#custom#command#alias(scheme, alias, ...) abort
|
||||
if a:scheme =~# '^/'
|
||||
throw gina#core#revelator#error(
|
||||
\ '/{pattern} cannot be used to define a command alias'
|
||||
\)
|
||||
endif
|
||||
let preference = gina#custom#preference(a:alias, 0)
|
||||
let preference.command.origin = a:scheme
|
||||
let preference.command.raw = get(a:000, 0, 0)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:build_remover(query) abort
|
||||
let terms = split(a:query, '|')
|
||||
let names = map(copy(terms), 'matchstr(v:val, ''^--\?\zs\S\+'')')
|
||||
let remover = map(
|
||||
\ range(len(terms)),
|
||||
\ '(terms[v:val] =~# ''^--'' ? ''--no-'' : ''-!'') . names[v:val]'
|
||||
\)
|
||||
return join(remover, '|')
|
||||
endfunction
|
48
bundle/gina.vim/autoload/gina/custom/mapping.vim
Normal file
48
bundle/gina.vim/autoload/gina/custom/mapping.vim
Normal file
@ -0,0 +1,48 @@
|
||||
function! gina#custom#mapping#map(scheme, lhs, rhs, ...) abort
|
||||
let options = get(a:000, 0, {})
|
||||
let preference = gina#custom#preference(a:scheme, 0)
|
||||
call add(preference.mapping.mappings, [a:lhs, a:rhs, options])
|
||||
endfunction
|
||||
|
||||
function! gina#custom#mapping#nmap(scheme, lhs, rhs, ...) abort
|
||||
let options = get(a:000, 0, {})
|
||||
let options.mode = 'n'
|
||||
call gina#custom#mapping#map(a:scheme, a:lhs, a:rhs, options)
|
||||
endfunction
|
||||
|
||||
function! gina#custom#mapping#vmap(scheme, lhs, rhs, ...) abort
|
||||
let options = get(a:000, 0, {})
|
||||
let options.mode = 'v'
|
||||
call gina#custom#mapping#map(a:scheme, a:lhs, a:rhs, options)
|
||||
endfunction
|
||||
|
||||
function! gina#custom#mapping#imap(scheme, lhs, rhs, ...) abort
|
||||
let options = get(a:000, 0, {})
|
||||
let options.mode = 'i'
|
||||
call gina#custom#mapping#map(a:scheme, a:lhs, a:rhs, options)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:apply_preference(preference) abort
|
||||
for [lhs, rhs, options] in a:preference.mapping.mappings
|
||||
call gina#util#map(lhs, rhs, options)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:FileType() abort
|
||||
let scheme = gina#core#buffer#param('%', 'scheme')
|
||||
if empty(scheme)
|
||||
return
|
||||
endif
|
||||
for preference in gina#custom#preferences(scheme)
|
||||
call s:apply_preference(preference)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
" Autocmd --------------------------------------------------------------------
|
||||
augroup gina_custom_mapping_internal
|
||||
autocmd! *
|
||||
autocmd FileType * call s:FileType()
|
||||
augroup END
|
157
bundle/gina.vim/autoload/gina/process.vim
Normal file
157
bundle/gina.vim/autoload/gina/process.vim
Normal file
@ -0,0 +1,157 @@
|
||||
let s:Job = vital#gina#import('System.Job')
|
||||
let s:Guard = vital#gina#import('Vim.Guard')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
let s:t_dict = type({})
|
||||
let s:no_askpass_commands = [
|
||||
\ 'init',
|
||||
\ 'config',
|
||||
\]
|
||||
let s:runnings = {}
|
||||
|
||||
|
||||
function! gina#process#runnings() abort
|
||||
return items(s:runnings)
|
||||
endfunction
|
||||
|
||||
function! gina#process#register(job, ...) abort
|
||||
if get(a:000, 0, 0)
|
||||
let s:runnings['pseudo:' . a:job] = a:job
|
||||
call gina#core#emitter#emit('process:registered:pseudo', a:job)
|
||||
else
|
||||
let s:runnings['job:' . a:job.pid()] = a:job
|
||||
call gina#core#emitter#emit('process:registered', a:job.pid(), a:job.params.scheme, a:job.args)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#process#unregister(job, ...) abort
|
||||
if get(a:000, 0, 0)
|
||||
silent! unlet s:runnings['pseudo:' . a:job]
|
||||
call gina#core#emitter#emit('process:unregistered:pseudo', a:job)
|
||||
else
|
||||
silent! unlet s:runnings['job:' . a:job.pid()]
|
||||
call gina#core#emitter#emit('process:unregistered', a:job.pid(), a:job.params.scheme, a:job.args)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gina#process#wait(...) abort
|
||||
let timeout = get(a:000, 0, v:null)
|
||||
let timeout = timeout is# v:null ? v:null : timeout / 1000.0
|
||||
let start_time = reltime()
|
||||
let updatetime = g:gina#process#updatetime . 'm'
|
||||
call gina#core#emitter#emit('wait:start')
|
||||
while timeout is# v:null || timeout > reltimefloat(reltime(start_time))
|
||||
if empty(s:runnings)
|
||||
call gina#core#emitter#emit('wait:end')
|
||||
return
|
||||
endif
|
||||
execute 'sleep' updatetime
|
||||
endwhile
|
||||
call gina#core#emitter#emit('wait:timeout')
|
||||
return -1
|
||||
endfunction
|
||||
|
||||
function! gina#process#open(git, args, ...) abort
|
||||
let args = type(a:args) == s:t_dict ? a:args : gina#core#args#raw(a:args)
|
||||
let pipe = extend(gina#process#pipe#default(), get(a:000, 0, {}))
|
||||
let pipe.params = get(args, 'params', {})
|
||||
let pipe.params.scheme = get(pipe.params, 'scheme', args.get(0, ''))
|
||||
let LC_ALL_saved = exists('$LC_ALL') ? $LC_ALL : v:null
|
||||
let GIT_EDITOR_saved = exists('$GIT_EDITOR') ? $GIT_EDITOR : v:null
|
||||
try
|
||||
let $LC_ALL = 'C'
|
||||
unlet $GIT_EDITOR
|
||||
let job = s:Job.start(s:build_raw_args(a:git, args), pipe)
|
||||
finally
|
||||
if LC_ALL_saved is# v:null
|
||||
unlet $LC_ALL
|
||||
else
|
||||
let $LC_ALL = LC_ALL_saved
|
||||
endif
|
||||
if GIT_EDITOR_saved is# v:null
|
||||
unlet $GIT_EDITOR
|
||||
else
|
||||
let $GIT_EDITOR = GIT_EDITOR_saved
|
||||
endif
|
||||
endtry
|
||||
call job.on_start()
|
||||
call gina#core#console#debug(printf('process: %s', join(job.args)))
|
||||
return job
|
||||
endfunction
|
||||
|
||||
function! gina#process#call(git, args, ...) abort
|
||||
let options = extend({
|
||||
\ 'timeout': g:gina#process#timeout,
|
||||
\}, get(a:000, 0, {})
|
||||
\)
|
||||
let pipe = gina#process#pipe#store()
|
||||
let job = gina#process#open(a:git, a:args, pipe)
|
||||
let status = job.wait(options.timeout)
|
||||
return {
|
||||
\ 'args': job.args,
|
||||
\ 'status': status,
|
||||
\ 'stdout': pipe.stdout,
|
||||
\ 'stderr': pipe.stderr,
|
||||
\ 'content': pipe.content,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! gina#process#call_or_fail(git, args, ...) abort
|
||||
let result = call('gina#process#call', [a:git, a:args] + a:000)
|
||||
if result.status
|
||||
throw gina#process#errormsg(result)
|
||||
endif
|
||||
return result
|
||||
endfunction
|
||||
|
||||
function! gina#process#inform(result) abort
|
||||
redraw | echo
|
||||
if a:result.status
|
||||
call gina#core#console#warn('Fail: ' . join(a:result.args))
|
||||
endif
|
||||
call gina#core#console#echo(s:String.remove_ansi_sequences(
|
||||
\ join(a:result.content, "\n"))
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! gina#process#errormsg(result) abort
|
||||
return gina#core#revelator#error(printf(
|
||||
\ "Fail: %s\n%s",
|
||||
\ join(a:result.args),
|
||||
\ join(a:result.content, "\n")
|
||||
\))
|
||||
endfunction
|
||||
|
||||
function! gina#process#build_raw_args(git, args) abort
|
||||
return s:build_raw_args(a:git, a:args)
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:build_raw_args(git, args) abort
|
||||
let args = gina#core#args#raw(g:gina#process#command).raw
|
||||
if !empty(a:git) && isdirectory(a:git.worktree)
|
||||
call extend(args, ['-C', a:git.worktree])
|
||||
endif
|
||||
call extend(args, a:args.raw)
|
||||
call filter(map(args, 's:expand(v:val)'), '!empty(v:val)')
|
||||
" Assign env GIT_TERMINAL_PROMPT/GIT_ASKPASS if necessary
|
||||
if index(s:no_askpass_commands, a:args.get(0)) == -1
|
||||
call gina#core#askpass#wrap(a:git, args)
|
||||
endif
|
||||
return args
|
||||
endfunction
|
||||
|
||||
function! s:expand(value) abort
|
||||
if a:value =~# '^\%([%#]\|<\w\+>\)\%(:[p8~.htreS]\|:g\?s?\S\+?\S\+?\)*$'
|
||||
return gina#core#path#expand(a:value)
|
||||
endif
|
||||
return a:value
|
||||
endfunction
|
||||
|
||||
|
||||
call gina#config(expand('<sfile>'), {
|
||||
\ 'command': 'git --no-pager -c core.editor=false -c color.status=always',
|
||||
\ 'updatetime': 100,
|
||||
\ 'timeout': 10000,
|
||||
\})
|
202
bundle/gina.vim/autoload/gina/process/pipe.vim
Normal file
202
bundle/gina.vim/autoload/gina/process/pipe.vim
Normal file
@ -0,0 +1,202 @@
|
||||
let s:Guard = vital#gina#import('Vim.Guard')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
|
||||
function! s:extend_content(content, data) abort
|
||||
unlockvar 1 a:content
|
||||
let a:content[-1] .= a:data[0]
|
||||
call extend(a:content, a:data[1:])
|
||||
lockvar 1 a:content
|
||||
endfunction
|
||||
|
||||
|
||||
" Default pipe -------------------------------------------------------------
|
||||
function! gina#process#pipe#default() abort
|
||||
let pipe = deepcopy(s:default_pipe)
|
||||
return pipe
|
||||
endfunction
|
||||
|
||||
function! s:_default_pipe_on_start() abort dict
|
||||
call gina#process#register(self)
|
||||
endfunction
|
||||
|
||||
function! s:_default_pipe_on_exit(exitval) abort dict
|
||||
call gina#process#unregister(self)
|
||||
endfunction
|
||||
|
||||
let s:default_pipe = {
|
||||
\ 'on_start': function('s:_default_pipe_on_start'),
|
||||
\ 'on_exit': function('s:_default_pipe_on_exit'),
|
||||
\}
|
||||
|
||||
|
||||
" Store pipe ---------------------------------------------------------------
|
||||
function! gina#process#pipe#store() abort
|
||||
let pipe = deepcopy(s:store_pipe)
|
||||
let pipe.stdout = ['']
|
||||
let pipe.stderr = ['']
|
||||
let pipe.content = ['']
|
||||
lockvar 1 pipe.stdout
|
||||
lockvar 1 pipe.stderr
|
||||
lockvar 1 pipe.content
|
||||
return pipe
|
||||
endfunction
|
||||
|
||||
function! s:_store_pipe_on_receive(event, data) abort dict
|
||||
call map(a:data, 'v:val[-1:] ==# "\r" ? v:val[:-2] : v:val')
|
||||
call s:extend_content(self[a:event], a:data)
|
||||
call s:extend_content(self.content, a:data)
|
||||
endfunction
|
||||
|
||||
function! s:_store_pipe_on_exit(exitval) abort dict
|
||||
" Content may has an extra empty line (POSIX text) so remove it
|
||||
if has_key(self, 'stdout') && empty(self.stdout[-1])
|
||||
unlockvar 1 self.stdout
|
||||
call remove(self.stdout, -1)
|
||||
lockvar 1 self.stdout
|
||||
endif
|
||||
if has_key(self, 'stderr') && empty(self.stderr[-1])
|
||||
unlockvar 1 self.stderr
|
||||
call remove(self.stderr, -1)
|
||||
lockvar 1 self.stderr
|
||||
endif
|
||||
if has_key(self, 'content') && empty(self.content[-1])
|
||||
unlockvar 1 self.content
|
||||
call remove(self.content, -1)
|
||||
lockvar 1 self.content
|
||||
endif
|
||||
call call('s:_default_pipe_on_exit', [a:exitval], self)
|
||||
endfunction
|
||||
|
||||
let s:store_pipe = {
|
||||
\ 'on_start': function('s:_default_pipe_on_start'),
|
||||
\ 'on_stdout': function('s:_store_pipe_on_receive', ['stdout']),
|
||||
\ 'on_stderr': function('s:_store_pipe_on_receive', ['stderr']),
|
||||
\ 'on_exit': function('s:_store_pipe_on_exit'),
|
||||
\}
|
||||
|
||||
|
||||
" Echo pipe ----------------------------------------------------------------
|
||||
function! gina#process#pipe#echo() abort
|
||||
let pipe = deepcopy(s:echo_pipe)
|
||||
let pipe.stdout = ['']
|
||||
let pipe.stderr = ['']
|
||||
let pipe.content = ['']
|
||||
lockvar 1 pipe.stdout
|
||||
lockvar 1 pipe.stderr
|
||||
lockvar 1 pipe.content
|
||||
return pipe
|
||||
endfunction
|
||||
|
||||
function! s:_echo_pipe_on_exit(exitval) abort dict
|
||||
if len(self.content)
|
||||
call gina#core#console#message(
|
||||
\ s:String.remove_ansi_sequences(join(self.content, "\n")),
|
||||
\)
|
||||
endif
|
||||
call call('s:_store_pipe_on_exit', [a:exitval], self)
|
||||
endfunction
|
||||
|
||||
let s:echo_pipe = {
|
||||
\ 'on_start': function('s:_default_pipe_on_start'),
|
||||
\ 'on_stdout': function('s:_store_pipe_on_receive', ['stdout']),
|
||||
\ 'on_stderr': function('s:_store_pipe_on_receive', ['stderr']),
|
||||
\ 'on_exit': function('s:_echo_pipe_on_exit'),
|
||||
\}
|
||||
|
||||
|
||||
" Stream pipe --------------------------------------------------------------
|
||||
function! gina#process#pipe#stream(...) abort
|
||||
let pipe = deepcopy(s:stream_pipe)
|
||||
let pipe.writer = gina#core#writer#new(a:0 ? a:1 : s:stream_pipe_writer)
|
||||
let pipe.stderr = ['']
|
||||
let pipe.content = ['']
|
||||
lockvar 1 pipe.stderr
|
||||
lockvar 1 pipe.content
|
||||
return pipe
|
||||
endfunction
|
||||
|
||||
function! s:_stream_pipe_on_start() abort dict
|
||||
call call('s:_default_pipe_on_start', [], self)
|
||||
let self.writer._job = self
|
||||
call self.writer.start()
|
||||
endfunction
|
||||
|
||||
function! s:_stream_pipe_on_receive(data) abort dict
|
||||
call map(a:data, 'v:val[-1:] ==# "\r" ? v:val[:-2] : v:val')
|
||||
call self.writer.write(a:data)
|
||||
endfunction
|
||||
|
||||
function! s:_stream_pipe_on_exit(data) abort dict
|
||||
call self.writer.stop()
|
||||
call call('s:_echo_pipe_on_exit', [a:data], self)
|
||||
endfunction
|
||||
|
||||
let s:stream_pipe = {
|
||||
\ 'on_start': function('s:_stream_pipe_on_start'),
|
||||
\ 'on_stdout': function('s:_stream_pipe_on_receive'),
|
||||
\ 'on_stderr': function('s:_store_pipe_on_receive', ['stderr']),
|
||||
\ 'on_exit': function('s:_stream_pipe_on_exit'),
|
||||
\}
|
||||
|
||||
" Stream pipe writer -------------------------------------------------------
|
||||
function! gina#process#pipe#stream_writer() abort
|
||||
return deepcopy(s:stream_pipe_writer)
|
||||
endfunction
|
||||
|
||||
function! s:_discard_winview() abort
|
||||
augroup gina_process_pipe_stream_pipe_writer_internal
|
||||
autocmd! * <buffer=abuf>
|
||||
autocmd CursorMoved <buffer=abuf>
|
||||
\ silent! unlet! b:gina_winview |
|
||||
\ autocmd! gina_process_pipe_stream_pipe_writer_internal * <buffer=abuf>
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:_stream_pipe_writer_on_start() abort dict
|
||||
let self._spinner = gina#core#spinner#start(self.bufnr)
|
||||
call gina#process#register('writer:' . self.bufnr, 1)
|
||||
call gina#core#emitter#emit('writer:started', self.bufnr)
|
||||
" When user moves cursor, remove 'b:gina_winview' to prevent unwilling
|
||||
" cursor change after completion
|
||||
augroup gina_process_pipe_stream_pipe_writer_internal
|
||||
execute printf('autocmd! * <buffer=%d>', self.bufnr)
|
||||
execute printf(
|
||||
\ 'autocmd CursorMoved <buffer=%d> call s:_discard_winview()',
|
||||
\ self.bufnr
|
||||
\)
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:_stream_pipe_writer_on_exit() abort dict
|
||||
call self._job.stop()
|
||||
call self._spinner.stop()
|
||||
|
||||
let focus = gina#core#buffer#focus(self.bufnr)
|
||||
if empty(focus) || bufnr('%') != self.bufnr
|
||||
call gina#core#emitter#emit('writer:stopped', self.bufnr)
|
||||
call gina#process#unregister('writer:' . self.bufnr, 1)
|
||||
return
|
||||
endif
|
||||
try
|
||||
if exists('b:gina_winview')
|
||||
silent! call winrestview(b:gina_winview)
|
||||
endif
|
||||
finally
|
||||
call focus.restore()
|
||||
call gina#core#emitter#emit('writer:stopped', self.bufnr)
|
||||
call gina#process#unregister('writer:' . self.bufnr, 1)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
let s:stream_pipe_writer = {
|
||||
\ 'on_start': function('s:_stream_pipe_writer_on_start'),
|
||||
\ 'on_exit': function('s:_stream_pipe_writer_on_exit'),
|
||||
\}
|
||||
|
||||
|
||||
" Save winview on BufUnload while winsaveview() returns unwilling value
|
||||
" on BufReadCmd
|
||||
augroup gina_process_pipe_internal
|
||||
autocmd! *
|
||||
autocmd BufUnload gina://* let b:gina_winview = winsaveview()
|
||||
augroup END
|
251
bundle/gina.vim/autoload/gina/util.vim
Normal file
251
bundle/gina.vim/autoload/gina/util.vim
Normal file
@ -0,0 +1,251 @@
|
||||
let s:Dict = vital#gina#import('Data.Dict')
|
||||
let s:File = vital#gina#import('System.File')
|
||||
let s:String = vital#gina#import('Data.String')
|
||||
let s:DIRECTION_PATTERN = printf('\<\%%(%s\)\>', join([
|
||||
\ 'lefta\%[bove]',
|
||||
\ 'abo\%[veleft]',
|
||||
\ 'rightb\%[elow]',
|
||||
\ 'bel\%[owright]',
|
||||
\ 'to\%[pleft]',
|
||||
\ 'bo\%[tright]',
|
||||
\], '\|')
|
||||
\)
|
||||
let s:t_list = type([])
|
||||
let s:timer_syncbind = v:null
|
||||
let s:timer_diffupdate = v:null
|
||||
|
||||
|
||||
function! gina#util#contain_direction(mods) abort
|
||||
return a:mods =~# s:DIRECTION_PATTERN
|
||||
endfunction
|
||||
|
||||
function! gina#util#get(obj, key, ...) abort
|
||||
let val = get(a:obj, a:key, v:null)
|
||||
return val is# v:null ? get(a:000, 0, '') : val
|
||||
endfunction
|
||||
|
||||
function! gina#util#map(lhs, rhs, ...) abort
|
||||
let options = extend({
|
||||
\ 'mode': '',
|
||||
\ 'noremap': 0,
|
||||
\ 'buffer': 1,
|
||||
\ 'nowait': 0,
|
||||
\ 'silent': 0,
|
||||
\ 'special': 0,
|
||||
\ 'script': 0,
|
||||
\ 'unique': 0,
|
||||
\ 'expr': 0,
|
||||
\}, get(a:000, 0, {})
|
||||
\)
|
||||
let command = join([
|
||||
\ options.mode . (options.noremap ? 'noremap' : 'map'),
|
||||
\ options.buffer ? '<buffer>' : '',
|
||||
\ options.nowait ? '<nowait>' : '',
|
||||
\ options.silent ? '<silent>' : '',
|
||||
\ options.special ? '<special>' : '',
|
||||
\ options.script ? '<script>' : '',
|
||||
\ options.unique ? '<unique>' : '',
|
||||
\ options.expr ? '<expr>' : '',
|
||||
\ a:lhs, a:rhs
|
||||
\])
|
||||
execute command
|
||||
endfunction
|
||||
|
||||
function! gina#util#yank(value) abort
|
||||
call setreg(v:register, a:value)
|
||||
endfunction
|
||||
|
||||
function! gina#util#open(uri) abort
|
||||
call s:File.open(a:uri)
|
||||
endfunction
|
||||
|
||||
function! gina#util#filter(arglead, candidates, ...) abort
|
||||
let hidden_pattern = get(a:000, 0, '')
|
||||
let pattern = '^' . s:String.escape_pattern(a:arglead)
|
||||
let candidates = copy(a:candidates)
|
||||
if empty(a:arglead) && !empty(hidden_pattern)
|
||||
call filter(candidates, 'v:val !~# hidden_pattern')
|
||||
endif
|
||||
call filter(candidates, 'v:val =~# pattern')
|
||||
return candidates
|
||||
endfunction
|
||||
|
||||
function! gina#util#shellescape(value, ...) abort
|
||||
if empty(a:value)
|
||||
return ''
|
||||
endif
|
||||
let value = type(a:value) == s:t_list
|
||||
\ ? join(map(copy(a:value), 'shellescape(v:val)'))
|
||||
\ : shellescape(a:value)
|
||||
let prefix = get(a:000, 0, '')
|
||||
return prefix . value
|
||||
endfunction
|
||||
|
||||
function! gina#util#fnameescape(value, ...) abort
|
||||
if empty(a:value)
|
||||
return ''
|
||||
endif
|
||||
let value = type(a:value) == s:t_list
|
||||
\ ? join(map(copy(a:value), 'fnameescape(v:val)'))
|
||||
\ : fnameescape(a:value)
|
||||
let prefix = get(a:000, 0, '')
|
||||
return prefix . value
|
||||
endfunction
|
||||
|
||||
function! gina#util#windo(expr) abort
|
||||
let winid = win_getid()
|
||||
try
|
||||
execute printf('windo %s', a:expr)
|
||||
finally
|
||||
call win_gotoid(winid)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#util#bufdo(expr, ...) abort
|
||||
let bang = a:0 ? '!' : ''
|
||||
let winid = win_getid()
|
||||
try
|
||||
execute printf('bufdo%s %s', bang, a:expr)
|
||||
finally
|
||||
call win_gotoid(winid)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#util#doautocmd(name, ...) abort
|
||||
let pattern = get(a:000, 0, '')
|
||||
let expr = '#' . a:name
|
||||
let eis = split(&eventignore, ',')
|
||||
if index(eis, a:name) != -1 || index(eis, 'all') != -1 || !exists(expr)
|
||||
" the specified event is ignored or does not exists
|
||||
return
|
||||
endif
|
||||
let is_pseudo_required = empty(pattern) && !exists('#' . a:name . '#*')
|
||||
if is_pseudo_required
|
||||
" NOTE:
|
||||
" autocmd XXXXX <pattern> exists but not sure if the current buffer name
|
||||
" match with the <pattern> so register an empty autocmd to prevent
|
||||
" 'No matching autocommands' warning
|
||||
augroup gina_internal_util_doautocmd
|
||||
autocmd! *
|
||||
execute printf('autocmd %s * :', a:name)
|
||||
augroup END
|
||||
endif
|
||||
let nomodeline = has('patch-7.4.438') && a:name ==# 'User'
|
||||
\ ? '<nomodeline> '
|
||||
\ : ''
|
||||
try
|
||||
execute printf('doautocmd %s %s %s', nomodeline, a:name, pattern)
|
||||
finally
|
||||
if is_pseudo_required
|
||||
augroup gina_internal_util_doautocmd
|
||||
autocmd! *
|
||||
augroup END
|
||||
endif
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! gina#util#winwidth(winnr) abort
|
||||
let width = winwidth(a:winnr)
|
||||
let width -= &foldcolumn
|
||||
let width -= s:is_sign_visible(winbufnr(a:winnr)) ? 2 : 0
|
||||
let width -= (&number || &relativenumber)
|
||||
\ ? len(string(line('$'))) + 1
|
||||
\ : 0
|
||||
return width
|
||||
endfunction
|
||||
|
||||
function! gina#util#syncbind() abort
|
||||
" NOTE:
|
||||
" 'syncbind' does not work just after a buffer has opened
|
||||
" so use timer to delay the command.
|
||||
silent! call timer_stop(s:timer_syncbind)
|
||||
let s:timer_syncbind = timer_start(50, function('s:syncbind'))
|
||||
endfunction
|
||||
|
||||
function! gina#util#diffthis() abort
|
||||
diffthis
|
||||
augroup gina_internal_util_diffthis_local
|
||||
autocmd! * <buffer>
|
||||
autocmd BufReadPost <buffer>
|
||||
\ if &diff && &foldmethod !=# 'diff' |
|
||||
\ setlocal foldmethod=diff |
|
||||
\ endif
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! gina#util#diffupdate() abort
|
||||
" NOTE:
|
||||
" 'diffupdate' does not work just after a buffer has opened
|
||||
" so use timer to delay the command.
|
||||
silent! call timer_stop(s:timer_diffupdate)
|
||||
let s:timer_diffupdate = timer_start(100, function('s:diffupdate', [bufnr('%')]))
|
||||
endfunction
|
||||
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:syncbind(...) abort
|
||||
syncbind
|
||||
silent! wincmd p
|
||||
silent! wincmd p
|
||||
endfunction
|
||||
|
||||
function! s:diffoff(update) abort
|
||||
augroup gina_internal_util_diffthis
|
||||
autocmd! * <buffer>
|
||||
augroup END
|
||||
diffoff
|
||||
if a:update
|
||||
call s:diffupdate(bufnr('%'))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:diffoff_all() abort
|
||||
let winid = win_getid()
|
||||
for winnr in range(1, winnr('$'))
|
||||
if getwinvar(winnr, '&diff')
|
||||
call win_gotoid(win_getid(winnr))
|
||||
call s:diffoff(0)
|
||||
endif
|
||||
endfor
|
||||
call win_gotoid(winid)
|
||||
call s:diffupdate(bufnr('%'))
|
||||
endfunction
|
||||
|
||||
function! s:diffupdate(bufnr, ...) abort
|
||||
let winid = bufwinid(a:bufnr)
|
||||
if winid == -1
|
||||
return
|
||||
endif
|
||||
let winid_saved = win_getid()
|
||||
try
|
||||
if winid != winid_saved
|
||||
call win_gotoid(winid)
|
||||
endif
|
||||
diffupdate
|
||||
syncbind
|
||||
finally
|
||||
call win_gotoid(winid_saved)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:diffcount() abort
|
||||
let indicators = map(
|
||||
\ range(1, winnr('$')),
|
||||
\ 'getwinvar(v:val, ''&diff'')'
|
||||
\)
|
||||
let indicators = filter(indicators, 'v:val')
|
||||
return len(indicators)
|
||||
endfunction
|
||||
|
||||
function! s:call_super(cls, method, ...) abort dict
|
||||
return call(a:cls.__super[a:method], a:000, self)
|
||||
endfunction
|
||||
|
||||
function! s:is_sign_visible(bufnr) abort
|
||||
if !exists('&signcolumn') || &signcolumn ==# 'auto'
|
||||
return len(split(execute('sign place buffer=' . a:bufnr), '\r\?\n')) > 1
|
||||
else
|
||||
return &signcolumn ==# 'yes'
|
||||
endif
|
||||
endfunction
|
573
bundle/gina.vim/autoload/vital/__gina__/Action.vim
Normal file
573
bundle/gina.vim/autoload/vital/__gina__/Action.vim
Normal file
@ -0,0 +1,573 @@
|
||||
let s:t_funcref = type(function('tr'))
|
||||
let s:t_list = type([])
|
||||
|
||||
let s:PREFIX = '_vital_action_binder_'
|
||||
let s:UNIQUE = sha256(expand('<sfile>:p'))
|
||||
let s:MODIFIERS = [
|
||||
\ 'aboveleft',
|
||||
\ 'belowright',
|
||||
\ 'botright',
|
||||
\ 'browse',
|
||||
\ 'confirm',
|
||||
\ 'hide',
|
||||
\ 'keepalt',
|
||||
\ 'keepjumps',
|
||||
\ 'keepmarks',
|
||||
\ 'keeppatterns',
|
||||
\ 'lockmarks',
|
||||
\ 'noswapfile',
|
||||
\ 'silent',
|
||||
\ 'tab',
|
||||
\ 'topleft',
|
||||
\ 'verbose',
|
||||
\ 'vertical',
|
||||
\]
|
||||
|
||||
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:Revelator = a:V.import('App.Revelator')
|
||||
endfunction
|
||||
|
||||
function! s:_vital_depends() abort
|
||||
return ['App.Revelator']
|
||||
endfunction
|
||||
|
||||
function! s:_vital_created(module) abort
|
||||
let a:module.name = 'action'
|
||||
let a:module.mark_sign_text = '*'
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:get() abort
|
||||
return get(b:, s:PREFIX . s:UNIQUE, v:null)
|
||||
endfunction
|
||||
|
||||
function! s:attach(candidates, ...) abort dict
|
||||
let binder = copy(s:binder)
|
||||
call extend(binder, {
|
||||
\ 'name': substitute(self.name, '\W', '-', 'g'),
|
||||
\ '_candidates': a:candidates,
|
||||
\ 'actions': {},
|
||||
\ 'aliases': {},
|
||||
\ 'markable': 0,
|
||||
\})
|
||||
call extend(binder, get(a:000, 0, {}))
|
||||
" Lock methods
|
||||
lockvar binder._get_candidates
|
||||
lockvar binder._get_marked_candidates
|
||||
lockvar binder.define
|
||||
lockvar binder.alias
|
||||
lockvar binder.action
|
||||
lockvar binder.call
|
||||
" Define builtin actions
|
||||
call binder.define('builtin:echo', function('s:_action_echo'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Echo candidates',
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:help', function('s:_action_help'), {
|
||||
\ 'description': 'Show a help of actions',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'repeatable': 0,
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:help:all', function('s:_action_help'), {
|
||||
\ 'description': 'Show a help of actions including hidden actions',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'options': { 'all': 1 },
|
||||
\ 'repeatable': 0,
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:choice', function('s:_action_choice'), {
|
||||
\ 'description': 'Select an action to perform',
|
||||
\ 'mapping_mode': 'inv',
|
||||
\ 'repeatable': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:repeat', function('s:_action_repeat'), {
|
||||
\ 'description': 'Repeat a previous repeatable action',
|
||||
\ 'mapping_mode': 'inv',
|
||||
\ 'repeatable': 0,
|
||||
\})
|
||||
call binder.alias('echo', 'builtin:echo')
|
||||
call binder.alias('help', 'builtin:help')
|
||||
call binder.alias('help:all', 'builtin:help:all')
|
||||
execute printf('nmap <buffer> ? <Plug>(%s-builtin-help)', binder.name)
|
||||
execute printf('nmap <buffer> a <Plug>(%s-builtin-choice)', binder.name)
|
||||
execute printf('vmap <buffer> a <Plug>(%s-builtin-choice)', binder.name)
|
||||
execute printf('imap <buffer> a <Plug>(%s-builtin-choice)', binder.name)
|
||||
execute printf('nmap <buffer> . <Plug>(%s-builtin-repeat)', binder.name)
|
||||
execute printf('vmap <buffer> . <Plug>(%s-builtin-repeat)', binder.name)
|
||||
execute printf('imap <buffer> . <Plug>(%s-builtin-repeat)', binder.name)
|
||||
if binder.markable
|
||||
execute printf(
|
||||
\ 'sign define %s linehl=%s texthl=%s text=%s',
|
||||
\ 'VitalActionMarkSelectedSign',
|
||||
\ 'VitalActionMarkSelectedLine',
|
||||
\ 'VitalActionMarkSelected',
|
||||
\ self.mark_sign_text,
|
||||
\)
|
||||
call binder.define('builtin:mark', function('s:_action_mark'), {
|
||||
\ 'description': 'Mark/Unmark selected candidates',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['__lnum'],
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:mark:set', function('s:_action_mark_set'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Mark selected candidates',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['__lnum'],
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:mark:unset', function('s:_action_mark_unset'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Unmark selected candidates',
|
||||
\ 'mapping_mode': 'nv',
|
||||
\ 'requirements': ['__lnum'],
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.define('builtin:mark:unall', function('s:_action_mark_unall'), {
|
||||
\ 'hidden': 1,
|
||||
\ 'description': 'Unmark all candidates',
|
||||
\ 'mapping_mode': 'n',
|
||||
\ 'use_marks': 0,
|
||||
\ 'clear_marks': 0,
|
||||
\})
|
||||
call binder.alias('mark', 'builtin:mark')
|
||||
call binder.alias('mark:unall', 'builtin:mark:unall')
|
||||
execute printf('nmap <buffer> mm <Plug>(%s-builtin-mark)', binder.name)
|
||||
execute printf('vmap <buffer> mm <Plug>(%s-builtin-mark)', binder.name)
|
||||
execute printf('nmap <buffer> m+ <Plug>(%s-builtin-mark-set)', binder.name)
|
||||
execute printf('vmap <buffer> m+ <Plug>(%s-builtin-mark-set)', binder.name)
|
||||
execute printf('nmap <buffer> m- <Plug>(%s-builtin-mark-unset)', binder.name)
|
||||
execute printf('vmap <buffer> m- <Plug>(%s-builtin-mark-unset)', binder.name)
|
||||
execute printf('nmap <buffer> m* <Plug>(%s-builtin-mark-unall)', binder.name)
|
||||
execute printf('nmap <buffer> <C-j> <Plug>(%s-builtin-mark)j', binder.name)
|
||||
execute printf('nmap <buffer> <C-k> k<Plug>(%s-builtin-mark)', binder.name)
|
||||
endif
|
||||
let b:{s:PREFIX . s:UNIQUE} = binder
|
||||
return binder
|
||||
endfunction
|
||||
|
||||
|
||||
" Instance -------------------------------------------------------------------
|
||||
let s:binder = {}
|
||||
|
||||
function! s:binder._get_candidates(...) abort
|
||||
let fline = get(a:000, 0, line('.'))
|
||||
let lline = get(a:000, 1, fline)
|
||||
if type(self._candidates) == s:t_funcref
|
||||
let candidates = self._candidates(fline, lline)
|
||||
else
|
||||
let candidates = self._candidates[fline-1 : lline-1]
|
||||
endif
|
||||
return map(
|
||||
\ deepcopy(candidates),
|
||||
\ 'extend(v:val, {''__lnum'': fline + v:key})'
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:binder._get_marked_candidates() abort
|
||||
if !self.markable
|
||||
throw 'vital: Action: The action binder is not markable'
|
||||
endif
|
||||
let signs = s:_get_signs()
|
||||
let lnums = map(signs, 'v:val.line')
|
||||
let candidates = []
|
||||
call map(lnums, 'extend(candidates, self._get_candidates(v:val, v:val))')
|
||||
return candidates
|
||||
endfunction
|
||||
|
||||
function! s:binder.define(name, callback, ...) abort
|
||||
let action = extend({
|
||||
\ 'callback': a:callback,
|
||||
\ 'name': a:name,
|
||||
\ 'description': '',
|
||||
\ 'mapping': '',
|
||||
\ 'mapping_mode': '',
|
||||
\ 'requirements': [],
|
||||
\ 'options': {},
|
||||
\ 'hidden': 0,
|
||||
\ 'repeatable': 1,
|
||||
\ 'use_marks': 1,
|
||||
\ 'clear_marks': 1,
|
||||
\}, get(a:000, 0, {}),
|
||||
\)
|
||||
if empty(action.mapping)
|
||||
let action.mapping = printf(
|
||||
\ '<Plug>(%s-%s)',
|
||||
\ substitute(self.name, '\W', '-', 'g'),
|
||||
\ substitute(action.name, '\W', '-', 'g'),
|
||||
\)
|
||||
endif
|
||||
for mode in split(action.mapping_mode, '\zs')
|
||||
execute printf(
|
||||
\ '%snoremap <buffer><silent> %s %s:%scall <SID>_call_for_mapping(''%s'')<CR>',
|
||||
\ mode,
|
||||
\ action.mapping,
|
||||
\ mode =~# '[i]' ? '<Esc>' : '',
|
||||
\ mode =~# '[ni]' ? '<C-u>' : '',
|
||||
\ a:name,
|
||||
\)
|
||||
endfor
|
||||
let self.actions[action.name] = action
|
||||
endfunction
|
||||
|
||||
function! s:binder.alias(alias, expr, ...) abort
|
||||
let alias = extend({
|
||||
\ 'name': matchstr(a:expr, '\S\+$'),
|
||||
\ 'expr': a:expr,
|
||||
\ 'alias': a:alias,
|
||||
\}, get(a:000, 0, {}),
|
||||
\)
|
||||
let self.aliases[a:alias] = alias
|
||||
endfunction
|
||||
|
||||
function! s:binder.action(expr) abort
|
||||
let mods = matchstr(a:expr, '^\%(.* \)\?\ze\S\+$')
|
||||
let name = matchstr(a:expr, '\S\+$')
|
||||
if has_key(self.aliases, name)
|
||||
let alias = self.aliases[name]
|
||||
return self.action(mods . alias.expr)
|
||||
elseif has_key(self.actions, name)
|
||||
return [mods, self.actions[name]]
|
||||
endif
|
||||
" If only one action/alias could be determine, use it.
|
||||
let candidates = filter(
|
||||
\ keys(self.aliases) + keys(self.actions),
|
||||
\ 'v:val =~# ''^'' . name'
|
||||
\)
|
||||
" Shorter to Longer
|
||||
call sort(candidates, { a, b -> len(a) - len(b) })
|
||||
if empty(candidates)
|
||||
throw s:Revelator.warning(printf(
|
||||
\ 'No corresponding action has found for "%s"',
|
||||
\ a:expr
|
||||
\))
|
||||
else
|
||||
" Use the first match
|
||||
return self.action(mods . candidates[0])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:binder.call(expr, ...) abort range
|
||||
let [mods, action] = self.action(a:expr)
|
||||
if a:0 == 1 && type(a:1) == s:t_list
|
||||
let candidates = a:1
|
||||
elseif self.markable && action.use_marks
|
||||
let candidates = self._get_marked_candidates()
|
||||
let candidates = empty(candidates)
|
||||
\ ? call(self._get_candidates, a:000, self)
|
||||
\ : candidates
|
||||
else
|
||||
let candidates = call(self._get_candidates, a:000, self)
|
||||
endif
|
||||
if !empty(action.requirements)
|
||||
call filter(
|
||||
\ candidates,
|
||||
\ 's:_is_satisfied(v:val, action.requirements)',
|
||||
\)
|
||||
endif
|
||||
let options = extend({
|
||||
\ 'mods': mods,
|
||||
\}, action.options
|
||||
\)
|
||||
if self.markable && action.clear_marks
|
||||
call self.call('builtin:mark:unall')
|
||||
endif
|
||||
call call(action.callback, [candidates, options], self)
|
||||
return [mods, action]
|
||||
endfunction
|
||||
|
||||
|
||||
" Actions --------------------------------------------------------------------
|
||||
function! s:_action_echo(candidates, options) abort
|
||||
for candidate in a:candidates
|
||||
echo string(candidate)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:_action_help(candidates, options) abort dict
|
||||
let mappings = s:_find_mappings(self)
|
||||
let actions = values(self.actions)
|
||||
if !get(a:options, 'all')
|
||||
call filter(actions, '!v:val.hidden')
|
||||
endif
|
||||
let rows = []
|
||||
let longest1 = 0
|
||||
let longest2 = 0
|
||||
let longest3 = 0
|
||||
for action in actions
|
||||
let mapping = get(mappings, action.mapping, {})
|
||||
let lhs = !empty(action.mapping) && !empty(mapping) ? mapping.lhs : ''
|
||||
let alias = s:_find_alias(self, action)
|
||||
let identifier = empty(alias)
|
||||
\ ? action.name
|
||||
\ : printf('%s [%s]', action.name, alias)
|
||||
let hidden = action.hidden ? '*' : ' '
|
||||
let description = action.description
|
||||
let mapping = printf('%s [%s]', action.mapping, action.mapping_mode)
|
||||
call add(rows, [
|
||||
\ lhs,
|
||||
\ identifier,
|
||||
\ hidden,
|
||||
\ description,
|
||||
\ mapping,
|
||||
\])
|
||||
let longest1 = len(lhs) > longest1 ? len(lhs) : longest1
|
||||
let longest2 = len(identifier) > longest2 ? len(identifier) : longest2
|
||||
let longest3 = len(description) > longest3 ? len(description) : longest3
|
||||
endfor
|
||||
|
||||
let content = []
|
||||
let pattern = printf(
|
||||
\ '%%-%ds %%-%ds %%s %%-%ds %%s',
|
||||
\ longest1,
|
||||
\ longest2,
|
||||
\ longest3,
|
||||
\)
|
||||
for params in sort(rows, 's:_compare')
|
||||
call add(content, call('printf', [pattern] + params))
|
||||
endfor
|
||||
redraw | echo join(content, "\n")
|
||||
endfunction
|
||||
|
||||
function! s:_action_choice(candidates, options) abort dict
|
||||
let s:_binder = self
|
||||
call inputsave()
|
||||
try
|
||||
echohl Question
|
||||
redraw | echo
|
||||
let fname = s:_get_function_name(function('s:_complete_action_aliases'))
|
||||
let aname = input(
|
||||
\ 'action: ', '',
|
||||
\ printf('customlist,%s', fname),
|
||||
\)
|
||||
redraw | echo
|
||||
finally
|
||||
echohl None
|
||||
call inputrestore()
|
||||
endtry
|
||||
if empty(aname)
|
||||
return
|
||||
endif
|
||||
let [mods, action] = self.call(aname, a:candidates)
|
||||
if action.repeatable
|
||||
let self._previous_action = [mods, action]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:_action_repeat(candidates, options) abort dict
|
||||
let [mods, action] = get(self, '_previous_action', ['', {}])
|
||||
if empty(action)
|
||||
return
|
||||
endif
|
||||
return self.call(join([mods, action.name]), a:candidates)
|
||||
endfunction
|
||||
|
||||
function! s:_action_mark(candidates, options) abort dict
|
||||
let set_candidates = []
|
||||
let unset_candidates = []
|
||||
let signmap = s:_get_signmap()
|
||||
for candidate in a:candidates
|
||||
if has_key(signmap, string(candidate.__lnum))
|
||||
call add(unset_candidates, candidate)
|
||||
else
|
||||
call add(set_candidates, candidate)
|
||||
endif
|
||||
endfor
|
||||
call self.call('builtin:mark:set', set_candidates)
|
||||
call self.call('builtin:mark:unset', unset_candidates)
|
||||
endfunction
|
||||
|
||||
function! s:_action_mark_set(candidates, options) abort dict
|
||||
let options = extend({}, a:options)
|
||||
let lnums = map(a:candidates, 'v:val.__lnum')
|
||||
let bufnr = bufnr('%')
|
||||
for lnum in lnums
|
||||
execute printf(
|
||||
\ 'sign place %d line=%d name=%s buffer=%d',
|
||||
\ lnum, lnum,
|
||||
\ 'VitalActionMarkSelectedSign',
|
||||
\ bufnr,
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:_action_mark_unset(candidates, options) abort dict
|
||||
let options = extend({}, a:options)
|
||||
let lnums = map(a:candidates, 'v:val.__lnum')
|
||||
let bufnr = bufnr('%')
|
||||
for lnum in lnums
|
||||
execute printf(
|
||||
\ 'sign unplace %d buffer=%d',
|
||||
\ lnum, bufnr,
|
||||
\)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:_action_mark_unall(candidates, options) abort dict
|
||||
let options = extend({}, a:options)
|
||||
let bufnr = bufnr('%')
|
||||
execute printf('sign unplace * buffer=%d', bufnr)
|
||||
endfunction
|
||||
|
||||
|
||||
" Privates -------------------------------------------------------------------
|
||||
function! s:_is_satisfied(candidate, requirements) abort
|
||||
for requirement in a:requirements
|
||||
if !has_key(a:candidate, requirement)
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:_compare(i1, i2) abort
|
||||
return a:i1[1] == a:i2[1] ? 0 : a:i1[1] > a:i2[1] ? 1 : -1
|
||||
endfunction
|
||||
|
||||
function! s:_compare_length(w1, w2) abort
|
||||
let l1 = len(a:w1)
|
||||
let l2 = len(a:w2)
|
||||
return l1 == l2 ? 0 : l1 > l2 ? 1 : -1
|
||||
endfunction
|
||||
|
||||
function! s:_find_mappings(binder) abort
|
||||
let content = s:_execute('map')
|
||||
let rhss = filter(
|
||||
\ map(values(a:binder.actions), 'v:val.mapping'),
|
||||
\ '!empty(v:val)'
|
||||
\)
|
||||
let rhsp = printf('\%%(%s\)', join(map(rhss, 'escape(v:val, ''\'')'), '\|'))
|
||||
let rows = filter(split(content, '\r\?\n'), 'v:val =~# ''@.*'' . rhsp')
|
||||
let pattern = '\(...\)\(\S\+\)'
|
||||
let mappings = {}
|
||||
for row in rows
|
||||
let [mode, lhs] = matchlist(row, pattern)[1 : 2]
|
||||
let rhs = matchstr(row, rhsp)
|
||||
let mappings[rhs] = {
|
||||
\ 'mode': mode,
|
||||
\ 'lhs': lhs,
|
||||
\ 'rhs': rhs,
|
||||
\}
|
||||
endfor
|
||||
return mappings
|
||||
endfunction
|
||||
|
||||
function! s:_find_alias(binder, action) abort
|
||||
let aliases = filter(
|
||||
\ values(a:binder.aliases),
|
||||
\ 'v:val.name ==# a:action.name',
|
||||
\)
|
||||
" Remove aliases with mods
|
||||
call filter(aliases, 'v:val.name ==# v:val.expr')
|
||||
" Prefer shorter name
|
||||
let candidates = sort(
|
||||
\ map(aliases, 'v:val.alias'),
|
||||
\ function('s:_compare_length')
|
||||
\)
|
||||
return get(candidates, 0, '')
|
||||
endfunction
|
||||
|
||||
function! s:_complete_action_aliases(arglead, cmdline, cursorpos) abort
|
||||
let terms = split(a:arglead, ' ', 1)
|
||||
" Build modifier candidates
|
||||
let modifiers = terms[:-2]
|
||||
let candidates = map(
|
||||
\ filter(copy(s:MODIFIERS), 'index(modifiers, v:val) == -1'),
|
||||
\ 'v:val . '' '''
|
||||
\)
|
||||
" Build action/alias candidates
|
||||
let arglead = terms[-1]
|
||||
let binder = s:_binder
|
||||
let actions = values(binder.actions)
|
||||
if empty(arglead)
|
||||
call filter(actions, '!v:val.hidden')
|
||||
endif
|
||||
call extend(candidates, sort(extend(
|
||||
\ map(actions, 'v:val.name'),
|
||||
\ keys(binder.aliases),
|
||||
\)), 0)
|
||||
call filter(uniq(candidates), 'v:val =~# ''^'' . arglead')
|
||||
call map(candidates, 'join(modifiers + [v:val])')
|
||||
return candidates
|
||||
endfunction
|
||||
|
||||
function! s:_call_for_mapping(name) abort range
|
||||
let binder = s:get()
|
||||
return s:Revelator.call(binder.call, [a:name, a:firstline, a:lastline], binder)
|
||||
endfunction
|
||||
|
||||
function! s:_get_signs() abort
|
||||
let content = split(s:_execute('sign place buffer=' . bufnr('%')), '\r\?\n')
|
||||
let signs = map(
|
||||
\ filter(content, 'v:val =~# ''^\s\{4}'''),
|
||||
\ 's:_parse_sign(v:val)',
|
||||
\)
|
||||
return filter(signs, 'v:val.name ==# ''VitalActionMarkSelectedSign''')
|
||||
endfunction
|
||||
|
||||
function! s:_get_signmap() abort
|
||||
let signmap = {}
|
||||
for sign in s:_get_signs()
|
||||
let signmap[sign.line] = sign
|
||||
endfor
|
||||
return signmap
|
||||
endfunction
|
||||
|
||||
function! s:_parse_sign(sign) abort
|
||||
let terms = split(a:sign)
|
||||
let sign = {}
|
||||
for term in terms
|
||||
let m = split(term, '=')
|
||||
let sign[m[0]] = m[1]
|
||||
endfor
|
||||
return sign
|
||||
endfunction
|
||||
|
||||
|
||||
" Compatibility --------------------------------------------------------------
|
||||
if has('patch-7.4.1842')
|
||||
function! s:_get_function_name(fn) abort
|
||||
return get(a:fn, 'name')
|
||||
endfunction
|
||||
else
|
||||
function! s:_get_function_name(fn) abort
|
||||
return matchstr(string(a:fn), 'function(''\zs.*\ze''')
|
||||
endfunction
|
||||
endif
|
||||
|
||||
if exists('*execute')
|
||||
let s:_execute = function('execute')
|
||||
else
|
||||
function! s:_execute(command) abort
|
||||
try
|
||||
redir => content
|
||||
silent execute a:command
|
||||
finally
|
||||
redir END
|
||||
endtry
|
||||
return content
|
||||
endfunction
|
||||
endif
|
||||
|
||||
|
||||
" Highlight ------------------------------------------------------------------
|
||||
function! s:_define_mark_highlights() abort
|
||||
highlight default link VitalActionMarkSelectedLine Search
|
||||
highlight default link VitalActionMarkSelected Search
|
||||
endfunction
|
||||
|
||||
augroup vital_action_internal
|
||||
autocmd! *
|
||||
autocmd ColorScheme * call s:_define_mark_highlights()
|
||||
augroup END
|
||||
|
||||
call s:_define_mark_highlights()
|
323
bundle/gina.vim/autoload/vital/__gina__/Argument.vim
Normal file
323
bundle/gina.vim/autoload/vital/__gina__/Argument.vim
Normal file
@ -0,0 +1,323 @@
|
||||
let s:t_number = type(0)
|
||||
let s:t_string = type('')
|
||||
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:Guard = a:V.import('Vim.Guard')
|
||||
let s:List = a:V.import('Data.List')
|
||||
let s:String = a:V.import('Data.String')
|
||||
endfunction
|
||||
|
||||
function! s:_vital_depends() abort
|
||||
return ['Vim.Guard', 'Data.List', 'Data.String']
|
||||
endfunction
|
||||
|
||||
function! s:_vital_created(module) abort
|
||||
" build pattern for parsing arguments
|
||||
let single_quote = '''\zs[^'']\+\ze'''
|
||||
let double_quote = '"\zs[^"]\+\ze"'
|
||||
let bare_strings = '\%(\\\s\|[^ ''"]\)\+'
|
||||
let s:PARSE_PATTERN = printf(
|
||||
\ '\%%(%s\)*\zs\%%(\s\+\|$\)\ze',
|
||||
\ join([single_quote, double_quote, bare_strings], '\|')
|
||||
\)
|
||||
let s:NORM_PATTERN = '^\%("\zs.*\ze"\|''\zs.*\ze''\|.*\)$'
|
||||
endfunction
|
||||
|
||||
function! s:parse(cmdline) abort
|
||||
return s:norm(split(a:cmdline, s:PARSE_PATTERN))
|
||||
endfunction
|
||||
|
||||
function! s:norm(terms) abort
|
||||
return map(copy(a:terms), 's:_norm_term(v:val)')
|
||||
endfunction
|
||||
|
||||
function! s:new(...) abort
|
||||
if a:0 > 0
|
||||
let init = type(a:1) == s:t_string ? s:parse(a:1) : s:norm(a:1)
|
||||
else
|
||||
let init = []
|
||||
endif
|
||||
let args = copy(s:args)
|
||||
let args.raw = init
|
||||
return args
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:_norm_term(term) abort
|
||||
let m = matchlist(a:term, '^\(-\w\|--\S\+=\)\(.\+\)')
|
||||
if empty(m)
|
||||
return matchstr(a:term, s:NORM_PATTERN)
|
||||
endif
|
||||
return m[1] . matchstr(m[2], s:NORM_PATTERN)
|
||||
endfunction
|
||||
|
||||
function! s:_parse_term(term) abort
|
||||
let m = matchlist(a:term, '^\(-\w\|--\S\+=\)\(.\+\)')
|
||||
if empty(m)
|
||||
return a:term =~# '^--\?\S\+' ? [a:term, 1] : ['', a:term]
|
||||
else
|
||||
return [substitute(m[1], '=$', '', ''), m[2]]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:_build_term(key, value) abort
|
||||
if type(a:value) == s:t_number
|
||||
return a:value == 0 ? '' : a:key
|
||||
elseif empty(a:key) || a:key =~# '^-\w$'
|
||||
return a:key . a:value
|
||||
else
|
||||
return a:key . '=' . a:value
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:_build_pattern(query) abort
|
||||
let patterns = split(a:query, '|')
|
||||
call map(patterns, 's:String.escape_pattern(v:val)')
|
||||
call map(patterns, 'v:val =~# ''^--\S\+'' ? v:val . ''\%(=\|$\)'' : v:val')
|
||||
return printf('^\%%(%s\)', join(patterns, '\|'))
|
||||
endfunction
|
||||
|
||||
function! s:_is_query(query) abort
|
||||
return a:query =~# '^--\?\S\+\%(|--\?\S\+\)*$'
|
||||
endfunction
|
||||
|
||||
|
||||
" Instance -------------------------------------------------------------------
|
||||
let s:args = {}
|
||||
|
||||
function! s:_index(pattern) abort dict
|
||||
let guard = s:Guard.store(['&l:iskeyword'])
|
||||
try
|
||||
setlocal iskeyword&
|
||||
let indices = range(0, len(self.raw)-1)
|
||||
for index in indices
|
||||
let term = self.raw[index]
|
||||
if term ==# '--'
|
||||
return -1
|
||||
elseif term =~# a:pattern
|
||||
return index
|
||||
endif
|
||||
endfor
|
||||
return -1
|
||||
finally
|
||||
call guard.restore()
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:_has(pattern) abort dict
|
||||
return call('s:_index', [a:pattern], self) != -1
|
||||
endfunction
|
||||
|
||||
function! s:_get(pattern, default) abort dict
|
||||
let index = call('s:_index', [a:pattern], self)
|
||||
if index == -1
|
||||
return a:default
|
||||
endif
|
||||
return self.raw[index]
|
||||
endfunction
|
||||
|
||||
function! s:_pop(pattern, default) abort dict
|
||||
let index = call('s:_index', [a:pattern], self)
|
||||
if index == -1
|
||||
return a:default
|
||||
endif
|
||||
return remove(self.raw, index)
|
||||
endfunction
|
||||
|
||||
function! s:_set(pattern, term) abort dict
|
||||
let index = call('s:_index', [a:pattern], self)
|
||||
if index == -1
|
||||
let tail = index(self.raw, '--')
|
||||
let tail = tail == -1 ? len(self.raw) : tail
|
||||
call insert(self.raw, a:term, tail)
|
||||
else
|
||||
let self.raw[index] = a:term
|
||||
endif
|
||||
return self
|
||||
endfunction
|
||||
|
||||
function! s:_index_o(query) abort dict
|
||||
return call('s:_index', [s:_build_pattern(a:query)], self)
|
||||
endfunction
|
||||
|
||||
function! s:_has_o(query) abort dict
|
||||
return call('s:_has', [s:_build_pattern(a:query)], self)
|
||||
endfunction
|
||||
|
||||
function! s:_get_o(query, default) abort dict
|
||||
let index = call('s:_index_o', [a:query], self)
|
||||
if index == -1
|
||||
return a:default
|
||||
endif
|
||||
return s:_parse_term(self.raw[index])[1]
|
||||
endfunction
|
||||
|
||||
function! s:_pop_o(query, default) abort dict
|
||||
let index = call('s:_index_o', [a:query], self)
|
||||
if index == -1
|
||||
return a:default
|
||||
endif
|
||||
return s:_parse_term(remove(self.raw, index))[1]
|
||||
endfunction
|
||||
|
||||
function! s:_set_o(query, value) abort dict
|
||||
let index = call('s:_index_o', [a:query], self)
|
||||
if index == -1
|
||||
let tail = index(self.raw, '--')
|
||||
let tail = tail == -1 ? len(self.raw) : tail
|
||||
let term = s:_build_term(split(a:query, '|')[-1], a:value)
|
||||
call insert(self.raw, term, tail)
|
||||
else
|
||||
let term = s:_build_term(s:_parse_term(self.raw[index])[0], a:value)
|
||||
let self.raw[index] = term
|
||||
endif
|
||||
return self
|
||||
endfunction
|
||||
|
||||
function! s:_index_p(query) abort dict
|
||||
if a:query < 0
|
||||
throw 'vital: Argument: {query} (n-th) requires to be positive.'
|
||||
endif
|
||||
let indices = range(0, len(self.raw)-1)
|
||||
let pattern = '^--\?\w\+'
|
||||
let counter = 0
|
||||
for index in indices
|
||||
let term = self.raw[index]
|
||||
if term ==# '--'
|
||||
return -1
|
||||
elseif term !~# pattern
|
||||
let counter += 1
|
||||
if counter == a:query + 1
|
||||
return index
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
return -1
|
||||
endfunction
|
||||
|
||||
function! s:_has_p(query) abort dict
|
||||
return call('s:_index_p', [a:query], self) != -1
|
||||
endfunction
|
||||
|
||||
function! s:_get_p(query, ...) abort dict
|
||||
let default = get(a:000, 0, '')
|
||||
let index = call('s:_index_p', [a:query], self)
|
||||
if index == -1
|
||||
return default
|
||||
endif
|
||||
return self.raw[index]
|
||||
endfunction
|
||||
|
||||
function! s:_pop_p(query, ...) abort dict
|
||||
let default = get(a:000, 0, '')
|
||||
let index = call('s:_index_p', [a:query], self)
|
||||
if index == -1
|
||||
return default
|
||||
endif
|
||||
return remove(self.raw, index)
|
||||
endfunction
|
||||
|
||||
function! s:_set_p(query, value) abort dict
|
||||
let index = call('s:_index_p', [a:query], self)
|
||||
if index == -1
|
||||
let tail = index(self.raw, '--')
|
||||
let tail = tail == -1 ? len(self.raw) : tail
|
||||
let n = (a:query + 1) - len(filter(
|
||||
\ self.raw[:tail-1],
|
||||
\ 'v:val !~# ''^--\?\w\+'''
|
||||
\))
|
||||
call extend(self.raw, repeat([''], n), tail)
|
||||
let self.raw[tail - 1 + n] = a:value
|
||||
else
|
||||
let self.raw[index] = a:value
|
||||
endif
|
||||
let self.raw = s:List.flatten(self.raw)
|
||||
return self
|
||||
endfunction
|
||||
|
||||
function! s:args.hash() abort
|
||||
return sha256(string(self.raw))
|
||||
endfunction
|
||||
|
||||
function! s:args.lock() abort
|
||||
lockvar self
|
||||
return self
|
||||
endfunction
|
||||
|
||||
function! s:args.clone() abort
|
||||
let args = deepcopy(self)
|
||||
lockvar 1 args
|
||||
return args
|
||||
endfunction
|
||||
|
||||
function! s:args.index(query) abort
|
||||
return type(a:query) == s:t_string
|
||||
\ ? s:_is_query(a:query)
|
||||
\ ? call('s:_index_o', [a:query], self)
|
||||
\ : call('s:_index', [a:query], self)
|
||||
\ : call('s:_index_p', [a:query], self)
|
||||
endfunction
|
||||
|
||||
function! s:args.has(query, ...) abort
|
||||
return type(a:query) == s:t_string
|
||||
\ ? s:_is_query(a:query)
|
||||
\ ? call('s:_has_o', [a:query], self)
|
||||
\ : call('s:_has', [a:query], self)
|
||||
\ : call('s:_has_p', [a:query], self)
|
||||
endfunction
|
||||
|
||||
function! s:args.get(query, ...) abort
|
||||
return type(a:query) == s:t_string
|
||||
\ ? s:_is_query(a:query)
|
||||
\ ? call('s:_get_o', [a:query, get(a:000, 0, 0)], self)
|
||||
\ : call('s:_get', [a:query, get(a:000, 0, '')], self)
|
||||
\ : call('s:_get_p', [a:query, get(a:000, 0, '')], self)
|
||||
endfunction
|
||||
|
||||
function! s:args.pop(query, ...) abort
|
||||
return type(a:query) == s:t_string
|
||||
\ ? s:_is_query(a:query)
|
||||
\ ? call('s:_pop_o', [a:query, get(a:000, 0, 0)], self)
|
||||
\ : call('s:_pop', [a:query, get(a:000, 0, '')], self)
|
||||
\ : call('s:_pop_p', [a:query, get(a:000, 0, '')], self)
|
||||
endfunction
|
||||
|
||||
function! s:args.set(query, value) abort
|
||||
return type(a:query) == s:t_string
|
||||
\ ? s:_is_query(a:query)
|
||||
\ ? call('s:_set_o', [a:query, a:value], self)
|
||||
\ : call('s:_set', [a:query, a:value], self)
|
||||
\ : call('s:_set_p', [a:query, a:value], self)
|
||||
endfunction
|
||||
|
||||
function! s:args.split() abort
|
||||
let tail = index(self.raw, '--')
|
||||
if tail == -1
|
||||
return [copy(self.raw), []]
|
||||
elseif tail == 0
|
||||
return [[], self.raw[1:]]
|
||||
else
|
||||
return [self.raw[:tail-1], self.raw[tail+1:]]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:args.effective(...) abort
|
||||
if a:0 == 0
|
||||
return self.split()[0]
|
||||
else
|
||||
let residual = self.residual()
|
||||
let self.raw = empty(residual) ? a:1 : a:1 + ['--'] + residual
|
||||
return self
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:args.residual(...) abort
|
||||
if a:0 == 0
|
||||
return self.split()[1]
|
||||
else
|
||||
let effective = self.effective()
|
||||
let self.raw = effective + ['--'] + a:1
|
||||
return self
|
||||
endif
|
||||
endfunction
|
181
bundle/gina.vim/autoload/vital/__gina__/Git.vim
Normal file
181
bundle/gina.vim/autoload/vital/__gina__/Git.vim
Normal file
@ -0,0 +1,181 @@
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:INI = a:V.import('Text.INI')
|
||||
let s:Path = a:V.import('System.Filepath')
|
||||
let s:String = a:V.import('Data.String')
|
||||
let s:Store = a:V.import('System.Store')
|
||||
endfunction
|
||||
|
||||
function! s:_vital_depends() abort
|
||||
return [
|
||||
\ 'Text.INI',
|
||||
\ 'System.Filepath',
|
||||
\ 'Data.String',
|
||||
\ 'System.Store',
|
||||
\]
|
||||
endfunction
|
||||
|
||||
function! s:new(path) abort
|
||||
let path = s:Path.remove_last_separator(expand(a:path))
|
||||
let dirpath = isdirectory(path) ? path : fnamemodify(path, ':p:h')
|
||||
let dirpath = simplify(s:Path.abspath(s:Path.realpath(path)))
|
||||
|
||||
" Find worktree
|
||||
let dgit = finddir('.git', fnameescape(dirpath) . ';')
|
||||
let dgit = empty(dgit) ? '' : fnamemodify(dgit, ':p:h')
|
||||
let fgit = findfile('.git', fnameescape(dirpath) . ';')
|
||||
let fgit = empty(fgit) ? '' : fnamemodify(fgit, ':p')
|
||||
let worktree = len(dgit) > len(fgit) ? dgit : fgit
|
||||
let worktree = empty(worktree) ? '' : fnamemodify(worktree, ':h')
|
||||
if empty(worktree)
|
||||
return {}
|
||||
endif
|
||||
|
||||
" Find repository
|
||||
let repository = s:Path.join(worktree, '.git')
|
||||
if filereadable(repository)
|
||||
" A '.git' may be a file which was created by '--separate-git-dir' option
|
||||
let lines = readfile(repository)
|
||||
if empty(lines)
|
||||
throw printf(
|
||||
\ 'vital: Git: An invalid .git file has found at "%s".',
|
||||
\ repository,
|
||||
\)
|
||||
endif
|
||||
let gitdir = matchstr(lines[0], '^gitdir:\s*\zs.\+$')
|
||||
let is_abs = s:Path.is_absolute(gitdir)
|
||||
let repository = is_abs ? gitdir : repository[:-5] . gitdir
|
||||
let repository = empty(repository) ? '' : fnamemodify(repository, ':p:h')
|
||||
endif
|
||||
|
||||
" Find commondir
|
||||
let commondir = ''
|
||||
if filereadable(s:Path.join(repository, 'commondir'))
|
||||
let commondir = readfile(s:Path.join(repository, 'commondir'))[0]
|
||||
let commondir = s:Path.join(repository, commondir)
|
||||
endif
|
||||
|
||||
let git = {
|
||||
\ 'worktree': simplify(s:Path.realpath(worktree)),
|
||||
\ 'repository': simplify(s:Path.realpath(repository)),
|
||||
\ 'commondir': simplify(s:Path.realpath(commondir)),
|
||||
\}
|
||||
lockvar git.worktree
|
||||
lockvar git.repository
|
||||
lockvar git.commondir
|
||||
return git
|
||||
endfunction
|
||||
|
||||
function! s:abspath(git, path) abort
|
||||
let relpath = s:Path.realpath(expand(a:path))
|
||||
if s:Path.is_absolute(relpath)
|
||||
return relpath
|
||||
endif
|
||||
return s:Path.join(a:git.worktree, relpath)
|
||||
endfunction
|
||||
|
||||
function! s:relpath(git, path) abort
|
||||
let abspath = s:Path.realpath(expand(a:path))
|
||||
if s:Path.is_relative(abspath)
|
||||
return abspath
|
||||
endif
|
||||
let pattern = s:String.escape_pattern(a:git.worktree . s:Path.separator())
|
||||
return abspath =~# '^' . pattern
|
||||
\ ? matchstr(abspath, '^' . pattern . '\zs.*')
|
||||
\ : abspath
|
||||
endfunction
|
||||
|
||||
function! s:resolve(git, path) abort
|
||||
let path = s:Path.realpath(a:path)
|
||||
let path1 = s:Path.join(a:git.repository, path)
|
||||
let path2 = empty(a:git.commondir)
|
||||
\ ? ''
|
||||
\ : s:Path.join(a:git.commondir, path)
|
||||
return filereadable(path1) || isdirectory(path1)
|
||||
\ ? path1
|
||||
\ : filereadable(path2) || isdirectory(path2)
|
||||
\ ? path2
|
||||
\ : path1
|
||||
endfunction
|
||||
|
||||
" The search paths are documented at
|
||||
" https://git-scm.com/docs/git-rev-parse
|
||||
function! s:ref(git, refname) abort
|
||||
let refname = a:refname ==# '@' ? 'HEAD' : a:refname
|
||||
let candidates = [
|
||||
\ refname,
|
||||
\ printf('refs/%s', refname),
|
||||
\ printf('refs/tags/%s', refname),
|
||||
\ printf('refs/heads/%s', refname),
|
||||
\ printf('refs/remotes/%s', refname),
|
||||
\ printf('refs/remotes/%s/HEAD', refname),
|
||||
\]
|
||||
let packed_refs = s:_get_packed_refs(a:git)
|
||||
for candidate in candidates
|
||||
let ref = s:_get_reference(a:git, candidate, packed_refs)
|
||||
if !empty(ref)
|
||||
return ref
|
||||
endif
|
||||
endfor
|
||||
return {}
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:_get_packed_refs(git) abort
|
||||
let path = s:resolve(a:git, 'packed-refs')
|
||||
if !filereadable(path)
|
||||
return []
|
||||
endif
|
||||
let store = s:Store.of(path)
|
||||
let packed_refs = store.get('packed-refs', [])
|
||||
if !empty(packed_refs)
|
||||
return packed_refs
|
||||
endif
|
||||
let packed_refs = readfile(path)
|
||||
call store.set('packed-refs', packed_refs)
|
||||
return packed_refs
|
||||
endfunction
|
||||
|
||||
function! s:_get_reference(git, refname, packed_refs) abort
|
||||
let ref = s:_get_reference_trad(a:git, a:refname, a:packed_refs)
|
||||
if !empty(ref)
|
||||
return ref
|
||||
endif
|
||||
return s:_get_reference_packed(a:git, a:refname, a:packed_refs)
|
||||
endfunction
|
||||
|
||||
function! s:_get_reference_trad(git, refname, packed_refs) abort
|
||||
let path = s:resolve(a:git, a:refname)
|
||||
if !filereadable(path)
|
||||
return {}
|
||||
endif
|
||||
let content = get(readfile(path), 0, '')
|
||||
if content =~# '^ref:'
|
||||
return s:_get_reference(
|
||||
\ a:git,
|
||||
\ matchstr(content, '^ref:\s\+\zs.\+'),
|
||||
\ a:packed_refs,
|
||||
\)
|
||||
endif
|
||||
let name = matchstr(a:refname, '^refs/\%(heads\|remotes\|tags\)/\zs.*')
|
||||
return {
|
||||
\ 'name': empty(name) ? content[:7] : name,
|
||||
\ 'path': a:refname,
|
||||
\ 'hash': content,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! s:_get_reference_packed(git, refname, packed_refs) abort
|
||||
let m = matchstr(a:packed_refs, '\s' . a:refname . '$')
|
||||
if empty(m)
|
||||
return {}
|
||||
endif
|
||||
let m = matchlist(m, '^\([0-9a-f]\+\)\s\+\(.*\)$')
|
||||
let refname = m[2]
|
||||
let name = matchstr(refname, '^refs/\%(heads\|remotes\|tags\)/\zs.*')
|
||||
return {
|
||||
\ 'name': name,
|
||||
\ 'path': refname,
|
||||
\ 'hash': m[1],
|
||||
\}
|
||||
endfunction
|
104
bundle/gina.vim/autoload/vital/__gina__/Options.vim
Normal file
104
bundle/gina.vim/autoload/vital/__gina__/Options.vim
Normal file
@ -0,0 +1,104 @@
|
||||
let s:t_list = type([])
|
||||
let s:t_func = type(function('tr'))
|
||||
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:String = a:V.import('Data.String')
|
||||
endfunction
|
||||
|
||||
function! s:_vital_depends() abort
|
||||
return ['Data.String']
|
||||
endfunction
|
||||
|
||||
function! s:new() abort
|
||||
let options = copy(s:options)
|
||||
let options._options = {}
|
||||
return options
|
||||
endfunction
|
||||
|
||||
|
||||
" Private --------------------------------------------------------------------
|
||||
function! s:_filter(arglead, candidates) abort
|
||||
let pattern = '^' . s:String.escape_pattern(a:arglead)
|
||||
let candidates = copy(a:candidates)
|
||||
call filter(candidates, 'v:val =~# pattern')
|
||||
return candidates
|
||||
endfunction
|
||||
|
||||
function! s:_complete_choice(arglead, cmdline, cursorpos, choices) abort
|
||||
let leading = matchstr(a:arglead, '\%(-[^-]\|--\S\+=\)')
|
||||
let candidates = map(
|
||||
\ copy(a:choices),
|
||||
\ 'leading . v:val'
|
||||
\)
|
||||
return s:_filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
function! s:_complete_callback(arglead, cmdline, cursorpos, callback) abort
|
||||
let m = matchlist(a:arglead, '\(-[^-]\|--\S\+=\)\(.*\)')
|
||||
let candidates = a:callback(m[2], a:cmdline, a:cursorpos)
|
||||
return map(
|
||||
\ copy(candidates),
|
||||
\ 'm[1] . v:val'
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:_compare_options(lhs, rhs) abort
|
||||
let lhs = a:lhs.names[-1]
|
||||
let rhs = a:rhs.names[-1]
|
||||
return lhs ==# rhs ? 0 : (lhs < rhs ? -1 : 1)
|
||||
endfunction
|
||||
|
||||
|
||||
" Options --------------------------------------------------------------------
|
||||
let s:options = {}
|
||||
|
||||
function! s:options.define(query, description, ...) abort
|
||||
let self._options[a:query] = extend(copy(s:option), {
|
||||
\ 'names': split(a:query, '|'),
|
||||
\ 'value': a:0 ? a:1 : v:null,
|
||||
\ 'description': a:description,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! s:options.help(...) abort
|
||||
let lwidth = max(map(keys(self._options), 'len(v:val)')) + 3
|
||||
let rwidth = get(a:000, 0, 80) - lwidth
|
||||
let text = ['options:']
|
||||
call map(
|
||||
\ sort(values(self._options), function('s:_compare_options')),
|
||||
\ 'extend(text, v:val.help(lwidth, rwidth))'
|
||||
\)
|
||||
redraw | echo join(text, "\n")
|
||||
endfunction
|
||||
|
||||
function! s:options.complete(arglead, cmdline, cursorpos) abort
|
||||
let leading = matchstr(a:arglead, '^\%(-[^-]\|--\S\+=\)')
|
||||
let candidates = []
|
||||
for option in values(self._options)
|
||||
if index(option.names, leading) != -1
|
||||
return option.complete(a:arglead, a:cmdline, a:cursorpos)
|
||||
endif
|
||||
call extend(candidates, option.names)
|
||||
endfor
|
||||
return s:_filter(a:arglead, candidates)
|
||||
endfunction
|
||||
|
||||
|
||||
" Option ---------------------------------------------------------------------
|
||||
let s:option = {}
|
||||
|
||||
function! s:option.help(lwidth, rwidth) abort
|
||||
let rhs = s:String.wrap(self.description, a:rwidth)
|
||||
let lhs = [s:String.pad_right(join(self.names), a:lwidth)]
|
||||
let lhs += repeat([repeat(' ', a:lwidth)], len(rhs) - 1)
|
||||
return map(range(len(lhs)), 'lhs[v:val] . rhs[v:val]')
|
||||
endfunction
|
||||
|
||||
function! s:option.complete(arglead, cmdline, cursorpos) abort
|
||||
if type(self.value) == s:t_list
|
||||
return s:_complete_choice(a:arglead, a:cmdline, a:cursorpos, self.value)
|
||||
elseif type(self.value) == s:t_func
|
||||
return s:_complete_callback(a:arglead, a:cmdline, a:cursorpos, self.value)
|
||||
endif
|
||||
return []
|
||||
endfunction
|
87
bundle/gina.vim/autoload/vital/__gina__/System/Store.vim
Normal file
87
bundle/gina.vim/autoload/vital/__gina__/System/Store.vim
Normal file
@ -0,0 +1,87 @@
|
||||
let s:t_list = type([])
|
||||
let s:store_cache = {}
|
||||
|
||||
function! s:hash(pathlist) abort
|
||||
return sha256(join(sort(a:pathlist)))
|
||||
endfunction
|
||||
|
||||
function! s:get_slug_expr() abort
|
||||
return 'matchstr(expand(''<sfile>''), ''\zs[^. ]\+$'')'
|
||||
endfunction
|
||||
|
||||
function! s:of(pathlist) abort
|
||||
let pathlist = type(a:pathlist) == s:t_list
|
||||
\ ? a:pathlist
|
||||
\ : [a:pathlist]
|
||||
let hash = s:hash(pathlist)
|
||||
if has_key(s:store_cache, hash)
|
||||
return s:store_cache[hash]
|
||||
endif
|
||||
let store = copy(s:store)
|
||||
let store.caches = {}
|
||||
let store.pathlist = copy(pathlist)
|
||||
lockvar store.pathlist
|
||||
let s:store_cache[hash] = store
|
||||
return store
|
||||
endfunction
|
||||
|
||||
function! s:remove(pathlist) abort
|
||||
let pathlist = type(a:pathlist) == s:t_list
|
||||
\ ? a:pathlist
|
||||
\ : [a:pathlist]
|
||||
let hash = s:hash(pathlist)
|
||||
silent! unlet s:store_cache[hash]
|
||||
endfunction
|
||||
|
||||
|
||||
" Store instance -------------------------------------------------------------
|
||||
let s:store = {}
|
||||
|
||||
function! s:store.is_expired(name) abort
|
||||
let cache = get(self.caches, a:name, {})
|
||||
if empty(cache)
|
||||
return 1
|
||||
endif
|
||||
for i in range(len(self.pathlist))
|
||||
let uptime1 = getftime(self.pathlist[i])
|
||||
let uptime2 = cache.uptimes[i]
|
||||
if uptime1 != uptime2 && (uptime1 == -1 || uptime2 == -1)
|
||||
return 1
|
||||
elseif uptime1 > uptime2
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! s:store.has(name) abort
|
||||
return has_key(self.caches, a:name)
|
||||
endfunction
|
||||
|
||||
function! s:store.get(name, ...) abort
|
||||
let default = get(a:000, 0)
|
||||
if self.is_expired(a:name)
|
||||
return default
|
||||
endif
|
||||
return self.caches[a:name].cache
|
||||
endfunction
|
||||
|
||||
function! s:store.set(name, value) abort
|
||||
let uptimes = map(copy(self.pathlist), 'getftime(v:val)')
|
||||
let cache = {
|
||||
\ 'cache': a:value,
|
||||
\ 'uptimes': uptimes
|
||||
\}
|
||||
let self.caches[a:name] = cache
|
||||
return self
|
||||
endfunction
|
||||
|
||||
function! s:store.remove(name) abort
|
||||
silent! unlet self.caches[a:name]
|
||||
return self
|
||||
endfunction
|
||||
|
||||
function! s:store.clear() abort
|
||||
let self.caches = {}
|
||||
return self
|
||||
endfunction
|
9
bundle/gina.vim/autoload/vital/_gina.vim
Normal file
9
bundle/gina.vim/autoload/vital/_gina.vim
Normal file
@ -0,0 +1,9 @@
|
||||
let s:_plugin_name = expand('<sfile>:t:r')
|
||||
|
||||
function! vital#{s:_plugin_name}#new() abort
|
||||
return vital#{s:_plugin_name[1:]}#new()
|
||||
endfunction
|
||||
|
||||
function! vital#{s:_plugin_name}#function(funcname) abort
|
||||
silent! return function(a:funcname)
|
||||
endfunction
|
78
bundle/gina.vim/autoload/vital/_gina/App/Emitter.vim
Normal file
78
bundle/gina.vim/autoload/vital/_gina/App/Emitter.vim
Normal file
@ -0,0 +1,78 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_gina#App#Emitter#import() abort', printf("return map({'subscribe': '', 'unsubscribe': '', 'emit': '', 'add_middleware': '', 'remove_middleware': ''}, \"vital#_gina#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
let s:listeners = {}
|
||||
let s:middlewares = []
|
||||
|
||||
function! s:subscribe(name, listener, ...) abort
|
||||
let dict = get(a:000, 0, v:null)
|
||||
let s:listeners[a:name] = get(s:listeners, a:name, [])
|
||||
call add(s:listeners[a:name], [a:listener, dict])
|
||||
endfunction
|
||||
|
||||
function! s:unsubscribe(...) abort
|
||||
if a:0 == 0
|
||||
let s:listeners = {}
|
||||
elseif a:0 == 1
|
||||
let s:listeners[a:1] = []
|
||||
else
|
||||
let dict = a:0 == 3 ? a:3 : v:null
|
||||
let s:listeners[a:1] = get(s:listeners, a:1, [])
|
||||
let index = index(s:listeners[a:1], [a:2, dict])
|
||||
if index != -1
|
||||
call remove(s:listeners[a:1], index)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:add_middleware(middleware) abort
|
||||
call add(s:middlewares, a:middleware)
|
||||
endfunction
|
||||
|
||||
function! s:remove_middleware(...) abort
|
||||
if a:0 == 0
|
||||
let s:middlewares = []
|
||||
else
|
||||
let index = index(s:middlewares, a:1)
|
||||
if index != -1
|
||||
call remove(s:middlewares, index)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:emit(name, ...) abort
|
||||
let attrs = copy(a:000)
|
||||
let listeners = copy(get(s:listeners, a:name, []))
|
||||
let middlewares = map(s:middlewares, 'extend(copy(s:middleware), v:val)')
|
||||
for middleware in middlewares
|
||||
call call(middleware.on_emit_pre, [a:name, listeners, attrs], middleware)
|
||||
endfor
|
||||
for [l:Listener, dict] in listeners
|
||||
if empty(dict)
|
||||
call call(Listener, attrs)
|
||||
else
|
||||
call call(Listener, attrs, dict)
|
||||
endif
|
||||
endfor
|
||||
for middleware in middlewares
|
||||
call call(middleware.on_emit_post, [a:name, listeners, attrs], middleware)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
|
||||
" Middleware skeleton --------------------------------------------------------
|
||||
let s:middleware = {}
|
||||
|
||||
function! s:middleware.on_emit_pre(name, listeners, attrs) abort
|
||||
" Users can override this method
|
||||
endfunction
|
||||
|
||||
function! s:middleware.on_emit_post(name, listeners, attrs) abort
|
||||
" Users can override this method
|
||||
endfunction
|
126
bundle/gina.vim/autoload/vital/_gina/App/Revelator.vim
Normal file
126
bundle/gina.vim/autoload/vital/_gina/App/Revelator.vim
Normal file
@ -0,0 +1,126 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_gina#App#Revelator#import() abort', printf("return map({'info': '', '_vital_depends': '', 'unregister': '', '_vital_created': '', 'register': '', 'call': '', 'message': '', 'warning': '', 'critical': '', 'get_default_receiver': '', 'error': '', '_vital_loaded': ''}, \"vital#_gina#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
let s:t_number = type(0)
|
||||
let s:receivers = []
|
||||
|
||||
|
||||
function! s:_vital_loaded(V) abort
|
||||
let s:Console = a:V.import('Vim.Console')
|
||||
endfunction
|
||||
|
||||
function! s:_vital_depends() abort
|
||||
return ['Vim.Console']
|
||||
endfunction
|
||||
|
||||
function! s:_vital_created(module) abort
|
||||
call a:module.register(s:get_default_receiver())
|
||||
endfunction
|
||||
|
||||
function! s:message(category, msg) abort
|
||||
return printf(
|
||||
\ 'vital: App.Revelator: %s: %s',
|
||||
\ a:category,
|
||||
\ a:msg,
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! s:info(msg) abort
|
||||
let v:statusmsg = a:msg
|
||||
return s:message('INFO', a:msg)
|
||||
endfunction
|
||||
|
||||
function! s:warning(msg) abort
|
||||
let v:warningmsg = a:msg
|
||||
return s:message('WARNING', a:msg)
|
||||
endfunction
|
||||
|
||||
function! s:error(msg) abort
|
||||
let v:errmsg = a:msg
|
||||
return s:message('ERROR', a:msg)
|
||||
endfunction
|
||||
|
||||
function! s:critical(msg) abort
|
||||
let v:errmsg = a:msg
|
||||
return s:message('CRITICAL', a:msg)
|
||||
endfunction
|
||||
|
||||
function! s:call(func, arglist, ...) abort
|
||||
let receivers_saved = copy(s:receivers)
|
||||
let dict = a:0 ? a:1 : 0
|
||||
try
|
||||
return type(dict) == s:t_number
|
||||
\ ? call(a:func, a:arglist)
|
||||
\ : call(a:func, a:arglist, dict)
|
||||
catch /^vital: App\.Revelator: /
|
||||
call s:_receive(v:exception, v:throwpoint)
|
||||
finally
|
||||
let s:receivers = receivers_saved
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:register(receiver) abort
|
||||
call add(s:receivers, a:receiver)
|
||||
endfunction
|
||||
|
||||
function! s:unregister(receiver) abort
|
||||
let index = index(s:receivers, a:receiver)
|
||||
if index != -1
|
||||
call remove(s:receivers, index)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_default_receiver() abort
|
||||
return function('s:_default_receiver')
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:_receive(exception, throwpoint) abort
|
||||
let m = matchlist(a:exception, '^vital: App\.Revelator: \(.\{-}\): \(.*\)$')
|
||||
if len(m)
|
||||
let category = m[1]
|
||||
let message = m[2]
|
||||
let revelation = {
|
||||
\ 'category': m[1],
|
||||
\ 'message': m[2],
|
||||
\ 'exception': a:exception,
|
||||
\ 'throwpoint': a:throwpoint,
|
||||
\}
|
||||
for l:Receiver in reverse(copy(s:receivers))
|
||||
if call(Receiver, [revelation])
|
||||
return
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
throw a:exception . "\n" . a:throwpoint
|
||||
endfunction
|
||||
|
||||
function! s:_default_receiver(revelation) abort
|
||||
if a:revelation.category ==# 'INFO'
|
||||
redraw
|
||||
call s:Console.info(a:revelation.message)
|
||||
call s:Console.debug(a:revelation.throwpoint)
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'WARNING'
|
||||
redraw
|
||||
call s:Console.warn(a:revelation.message)
|
||||
call s:Console.debug(a:revelation.throwpoint)
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'ERROR'
|
||||
redraw
|
||||
call s:Console.error(a:revelation.message)
|
||||
call s:Console.debug(a:revelation.throwpoint)
|
||||
return 1
|
||||
elseif a:revelation.category ==# 'CRITICAL'
|
||||
redraw
|
||||
call s:Console.error(a:revelation.message)
|
||||
call s:Console.error(a:revelation.throwpoint)
|
||||
return 1
|
||||
endif
|
||||
endfunction
|
186
bundle/gina.vim/autoload/vital/_gina/Bitwise.vim
Normal file
186
bundle/gina.vim/autoload/vital/_gina/Bitwise.vim
Normal file
@ -0,0 +1,186 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_gina#Bitwise#import() abort', printf("return map({'uint8': '', 'rotate16l': '', 'uint16': '', 'rotate8l': '', 'rotate16r': '', 'uint32': '', 'rotate8r': '', '_vital_loaded': '', 'rshift': '', 'lshift': '', 'lshift32': '', 'rotate32l': '', 'rshift32': '', 'rotate32r': '', 'sign_extension': '', '_vital_created': '', 'rotate64l': '', 'compare': '', 'uint64': '', 'rotate64r': ''}, \"vital#_gina#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
" bitwise operators
|
||||
" moved from github.com/ynkdir/vim-funlib
|
||||
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
" inner utility
|
||||
|
||||
let s:bits = has('num64') ? 64 : 32
|
||||
let s:mask = s:bits - 1
|
||||
let s:mask32 = 32 - 1
|
||||
|
||||
let s:pow2 = [1]
|
||||
for s:i in range(s:mask)
|
||||
call add(s:pow2, s:pow2[-1] * 2)
|
||||
endfor
|
||||
unlet s:i
|
||||
|
||||
let s:min = s:pow2[-1]
|
||||
|
||||
" 32bit/64bit common method
|
||||
function! s:_throw(msg) abort
|
||||
throw 'vital: Bitwise: ' . a:msg
|
||||
endfunction
|
||||
|
||||
" compare as unsigned int
|
||||
function! s:compare(a, b) abort
|
||||
if (a:a >= 0 && a:b >= 0) || (a:a < 0 && a:b < 0)
|
||||
return a:a < a:b ? -1 : a:a > a:b ? 1 : 0
|
||||
else
|
||||
return a:a < 0 ? 1 : -1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:lshift(a, n) abort
|
||||
return a:a * s:pow2[and(a:n, s:mask)]
|
||||
endfunction
|
||||
|
||||
function! s:rshift(a, n) abort
|
||||
let n = and(a:n, s:mask)
|
||||
return n == 0 ? a:a :
|
||||
\ a:a < 0 ? (a:a - s:min) / s:pow2[n] + s:pow2[-2] / s:pow2[n - 1]
|
||||
\ : a:a / s:pow2[n]
|
||||
endfunction
|
||||
|
||||
" 32bit or 64bit specific method
|
||||
" define sign_extension
|
||||
" lshift32/rshift32 64bit only implementation.
|
||||
if has('num64')
|
||||
" NOTE:
|
||||
" An int literal larger than or equal to 0x8000000000000000 will be rounded
|
||||
" to 0x7FFFFFFFFFFFFFFF after Vim 8.0.0219, so create it without literal
|
||||
let s:xFFFFFFFF00000000 = 0xFFFFFFFF * s:pow2[and(32, s:mask)]
|
||||
function! s:sign_extension(n) abort
|
||||
if and(a:n, 0x80000000)
|
||||
return or(a:n, s:xFFFFFFFF00000000)
|
||||
else
|
||||
return and(a:n, 0xFFFFFFFF)
|
||||
endif
|
||||
endfunction
|
||||
function! s:lshift32(a, n) abort
|
||||
return and(s:lshift(a:a, and(a:n, s:mask32)), 0xFFFFFFFF)
|
||||
endfunction
|
||||
function! s:rshift32(a, n) abort
|
||||
return s:rshift(and(a:a, 0xFFFFFFFF), and(a:n, s:mask32))
|
||||
endfunction
|
||||
else
|
||||
function! s:sign_extension(n) abort
|
||||
return a:n
|
||||
endfunction
|
||||
endif
|
||||
|
||||
" 32bit or 64bit specific method
|
||||
" builtin and funcref setup at module creation time.
|
||||
" define and/or/xor/invert built-in
|
||||
" lshift32/rshift32 32bit only altnative define.
|
||||
function! s:_vital_created(module) abort
|
||||
for op in ['and', 'or', 'xor', 'invert']
|
||||
let a:module[op] = function(op)
|
||||
let s:[op] = a:module[op]
|
||||
endfor
|
||||
if !has('num64')
|
||||
let a:module.lshift32 = a:module.lshift
|
||||
let a:module.rshift32 = a:module.rshift
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" setup at module loaded time.
|
||||
" define inner utility part2 : use defined method
|
||||
function! s:_vital_loaded(V) abort
|
||||
if has('num64')
|
||||
let s:mask32bit = 0xFFFFFFFF
|
||||
let s:mask64bit = or(
|
||||
\ s:lshift(s:mask32bit, 32),
|
||||
\ s:mask32bit
|
||||
\)
|
||||
else
|
||||
let s:mask32bit = or(
|
||||
\ s:lshift(0xFFFF, 16),
|
||||
\ 0xFFFF
|
||||
\)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" 32bit/64bit common method part2 : use defined method
|
||||
|
||||
function! s:uint8(value) abort
|
||||
return and(a:value, 0xFF)
|
||||
endfunction
|
||||
|
||||
function! s:uint16(value) abort
|
||||
return and(a:value, 0xFFFF)
|
||||
endfunction
|
||||
|
||||
function! s:uint32(value) abort
|
||||
return and(a:value, s:mask32bit)
|
||||
endfunction
|
||||
|
||||
function! s:rotate8l(data, bits) abort
|
||||
let data = s:uint8(a:data)
|
||||
return s:uint8(or(s:lshift(data, a:bits),
|
||||
\ s:rshift(data, 8 - a:bits)))
|
||||
endfunction
|
||||
function! s:rotate8r(data, bits) abort
|
||||
return s:rotate8l(a:data, 8 - a:bits)
|
||||
endfunction
|
||||
|
||||
function! s:rotate16l(data, bits) abort
|
||||
let data = s:uint16(a:data)
|
||||
return s:uint16(or(s:lshift(data, a:bits),
|
||||
\ s:rshift(data, 16 - a:bits)))
|
||||
endfunction
|
||||
function! s:rotate16r(data, bits) abort
|
||||
return s:rotate16l(a:data, 16 - a:bits)
|
||||
endfunction
|
||||
|
||||
function! s:rotate32l(data, bits) abort
|
||||
let data = s:uint32(a:data)
|
||||
return s:uint32(or(s:lshift(data, a:bits),
|
||||
\ s:rshift(data, 32 - a:bits)))
|
||||
endfunction
|
||||
function! s:rotate32r(data, bits) abort
|
||||
return s:rotate32l(a:data, 32 - a:bits)
|
||||
endfunction
|
||||
|
||||
" 32bit or 64bit specific method part2 : use defined method
|
||||
" define uint64/rotate64l 64bit only implementation.
|
||||
" 32bit throw exception.
|
||||
if has('num64')
|
||||
function! s:uint64(value) abort
|
||||
return and(a:value, s:mask64bit)
|
||||
endfunction
|
||||
|
||||
function! s:rotate64l(data, bits) abort
|
||||
let data = s:uint64(a:data)
|
||||
return s:uint64(or(s:lshift(data, a:bits),
|
||||
\ s:rshift(data, 64 - a:bits)))
|
||||
endfunction
|
||||
else
|
||||
function! s:uint64(value) abort
|
||||
call s:_throw('64bit unsupport.')
|
||||
endfunction
|
||||
|
||||
function! s:rotate64l(data, bits) abort
|
||||
call s:_throw('64bit unsupport.')
|
||||
endfunction
|
||||
endif
|
||||
|
||||
" When 32bit throw exception.
|
||||
function! s:rotate64r(data, bits) abort
|
||||
return s:rotate64l(a:data, 64 - a:bits)
|
||||
endfunction
|
||||
|
||||
let &cpo = s:save_cpo
|
||||
unlet s:save_cpo
|
||||
|
||||
" vim:set et ts=2 sts=2 sw=2 tw=0:
|
44
bundle/gina.vim/autoload/vital/_gina/Config.vim
Normal file
44
bundle/gina.vim/autoload/vital/_gina/Config.vim
Normal file
@ -0,0 +1,44 @@
|
||||
" ___vital___
|
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
|
||||
" Do not modify the code nor insert new lines before '" ___vital___'
|
||||
function! s:_SID() abort
|
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
|
||||
endfunction
|
||||
execute join(['function! vital#_gina#Config#import() abort', printf("return map({'define': '', 'translate': '', 'config': ''}, \"vital#_gina#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
|
||||
delfunction s:_SID
|
||||
" ___vital___
|
||||
let s:plugin_name = expand('<sfile>:p:h:t')
|
||||
let s:plugin_name = s:plugin_name =~# '^__.\+__$'
|
||||
\ ? s:plugin_name[2:-3]
|
||||
\ : s:plugin_name =~# '^_.\+$'
|
||||
\ ? s:plugin_name[1:]
|
||||
\ : s:plugin_name
|
||||
|
||||
function! s:define(prefix, default) abort
|
||||
let prefix = a:prefix =~# '^g:' ? a:prefix : 'g:' . a:prefix
|
||||
for [key, Value] in items(a:default)
|
||||
let name = prefix . '#' . key
|
||||
if !exists(name)
|
||||
execute 'let ' . name . ' = ' . string(Value)
|
||||
endif
|
||||
unlet Value
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:config(scriptfile, default) abort
|
||||
let prefix = s:translate(a:scriptfile)
|
||||
call s:define(prefix, a:default)
|
||||
endfunction
|
||||
|
||||
function! s:translate(scriptfile) abort
|
||||
let path = fnamemodify(a:scriptfile, ':gs?\\?/?')
|
||||
let name = matchstr(path, printf(
|
||||
\ 'autoload/\zs\%%(%s\.vim\|%s/.*\)$',
|
||||
\ s:plugin_name,
|
||||
\ s:plugin_name,
|
||||
\))
|
||||
let name = substitute(name, '\.vim$', '', '')
|
||||
let name = substitute(name, '/', '#', 'g')
|
||||
let name = substitute(name, '\%(^#\|#$\)', '', 'g')
|
||||
return 'g:' . name
|
||||
endfunction
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user