Regenerate nvim config
This commit is contained in:
@ -0,0 +1,10 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 0%
|
||||
patch:
|
||||
default:
|
||||
target: 0%
|
||||
|
||||
comment: false
|
||||
@ -0,0 +1,3 @@
|
||||
[run]
|
||||
plugins = covimerage
|
||||
data_file = .coverage_covimerage
|
||||
90
config/neovim/store/lazy-plugins/git-messenger.vim/.github/workflows/ci.yml
vendored
Normal file
90
config/neovim/store/lazy-plugins/git-messenger.vim/.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Unit tests
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
neovim: [true, false]
|
||||
version: [stable, nightly]
|
||||
# Exclude Neovim nightly since Neovim's release workflow is fairly unstable: https://github.com/neovim/neovim/actions/workflows/release.yml
|
||||
exclude:
|
||||
- neovim: true
|
||||
version: nightly
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set Git user for running Git commands in unit tests
|
||||
run: |
|
||||
git config --global user.email "users@noreply.github.com"
|
||||
git config --global user.name "github-action"
|
||||
- name: Fetch Git history for unit tests
|
||||
run: git fetch --no-tags --prune --unshallow
|
||||
- name: Checkout themis.vim
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: thinca/vim-themis
|
||||
path: vim-themis
|
||||
- uses: rhysd/action-setup-vim@v1
|
||||
id: vim
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
neovim: ${{ matrix.neovim }}
|
||||
- name: Run unit tests
|
||||
env:
|
||||
THEMIS_VIM: ${{ steps.vim.outputs.executable }}
|
||||
THEMIS_PROFILE: profile.txt
|
||||
run: ./vim-themis/bin/themis ./test/all.vimspec
|
||||
- uses: actions/setup-python@v1
|
||||
- name: Report coverage
|
||||
run: |
|
||||
# https://github.com/Vimjas/covimerage/issues/95
|
||||
pip install 'click<8.0.0'
|
||||
pip install covimerage
|
||||
covimerage write_coverage profile.txt
|
||||
coverage report
|
||||
coverage xml
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
test-windows:
|
||||
name: Unit tests on Windows
|
||||
strategy:
|
||||
matrix:
|
||||
neovim: [true, false]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set Git user for running Git commands in unit tests
|
||||
run: |
|
||||
git config --global user.email "users@noreply.github.com"
|
||||
git config --global user.name "github-action"
|
||||
- name: Fetch Git history for unit tests
|
||||
run: git fetch --no-tags --prune --unshallow
|
||||
- name: Checkout themis.vim
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: thinca/vim-themis
|
||||
path: vim-themis
|
||||
- uses: rhysd/action-setup-vim@v1
|
||||
id: vim
|
||||
with:
|
||||
version: stable
|
||||
neovim: ${{ matrix.neovim }}
|
||||
- name: Run unit tests
|
||||
env:
|
||||
THEMIS_VIM: ${{ steps.vim.outputs.executable }}
|
||||
run: ./vim-themis/bin/themis ./test/all.vimspec
|
||||
|
||||
vint:
|
||||
name: Run vint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v1
|
||||
- run: pip install vim-vint
|
||||
- run: vint --warning --verbose --enable-neovim ./autoload ./plugin
|
||||
5
config/neovim/store/lazy-plugins/git-messenger.vim/.gitignore
vendored
Normal file
5
config/neovim/store/lazy-plugins/git-messenger.vim/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/vim-themis
|
||||
/htmlcov
|
||||
/coverage.xml
|
||||
/.coverage_covimerage
|
||||
/doc/tags
|
||||
20
config/neovim/store/lazy-plugins/git-messenger.vim/LICENSE
Normal file
20
config/neovim/store/lazy-plugins/git-messenger.vim/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 rhysd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
357
config/neovim/store/lazy-plugins/git-messenger.vim/README.md
Normal file
357
config/neovim/store/lazy-plugins/git-messenger.vim/README.md
Normal file
@ -0,0 +1,357 @@
|
||||
git-messenger.vim
|
||||
=================
|
||||
[![Build Status][ci-badge]][ci]
|
||||
[![codecov][codecov-badge]][codecov]
|
||||
|
||||
[git-messenger.vim][repo] is a Vim/Neovim plugin to reveal the hidden message from Git under the
|
||||
cursor quickly. It shows the history of commits under the cursor in popup window.
|
||||
|
||||
This plugin shows the message of the last commit in a 'popup window'. If the last commit is not
|
||||
convenient, you can explore older commits in the popup window. Additionally you can also check diff
|
||||
of the commit.
|
||||
|
||||
The popup window is implemented in
|
||||
|
||||
- Floating window on Neovim (0.4 or later)
|
||||
- Preview window on Vim (8 or later) or Neovim (0.3 or earlier)
|
||||
|
||||
The floating window is definitely recommended since it can shows the information near the cursor.
|
||||
|
||||
I wrote [a Japanese blogpost for this plugin](https://rhysd.hatenablog.com/entry/2019/03/10/230119).
|
||||
|
||||
|
||||
|
||||
## Why?
|
||||
|
||||
When you're modifying unfamiliar codes, you would sometimes wonder 'why was this line added?' or
|
||||
'why this value was chosen?' in the source code. The answer sometimes lays in a commit message,
|
||||
especially in message of the last commit which modifies the line.
|
||||
|
||||
|
||||
|
||||
## Screenshot
|
||||
|
||||
#### Show popup window with Neovim v0.4.0-dev
|
||||
|
||||
<img alt="main screenshot" src="https://github.com/rhysd/ss/blob/master/git-messenger.vim/demo.gif?raw=true" width=763 height=556 />
|
||||
|
||||
#### Exploring older commits
|
||||
|
||||
<img alt="history screenshot" src="https://github.com/rhysd/ss/blob/master/git-messenger.vim/history.gif?raw=true" width=510 height=252 />
|
||||
|
||||
#### Exploring diff of the commit (you may be also interested in `g:git_messenger_include_diff`)
|
||||
|
||||
<img alt="diff screenshot" src="https://github.com/rhysd/ss/blob/master/git-messenger.vim/diff.gif?raw=true" width=742 height=492 />
|
||||
|
||||
#### Switching unified diffs and word diffs
|
||||
|
||||
<img alt="word diff screenshot" src="https://github.com/rhysd/ss/blob/master/git-messenger.vim/worddiff.gif?raw=true" width=661 height=492 />
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Please ensure the following requirement before installing this plugin.
|
||||
|
||||
- Git v1.8.5 or later (for `-C` option of `git` command)
|
||||
|
||||
If you use any package manager, please follow its instruction.
|
||||
|
||||
With [vim-plug](https://github.com/junegunn/vim-plug):
|
||||
|
||||
```vim
|
||||
Plug 'rhysd/git-messenger.vim'
|
||||
```
|
||||
|
||||
With [dein.vim](https://github.com/Shougo/dein.vim):
|
||||
|
||||
```vim
|
||||
call dein#add('rhysd/git-messenger.vim', {
|
||||
\ 'lazy' : 1,
|
||||
\ 'on_cmd' : 'GitMessenger',
|
||||
\ 'on_map' : '<Plug>(git-messenger)',
|
||||
\ })
|
||||
```
|
||||
|
||||
With [minpac](https://github.com/k-takata/minpac):
|
||||
|
||||
```vim
|
||||
call minpac#add('rhysd/git-messenger.vim')
|
||||
```
|
||||
|
||||
if you use Vim's builtin packager, please follow the instruction at `:help pack-add`.
|
||||
|
||||
To enable a floating window support, you need to install Neovim 0.4 or later.
|
||||
Please follow [the official instruction][nvim-install].
|
||||
|
||||
To check if Neovim's floating window feature is available, try `:checkhealth`.
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Only one mapping (or one command) provides all features of this plugin. Briefly, move cursor to
|
||||
the position and run `:GitMessenger` or `<Leader>gm`. If you see an error message, please try
|
||||
[health check](#health-check).
|
||||
|
||||
### Commands
|
||||
|
||||
```
|
||||
:GitMessenger
|
||||
```
|
||||
|
||||
Behavior of this command is depending on the situation. You can do every operations only with this
|
||||
mapping.
|
||||
|
||||
- Normally, it opens a popup window with the last commit message
|
||||
- When a popup window is already open, it moves cursor into the window
|
||||
- When a cursor is within a popup window, it closes the window
|
||||
|
||||
It opens a popup window with the last commit message which modified the line at cursor. The popup
|
||||
window shows following contents:
|
||||
|
||||
- **History:** `History: {page number}` In popup window, `o`/`O` navigates to previous/next commit.
|
||||
- **Commit:** `Commit: {hash}` The commit hash
|
||||
- **Author:** `Author: {name}<{email}>` Author name and mail address of the commit
|
||||
- **Committer:** `Committer: {name}<{email}>` Committer name and mail address of the commit when
|
||||
committer is different from author
|
||||
- **Date:** `Date: {date}` Author date of the commit in system format
|
||||
- When a committer date and an author date are different (e.g. the commit was created again with
|
||||
`git commit --amend`), both **Author Date:** and **Committer Date:** are shown.
|
||||
- **Summary:** First line after `Date:` header line is a summary of commit
|
||||
- **Body:** After summary, commit body is put (if the commit has body)
|
||||
|
||||
The popup window will be automatically closed when you move the cursor so you don't need to close
|
||||
it manually.
|
||||
|
||||
Running this command while a popup window is open, it moves the cursor into the window.
|
||||
This behavior is useful when the commit message is too long and window cannot show the whole content.
|
||||
By moving the cursor into the popup window, you can see the rest of contents by scrolling it.
|
||||
You can also see the older commits.
|
||||
|
||||
Following mappings are defined within popup window.
|
||||
|
||||
| Mapping | Description |
|
||||
|---------|--------------------------------------------------------------|
|
||||
| `q` | Close the popup window |
|
||||
| `o` | **o**lder. Back to older commit at the line |
|
||||
| `O` | Opposite to `o`. Forward to newer commit at the line |
|
||||
| `d` | Toggle unified diff hunks only in current file of the commit |
|
||||
| `D` | Toggle all unified diff hunks of the commit |
|
||||
| `r` | Toggle word diff hunks only in current file of the commit |
|
||||
| `R` | Toggle all word diff hunks of current commit |
|
||||
| `?` | Show mappings help |
|
||||
|
||||
### Mappings
|
||||
|
||||
```
|
||||
<Plug>(git-messenger)
|
||||
```
|
||||
|
||||
The same as running `:GitMessenger` command.
|
||||
|
||||
By default, this plugin defines following mapping.
|
||||
|
||||
```vim
|
||||
nmap <Leader>gm <Plug>(git-messenger)
|
||||
```
|
||||
|
||||
If you don't like the default mapping, set `g:git_messenger_no_default_mappings` to `v:true` in
|
||||
your `.vimrc` or `init.vim` and map the `<Plug>` mapping to your favorite key sequence.
|
||||
|
||||
For example:
|
||||
|
||||
```vim
|
||||
nmap <C-w>m <Plug>(git-messenger)
|
||||
```
|
||||
|
||||
Some other additional `<Plug>` mappings. Please read [`:help git-messenger`][doc].
|
||||
|
||||
### Variables
|
||||
|
||||
Some global variables are available to configure the behavior of this plugin.
|
||||
|
||||
#### `g:git_messenger_close_on_cursor_moved` (Default: `v:true`)
|
||||
|
||||
When this value is set to `v:false`, a popup window is no longer closed automatically when moving a
|
||||
cursor after the window is shown up.
|
||||
|
||||
#### `g:git_messenger_include_diff` (Default: `"none"`)
|
||||
|
||||
One of `"none"`, `"current"`, `"all"`.
|
||||
|
||||
When this value is not set to `"none"`, a popup window includes diff hunks of the commit at showing
|
||||
up. `"current"` includes diff hunks of only current file in the commit. `"all"` includes all diff
|
||||
hunks in the commit.
|
||||
|
||||
Please note that typing `d`/`D` or `r`/`R` in popup window toggle showing diff
|
||||
hunks even if this value is set to `"none"`.
|
||||
|
||||
#### `g:git_messenger_git_command` (Default: `"git"`)
|
||||
|
||||
`git` command to retrieve commit messages. If your `git` executable is not in `$PATH` directories,
|
||||
please specify the path to the executable.
|
||||
|
||||
#### `g:git_messenger_no_default_mappings` (Default: `v:false`)
|
||||
|
||||
When this value is set to `v:true`, it does not define any key mappings. `<Plug>` mappings are still
|
||||
defined since they don't make any conflicts with existing mappings.
|
||||
|
||||
#### `g:git_messenger_into_popup_after_show` (Default: `v:true`)
|
||||
|
||||
When this value is set to `v:false`, running `:GitMessenger` or `<plug>(git-messenger)` again after
|
||||
showing a popup does not move the cursor in the window.
|
||||
|
||||
#### `g:git_messenger_always_into_popup` (Default: `v:false`)
|
||||
|
||||
When this value is set to `v:true`, the cursor goes into a popup window when running `:GitMessenger`
|
||||
or `<Plug>(git-messenger)`.
|
||||
|
||||
#### `g:git_messenger_extra_blame_args` (Default: `""`)
|
||||
|
||||
When this variable is set the contents will be appended to the git blame
|
||||
command. Use it to add options (like -w).
|
||||
|
||||
#### `g:git_messenger_preview_mods` (Default: `""`)
|
||||
|
||||
This variable is effective only when opening preview window (on Neovim (0.3.0 or earlier) or Vim).
|
||||
|
||||
Command modifiers for opening preview window. The value will be passed as prefix of `:pedit` command.
|
||||
For example, setting `"botright"` to the variable opens a preview window at bottom of the current
|
||||
window. Please see `:help <mods>` for more details.
|
||||
|
||||
#### `g:git_messenger_max_popup_height` (Default: `v:null`)
|
||||
|
||||
Max lines of popup window in an integer value. Setting `v:null` means no limit.
|
||||
|
||||
#### `g:git_messenger_max_popup_width` (Default: `v:null`)
|
||||
|
||||
Max characters of popup window in an integer value. Setting `v:null` means no limit.
|
||||
|
||||
#### `g:git_messenger_date_format` (Default: `"%c"`)
|
||||
|
||||
String value to format dates in popup window. Please see `:help strftime()` to know the details of
|
||||
the format.
|
||||
|
||||
```vim
|
||||
" Example: '2019 May 26 03:27:43'
|
||||
let g:git_messenger_date_format = "%Y %b %d %X"
|
||||
```
|
||||
|
||||
#### `g:git_messenger_conceal_word_diff_marker` (Default: `v:true`)
|
||||
|
||||
When this value is set to `v:true`, markers for word diffs like `[-`, `-]`, `{+`, `+}` are concealed.
|
||||
Set `v:false` when you don't want to hide them.
|
||||
|
||||
Note: Word diff is enabled by typing "r" in a popup window.
|
||||
|
||||
#### `g:git_messenger_floating_win_opts` (Default `{}`)
|
||||
|
||||
Options passed to `nvim_open_win()` on opening a popup window. This is useful when you want to
|
||||
override some window options.
|
||||
|
||||
The following example will add single border line to the window.
|
||||
|
||||
```vim
|
||||
let g:git_messenger_floating_win_opts = { 'border': 'single' }
|
||||
```
|
||||
|
||||
#### `g:git_messenger_popup_content_margins` (Default: `v:true`)
|
||||
|
||||
Setting `v:true` means adding margins in popup window. Blank lines at the top and bottom of popup
|
||||
content are inserted. And every line is indented with one whitespace character.
|
||||
Setting `v:false` to this variable removes all the margins.
|
||||
|
||||
### Popup Window Highlight
|
||||
|
||||
This plugin uses color definitions from your colorscheme for highlighting stuffs in popup window by
|
||||
default. This is done by linking highlight groups in [syntax/gitmessengerpopup.vim](syntax/gitmessengerpopup.vim)
|
||||
on `gitmessengerpopup` filetype. Highlights for diff are common with normal `diff` filetype syntax
|
||||
highlighting.
|
||||
|
||||
If the groups don't fit, please rearrange the highlight with `:hi link`. For example:
|
||||
|
||||
```vim
|
||||
" Normal color in popup window with 'CursorLine'
|
||||
hi link gitmessengerPopupNormal CursorLine
|
||||
|
||||
" Header such as 'Commit:', 'Author:' with 'Statement' highlight group
|
||||
hi link gitmessengerHeader Statement
|
||||
|
||||
" Commit hash at 'Commit:' header with 'Special' highlight group
|
||||
hi link gitmessengerHash Special
|
||||
|
||||
" History number at 'History:' header with 'Title' highlight group
|
||||
hi link gitmessengerHistory Title
|
||||
```
|
||||
|
||||
For another example, if you want to define colors directly, defining the colors with `:hi` works
|
||||
fine as follows.
|
||||
|
||||
```vim
|
||||
hi gitmessengerPopupNormal term=None guifg=#eeeeee guibg=#333333 ctermfg=255 ctermbg=234
|
||||
hi gitmessengerHeader term=None guifg=#88b8f6 ctermfg=111
|
||||
hi gitmessengerHash term=None guifg=#f0eaaa ctermfg=229
|
||||
hi gitmessengerHistory term=None guifg=#fd8489 ctermfg=210
|
||||
```
|
||||
|
||||
Note: If your colorscheme does not allocate proper color for `NormalFloat`, you may need to set
|
||||
proper color to `gitmessengerPopupNormal`.
|
||||
|
||||
Note: `gitmessengerPopupNormal` is only available on Neovim since `winhighlight` option is used.
|
||||
On Vim, simply `Normal` highlight group is used for normal color.
|
||||
|
||||
### Configuration for Popup Window
|
||||
|
||||
Filetype `gitmessengerpopup` is set in the popup window. Please hook `FileType` event to do some
|
||||
local setup within a popup window.
|
||||
|
||||
For example:
|
||||
|
||||
```vim
|
||||
function! s:setup_git_messenger_popup() abort
|
||||
" Your favorite configuration here
|
||||
|
||||
" For example, set go back/forward history to <C-o>/<C-i>
|
||||
nmap <buffer><C-o> o
|
||||
nmap <buffer><C-i> O
|
||||
endfunction
|
||||
autocmd FileType gitmessengerpopup call <SID>setup_git_messenger_popup()
|
||||
```
|
||||
|
||||
Recent Neovim supports adding border lines to floating windows. `git_messenger_floating_win_opts`
|
||||
is available to set the options. And `g:git_messenger_popup_content_margins` can remove margins
|
||||
within popup content if you feel margins are unnecessary.
|
||||
|
||||
```vim
|
||||
let g:git_messenger_floating_win_opts = { 'border': 'single' }
|
||||
let g:git_messenger_popup_content_margins = v:false
|
||||
```
|
||||
|
||||
<img alt="popup with border" src="https://github.com/rhysd/ss/blob/master/git-messenger.vim/border_popup.png?raw=true" width=684 height=203 />
|
||||
|
||||
### Health Check
|
||||
|
||||
This plugin supports a health checker on Neovim. When you see some error, please run `:checkhealth`
|
||||
to check your environment is ready for use of this plugin.
|
||||
|
||||
On Vim, please install [vim-healthcheck](https://github.com/rhysd/vim-healthcheck) and run
|
||||
`:CheckHealth`. It's a plugin to run `:checkhealth` on Vim.
|
||||
|
||||
### Known Issues
|
||||
|
||||
- On Windows, `git` command installed via MSYS does not work. Please use [Git for Windows][git-win]
|
||||
for now. This issue is tracked at [#57](https://github.com/rhysd/git-messenger.vim/issues/57).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Distributed under [the MIT License](LICENSE)
|
||||
|
||||
[repo]: https://github.com/rhysd/git-messenger.vim
|
||||
[ci-badge]: https://github.com/rhysd/git-messenger.vim/workflows/CI/badge.svg?branch=master&event=push
|
||||
[ci]: https://github.com/rhysd/git-messenger.vim/actions?query=workflow%3ACI+branch%3Amaster
|
||||
[codecov]: https://codecov.io/gh/rhysd/git-messenger.vim
|
||||
[codecov-badge]: https://codecov.io/gh/rhysd/git-messenger.vim/branch/master/graph/badge.svg
|
||||
[doc]: ./doc/git-messenger.txt
|
||||
[nvim-install]: https://github.com/neovim/neovim/wiki/Installing-Neovim
|
||||
[git-win]: https://gitforwindows.org/
|
||||
@ -0,0 +1,161 @@
|
||||
" Global variables
|
||||
let g:git_messenger_close_on_cursor_moved = get(g:, 'git_messenger_close_on_cursor_moved', v:true)
|
||||
let g:git_messenger_git_command = get(g:, 'git_messenger_git_command', 'git')
|
||||
let g:git_messenger_into_popup_after_show = get(g:, 'git_messenger_into_popup_after_show', v:true)
|
||||
let g:git_messenger_always_into_popup = get(g:, 'git_messenger_always_into_popup', v:false)
|
||||
let g:git_messenger_preview_mods = get(g:, 'git_messenger_preview_mods', '')
|
||||
let g:git_messenger_extra_blame_args = get(g:, 'git_messenger_extra_blame_args', '')
|
||||
let g:git_messenger_include_diff = get(g:, 'git_messenger_include_diff', 'none')
|
||||
let g:git_messenger_max_popup_height = get(g:, 'git_messenger_max_popup_height', v:null)
|
||||
let g:git_messenger_max_popup_width = get(g:, 'git_messenger_max_popup_width', v:null)
|
||||
let g:git_messenger_date_format = get(g:, 'git_messenger_date_format', '%c')
|
||||
let g:git_messenger_conceal_word_diff_marker = get(g:, 'git_messenger_conceal_word_diff_marker', 1)
|
||||
let g:git_messenger_floating_win_opts = get(g:, 'git_messenger_floating_win_opts', {})
|
||||
let g:git_messenger_popup_content_margins = get(g:, 'git_messenger_popup_content_margins', v:true)
|
||||
|
||||
" All popup instances keyed by opener's bufnr to manage lifetime of popups
|
||||
let s:all_popups = {}
|
||||
|
||||
function! s:on_cursor_moved() abort
|
||||
let bufnr = bufnr('%')
|
||||
if !has_key(s:all_popups, bufnr)
|
||||
autocmd! plugin-git-messenger-close * <buffer>
|
||||
return
|
||||
endif
|
||||
if s:all_popups[bufnr].cursor_moved()
|
||||
autocmd! plugin-git-messenger-close * <buffer>
|
||||
call gitmessenger#close_popup(bufnr)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_buf_enter(bufnr) abort
|
||||
let popup = s:popup_for(a:bufnr)
|
||||
|
||||
if popup is v:null
|
||||
autocmd! plugin-git-messenger-buf-enter
|
||||
return
|
||||
endif
|
||||
|
||||
let b = bufnr('%')
|
||||
" When entering/exiting popup window, do nothing
|
||||
if popup.bufnr == b
|
||||
return
|
||||
endif
|
||||
|
||||
" This triggers s:on_close()
|
||||
call popup.close()
|
||||
|
||||
if empty(s:all_popups)
|
||||
autocmd! plugin-git-messenger-buf-enter
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_open(blame) dict abort
|
||||
if !has_key(a:blame.popup, 'bufnr')
|
||||
" For some reason, popup was already closed
|
||||
unlet! s:all_popups[a:blame.popup.opener_bufnr]
|
||||
return
|
||||
endif
|
||||
|
||||
let opener_bufnr = a:blame.popup.opener_bufnr
|
||||
let s:all_popups[opener_bufnr] = a:blame.popup
|
||||
|
||||
if g:git_messenger_close_on_cursor_moved
|
||||
augroup plugin-git-messenger-close
|
||||
autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call <SID>on_cursor_moved()
|
||||
augroup END
|
||||
endif
|
||||
|
||||
augroup plugin-git-messenger-buf-enter
|
||||
execute 'autocmd BufEnter,WinEnter * call <SID>on_buf_enter(' . opener_bufnr . ')'
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:on_close(popup) dict abort
|
||||
unlet! s:all_popups[a:popup.opener_bufnr]
|
||||
endfunction
|
||||
|
||||
function! s:on_error(errmsg) abort
|
||||
echohl ErrorMsg
|
||||
" Avoid ^@
|
||||
for line in split(a:errmsg, '\r\=\n')
|
||||
echomsg line
|
||||
endfor
|
||||
echohl None
|
||||
endfunction
|
||||
|
||||
" file: string
|
||||
" line: number
|
||||
" bufnr: number
|
||||
" opts?: {}
|
||||
function! gitmessenger#new(file, line, bufnr, ...) abort
|
||||
" When cursor is in popup window, close the window
|
||||
if gitmessenger#popup#close_current_popup()
|
||||
return
|
||||
endif
|
||||
|
||||
" Just after opening a popup window, move cursor into the window
|
||||
if g:git_messenger_into_popup_after_show && has_key(s:all_popups, a:bufnr)
|
||||
let p = s:all_popups[a:bufnr]
|
||||
if has_key(p, 'bufnr')
|
||||
call p.into()
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
let opts = get(a:, 1, {})
|
||||
let opts.pos = getpos('.')
|
||||
" Close previous popup
|
||||
if has_key(s:all_popups, a:bufnr)
|
||||
call s:all_popups[a:bufnr].close()
|
||||
endif
|
||||
|
||||
let blame = gitmessenger#blame#new(a:file, a:line, {
|
||||
\ 'did_open': funcref('s:on_open', [], opts),
|
||||
\ 'did_close': funcref('s:on_close', [], opts),
|
||||
\ 'on_error': funcref('s:on_error'),
|
||||
\ 'enter_popup': g:git_messenger_always_into_popup,
|
||||
\ })
|
||||
if blame isnot v:null
|
||||
call blame.start()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:popup_for(bufnr) abort
|
||||
if !has_key(s:all_popups, a:bufnr)
|
||||
return v:null
|
||||
endif
|
||||
|
||||
let popup = s:all_popups[a:bufnr]
|
||||
if !has_key(popup, 'bufnr')
|
||||
" Here should be unreachable
|
||||
unlet! s:all_popups[a:bufnr]
|
||||
return v:null
|
||||
endif
|
||||
|
||||
return popup
|
||||
endfunction
|
||||
|
||||
function! gitmessenger#close_popup(bufnr) abort
|
||||
if gitmessenger#popup#close_current_popup()
|
||||
return
|
||||
endif
|
||||
let p = s:popup_for(a:bufnr)
|
||||
if p isnot v:null
|
||||
call p.close()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gitmessenger#scroll(bufnr, map) abort
|
||||
let p = s:popup_for(a:bufnr)
|
||||
if p isnot v:null
|
||||
call p.scroll(a:map)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! gitmessenger#into_popup(bufnr) abort
|
||||
let p = s:popup_for(a:bufnr)
|
||||
if p isnot v:null
|
||||
call p.into()
|
||||
endif
|
||||
endfunction
|
||||
@ -0,0 +1,502 @@
|
||||
let s:blame = {}
|
||||
|
||||
function! s:git_cmd_failure(git) abort
|
||||
return printf(
|
||||
\ 'git-messenger: %s: `%s %s` exited with non-zero status %d',
|
||||
\ join(a:git.stderr, ' '),
|
||||
\ a:git.cmd,
|
||||
\ join(a:git.args, ' '),
|
||||
\ a:git.exit_status
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! s:blame__error(msg) dict abort
|
||||
if has_key(self.opts, 'on_error')
|
||||
call self.opts.on_error(a:msg)
|
||||
else
|
||||
throw a:msg
|
||||
endif
|
||||
endfunction
|
||||
let s:blame.error = funcref('s:blame__error')
|
||||
|
||||
function! s:blame__render() dict abort
|
||||
let self.popup.contents = self.state.contents
|
||||
if self.state.prev_diff !=# self.state.diff
|
||||
call self.popup.set_buf_var('__gitmessenger_diff', self.state.diff)
|
||||
let prev_is_word = self.state.prev_diff =~# '\.word$'
|
||||
let is_word = self.state.diff =~# '\.word$'
|
||||
if self.state.diff !=# 'none' && prev_is_word != is_word
|
||||
call self.popup.set_buf_var('&syntax', 'gitmessengerpopup')
|
||||
endif
|
||||
endif
|
||||
call self.popup.update()
|
||||
endfunction
|
||||
let s:blame.render = funcref('s:blame__render')
|
||||
|
||||
function! s:blame__back() dict abort
|
||||
if self.state.back()
|
||||
call self.render()
|
||||
return
|
||||
endif
|
||||
|
||||
if self.prev_commit ==# '' || self.oldest_commit =~# '^0\+$'
|
||||
echo 'git-messenger: No older commit found'
|
||||
return
|
||||
endif
|
||||
|
||||
" Reset current state
|
||||
call self.state.set_diff('none')
|
||||
|
||||
let args = ['--no-pager', 'blame', self.prev_commit, '-L', self.line . ',+1', '--porcelain'] + split(g:git_messenger_extra_blame_args, ' ') + ['--', self.blame_file]
|
||||
call self.spawn_git(args, 's:blame__after_blame')
|
||||
endfunction
|
||||
let s:blame.back = funcref('s:blame__back')
|
||||
|
||||
function! s:blame__forward() dict abort
|
||||
if self.state.forward()
|
||||
call self.render()
|
||||
elseif self.state.commit !=# ''
|
||||
echo 'git-messenger: ' . self.state.commit . ' is the latest commit'
|
||||
else
|
||||
echo 'git-messenger: The latest commit'
|
||||
endif
|
||||
endfunction
|
||||
let s:blame.forward = funcref('s:blame__forward')
|
||||
|
||||
function! s:blame__open_popup() dict abort
|
||||
if has_key(self, 'popup') && has_key(self.popup, 'bufnr')
|
||||
" Already popup is open. It means that now older commit is showing up.
|
||||
" Save the contents to history and show the contents in current
|
||||
" popup.
|
||||
call self.state.push()
|
||||
call self.state.save()
|
||||
call self.render()
|
||||
return
|
||||
endif
|
||||
|
||||
let opts = {
|
||||
\ 'filetype': 'gitmessengerpopup',
|
||||
\ 'mappings': {
|
||||
\ 'q': [{-> execute('close', '')}, 'Close popup window'],
|
||||
\ 'o': [funcref(self.back, [], self), 'Back to older commit'],
|
||||
\ 'O': [funcref(self.forward, [], self), 'Forward to newer commit'],
|
||||
\ 'd': [funcref(self.reveal_diff, [v:false, v:false], self), "Toggle current file's diffs"],
|
||||
\ 'D': [funcref(self.reveal_diff, [v:true, v:false], self), 'Toggle all diffs'],
|
||||
\ 'r': [funcref(self.reveal_diff, [v:false, v:true], self), "Toggle current file's word diffs"],
|
||||
\ 'R': [funcref(self.reveal_diff, [v:true, v:true], self), 'Toggle all word diffs'],
|
||||
\ },
|
||||
\ }
|
||||
if has_key(self.opts, 'did_close')
|
||||
let opts.did_close = self.opts.did_close
|
||||
endif
|
||||
if has_key(self.opts, 'enter_popup')
|
||||
let opts.enter = self.opts.enter_popup
|
||||
endif
|
||||
|
||||
call self.state.push()
|
||||
let self.popup = gitmessenger#popup#new(self.state.contents, opts)
|
||||
call self.popup.open()
|
||||
|
||||
if has_key(self.opts, 'did_open')
|
||||
call self.opts.did_open(self)
|
||||
endif
|
||||
endfunction
|
||||
let s:blame.open_popup = funcref('s:blame__open_popup')
|
||||
|
||||
function! s:blame__append_lines(lines) dict abort
|
||||
if !g:git_messenger_popup_content_margins
|
||||
let self.state.contents += ['']
|
||||
endif
|
||||
|
||||
let lines = a:lines
|
||||
if lines[-1] ==# ''
|
||||
" Strip last newline
|
||||
let lines = lines[:-2]
|
||||
endif
|
||||
|
||||
let skip_first_nl = v:true
|
||||
for line in lines
|
||||
if skip_first_nl && line ==# ''
|
||||
continue
|
||||
else
|
||||
let skip_first_nl = v:false
|
||||
endif
|
||||
|
||||
if g:git_messenger_popup_content_margins
|
||||
if line ==# ''
|
||||
let self.state.contents += ['']
|
||||
else
|
||||
let self.state.contents += [' ' . line]
|
||||
endif
|
||||
else
|
||||
let self.state.contents += [line]
|
||||
endif
|
||||
endfor
|
||||
|
||||
if g:git_messenger_popup_content_margins && self.state.contents[-1] !~# '^\s*$'
|
||||
let self.state.contents += ['']
|
||||
endif
|
||||
endfunction
|
||||
let s:blame.append_lines = funcref('s:blame__append_lines')
|
||||
|
||||
function! s:blame__after_diff(next_diff, git) dict abort
|
||||
let self.failed = a:git.exit_status != 0
|
||||
|
||||
if self.failed
|
||||
call self.error(s:git_cmd_failure(a:git))
|
||||
return
|
||||
endif
|
||||
|
||||
let popup_open = has_key(self, 'popup')
|
||||
|
||||
if a:git.stdout == [] || a:git.stdout == [''] ||
|
||||
\ (popup_open && !has_key(self.popup, 'bufnr'))
|
||||
return
|
||||
endif
|
||||
|
||||
" When getting diff with `git show --pretty=format:%b`, it may contain
|
||||
" commit body. By removing line until 'diff --git ...' line, the body is
|
||||
" removed (#35)
|
||||
while a:git.stdout !=# [] && stridx(a:git.stdout[0], 'diff --git ') !=# 0
|
||||
let a:git.stdout = a:git.stdout[1:]
|
||||
endwhile
|
||||
|
||||
call self.append_lines(a:git.stdout)
|
||||
call self.state.set_diff(a:next_diff)
|
||||
|
||||
if popup_open
|
||||
call self.render()
|
||||
else
|
||||
" Note: When g:git_messenger_include_diff is not 'none' and popup is
|
||||
" being opened for line which is not committed yet.
|
||||
" In the case, commit hash is 0000000000000000 and `git log` is not
|
||||
" available. So `git diff` is used instead and this callback is
|
||||
" called.
|
||||
call self.open_popup()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:blame__reveal_diff(include_all, word_diff) dict abort
|
||||
if a:include_all
|
||||
let next_diff = 'all'
|
||||
else
|
||||
let next_diff = 'current'
|
||||
endif
|
||||
if a:word_diff
|
||||
let next_diff .= '.word'
|
||||
endif
|
||||
|
||||
if self.state.diff ==# next_diff
|
||||
" Toggle diff
|
||||
let next_diff = 'none'
|
||||
endif
|
||||
|
||||
" Remove diff hunks from popup
|
||||
let saved = getpos('.')
|
||||
try
|
||||
keepjumps execute 1
|
||||
let diff_pattern = g:git_messenger_popup_content_margins ? '^ diff --git ' : '^diff --git '
|
||||
let diff_offset = g:git_messenger_popup_content_margins ? 2 : 3
|
||||
let diff_start = search(diff_pattern, 'ncW')
|
||||
if diff_start > 1
|
||||
let self.state.contents = self.state.contents[ : diff_start-diff_offset]
|
||||
endif
|
||||
finally
|
||||
keepjumps call setpos('.', saved)
|
||||
endtry
|
||||
|
||||
if next_diff ==# 'none'
|
||||
let self.state.diff = next_diff
|
||||
call self.render()
|
||||
return
|
||||
endif
|
||||
|
||||
let hash = self.state.commit
|
||||
if hash ==# ''
|
||||
call self.error('Not a valid commit hash: ' . hash)
|
||||
return
|
||||
endif
|
||||
if hash !~# '^0\+$'
|
||||
" `git diff hash^..hash` is not available since hash^ is invalid when
|
||||
" it is an initial commit.
|
||||
let args = ['--no-pager', 'show', '--no-color', '--pretty=format:%b', hash]
|
||||
else
|
||||
" When the line is not committed yet, show diff against HEAD (#26)
|
||||
let args = ['--no-pager', 'diff', '--no-color', 'HEAD']
|
||||
endif
|
||||
|
||||
if a:word_diff
|
||||
let args += ['--word-diff=plain']
|
||||
endif
|
||||
|
||||
if !a:include_all
|
||||
let args += ['--', self.state.diff_file_to]
|
||||
let prev = self.state.diff_file_from
|
||||
if prev !=# '' && prev != self.state.diff_file_to
|
||||
" Note: When file was renamed, both file name before rename and file
|
||||
" name after rename are necessary to show correct diff.
|
||||
" If only file name after rename is specified, it shows diff as if
|
||||
" the file was added at the commit not considering rename.
|
||||
let args += [prev]
|
||||
endif
|
||||
endif
|
||||
call self.spawn_git(args, funcref('s:blame__after_diff', [next_diff], self))
|
||||
endfunction
|
||||
let s:blame.reveal_diff = funcref('s:blame__reveal_diff')
|
||||
|
||||
function! s:blame__after_log(git) dict abort
|
||||
let self.failed = a:git.exit_status != 0
|
||||
|
||||
if self.failed
|
||||
call self.error(s:git_cmd_failure(a:git))
|
||||
return
|
||||
endif
|
||||
|
||||
if a:git.stdout != [] && a:git.stdout != ['']
|
||||
call self.append_lines(a:git.stdout)
|
||||
endif
|
||||
|
||||
call self.open_popup()
|
||||
endfunction
|
||||
|
||||
function! s:blame__after_blame(git) dict abort
|
||||
let self.failed = a:git.exit_status != 0
|
||||
|
||||
if self.failed
|
||||
if a:git.stderr[0] =~# 'has only \d\+ lines\='
|
||||
echo 'git-messenger: ' . get(self, 'oldest_commit', 'It') . ' is the oldest commit'
|
||||
return
|
||||
endif
|
||||
call self.error(s:git_cmd_failure(a:git))
|
||||
return
|
||||
endif
|
||||
|
||||
" Parse `blame --porcelain` output
|
||||
" Note: Output less than 11 lines are invalid. At least followings should
|
||||
" be included:
|
||||
" header, author, author-email, author-time, author-tz, committer-email,
|
||||
" committer-time, committer-tz, summary, filename
|
||||
let stdout = a:git.stdout
|
||||
if len(stdout) < 11
|
||||
" Note: '\n' is not "\n", it's intentional
|
||||
call self.error('Unexpected `git blame` output: ' . join(stdout, '\n'))
|
||||
return
|
||||
endif
|
||||
|
||||
" Blame header
|
||||
" {hash} {line number of original} {line number of final} {line offset in lines group}
|
||||
"
|
||||
" Please see 'THE PORCELAIN FORMAT' section of `man git-blame` for more
|
||||
" details
|
||||
let hash = matchstr(stdout[0], '^[[:xdigit:]]\+')
|
||||
if has_key(self, 'oldest_commit') && self.oldest_commit ==# hash
|
||||
echo 'git-messenger: ' . hash . ' is the oldest commit'
|
||||
return
|
||||
endif
|
||||
let not_committed_yet = hash =~# '^0\+$'
|
||||
|
||||
let author = matchstr(stdout[1], '^author \zs.\+')
|
||||
let author_email = matchstr(stdout[2], '^author-mail \zs\S\+')
|
||||
let committer = matchstr(stdout[5], '^committer \zs.\+')
|
||||
let headers = [
|
||||
\ ['History', '#' . self.state.history_no()],
|
||||
\ ['Commit', hash],
|
||||
\ ['Author', author . ' ' . author_email],
|
||||
\ ]
|
||||
|
||||
if author !=# committer
|
||||
let committer_email = matchstr(stdout[6], '^committer-mail \zs\S\+')
|
||||
let headers += [['Committer', committer . ' ' . committer_email]]
|
||||
endif
|
||||
|
||||
if exists('*strftime')
|
||||
let author_time = matchstr(stdout[3], '^author-time \zs\d\+')
|
||||
let committer_time = matchstr(stdout[7], '^committer-time \zs\d\+')
|
||||
if author_time ==# committer_time
|
||||
let headers += [['Date', strftime(g:git_messenger_date_format, str2nr(author_time))]]
|
||||
else
|
||||
let headers += [['Author Date', strftime(g:git_messenger_date_format, str2nr(author_time))]]
|
||||
let headers += [['Committer Date', strftime(g:git_messenger_date_format, str2nr(committer_time))]]
|
||||
endif
|
||||
endif
|
||||
|
||||
let header_width = 0
|
||||
for [key, _] in headers
|
||||
let len = len(key)
|
||||
if len > header_width
|
||||
let header_width = len
|
||||
endif
|
||||
endfor
|
||||
|
||||
let self.state.contents = []
|
||||
if g:git_messenger_popup_content_margins
|
||||
let self.state.contents += ['']
|
||||
endif
|
||||
|
||||
let margin = g:git_messenger_popup_content_margins ? ' ' : ''
|
||||
for [key, value] in headers
|
||||
let pad = repeat(' ', header_width - len(key))
|
||||
let line = printf('%s%s: %s%s', margin, key, pad, value)
|
||||
let self.state.contents += [line]
|
||||
endfor
|
||||
|
||||
if not_committed_yet
|
||||
let summary = 'This line is not committed yet'
|
||||
else
|
||||
let summary = matchstr(stdout[9], '^summary \zs.*')
|
||||
endif
|
||||
let self.state.contents += ['', margin . summary]
|
||||
if g:git_messenger_popup_content_margins
|
||||
let self.state.contents += ['']
|
||||
endif
|
||||
|
||||
" Reset the state
|
||||
let self.prev_commit = ''
|
||||
let self.blame_file = ''
|
||||
" Diff target file is fallback to blame target file
|
||||
let self.state.diff_file_to = self.blame_file
|
||||
|
||||
" Parse 'previous', 'boundary' and 'filename'
|
||||
for line in stdout[10:]
|
||||
" At final of output, the current line prefixed with tab is put
|
||||
if line[0] ==# "\t"
|
||||
break
|
||||
endif
|
||||
|
||||
" previous {hash} {next blame file path}
|
||||
"
|
||||
" where {next blame file path} is a relative path from root directory of
|
||||
" the repository.
|
||||
let m = matchlist(line, '^previous \([[:xdigit:]]\+\) \(.\+\)$')
|
||||
if m != []
|
||||
let self.prev_commit = m[1]
|
||||
let self.blame_file = m[2]
|
||||
continue
|
||||
endif
|
||||
|
||||
" filename {file path from root dir}
|
||||
"
|
||||
" where {file path} is a target file of the current commit.
|
||||
" The file name may be different from current editing file because
|
||||
" it might be renamed.
|
||||
let filename = matchstr(line, '^filename \zs.\+$')
|
||||
if filename !=# ''
|
||||
let self.state.diff_file_to = filename
|
||||
continue
|
||||
endif
|
||||
|
||||
" boundary
|
||||
" Boudary commit. It means current commit is the oldest.
|
||||
" Nothing to do
|
||||
endfor
|
||||
|
||||
" diff_file_from is the same as blame_file at this moment, but stored in
|
||||
" another variable since it should be stored in history.
|
||||
let self.state.diff_file_from = self.blame_file
|
||||
let self.oldest_commit = hash
|
||||
let self.state.commit = hash
|
||||
|
||||
" Check hash is 0000000000000000000000 it means that the line is not committed yet
|
||||
if hash =~# '^0\+$'
|
||||
if g:git_messenger_include_diff ==? 'none'
|
||||
call self.open_popup()
|
||||
return
|
||||
endif
|
||||
|
||||
" Note: To show diffs which are not committed yet, `git log` is not
|
||||
" available. Use `git diff` instead.
|
||||
let next_diff = 'all'
|
||||
let args = ['--no-pager', 'diff', 'HEAD']
|
||||
if g:git_messenger_include_diff ==? 'current'
|
||||
let next_diff = 'current'
|
||||
let args += [self.blame_file]
|
||||
endif
|
||||
call self.spawn_git(args, funcref('s:blame__after_diff', [next_diff], self))
|
||||
return
|
||||
endif
|
||||
|
||||
let args = ['--no-pager', 'log', '--no-color', '-n', '1', '--pretty=format:%b']
|
||||
if g:git_messenger_include_diff !=? 'none'
|
||||
if g:git_messenger_include_diff ==? 'current'
|
||||
call self.state.set_diff('current')
|
||||
else
|
||||
call self.state.set_diff('all')
|
||||
endif
|
||||
let args += ['-p', '-m']
|
||||
endif
|
||||
let args += [hash]
|
||||
|
||||
if g:git_messenger_include_diff ==? 'current'
|
||||
let args += ['--', self.state.diff_file_to]
|
||||
let prev = self.state.diff_file_from
|
||||
if prev !=# '' && prev != self.state.diff_file_to
|
||||
" Note: When file was renamed, both file name before rename and file
|
||||
" name after rename are necessary to show correct diff.
|
||||
" If only file name after rename is specified, it shows diff as if
|
||||
" the file was added at the commit not considering rename.
|
||||
let args += [prev]
|
||||
endif
|
||||
endif
|
||||
|
||||
call self.spawn_git(args, 's:blame__after_log')
|
||||
endfunction
|
||||
|
||||
function! s:blame__spawn_git(args, callback) dict abort
|
||||
let git = gitmessenger#git#new(g:git_messenger_git_command, self.git_root)
|
||||
let CB = a:callback
|
||||
if type(CB) == v:t_string
|
||||
let CB = funcref(CB, [], self)
|
||||
endif
|
||||
try
|
||||
call git.spawn(a:args, CB)
|
||||
catch /^git-messenger: /
|
||||
call self.error(v:exception)
|
||||
endtry
|
||||
endfunction
|
||||
let s:blame.spawn_git = funcref('s:blame__spawn_git')
|
||||
|
||||
function! s:blame__start() dict abort
|
||||
call self.spawn_git(
|
||||
\ ['--no-pager', 'blame', self.blame_file, '-L', self.line . ',+1', '--porcelain'] + split(g:git_messenger_extra_blame_args, ' '),
|
||||
\ 's:blame__after_blame')
|
||||
endfunction
|
||||
let s:blame.start = funcref('s:blame__start')
|
||||
|
||||
" interface Blame {
|
||||
" state: BlameHistory;
|
||||
" line: number;
|
||||
" git_root: string;
|
||||
" blame_file: string;
|
||||
" prev_commit?: string;
|
||||
" oldest_commit?: string;
|
||||
" opts: {
|
||||
" did_open: (b: Blame) => void;
|
||||
" did_close: (p: Popup) => void;
|
||||
" on_error: (errmsg: string) => void;
|
||||
" enter_popup: boolean;
|
||||
" };
|
||||
" }
|
||||
"
|
||||
" blame_file:
|
||||
" File path given to `git blame`. This can be relative to root of repo.
|
||||
" Note: This does not need to be put in BlameHistory state because it is
|
||||
" used by only `git blame`.
|
||||
function! gitmessenger#blame#new(file, line, opts) abort
|
||||
let file = resolve(a:file)
|
||||
let b = deepcopy(s:blame)
|
||||
let b.state = gitmessenger#history#new(file)
|
||||
let b.line = a:line
|
||||
let b.blame_file = file
|
||||
let b.opts = a:opts
|
||||
|
||||
let dir = fnamemodify(file, ':p:h')
|
||||
let b.git_root = gitmessenger#git#root_dir(dir)
|
||||
|
||||
" Validations
|
||||
if b.git_root ==# ''
|
||||
call b.error("git-messenger: Directory '" . dir . "' is not inside a Git repository")
|
||||
return v:null
|
||||
endif
|
||||
|
||||
return b
|
||||
endfunction
|
||||
@ -0,0 +1,174 @@
|
||||
let s:SEP = has('win32') ? '\' : '/'
|
||||
|
||||
function! s:find_dotgit(from) abort
|
||||
let dir = finddir('.git', a:from . ';')
|
||||
let file = findfile('.git', a:from . ';')
|
||||
|
||||
if dir ==# '' && file ==# ''
|
||||
return ''
|
||||
endif
|
||||
|
||||
" When .git directory is below the current working directory, finddir()
|
||||
" returns a relative path. So ensuring an absolute path here.
|
||||
let dir = dir ==# '' ? '' : fnamemodify(dir, ':p')
|
||||
|
||||
" When .git exists in current directory, findfile() returns relative path
|
||||
" '.git' though finddir() returns an absolute path '/path/to/.git' (#49).
|
||||
" Since path length will be compared, they must be both abusolute path.
|
||||
let file = file ==# '' ? '' : fnamemodify(file, ':p')
|
||||
|
||||
" Choose larger (deeper) path (#48). When worktree directory is put in its
|
||||
" main repository, the .git directory which is near to `from` should be
|
||||
" chosen.
|
||||
" When `dir` or `file` is empty, the other is chosen so we don't need to
|
||||
" care about empty string here.
|
||||
let dotgit = len(dir) > len(file) ? dir : file
|
||||
|
||||
" Inside .git directory is outside repository
|
||||
" This check must be done before chopping last path separator otherwise it
|
||||
" matches to directory like /path/to/.github/ (#70)
|
||||
if stridx(a:from, dotgit) == 0
|
||||
return ''
|
||||
endif
|
||||
|
||||
if dotgit[-1:] ==# s:SEP
|
||||
" [:-2] chops last path separator (/path/to/.git/ => /path/to/.git)
|
||||
let dotgit = dotgit[:-2]
|
||||
endif
|
||||
|
||||
return dotgit
|
||||
endfunction
|
||||
|
||||
" Params:
|
||||
" path: string
|
||||
" base path to find .git in ancestor directories
|
||||
" Returns:
|
||||
" string
|
||||
" empty string means root directory was not found
|
||||
function! gitmessenger#git#root_dir(from) abort
|
||||
let from = fnameescape(fnamemodify(a:from, ':p'))
|
||||
if from[-1:] ==# s:SEP
|
||||
" [:-2] chops last path separator
|
||||
let from = from[:-2]
|
||||
endif
|
||||
let dotgit = s:find_dotgit(from)
|
||||
if dotgit ==# ''
|
||||
return ''
|
||||
endif
|
||||
|
||||
" /path/to/.git => /path/to
|
||||
return fnamemodify(dotgit, ':h')
|
||||
endfunction
|
||||
|
||||
let s:git = {}
|
||||
|
||||
if has('nvim')
|
||||
function! s:on_output_nvim(job, data, event) dict abort
|
||||
if a:data == ['']
|
||||
return
|
||||
endif
|
||||
" Output from Git might contain \r for example when the commit
|
||||
" author's text editor uses \r\n for newlines. But Neovim reads output
|
||||
" from the process line by line based on \n. The trailing \r remains
|
||||
" when \r\n is used for newlines. This removes the trailing \r (#75).
|
||||
call map(a:data, 'v:val[len(v:val)-1] ==# "\r" ? v:val[:-2] : v:val')
|
||||
let self[a:event][-1] .= a:data[0]
|
||||
call extend(self[a:event], a:data[1:])
|
||||
endfunction
|
||||
|
||||
function! s:on_exit_nvim(job, code, event) dict abort
|
||||
let self.exit_status = a:code
|
||||
call self.on_exit(self)
|
||||
endfunction
|
||||
else
|
||||
function! s:git__finalize_vim(ch) dict abort
|
||||
if has_key(self, 'finalized') && self.finalized
|
||||
return
|
||||
endif
|
||||
|
||||
" Note:
|
||||
" Workaround for Vim's exit_cb behavior. When the callback is called,
|
||||
" sometimes channel for stdout and/or stderr is not closed yet. So
|
||||
" their status may be 'open'. As workaround for the behavior, we do
|
||||
" polling to check the channel statuses with 1 msec interval until the
|
||||
" statuses are set to 'close'. (#16)
|
||||
let out_opt = {'part': 'out'}
|
||||
let err_opt = {'part': 'err'}
|
||||
while 1
|
||||
let out_status = ch_status(a:ch, out_opt)
|
||||
let err_status = ch_status(a:ch, err_opt)
|
||||
if out_status !=# 'open' && out_status !=# 'buffered' &&
|
||||
\ err_status !=# 'open' && err_status !=# 'buffered'
|
||||
let self.finalized = v:true
|
||||
call self.on_exit(self)
|
||||
return
|
||||
endif
|
||||
sleep 1m
|
||||
endwhile
|
||||
endfunction
|
||||
let s:git.finalize_vim = funcref('s:git__finalize_vim')
|
||||
|
||||
function! s:on_output_vim(event, ch, msg) dict abort
|
||||
call extend(self[a:event], split(a:msg, '\r\=\n', 1))
|
||||
endfunction
|
||||
|
||||
function! s:on_exit_vim(ch, code) dict abort
|
||||
let self.exit_status = a:code
|
||||
call self.finalize_vim(a:ch)
|
||||
endfunction
|
||||
endif
|
||||
|
||||
" Params:
|
||||
" args: string[]
|
||||
" on_exit: (git: Git) => void
|
||||
" Returns:
|
||||
" Job ID of the spawned process
|
||||
function! s:git__spawn(args, on_exit) dict abort
|
||||
let cmdline = [self.cmd, '-C', self.dir] + a:args
|
||||
if has('nvim')
|
||||
let self.stdout = ['']
|
||||
let self.stderr = ['']
|
||||
let job_id = jobstart(cmdline, {
|
||||
\ 'cwd': self.dir,
|
||||
\ 'on_stdout' : funcref('s:on_output_nvim', [], self),
|
||||
\ 'on_stderr' : funcref('s:on_output_nvim', [], self),
|
||||
\ 'on_exit' : funcref('s:on_exit_nvim', [], self),
|
||||
\ })
|
||||
if job_id == 0
|
||||
throw 'git-messenger: Invalid arguments: ' . string(a:args)
|
||||
elseif job_id == -1
|
||||
throw 'git-messenger: Command does not exist: ' . self.cmd
|
||||
endif
|
||||
else
|
||||
let self.stdout = []
|
||||
let self.stderr = []
|
||||
let job_id = job_start(cmdline, {
|
||||
\ 'cwd': self.dir,
|
||||
\ 'out_cb' : funcref('s:on_output_vim', ['stdout'], self),
|
||||
\ 'err_cb' : funcref('s:on_output_vim', ['stderr'], self),
|
||||
\ 'exit_cb' : funcref('s:on_exit_vim', [], self),
|
||||
\ })
|
||||
endif
|
||||
let self.job_id = job_id
|
||||
let self.on_exit = a:on_exit
|
||||
let self.args = a:args
|
||||
return job_id
|
||||
endfunction
|
||||
let s:git.spawn = funcref('s:git__spawn')
|
||||
|
||||
" Creates new Git instance. Git instance represents one-shot Git command
|
||||
" asynchronous execution.
|
||||
"
|
||||
" Params:
|
||||
" cmd: string
|
||||
" 'git' command to run Git
|
||||
" dir: string
|
||||
" Directory path to run Git
|
||||
" Returns:
|
||||
" Git object
|
||||
function! gitmessenger#git#new(cmd, dir) abort
|
||||
let g = deepcopy(s:git)
|
||||
let g.cmd = a:cmd
|
||||
let g.dir = a:dir
|
||||
return g
|
||||
endfunction
|
||||
@ -0,0 +1,132 @@
|
||||
" Note: Index 0 means the latest entry of history
|
||||
|
||||
" interface BlameState {
|
||||
" commit: string;
|
||||
" contents: string[];
|
||||
" diff_file_to: string;
|
||||
" diff_file_from: string;
|
||||
" diff: 'none' | 'all' | 'current';
|
||||
" }
|
||||
"
|
||||
" interface BlameHistory extends BlameState {
|
||||
" _index: number;
|
||||
" _history: BlameState[];
|
||||
" }
|
||||
"
|
||||
" History of chain of `git blame` with contents.
|
||||
"
|
||||
" contents:
|
||||
" Lines of contents of popup
|
||||
" diff_file_to:
|
||||
" File path for diff. It represents the file path after the commit.
|
||||
" When the file was renamed while the commit, it is different from 'diff_file_from'
|
||||
" diff_file_from:
|
||||
" File path for diff. It represents the file path before the commit.
|
||||
" When the file was renamed while the commit, it is different from 'diff_file_to'
|
||||
" diff:
|
||||
" Diff type. Please see document for g:git_messenger_include_diff
|
||||
" commit:
|
||||
" Commit hash of the commit
|
||||
" _index:
|
||||
" Index of history which indicates current state. 0 means the latest history
|
||||
" _history:
|
||||
" History of chain of blame entries. Latter is older.
|
||||
let s:history = { '_index': 0, '_history': [] }
|
||||
|
||||
" Create new empty history entry as the latest
|
||||
function! s:history__push() dict abort
|
||||
" Note: copy() is necessary because the contents may be updated later
|
||||
" for diff
|
||||
" Note: 'commit' is a special key which will be never changed. This field
|
||||
" will be used for checking invariant state on saving the state
|
||||
let self._history += [{ 'commit': self.commit }]
|
||||
let self._index = len(self._history) - 1
|
||||
endfunction
|
||||
let s:history.push = funcref('s:history__push')
|
||||
|
||||
" Save current state to current history entry
|
||||
function! s:history__save() dict abort
|
||||
if self._index > len(self._history)
|
||||
throw printf('FATAL: Invariant error on saving history. Index %d is out of range. Length of history is %d', self._index, len(self._history))
|
||||
endif
|
||||
|
||||
let e = self._history[self._index]
|
||||
if self.commit !=# e.commit
|
||||
throw printf('FATAL: Invariant error on saving history. Current commit hash %s is different from commit hash in history %s', self.commit, e.commit)
|
||||
endif
|
||||
|
||||
let e.diff = self.diff
|
||||
let e.contents = copy(self.contents)
|
||||
let e.diff_file_to = self.diff_file_to
|
||||
let e.commit = self.commit
|
||||
let e.diff_file_from = self.diff_file_from
|
||||
endfunction
|
||||
let s:history.save = funcref('s:history__save')
|
||||
|
||||
" Load specific history entry as current state
|
||||
function! s:history__load(index) dict abort
|
||||
let e = self._history[a:index]
|
||||
" Note: copy() is necessary because the contents may be updated later
|
||||
" for diff. Without copy(), it modifies array in self.history directly
|
||||
" but that's not intended.
|
||||
let self.contents = copy(e.contents)
|
||||
call self.set_diff(e.diff)
|
||||
let self.commit = e.commit
|
||||
let self.diff_file_to = e.diff_file_to
|
||||
let self.diff_file_from = e.diff_file_from
|
||||
let self._index = a:index
|
||||
endfunction
|
||||
let s:history._load = funcref('s:history__load')
|
||||
|
||||
function! s:history__history_no() dict abort
|
||||
return len(self._history)
|
||||
endfunction
|
||||
let s:history.history_no = funcref('s:history__history_no')
|
||||
|
||||
function! s:history__set_diff(diff) dict abort
|
||||
let self.prev_diff = self.diff
|
||||
let self.diff = a:diff
|
||||
endfunction
|
||||
let s:history.set_diff = funcref('s:history__set_diff')
|
||||
|
||||
" Go back to older. Load older history entry to current history.
|
||||
" Returns boolean which is true when older entry was found.
|
||||
function! s:history__back() dict abort
|
||||
let next_index = self._index + 1
|
||||
|
||||
call self.save()
|
||||
|
||||
if len(self._history) <= next_index
|
||||
return v:false
|
||||
endif
|
||||
|
||||
call self._load(next_index)
|
||||
return v:true
|
||||
endfunction
|
||||
let s:history.back = funcref('s:history__back')
|
||||
|
||||
" Go forward to newer. Load newer history entry to current state.
|
||||
" Returns boolean which is true when newer entry was found.
|
||||
function! s:history__forward() dict abort
|
||||
" Note: Index 0 is the latest entry
|
||||
let next_index = self._index - 1
|
||||
if next_index < 0
|
||||
return v:false
|
||||
endif
|
||||
|
||||
call self.save()
|
||||
call self._load(next_index)
|
||||
return v:true
|
||||
endfunction
|
||||
let s:history.forward = funcref('s:history__forward')
|
||||
|
||||
function! gitmessenger#history#new(filepath) abort
|
||||
let h = deepcopy(s:history)
|
||||
let h.contents = []
|
||||
let h.diff_file_to = a:filepath
|
||||
let h.diff_file_from = a:filepath
|
||||
let h.diff = 'none'
|
||||
let h.prev_diff = ''
|
||||
let h.commit = ''
|
||||
return h
|
||||
endfunction
|
||||
@ -0,0 +1,339 @@
|
||||
let s:popup = {}
|
||||
let s:floating_window_available = has('nvim') && exists('*nvim_win_set_config')
|
||||
|
||||
function! s:get_global_pos() abort
|
||||
let pos = win_screenpos('.')
|
||||
return [pos[0] + winline() - 1, pos[1] + wincol() - 1]
|
||||
endfunction
|
||||
|
||||
function! s:popup__close() dict abort
|
||||
if !has_key(self, 'bufnr')
|
||||
" Already closed
|
||||
return
|
||||
endif
|
||||
|
||||
let winnr = self.get_winnr()
|
||||
if winnr > 0
|
||||
" Without this 'noautocmd', the BufWipeout event will be triggered and
|
||||
" this function will be called again.
|
||||
noautocmd execute winnr . 'wincmd c'
|
||||
endif
|
||||
|
||||
unlet self.bufnr
|
||||
unlet self.win_id
|
||||
|
||||
if has_key(self.opts, 'did_close')
|
||||
call self.opts.did_close(self)
|
||||
endif
|
||||
endfunction
|
||||
let s:popup.close = funcref('s:popup__close')
|
||||
|
||||
function! s:popup__get_winnr() dict abort
|
||||
if !has_key(self, 'bufnr')
|
||||
return 0
|
||||
endif
|
||||
|
||||
" Note: bufwinnr() is not available here because there may be multiple
|
||||
" windows which open the buffer. This situation happens when enter <C-w>v
|
||||
" in popup window. It opens a new normal window with the popup's buffer.
|
||||
return win_id2win(self.win_id)
|
||||
endfunction
|
||||
let s:popup.get_winnr = funcref('s:popup__get_winnr')
|
||||
|
||||
function! s:popup__set_buf_var(name, value) dict abort
|
||||
if has_key(self, 'bufnr')
|
||||
call setbufvar(self.bufnr, a:name, a:value)
|
||||
endif
|
||||
endfunction
|
||||
let s:popup.set_buf_var = funcref('s:popup__set_buf_var')
|
||||
|
||||
function! s:popup__scroll(map) dict abort
|
||||
let winnr = self.get_winnr()
|
||||
if winnr == 0
|
||||
return
|
||||
endif
|
||||
execute winnr . 'wincmd w'
|
||||
sandbox let input = eval('"\<'.a:map.'>"')
|
||||
execute 'normal!' input
|
||||
wincmd p
|
||||
endfunction
|
||||
let s:popup.scroll = funcref('s:popup__scroll')
|
||||
|
||||
function! s:popup__into() dict abort
|
||||
let winnr = self.get_winnr()
|
||||
if winnr == 0
|
||||
return
|
||||
endif
|
||||
execute winnr . 'wincmd w'
|
||||
endfunction
|
||||
let s:popup.into = funcref('s:popup__into')
|
||||
|
||||
function! s:popup__window_size() dict abort
|
||||
let margin = g:git_messenger_popup_content_margins ? 1 : 0
|
||||
let has_max_width = type(g:git_messenger_max_popup_width) == v:t_number
|
||||
if has_max_width
|
||||
" ` - 1` for considering right margin
|
||||
let max_width = g:git_messenger_max_popup_width - margin
|
||||
endif
|
||||
|
||||
let width = 0
|
||||
let height = 0
|
||||
for line in self.contents
|
||||
let lw = strdisplaywidth(line)
|
||||
if lw > width
|
||||
if has_max_width && lw > max_width
|
||||
let height += lw / max_width + 1
|
||||
let width = max_width
|
||||
continue
|
||||
endif
|
||||
let width = lw
|
||||
endif
|
||||
let height += 1
|
||||
endfor
|
||||
let width += margin " right margin
|
||||
|
||||
if type(g:git_messenger_max_popup_height) == v:t_number && height > g:git_messenger_max_popup_height
|
||||
let height = g:git_messenger_max_popup_height
|
||||
endif
|
||||
|
||||
return [width, height]
|
||||
endfunction
|
||||
let s:popup.window_size = funcref('s:popup__window_size')
|
||||
|
||||
function! s:popup__floating_win_opts(width, height) dict abort
|
||||
let border = has_key(g:git_messenger_floating_win_opts, 'border')
|
||||
\ && index(
|
||||
\ ['single', 'double', 'rounded', 'solid'], g:git_messenger_floating_win_opts['border']
|
||||
\ ) != -1 ? 2 : 0
|
||||
|
||||
" &lines - 1 because it is not allowed to overlay a floating window on a status line.
|
||||
" Bottom line of a floating window must be less than line of command line. (#80)
|
||||
if self.opened_at[0] + a:height + border <= &lines - 1
|
||||
let vert = 'N'
|
||||
let row = self.opened_at[0]
|
||||
else
|
||||
let vert = 'S'
|
||||
let row = self.opened_at[0] - 1 - border
|
||||
endif
|
||||
|
||||
if self.opened_at[1] + a:width + border <= &columns
|
||||
let hor = 'W'
|
||||
let col = self.opened_at[1] - 1
|
||||
else
|
||||
let hor = 'E'
|
||||
let col = self.opened_at[1] - border
|
||||
endif
|
||||
|
||||
return extend({
|
||||
\ 'relative': 'editor',
|
||||
\ 'anchor': vert . hor,
|
||||
\ 'row': row,
|
||||
\ 'col': col,
|
||||
\ 'width': a:width,
|
||||
\ 'height': a:height,
|
||||
\ 'style': 'minimal',
|
||||
\ },
|
||||
\ g:git_messenger_floating_win_opts)
|
||||
endfunction
|
||||
let s:popup.floating_win_opts = funcref('s:popup__floating_win_opts')
|
||||
|
||||
function! s:popup__get_opener_winnr() dict abort
|
||||
let winnr = win_id2win(self.opener_winid)
|
||||
if winnr != 0
|
||||
return winnr
|
||||
endif
|
||||
let winnr = bufwinnr(self.opener_bufnr)
|
||||
if winnr > 0
|
||||
return winnr
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
let s:popup.get_opener_winnr = funcref('s:popup__get_opener_winnr')
|
||||
|
||||
function! s:popup__open() dict abort
|
||||
let self.opened_at = s:get_global_pos()
|
||||
let self.opener_bufnr = bufnr('%')
|
||||
let self.opener_winid = win_getid()
|
||||
let self.type = s:floating_window_available ? 'floating' : 'preview'
|
||||
|
||||
let [width, height] = self.window_size()
|
||||
|
||||
" Open window
|
||||
if self.type ==# 'floating'
|
||||
let opts = self.floating_win_opts(width, height)
|
||||
let win_id = nvim_open_win(self.opener_bufnr, v:true, opts)
|
||||
else
|
||||
let curr_pos = getpos('.')
|
||||
let mods = 'noswapfile'
|
||||
if g:git_messenger_preview_mods !=# ''
|
||||
let mods .= ' ' . g:git_messenger_preview_mods
|
||||
endif
|
||||
|
||||
" :pedit! is not available since it refreshes the file buffer (#39)
|
||||
execute mods 'new'
|
||||
set previewwindow
|
||||
|
||||
call setpos('.', curr_pos)
|
||||
wincmd P
|
||||
execute height . 'wincmd _'
|
||||
let win_id = win_getid()
|
||||
endif
|
||||
|
||||
" Setup content
|
||||
enew!
|
||||
let popup_bufnr = bufnr('%')
|
||||
" Note: Set conceallevel for hiding word diff markers
|
||||
setlocal
|
||||
\ buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nonumber
|
||||
\ nocursorline wrap nonumber norelativenumber signcolumn=no nofoldenable
|
||||
\ nospell nolist nomodeline conceallevel=2
|
||||
call setline(1, self.contents)
|
||||
setlocal nomodified nomodifiable
|
||||
|
||||
" Setup highlights
|
||||
if has('nvim')
|
||||
setlocal winhighlight=Normal:gitmessengerPopupNormal
|
||||
endif
|
||||
|
||||
if has_key(self.opts, 'mappings')
|
||||
for m in keys(self.opts.mappings)
|
||||
execute printf('nnoremap <buffer><silent><nowait>%s :<C-u>call b:__gitmessenger_popup.opts.mappings["%s"][0]()<CR>', m, m)
|
||||
endfor
|
||||
nnoremap <buffer><silent><nowait>? :<C-u>call b:__gitmessenger_popup.echo_help()<CR>
|
||||
endif
|
||||
|
||||
if has_key(self.opts, 'filetype')
|
||||
let &l:filetype = self.opts.filetype
|
||||
endif
|
||||
|
||||
" Ensure to close popup
|
||||
let b:__gitmessenger_popup = self
|
||||
execute 'autocmd BufWipeout,BufLeave <buffer> call getbufvar(' . popup_bufnr . ', "__gitmessenger_popup").close()'
|
||||
|
||||
if has_key(self.opts, 'enter') && !self.opts.enter
|
||||
noautocmd wincmd p
|
||||
if self.type !=# 'floating'
|
||||
" Opening a preview window may move global position of the cursor.
|
||||
" `opened_at` is used for checking if the popup window should be
|
||||
" closed on `CursorMoved` event. If the position is not updated
|
||||
" here, the event wrongly will refer the position before opening
|
||||
" the preview window.
|
||||
let self.opened_at = s:get_global_pos()
|
||||
endif
|
||||
endif
|
||||
|
||||
let self.bufnr = popup_bufnr
|
||||
let self.win_id = win_id
|
||||
endfunction
|
||||
let s:popup.open = funcref('s:popup__open')
|
||||
|
||||
function! s:popup__update() dict abort
|
||||
" Note: `:noautocmd` to prevent BufLeave autocmd event (#13)
|
||||
" It should be ok because the cursor position is finally back to the first
|
||||
" position.
|
||||
|
||||
let prev_winnr = winnr()
|
||||
|
||||
let popup_winnr = self.get_winnr()
|
||||
if popup_winnr == 0
|
||||
return
|
||||
endif
|
||||
let opener_winnr = self.get_opener_winnr()
|
||||
if opener_winnr == 0
|
||||
return
|
||||
endif
|
||||
|
||||
if opener_winnr != prev_winnr
|
||||
noautocmd execute opener_winnr . 'wincmd w'
|
||||
endif
|
||||
|
||||
try
|
||||
let [width, height] = self.window_size()
|
||||
|
||||
" Window must be configured in opener buffer since the window position
|
||||
" is relative to cursor
|
||||
if self.type ==# 'floating'
|
||||
let id = win_getid(popup_winnr)
|
||||
if id == 0
|
||||
return
|
||||
endif
|
||||
let opts = self.floating_win_opts(width, height)
|
||||
call nvim_win_set_config(id, opts)
|
||||
endif
|
||||
|
||||
noautocmd execute popup_winnr . 'wincmd w'
|
||||
|
||||
if self.type ==# 'preview'
|
||||
execute height . 'wincmd _'
|
||||
endif
|
||||
|
||||
setlocal modifiable
|
||||
silent %delete _
|
||||
call setline(1, self.contents)
|
||||
setlocal nomodified nomodifiable
|
||||
finally
|
||||
if winnr() != prev_winnr
|
||||
noautocmd execute prev_winnr . 'wincmd w'
|
||||
endif
|
||||
endtry
|
||||
endfunction
|
||||
let s:popup.update = funcref('s:popup__update')
|
||||
|
||||
" Returns if the cursor moved since this popup window had opened
|
||||
function! s:popup__cursor_moved() dict abort
|
||||
return s:get_global_pos() != self.opened_at
|
||||
endfunction
|
||||
let s:popup.cursor_moved = funcref('s:popup__cursor_moved')
|
||||
|
||||
function! s:popup__echo_help() dict abort
|
||||
if has_key(self.opts, 'mappings')
|
||||
let maps = keys(self.opts.mappings)
|
||||
call sort(maps, 'i')
|
||||
let maps += ['?']
|
||||
|
||||
for map in maps
|
||||
if map ==# '?'
|
||||
let desc = 'Show this help'
|
||||
else
|
||||
let desc = self.opts.mappings[map][1]
|
||||
endif
|
||||
echohl Identifier | echo ' ' . map
|
||||
echohl Comment | echon ' : '
|
||||
echohl None | echon desc
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
let s:popup.echo_help = funcref('s:popup__echo_help')
|
||||
|
||||
" contents: string[] // lines of contents
|
||||
" opts: {
|
||||
" floating?: boolean;
|
||||
" bufnr?: number;
|
||||
" cursor?: [number, number]; // (line, col)
|
||||
" filetype?: string;
|
||||
" did_close?: (pupup: Popup) => void;
|
||||
" mappings?: {
|
||||
" [keyseq: string]: [() => void, string];
|
||||
" };
|
||||
" enter?: boolean
|
||||
" }
|
||||
function! gitmessenger#popup#new(contents, opts) abort
|
||||
let p = deepcopy(s:popup)
|
||||
let opts = { 'floating': v:true }
|
||||
call extend(opts, a:opts)
|
||||
let p.opts = opts
|
||||
let p.contents = a:contents
|
||||
return p
|
||||
endfunction
|
||||
|
||||
|
||||
" When current window is popup, close the window.
|
||||
" Returns true when popup window was closed
|
||||
function! gitmessenger#popup#close_current_popup() abort
|
||||
if !exists('b:__gitmessenger_popup')
|
||||
return 0
|
||||
endif
|
||||
call b:__gitmessenger_popup.close()
|
||||
" TODO?: Back to opened_at pos by setpos()
|
||||
return 1
|
||||
endfunction
|
||||
@ -0,0 +1,80 @@
|
||||
function! s:check_job() abort
|
||||
if !has('nvim') && !has('job')
|
||||
call health#report_error('Not supported since +job feature is not enabled')
|
||||
else
|
||||
call health#report_ok('+job is available to execute Git command')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:check_floating_window() abort
|
||||
if !has('nvim')
|
||||
return
|
||||
endif
|
||||
|
||||
if !exists('*nvim_win_set_config')
|
||||
call health#report_warn(
|
||||
\ 'Neovim 0.3.0 or earlier does not support floating window feature. Preview window is used instead',
|
||||
\ 'Please install Neovim 0.4.0 or later')
|
||||
return
|
||||
endif
|
||||
|
||||
" XXX: Temporary
|
||||
try
|
||||
noautocmd let win_id = nvim_open_win(bufnr('%'), v:false, {
|
||||
\ 'relative': 'editor',
|
||||
\ 'row': 0,
|
||||
\ 'col': 0,
|
||||
\ 'width': 2,
|
||||
\ 'height': 2,
|
||||
\ })
|
||||
noautocmd call nvim_win_close(win_id, v:true)
|
||||
catch /^Vim\%((\a\+)\)\=:E118/
|
||||
call health#report_error(
|
||||
\ 'Your Neovim is too old',
|
||||
\ [
|
||||
\ 'Please update Neovim to 0.4.0 or later',
|
||||
\ 'If the version does not fix the error, please make an issue at https://github.com/rhysd/git-messenger.vim',
|
||||
\ ])
|
||||
return
|
||||
endtry
|
||||
|
||||
call health#report_ok('Floating window is available for popup window')
|
||||
endfunction
|
||||
|
||||
function! s:check_git_binary() abort
|
||||
let cmd = get(g:, 'git_messenger_git_command', 'git')
|
||||
if !executable(cmd)
|
||||
call health#report_error('`' . cmd . '` command is not found. Please set proper command to g:git_messenger_git_command')
|
||||
return
|
||||
endif
|
||||
|
||||
let output = substitute(system(cmd . ' -C . --version'), '\r\=\n', '', 'g')
|
||||
if v:shell_error
|
||||
call health#report_error('Git command `' . cmd . '` is broken (v1.8.5 or later is required): ' . output)
|
||||
return
|
||||
endif
|
||||
|
||||
call health#report_ok('Git command `' . cmd . '` is available: ' . output)
|
||||
endfunction
|
||||
|
||||
function! s:check_vim_version() abort
|
||||
if has('nvim')
|
||||
return
|
||||
endif
|
||||
|
||||
if v:version < 800
|
||||
call health#report_error(
|
||||
\ 'Your Vim version is too old: ' . v:version,
|
||||
\ 'Please install Vim 8.0 or later')
|
||||
return
|
||||
endif
|
||||
|
||||
call health#report_ok('Vim version is fine: ' . v:version)
|
||||
endfunction
|
||||
|
||||
function! health#gitmessenger#check() abort
|
||||
call s:check_job()
|
||||
call s:check_git_binary()
|
||||
call s:check_floating_window()
|
||||
call s:check_vim_version()
|
||||
endfunction
|
||||
@ -0,0 +1,424 @@
|
||||
*git-messenger.txt* Reveal the hidden message by git under the cursor quickly
|
||||
|
||||
Author : rhysd <https://rhysd.github.io>
|
||||
|
||||
CONTENTS *git-messenger-contents*
|
||||
|
||||
Introduction |git-messenger-introduction|
|
||||
Screenshot |git-messenger-screenshot|
|
||||
Install |git-messenger-install|
|
||||
Commands |git-messenger-commands|
|
||||
Mappings |git-messenger-mappings|
|
||||
Variables |git-messenger-variables|
|
||||
Highlights |git-messenger-highlights|
|
||||
Popup Window Config |git-messenger-popup-window-config|
|
||||
Health Check |git-messenger-health-check|
|
||||
Known Issues |git-messenger-known-issues|
|
||||
Repository |git-messenger-repository-page|
|
||||
License |git-messenger-license|
|
||||
|
||||
|
||||
==============================================================================
|
||||
INTRODUCTION *git-messenger-introduction*
|
||||
|
||||
*git-messenger.vim* is a Vim/Neovim plugin to reveal the hidden message under
|
||||
the cursor quickly. It shows the last commit message under the cursor in popup
|
||||
window.
|
||||
|
||||
When you modifying unfamiliar codes, you would sometimes wonder 'why was this
|
||||
line added?' or 'why this value was chosen?' in the source code. The answer
|
||||
sometimes lays in a commit message, especially in message of the last commit
|
||||
which modifies the line.
|
||||
|
||||
This plugin shows the message of the last commit in a 'popup window'. The
|
||||
popup window is implemented in
|
||||
|
||||
- Floating window on Neovim 0.4 or later
|
||||
- Preview window on Vim 8+ or Neovim 0.3 or earlier
|
||||
|
||||
The floating window is definitely recommended since it can shows the
|
||||
information near the cursor.
|
||||
|
||||
If you see some error message, please try |git-messenger-health-check|.
|
||||
|
||||
|
||||
==============================================================================
|
||||
SCREENSHOT *git-messenger-screenshot*
|
||||
|
||||
1. Show popup window with Neovim v0.4.0-dev
|
||||
|
||||
https://github.com/rhysd/ss/blob/master/git-messenger.vim/demo.gif
|
||||
|
||||
2. Exploring older commits
|
||||
|
||||
https://github.com/rhysd/ss/blob/master/git-messenger.vim/history.gif
|
||||
|
||||
3. Exploring diff of the commit
|
||||
|
||||
https://github.com/rhysd/ss/blob/master/git-messenger.vim/diff.gif
|
||||
|
||||
You may be also interested in |g:git_messenger_include_diff|
|
||||
|
||||
|
||||
==============================================================================
|
||||
INSTALL *git-messenger-install*
|
||||
|
||||
Please ensure the following requirement before installing this plugin.
|
||||
|
||||
- Git v1.8.5 (for -C option of "git" command)
|
||||
|
||||
If you use any package manager, please follow its instruction.
|
||||
|
||||
With vim-plug:
|
||||
>
|
||||
Plug 'rhysd/git-messenger.vim'
|
||||
<
|
||||
With dein.vim:
|
||||
>
|
||||
call dein#add('rhysd/git-messenger.vim', {
|
||||
\ 'lazy' : 1,
|
||||
\ 'on_cmd' : 'GitMessenger',
|
||||
\ 'on_map' : '<Plug>(git-messenger-',
|
||||
\ })
|
||||
<
|
||||
With minpac:
|
||||
>
|
||||
call minpac#add('rhysd/git-messenger.vim')
|
||||
<
|
||||
If you're using Vim's builtin packager, please follow instruction at |pack-add|.
|
||||
|
||||
vim-plug: https://github.com/junegunn/vim-plug
|
||||
dein.vim: https://github.com/Shougo/dein.vim
|
||||
minpac: https://github.com/k-takata/minpac
|
||||
|
||||
To enable a floating window support, you need to install Neovim 0.4 or later.
|
||||
Please follow the official instruction.
|
||||
|
||||
https://github.com/neovim/neovim/wiki/Installing-Neovim
|
||||
|
||||
|
||||
==============================================================================
|
||||
COMMANDS *git-messenger-commands*
|
||||
|
||||
*:GitMessenger*
|
||||
|
||||
It opens a popup window with the last commit message which modified the line
|
||||
at cursor. The popup window shows following contents:
|
||||
|
||||
- History: Number of history. In popup window, h/l navigates to previous or
|
||||
next commit.
|
||||
- Commit: The commit hash
|
||||
- Author: Author name and mail address of the commit
|
||||
- Committer: Committer name and mail address of the commit when
|
||||
committer is different from author
|
||||
- Date: Author date of the commit in system format
|
||||
- When a committer date and an author date are different, both
|
||||
'Author Date' and 'Committer Date' are shown
|
||||
- Summary: First line after a date header line is a summary of commit
|
||||
- Body: After summary, commit body is put (if the commit has body)
|
||||
|
||||
The popup window will be automatically closed when you move the cursor so
|
||||
you don't need to close it manually.
|
||||
|
||||
Running command again after the popup window shows up moves the cursor into
|
||||
the window. This behavior is useful when the commit message is too long and
|
||||
window cannot show the whole content. By moving the cursor into the popup
|
||||
window, you can see the rest of contents by scrolling it. This behavior can
|
||||
be suppressed with |g:git_messenger_into_popup_after_show|.
|
||||
|
||||
Following mappings are defined within popup window.
|
||||
|
||||
| Mapping | Description |
|
||||
| ------- | ------------------------------------------------------------ |
|
||||
| q | Close the popup window |
|
||||
| o | "o"lder. Back to older commit at the line |
|
||||
| O | Opposite to older. Forward to newer commit at the line |
|
||||
| d | Toggle unified diff hunks only in current file of the commit |
|
||||
| D | Toggle all unified diff hunks of the commit |
|
||||
| r | Toggle word diff hunks only in current file of the commit |
|
||||
| R | Toggle all word diff hunks of the commit |
|
||||
| ? | Show mappings help |
|
||||
|
||||
*:GitMessengerClose*
|
||||
|
||||
Though a popup window is automatically closed by default, it closes the
|
||||
popup window explicitly. It is useful when you set
|
||||
|g:git_messenger_close_on_cursor_moved| to |v:false|.
|
||||
|
||||
|
||||
==============================================================================
|
||||
MAPPINGS *git-messenger-mappings*
|
||||
|
||||
Some <Plug> mappings are available to operate a popup window. They can be
|
||||
mapped to your favorite key sequences. For example:
|
||||
|
||||
*<Plug>(git-messenger)*
|
||||
|
||||
The same as running |:GitMessenger| command.
|
||||
|
||||
*<Plug>(git-messenger-close)*
|
||||
|
||||
The same as running |:GitMessengerClose| command.
|
||||
|
||||
*<Plug>(git-messenger-into-popup)*
|
||||
|
||||
Moves the cursor into the popup window. It's useful when you want to scroll
|
||||
the content and close the window.
|
||||
|
||||
*<Plug>(git-messenger-scroll-down-1)*
|
||||
|
||||
Scroll down the popup window by 1 line directly.
|
||||
|
||||
*<Plug>(git-messenger-scroll-up-1)*
|
||||
|
||||
Scroll up the popup window by 1 line directly.
|
||||
|
||||
*<Plug>(git-messenger-scroll-down-page)*
|
||||
|
||||
Scroll down the popup window by 1 page directly.
|
||||
|
||||
*<Plug>(git-messenger-scroll-up-page)*
|
||||
|
||||
Scroll up the popup window by 1 page directly.
|
||||
|
||||
*<Plug>(git-messenger-scroll-down-half)*
|
||||
|
||||
Scroll down the popup window by half page directly.
|
||||
|
||||
*<Plug>(git-messenger-scroll-up-half)*
|
||||
|
||||
Scroll up the popup window by half page directly.
|
||||
|
||||
|
||||
==============================================================================
|
||||
VARIABLES *git-messenger-variables*
|
||||
|
||||
Some global variables are available to configure the behavior of this plugin.
|
||||
|
||||
|
||||
*g:git_messenger_close_on_cursor_moved* (Default: |v:true|)
|
||||
|
||||
When this value is set to |v:false|, a popup window is no longer closed
|
||||
automatically when moving a cursor after the window is shown up.
|
||||
|
||||
*g:git_messenger_include_diff* (Default: "none")
|
||||
|
||||
When this value is set to |v:true|, a popup window includes diff hunks of the
|
||||
commit by default.
|
||||
Note: Typing "d" or "r" in popup window reveals diff hunks even if this
|
||||
value is set to |v:false|.
|
||||
|
||||
One of "none", "current", "all".
|
||||
|
||||
When this value is NOT set to "none", a popup window includes diff hunks of
|
||||
the commit at showing up. "current" includes diff hunks of only current file
|
||||
in the commit. "all" includes all diff hunks in the commit.
|
||||
|
||||
Note: Typing d/D or r/R mappings in popup window reveal diff hunks even if
|
||||
this value is set to "none".
|
||||
|
||||
*g:git_messenger_git_command* (Default: "git")
|
||||
|
||||
"git" command to retrieve commit messages. If your "git" executable is not
|
||||
in "$PATH" directories, please specify the path to the executable.
|
||||
|
||||
*g:git_messenger_no_default_mappings* (Default: |v:false|)
|
||||
|
||||
When this value is set to |v:true|, it does not define any key mappings.
|
||||
<Plug> mappings are still defined since they don't make any conflicts with
|
||||
existing mappings.
|
||||
|
||||
*g:git_messenger_into_popup_after_show* (Default: |v:true|)
|
||||
|
||||
When this value is set to |v:false|, run |:GitMessenger| or
|
||||
|<plug>(git-messenger)| again after showing a popup does not move the cursor
|
||||
in the window.
|
||||
|
||||
*g:git_messenger_always_into_popup* (Default: |v:false|)
|
||||
|
||||
When this value is set to |v:true|, the cursor goes into a popup window when
|
||||
running |:GitMessenger| or |<Plug>(git-messenger)|.
|
||||
|
||||
*g:git_messenger_extra_blame_args* (Default: "")
|
||||
|
||||
When this variable is set the contents will be appended to the git blame
|
||||
command. Use it to add options (like -w).
|
||||
|
||||
*g:git_messenger_preview_mods* (Default: "")
|
||||
|
||||
Command modifiers for opening preview window. The value will be passed as
|
||||
prefix of |:pedit| command.
|
||||
For example, setting "botright" to the variable opens a preview window at
|
||||
bottom of the current window. Please see |<mods>| for more details.
|
||||
|
||||
NOTE: This variable is effective only when opening preview window (on Neovim
|
||||
(0.3.0 or earlier) or Vim).
|
||||
|
||||
*g:git_messenger_max_popup_height* (Default: |v:null|)
|
||||
|
||||
Max lines of popup window in an integer value. Setting |v:null| means no
|
||||
limit.
|
||||
|
||||
*g:git_messenger_max_popup_width* (Default: |v:null|)
|
||||
|
||||
Max characters of popup window in an integer value. Setting |v:null| means no
|
||||
limit.
|
||||
|
||||
*g:git_messenger_date_format* (Default: "%c")
|
||||
|
||||
String value to format dates in popup window. Please see |strftime()| to know
|
||||
the details of the format.
|
||||
>
|
||||
" Example: '2019 May 26 03:27:43'
|
||||
let g:git_messenger_date_format = "%Y %b %d %X"
|
||||
<
|
||||
*g:git_messenger_conceal_word_diff_marker* (Default: |v:true|)
|
||||
|
||||
When this value is set to |v:true|, markers for word diffs like "[-", "-]",
|
||||
"{+", "+}" are concealed. Set |v:false| when you don't want to hide them.
|
||||
|
||||
Note: Word diff is enabled by typing "r" in a popup window.
|
||||
|
||||
*g:git_messenger_floating_win_opts* (Default: {})
|
||||
|
||||
Options passed to |nvim_open_win| on opening a popup window. This is useful
|
||||
when you want to override some window options.
|
||||
The following example will add single border line to the window.
|
||||
>
|
||||
let g:git_messenger_floating_win_opts = { 'border': 'single' }
|
||||
<
|
||||
*g:git_messenger_popup_content_margins* (Default: |v:true|)
|
||||
|
||||
Setting |v:true| means adding margins in popup window. Blank lines at the top
|
||||
and bottom of popup content are inserted. And every line is indented with one
|
||||
whitespace character.
|
||||
|
||||
Setting |v:false| to this variable removes all the margins. Removing margins
|
||||
might be useful when you enable borders of popup window with
|
||||
|g:git_messenger_floating_win_opts|.
|
||||
|
||||
==============================================================================
|
||||
HIGHLIGHTS *git-messenger-highlights*
|
||||
|
||||
|git-messenger.vim| uses color definitions from your colorscheme for
|
||||
highlighting stuffs in popup window by default. This is done by linking
|
||||
highlight groups on "gitmessengerpopup" filetype.
|
||||
Highlights for diff are common with normal |diff| filetype syntax highlighting.
|
||||
|
||||
If the groups don't fit, please rearrange the highlight with |:hi-link|.
|
||||
For example:
|
||||
>
|
||||
" Normal color in popup window with 'CursorLine'
|
||||
hi link gitmessengerPopupNormal CursorLine
|
||||
|
||||
" Header such as 'Commit:', 'Author:' with 'Statement' highlight group
|
||||
hi link gitmessengerHeader Statement
|
||||
|
||||
" Commit hash at 'Commit:' header with 'Special' highlight group
|
||||
hi link gitmessengerHash Special
|
||||
|
||||
" History number at 'History:' header with 'Title' highlight group
|
||||
hi link gitmessengerHistory Title
|
||||
|
||||
" Email address at 'Author:' header with 'Comment' highlight group
|
||||
hi link gitmessengerEmail Comment
|
||||
<
|
||||
For another example, if you want to define colors directly, defining the
|
||||
colors with |:hi| works fine as follows.
|
||||
>
|
||||
hi gitmessengerPopupNormal term=None guifg=#eeeeee guibg=#333333 ctermfg=255 ctermbg=234
|
||||
hi gitmessengerHeader term=None guifg=#88b8f6 ctermfg=111
|
||||
hi gitmessengerHash term=None guifg=#f0eaaa ctermfg=229
|
||||
hi gitmessengerHistory term=None guifg=#fd8489 ctermfg=210
|
||||
hi gitmessengerEmail term=None guifg=#999999 ctermfg=245
|
||||
<
|
||||
Note: If your colorscheme does not allocate proper color for |hl-NormalFloat|,
|
||||
you may need to set proper color to "gitmessengerPopupNormal". For example, if
|
||||
your colorscheme use the same color for both normal background color and popup
|
||||
background color, linking "gitmessengerPopupNormal" to different background
|
||||
color like |hl-CursorLine| would be better.
|
||||
|
||||
Note: "gitmessengerPopupNormal" is only available on Neovim since
|
||||
'winhighlight' option is used. On Vim, simply |hl-Normal| is used for normal
|
||||
color.
|
||||
|
||||
|
||||
==============================================================================
|
||||
CONFIGURATION IN POPUP WINDOW *git-messenger-popup-window-config*
|
||||
|
||||
Filetype *gitmessengerpopup* is set in the popup window. Please hook |FileType|
|
||||
event to do some local setup within a popup window.
|
||||
|
||||
For example:
|
||||
>
|
||||
function! s:setup_gitmessengerpopup() abort
|
||||
" For example, set go back/forward history to <C-o>/<C-i>
|
||||
nmap <buffer><C-o> o
|
||||
nmap <buffer><C-i> O
|
||||
endfunction
|
||||
autocmd FileType gitmessengerpopup call <SID>setup_gitmessengerpopup()
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
HEALTH CHECK *git-messenger-health-check*
|
||||
|
||||
|git-messenger.vim| supports a health checker on Neovim. When you see some
|
||||
error, please run `:checkhealth` to check your environment is ready for use
|
||||
of this plugin.
|
||||
>
|
||||
:checkhealth
|
||||
<
|
||||
On Vim, please install vim-healthcheck and run `:CheckHealth`. It's a plugin to
|
||||
run `:checkhealth` on Vim.
|
||||
>
|
||||
:CheckHealth
|
||||
<
|
||||
https://github.com/rhysd/vim-healthcheck
|
||||
|
||||
|
||||
==============================================================================
|
||||
KNOWN ISSUES *git-messenger-known-issues*
|
||||
|
||||
1.
|
||||
On Windows, `git` command installed via MSYS does not work. Please use
|
||||
Git for Windows for now. This issue is tracked at
|
||||
https://github.com/rhysd/git-messenger.vim/issues/57
|
||||
|
||||
|
||||
==============================================================================
|
||||
REPOSITORY *git-messenger-repository-page*
|
||||
|
||||
|git-messenger.vim| is developed at GitHub. Please make a new issue or PR to
|
||||
join development.
|
||||
|
||||
https://github.com/rhysd/git-messenger.vim
|
||||
|
||||
|
||||
==============================================================================
|
||||
LICENSE *git-messenger-license*
|
||||
|
||||
|git-messenger.vim| is distributed under The MIT license.
|
||||
|
||||
Copyright (c) 2019 rhysd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:ft=help:norl:et:fen:fdl=0:
|
||||
@ -0,0 +1,24 @@
|
||||
if exists('g:loaded_git_messenger')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_git_messenger = 1
|
||||
|
||||
" Global variables used in plugin/gitmessenger.vim
|
||||
let g:git_messenger_no_default_mappings = get(g:, 'git_messenger_no_default_mappings', v:false)
|
||||
|
||||
command! -nargs=0 -bar GitMessenger call gitmessenger#new(expand('%:p'), line('.'), bufnr('%'))
|
||||
command! -nargs=0 -bar GitMessengerClose call gitmessenger#close_popup(bufnr('%'))
|
||||
|
||||
nnoremap <silent><Plug>(git-messenger) :<C-u>GitMessenger<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-close) :<C-u>call gitmessenger#close_popup(bufnr('%'))<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-into-popup) :<C-u>call gitmessenger#into_popup(bufnr('%'))<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-scroll-down-1) :<C-u>call gitmessenger#scroll(bufnr('%'), 'C-e')<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-scroll-up-1) :<C-u>call gitmessenger#scroll(bufnr('%'), 'C-y')<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-scroll-down-page) :<C-u>call gitmessenger#scroll(bufnr('%'), 'C-f')<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-scroll-up-page) :<C-u>call gitmessenger#scroll(bufnr('%'), 'C-b')<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-scroll-down-half) :<C-u>call gitmessenger#scroll(bufnr('%'), 'C-d')<CR>
|
||||
nnoremap <silent><Plug>(git-messenger-scroll-up-half) :<C-u>call gitmessenger#scroll(bufnr('%'), 'C-u')<CR>
|
||||
|
||||
if !g:git_messenger_no_default_mappings
|
||||
nmap <Leader>gm <Plug>(git-messenger)
|
||||
endif
|
||||
@ -0,0 +1,50 @@
|
||||
if exists('b:current_syntax')
|
||||
finish
|
||||
endif
|
||||
|
||||
syn match gitmessengerHeader '^ \=\%(History\|Commit\|\%(Author \|Committer \)\=Date\|Author\|Committer\):' display
|
||||
syn match gitmessengerHash '\%(^ \=Commit: \+\)\@<=[[:xdigit:]]\+' display
|
||||
syn match gitmessengerHistory '\%(^ \=History: \+\)\@<=#\d\+' display
|
||||
syn match gitmessengerEmail '\%(^ \=\%(Author\|Committer\): \+.*\)\@<=<.\+>' display
|
||||
|
||||
" Diff included in popup
|
||||
" There are two types of diff format; 'none' 'current', 'all', 'current.word', 'all.word'.
|
||||
" 'current.word' and 'all.word' are for word diff. And 'current' and 'all' are " for unified diff.
|
||||
" Define different highlights for unified diffs and word diffs.
|
||||
" b:__gitmessenger_diff is set by Blame.render() in blame.vim.
|
||||
if get(b:, '__gitmessenger_diff', '') =~# '\.word$'
|
||||
if has('conceal') && get(g:, 'git_messenger_conceal_word_diff_marker', v:true)
|
||||
syn region diffWordsRemoved matchgroup=Conceal start=/\[-/ end=/-]/ concealends oneline
|
||||
syn region diffWordsAdded matchgroup=Conceal start=/{+/ end=/+}/ concealends oneline
|
||||
else
|
||||
syn region diffWordsRemoved start=/\[-/ end=/-]/ oneline
|
||||
syn region diffWordsAdded start=/{+/ end=/+}/ oneline
|
||||
endif
|
||||
else
|
||||
syn match diffRemoved "^ \=-.*" display
|
||||
syn match diffAdded "^ \=+.*" display
|
||||
endif
|
||||
|
||||
syn match diffFile "^ \=diff --git .*" display
|
||||
syn match diffOldFile "^ \=--- a\>.*" display
|
||||
syn match diffNewFile "^ \=+++ b\>.*" display
|
||||
syn match diffIndexLine "^ \=index \x\{7,}\.\.\x\{7,}.*" display
|
||||
syn match diffLine "^ \=@@ .*" display
|
||||
|
||||
hi def link gitmessengerHeader Identifier
|
||||
hi def link gitmessengerHash Comment
|
||||
hi def link gitmessengerHistory Constant
|
||||
hi def link gitmessengerEmail gitmessengerPopupNormal
|
||||
hi def link gitmessengerPopupNormal NormalFloat
|
||||
|
||||
hi def link diffOldFile diffFile
|
||||
hi def link diffNewFile diffFile
|
||||
hi def link diffIndexLine PreProc
|
||||
hi def link diffFile Type
|
||||
hi def link diffRemoved Special
|
||||
hi def link diffAdded Identifier
|
||||
hi def link diffWordsRemoved diffRemoved
|
||||
hi def link diffWordsAdded diffAdded
|
||||
hi def link diffLine Statement
|
||||
|
||||
let b:current_syntax = 'gitmessengerpopup'
|
||||
@ -0,0 +1,70 @@
|
||||
call themis#option('exclude', 'test/README.md')
|
||||
call themis#option('exclude', 'test/Guardfile')
|
||||
|
||||
if $THEMIS_PROFILE !=# ''
|
||||
execute 'profile' 'start' $THEMIS_PROFILE
|
||||
profile! file ./autoload/*
|
||||
profile! file ./plugin/*
|
||||
profile! file ./syntax/*
|
||||
endif
|
||||
|
||||
let s:helper = themis#helper('assert')
|
||||
call themis#helper('command').with(s:helper)
|
||||
|
||||
function! GetPopupNoWait() abort
|
||||
for b in range(1, bufnr('$'))
|
||||
let p = getbufvar(b, '__gitmessenger_popup', v:null)
|
||||
if p isnot v:null
|
||||
return p
|
||||
endif
|
||||
endfor
|
||||
return v:null
|
||||
endfunction
|
||||
function! GetPopup() abort
|
||||
return CallWithTimeout(1, funcref('GetPopupNoWait'))
|
||||
endfunction
|
||||
|
||||
function! CallWithTimeout(timeout, func) abort
|
||||
let total = 0
|
||||
while v:true
|
||||
let v = a:func()
|
||||
if v isnot v:null
|
||||
return v
|
||||
endif
|
||||
sleep 100m
|
||||
let total += 0.1
|
||||
if total >= a:timeout
|
||||
return v:null
|
||||
endif
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
function! WaitUntil(func, ...) abort
|
||||
let timeout = get(a:, 1, 1) " 1sec by default
|
||||
let total = 0
|
||||
while !a:func()
|
||||
sleep 100m
|
||||
let total += 0.1
|
||||
if total >= timeout
|
||||
" Note: v:true/v:false are not supported by themis.vim
|
||||
" https://github.com/thinca/vim-themis/pull/56
|
||||
return 0
|
||||
endif
|
||||
endwhile
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! WaitWhile(func, ...) abort
|
||||
return call('WaitUntil', [{-> !a:func()}] + a:000)
|
||||
endfunction
|
||||
|
||||
function! Git(dir, ...) abort
|
||||
let cmd = join(['git', '-C', shellescape(a:dir)] + a:000, ' ')
|
||||
let out = system(cmd)
|
||||
if v:shell_error
|
||||
throw '`' . cmd . '` failed: ' . out
|
||||
endif
|
||||
return out
|
||||
endfunction
|
||||
|
||||
let g:ON_TRAVIS = $TRAVIS !=# ''
|
||||
@ -0,0 +1,21 @@
|
||||
def run(target)
|
||||
target = target.to_s
|
||||
success = if target.endwith? 'nvim.vimspec'
|
||||
system({ 'THEMIS_VIM' => 'nvim' }, '../vim-themis/bin/themis', target)
|
||||
else
|
||||
system('../vim-themis/bin/themis', target)
|
||||
end
|
||||
puts(success ? 'OK' : 'NG')
|
||||
end
|
||||
|
||||
guard :shell do
|
||||
watch /^.+\.vim$/ do |m|
|
||||
puts "Changed #{m[0]}. Run all tests."
|
||||
run './test/'
|
||||
end
|
||||
|
||||
watch /^.+\.vimspec$/ do |m|
|
||||
puts "Changed #{m[0]}"
|
||||
run m[0]
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,49 @@
|
||||
### How to Run tests
|
||||
|
||||
Setup:
|
||||
|
||||
```
|
||||
$ cd /path/to/git-messenger.vim
|
||||
$ git clone https://github.com/thinca/vim-themis.git
|
||||
```
|
||||
|
||||
Run tests on `nvim`:
|
||||
|
||||
```
|
||||
$ THEMIS_VIM=nvim ./vim-themis/bin/themis test/all.vimspec
|
||||
```
|
||||
|
||||
Run tests on `vim`:
|
||||
|
||||
```
|
||||
$ ./vim-themis/bin/themis test/all.vimspec
|
||||
```
|
||||
|
||||
### How to run guard
|
||||
|
||||
Install [guard][] and [guard-shell][] as prerequisites.
|
||||
|
||||
```
|
||||
$ guard -G test/Guardfile
|
||||
```
|
||||
|
||||
It watches your file changes and runs tests automatically.
|
||||
|
||||
### How to take coverage
|
||||
|
||||
Set `$THEMIS_PROFILE` to take profiler log.
|
||||
|
||||
```
|
||||
$ THEMIS_PROFILE=profile.txt ./vim-themis/bin/themis test/all.vimspec
|
||||
```
|
||||
|
||||
It generates `profile.txt`. And run [covimerage][] to make a coverage file for `coverage` command.
|
||||
|
||||
```
|
||||
$ covimerage write_coverage profile.txt
|
||||
$ coverage report
|
||||
```
|
||||
|
||||
[guard]: https://github.com/guard/guard
|
||||
[guard-shell]: https://github.com/guard/guard-shell
|
||||
[covimerage]: https://github.com/Vimjas/covimerage
|
||||
1486
config/neovim/store/lazy-plugins/git-messenger.vim/test/all.vimspec
Normal file
1486
config/neovim/store/lazy-plugins/git-messenger.vim/test/all.vimspec
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user