Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ BUG FIXES:
* Fix highlighting of multi-line string concatenation in a var or const block.
[[GH-3750]](https://github.com/fatih/vim-go/pull/3750)
[[GH-3751]](https://github.com/fatih/vim-go/pull/3751)
* Fix :GoDefPop, :GoDefStack, and :GoDefStackClear when Vim's native tag stack
is used.
[[GH-3754]](https://github.com/fatih/vim-go/pull/3754)


## v1.29 - (April 18, 2025)
Expand Down
82 changes: 68 additions & 14 deletions autoload/go/def.vim
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,49 @@ set cpo&vim
let s:go_stack = []
let s:go_stack_level = 0

" s:usetagstack returns 1 when Vim's native tag stack is used to record the
" locations that jumps to definitions were made from and 0 when vim-go's own
" stack is used instead. Recording a jump needs settagstack()'s "t" action,
" which was added by patch 8.2.0077
" (https://github.com/vim/vim/commit/271fa08a35b8d320d3a40db4ddae83b698fdd4fb).
function! s:usetagstack() abort
return exists('*settagstack') && has('patch-8.2.0077')
endfunction

" s:stack returns the list of locations that jumps to definitions were made
" from and the index of the active entry within it. Each entry is a dictionary
" with file, line, col, and ident keys. The oldest entry is first.
function! s:stack() abort
if !s:usetagstack()
return [s:go_stack, s:go_stack_level]
endif

let l:tagstack = gettagstack(win_getid())

let l:stack = []
for l:item in l:tagstack.items
call add(l:stack, {
\ 'file': fnamemodify(bufname(l:item.from[0]), ':p'),
\ 'line': l:item.from[1],
\ 'col': l:item.from[2],
\ 'ident': get(l:item, 'tagname', ''),
\ })
endfor

return [l:stack, l:tagstack.curidx - 1]
endfunction

" s:setstacklevel makes the entry at index level the active entry of the
" stack.
function! s:setstacklevel(level) abort
if s:usetagstack()
call settagstack(win_getid(), {'curidx': a:level + 1})
return
endif

let s:go_stack_level = a:level
endfunction

" go#def#Jump jumps to a definition. Valid modes are 'tab', 'split', 'vsplit',
" and the empty string, ''.
function! go#def#Jump(mode, type) abort
Expand Down Expand Up @@ -106,7 +149,7 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
let ident = parts[3]
endif

if exists('*settagstack') && has('patch-8.2.0077')
if s:usetagstack()
let l:tag = expand('<cword>')
let l:pos = [bufnr('')] + getcurpos()[1:]
let l:stack_entry = {'bufnr': l:pos[0], 'from': l:pos, 'tagname': l:tag}
Expand Down Expand Up @@ -159,7 +202,7 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
" also align the line to middle of the view
normal! zz

if exists('*settagstack') && has('patch-8.2.0077')
if s:usetagstack()
" Jump was successful, write previous location to tag stack.
let l:winid = win_getid()
let l:stack = gettagstack(l:winid)
Expand Down Expand Up @@ -200,19 +243,21 @@ function! go#def#SelectStackEntry() abort
endfunction

function! go#def#StackUI() abort
if len(s:go_stack) == 0
let [l:stack, l:level] = s:stack()

if len(l:stack) == 0
call go#util#EchoError("godef stack empty")
return
endif

let stackOut = ['" <Up>,<Down>:navigate <Enter>:jump <Esc>,q:exit']

let i = 0
while i < len(s:go_stack)
let entry = s:go_stack[i]
while i < len(l:stack)
let entry = l:stack[i]
let prefix = ""

if i == s:go_stack_level
if i == l:level
let prefix = ">"
else
let prefix = " "
Expand All @@ -223,7 +268,7 @@ function! go#def#StackUI() abort
let i += 1
endwhile

if s:go_stack_level == i
if l:level == i
call add(stackOut, "> ")
endif

Expand All @@ -235,17 +280,24 @@ function! go#def#StackUI() abort
endfunction

function! go#def#StackClear(...) abort
if s:usetagstack()
call settagstack(win_getid(), {'items': []}, 'r')
return
endif

let s:go_stack = []
let s:go_stack_level = 0
endfunction

function! go#def#StackPop(...) abort
if len(s:go_stack) == 0
let [l:stack, l:level] = s:stack()

if len(l:stack) == 0
call go#util#EchoError("godef stack empty")
return
endif

if s:go_stack_level == 0
if l:level == 0
call go#util#EchoError("at bottom of the godef stack")
return
endif
Expand All @@ -256,12 +308,14 @@ function! go#def#StackPop(...) abort
let numPop = a:1
endif

let newLevel = str2nr(s:go_stack_level) - str2nr(numPop)
let newLevel = str2nr(l:level) - str2nr(numPop)
call go#def#Stack(newLevel + 1)
endfunction

function! go#def#Stack(...) abort
if len(s:go_stack) == 0
let [l:stack, l:level] = s:stack()

if len(l:stack) == 0
call go#util#EchoError("godef stack empty")
return
endif
Expand All @@ -283,9 +337,9 @@ function! go#def#Stack(...) abort

let jumpTarget = str2nr(jumpTarget) - 1

if jumpTarget >= 0 && jumpTarget < len(s:go_stack)
let s:go_stack_level = jumpTarget
let target = s:go_stack[s:go_stack_level]
if jumpTarget >= 0 && jumpTarget < len(l:stack)
call s:setstacklevel(jumpTarget)
let target = l:stack[jumpTarget]

" jump
if expand('%:p') != target["file"]
Expand Down
86 changes: 86 additions & 0 deletions autoload/go/def_test.vim
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,92 @@ func! Test_DefJump_gopls_MultipleCodeUnit_last() abort
endtry
endfunc

func! Test_DefStackPop() abort
if !go#util#has_job()
return
endif

let l:wd = getcwd()
try
let g:go_def_mode = 'gopls'

let l:tmp = gotest#write_file('defstack/pop/pop.go', [
\ 'package pop',
\ '',
\ 'func Example() {',
\ "\t\x1fcallee()",
\ '}',
\ '',
\ 'func callee() {',
\ '}',
\ ] )

let l:from = getpos('.')
let l:expected = [0, 7, 6, 0]

call go#def#Jump('', 0)

let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile

call assert_equal(l:expected, getpos('.'))

" popping the stack should return to the position that the jump was made
" from.
call go#def#StackPop()

call assert_equal(l:from, getpos('.'))
finally
call go#util#Chdir(l:wd)
call delete(l:tmp, 'rf')
endtry
endfunc

func! Test_DefStackClear() abort
if !go#util#has_job()
return
endif

let l:wd = getcwd()
try
let g:go_def_mode = 'gopls'

let l:tmp = gotest#write_file('defstack/clear/clear.go', [
\ 'package clear',
\ '',
\ 'func Example() {',
\ "\t\x1fcallee()",
\ '}',
\ '',
\ 'func callee() {',
\ '}',
\ ] )

let l:expected = [0, 7, 6, 0]

call go#def#Jump('', 0)

let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile

call assert_equal(l:expected, getpos('.'))

" once the stack has been cleared, there's nowhere to pop back to, so the
" cursor should not move.
call go#def#StackClear()
call go#def#StackPop()

call assert_equal(l:expected, getpos('.'))
finally
call go#util#Chdir(l:wd)
call delete(l:tmp, 'rf')
endtry
endfunc

" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
Expand Down
10 changes: 9 additions & 1 deletion doc/vim-go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ g<C-LeftMouse>

The per-window location stack is shared with |:GoDefType|.

When Vim supports |settagstack()| with the "t" action, the location stack
is Vim's own tag stack. It is then also shared with |:tag|, |:pop|,
|:tags|, CTRL-t, and any other plugin that uses the tag stack. The "t"
action was added by Vim patch 8.2.0077:
https://github.com/vim/vim/commit/271fa08a35b8d320d3a40db4ddae83b698fdd4fb

*:GoDefType*
:GoDefType

Expand Down Expand Up @@ -325,7 +331,9 @@ CTRL-t

By default [count]CTRL-t is enabled to invoke :GoDefPop. Similarly,
hitting CTRL-t without a prior count is equivalent to `:GoDefPop 1`. See
|'g:go_def_mapping_enabled'| to disable this.
|'g:go_def_mapping_enabled'| to disable this. When the location stack is
Vim's own tag stack, CTRL-t is left alone, because Vim's builtin CTRL-t
already pops that stack.

*:GoRun*
:GoRun[!] [expand]
Expand Down
Loading