Skip to content

Commit

Permalink
type h/l in popup window back/forward history
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Mar 9, 2019
1 parent 9754efe commit f6fa25a
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 34 deletions.
68 changes: 66 additions & 2 deletions autoload/gitmessenger/blame.vim
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,65 @@ function! s:git_cmd_failure(git) abort
\ )
endfunction

function! s:blame__back() dict abort
let next_index = self.index + 1

if len(self.history) > next_index
let self.index = next_index
let self.contents = self.history[next_index]
let self.popup.contents = self.contents
call self.popup.update()
return
endif

if self.oldest_commit =~# '^0\+$'
echom 'git-messenger: No older commit found'
return
endif

let args = ['--no-pager', 'blame', self.oldest_commit, self.file, '-L', self.line . ',+1', '--porcelain']
let cwd = fnamemodify(self.file, ':p:h')
let git = gitmessenger#git#new(g:git_messenger_git_command)
call git.spawn(args, cwd, funcref('s:blame__after_blame', [], self))
endfunction
let s:blame.back = funcref('s:blame__back')

function! s:blame__forward() dict abort
let next_index = self.index - 1
if next_index < 0
echom 'git-messenger: The latest commit'
return
endif

let self.index = next_index
let self.contents = self.history[next_index]
let self.popup.contents = self.contents
call self.popup.update()
endfunction
let s:blame.forward = funcref('s:blame__forward')

function! s:blame__open_popup() dict abort
let opts = { 'filetype': 'gitmessengerpopup' }
if has_key(self, 'popup') && has_key(self.popup, 'bufnr')
let self.history += [self.contents]
let self.index = len(self.history) - 1
let self.popup.contents = self.contents
call self.popup.update()
return
endif

let opts = {
\ 'filetype': 'gitmessengerpopup',
\ 'mappings': {
\ 'q': {-> execute('close')},
\ 'h': funcref(self.back, [], self),
\ 'l': funcref(self.forward, [], self),
\ },
\ }
if has_key(self.opts, 'did_close')
let opts.did_close = self.opts.did_close
endif

let self.history = [self.contents]
let self.popup = gitmessenger#popup#new(self.contents, opts)
call self.popup.open()

Expand Down Expand Up @@ -52,12 +105,21 @@ 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'
echom 'git-messenger: ' . get(self, 'oldest_commit', 'It') . ' is the oldest commit2'
return
endif
throw s:git_cmd_failure(a:git)
endif

" Parse `blame --porcelain` output
let stdout = a:git.stdout
let hash = matchstr(stdout[0], '^\S\+')
if has_key(self, 'oldest_commit') && self.oldest_commit ==# hash
echom 'git-messenger: ' . hash . ' is the oldest commit'
return
endif

let author = matchstr(stdout[1], '^author \zs.\+')
let author_email = matchstr(stdout[2], '^author-mail \zs\S\+')
let self.contents = [
Expand All @@ -73,6 +135,8 @@ function! s:blame__after_blame(git) dict abort
let summary = matchstr(stdout[9], '^summary \zs.*')
let self.contents += ['', ' ' . summary, '']

let self.oldest_commit = hash

" Check hash is 0000000000000000000000 it means that the line is not commited yet
if hash =~# '^0\+$'
call self.open_popup()
Expand Down Expand Up @@ -104,6 +168,6 @@ function! gitmessenger#blame#new(file, line, opts) abort
let b.line = a:line
let b.file = a:file
let b.opts = a:opts
let b.contents = []
let b.index = 0
return b
endfunction
136 changes: 104 additions & 32 deletions autoload/gitmessenger/popup.vim
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,10 @@ function! s:popup__into() dict abort
endfunction
let s:popup.into = funcref('s:popup__into')

function! s:popup__open() dict abort
function! s:popup__window_size() dict abort
" Note: Unlike col('.'), wincol() considers length of sign column
let opener_pos = getpos('.')
let opener_bufnr = bufnr('%')
let origin = win_screenpos(bufwinnr(opener_bufnr))
let abs_cursor_line = (origin[0] - 1) + opener_pos[1] - line('w0')
let origin = win_screenpos(bufwinnr(self.opener_bufnr))
let abs_cursor_line = (origin[0] - 1) + self.opened_at[1] - line('w0')
let abs_cursor_col = (origin[1] - 1) + wincol() - col('w0')

let width = 0
Expand All @@ -76,48 +74,63 @@ function! s:popup__open() dict abort
endfor
let width += 1 " right margin

" Open window
if s:floating_window_available
if opener_pos[1] + height <= line('w$')
let vert = 'N'
let row = 1
else
let vert = 'S'
let row = 0
endif
return [width, height]
endfunction
let s:popup.window_size = funcref('s:popup__window_size')

if opener_pos[2] + width <= &columns
let hor = 'W'
let col = 0
else
let hor = 'E'
let col = 1
endif
function! s:popup__floating_win_opts(width, height) dict abort
if self.opened_at[1] + a:height <= line('w$')
let vert = 'N'
let row = 1
else
let vert = 'S'
let row = 0
endif

if self.opened_at[2] + a:width <= &columns
let hor = 'W'
let col = 0
else
let hor = 'E'
let col = 1
endif

call nvim_open_win(opener_bufnr, v:true, width, height, {
\ 'relative': 'cursor',
\ 'anchor': vert . hor,
\ 'row': row,
\ 'col': col,
\ })
let self.type = 'floating'
return {
\ 'relative': 'cursor',
\ 'anchor': vert . hor,
\ 'row': row,
\ 'col': col,
\ }
endfunction
let s:popup.floating_win_opts = funcref('s:popup__floating_win_opts')

function! s:popup__open() dict abort
let self.opened_at = getpos('.')
let self.opener_bufnr = bufnr('%')
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)
call nvim_open_win(self.opener_bufnr, v:true, width, height, opts)
else
pedit!
wincmd P
execute height . 'wincmd _'
let self.type = 'preview'
endif

" Setup content
enew!
let popup_bufnr = bufnr('%')
setlocal
\ buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nonumber
\ nocursorline wrap nonumber norelativenumber signcolumn=no nofoldenable
\ nospell nolist nomodeline
if has_key(self.opts, 'filetype')
let &l:filetype = self.opts.filetype
endif
let popup_bufnr = bufnr('%')
call setline(1, self.contents)
setlocal nomodified nomodifiable

Expand All @@ -126,25 +139,84 @@ function! s:popup__open() dict abort
setlocal winhighlight=Normal:gitmessengerPopupNormal,EndOfBuffer:gitmessengerEndOfBuffer
endif

if has_key(self.opts, 'mappings')
for m in keys(self.opts.mappings)
execute printf('nnoremap <buffer><silent>%s :<C-u>call b:__gitmessenger_popup.opts.mappings["%s"]()<CR>', m, m)
endfor
endif

" Ensure to close popup
let b:__gitmessenger_popup = self
execute 'autocmd BufWipeout <buffer> call getbufvar(' . popup_bufnr . ', "__gitmessenger_popup").close()'

wincmd p

let self.bufnr = popup_bufnr
let self.opener_bufnr = opener_bufnr
let self.opened_at = opener_pos
endfunction
let s:popup.open = funcref('s:popup__open')

function! s:popup__update() dict abort
let prev_winnr = winnr()

let popup_winnr = bufwinnr(self.bufnr)
if popup_winnr < 0
return
endif
let opener_winnr = bufwinnr(self.opener_bufnr)
if opener_winnr < 0
return
endif

if opener_winnr != prev_winnr
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_config(id, width, height, opts)

" Window is not repainted due to bug of Neovim
" https://github.com/neovim/neovim/issues/9699
call timer_start(50, {_-> execute("normal! \<C-l>")})
endif

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
execute prev_winnr . 'wincmd w'
endif
endtry
endfunction
let s:popup.update = funcref('s:popup__update')

" 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;
" };
" }
function! gitmessenger#popup#new(contents, opts) abort
let p = deepcopy(s:popup)
Expand Down

0 comments on commit f6fa25a

Please sign in to comment.