Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: When jumping to a definition, increment the tag stack #517

Open
dylan-chong opened this issue Jul 15, 2018 · 7 comments · May be fixed by #917
Open

Feature request: When jumping to a definition, increment the tag stack #517

dylan-chong opened this issue Jul 15, 2018 · 7 comments · May be fixed by #917

Comments

@dylan-chong
Copy link
Contributor

dylan-chong commented Jul 15, 2018

This feature would be really useful for jumping back to where you came from, when you do multiple jumps and browse around in various files.

Just a note, C-]which pops the tag stack, is different to C-o, which is not necessarily jump-to-definition-related. The stack for C-o gets modified very often, for example when doing gg , so simply using this key would not be ideal, as I often have to press the scheme the times to come back to where I was before using the jump to definition feature.

@balta2ar
Copy link
Contributor

balta2ar commented Aug 1, 2018

Adding this was enough in my case, maybe it helps you too:

nnoremap <silent> gd :normal! m'<CR>:call LanguageClient_textDocument_definition()<CR>

@dylan-chong
Copy link
Contributor Author

@balta2ar Does that just create a mark? Marks are local to the current buffer, so if you go to the definition to a different file (so 90% of the time) then you won't be able to jump back.

@qrux0
Copy link

qrux0 commented Aug 7, 2018

Looks like vim/neovim doesn't have plugin API for tagstack interaction. (neovim/neovim#398)

All ctrl+] and ctrl+t stuff is hardcoded, and there is no way how to implement this feature except hooking these hotkeys and emulate tagstack behavior :-( .

@amgoyal
Copy link

amgoyal commented Feb 7, 2019

It seems that vim now have api's for incrementing tagstack, see the reference implementation here
https://github.com/natebosch/vim-lsc/blob/b8a04ab0721c7383290ee997dd3bd491b8f5f93c/autoload/lsc/reference.vim#L22-L28

it will be good to add now.

@yen3
Copy link

yen3 commented Jun 25, 2019

Thanks for @qrux0 and @amgoyal's comment. They give me the idea.

Maybe we don't have to rewrite <c-]> and <c-t>. We can add two function to push/pop the trace stack. I copy the concept and part of code from faith/vim-go and write the two proof-of-concept functions.

I have no idea about rust. If the idea is ok, I would try to learn rust and write rust version.

" ref: https://github.com/fatih/vim-go/blob/8b4b0e3cea0f41aeb6810fbc30d57e4a639d1ee6/autoload/go/def.vim

let s:lsp_stack = []
let s:lsp_stack_level = 0

function! MyGoToDefinition(...) abort
  " Get the current position
  let l:fname = expand('%:p')
  let l:line = line(".")
  let l:col = col(".")

  " Call the original function to jump to the definition
  let l:result = LanguageClient_runSync('LanguageClient#textDocument_definition', {
              \ 'handle': v:true,
              \ })

  " Get the position of definition
  let l:jump_fname = expand('%:p')
  let l:jump_line = line(".")
  let l:jump_col = col(".")

  " If the position is the same as previous, ignore the jump action
  if l:fname == l:jump_fname && l:line == l:jump_line
     return
  endif

  " Remove anything newer than the current position, just like basic
  " vim tag support
  if s:lsp_stack_level == 0
    let s:lsp_stack = []
  else
    let s:lsp_stack = s:lsp_stack[0:s:lsp_stack_level-1]
  endif

  " Push entry into stack
  let s:lsp_stack_level += 1
  let l:stack_entry = {'line': l:line, 'col': l:col, 'file': l:fname}
  call add(s:lsp_stack, l:stack_entry)
endfunction

function! MyTagStackPop() abort
  if s:lsp_stack_level == 0
    echo "lsp stack empty!"
    return
  endif

  " Get previous position
  let l:curr_stack_level = s:lsp_stack_level - 1
  let l:jump_entry = s:lsp_stack[l:curr_stack_level]

  " Pop stack
  let s:lsp_stack = s:lsp_stack[0:l:curr_stack_level]
  let s:lsp_stack_level = s:lsp_stack_level - 1

  " Jump to previous location
  if &modified
    exec 'hide edit'  l:jump_entry['file']
  else
    exec 'edit' l:jump_entry['file']
  endif

  call cursor(l:jump_entry['line'], l:jump_entry['col'])
  normal! zz
endfunction

nnoremap gd :call MyGoToDefinition()<cr>
nnoremap gt :<C-U>call MyTagStackPop()<cr>
"nnoremap <c-]> :call MyGoToDefinition()<cr>
"nnoremap <c-t> :<C-U>call MyTagStackPop()<cr>

@yen3
Copy link

yen3 commented Jul 2, 2019

The jedi-vim uses a trick to insert tags into vim's original stack. The benefit of the method is that the implementation does not need to manipulate a stack to memory user's behaviour.

But the function is still vim version. In next step, I would lean rust in my free time and try to write rust version.

function! MyGoToDefinition(...) abort
  " ref: https://github.com/davidhalter/jedi-vim/blob/master/pythonx/jedi_vim.py#L329-L345

  " Get the current position
  let l:fname = expand('%:p')
  let l:line = line(".")
  let l:col = col(".")
  let l:word = expand("<cword>")

  " Call the original function to jump to the definition
  let l:result = LanguageClient_runSync(
                  \ 'LanguageClient#textDocument_definition', {
                  \ 'handle': v:true,
                  \ })

  " Get the position of definition
  let l:jump_fname = expand('%:p')
  let l:jump_line = line(".")
  let l:jump_col = col(".")

  " If the position is the same as previous, ignore the jump action
  if l:fname == l:jump_fname && l:line == l:jump_line
    return
  endif

  " Workaround: Jump to origial file. If the function is in rust, there is a
  " way to ignore the behaviour
  if &modified
    exec 'hide edit'  l:fname
  else
    exec 'edit' l:fname
  endif
  call cursor(l:line, l:col)

  " Store the original setting
  let l:ori_wildignore = &wildignore
  let l:ori_tags = &tags

  " Write a temp tags file
  let l:temp_tags_fname = tempname()
  let l:temp_tags_content = printf("%s\t%s\t%s", l:word, l:jump_fname,
      \ printf("call cursor(%d, %d)", l:jump_line, l:jump_col+1))
  call writefile([l:temp_tags_content], l:temp_tags_fname)

  " Set temporary new setting
  set wildignore=
  let &tags = l:temp_tags_fname

  " Add to new stack
  execute ":tjump " . l:word

  " Restore original setting
  let &tags = l:ori_tags
  let &wildignore = l:ori_wildignore

  " Remove temporary file
  if filereadable(l:temp_tags_fname)
    call delete(l:temp_tags_fname, "rf")
  endif
endfunction

nnoremap gd :call MyGoToDefinition()<cr>
" No need to remap <c-t> anymore

@nvlbg
Copy link

nvlbg commented Oct 22, 2021

I slightly modified the script by @yen3 to make it asynchronous:

function! MyGoToDefinition(...) abort
  " ref: https://github.com/davidhalter/jedi-vim/blob/master/pythonx/jedi_vim.py#L329-L345

  " Get the current position
  let pos = {
          \ 'fname': expand('%:p'),
          \ 'line': line("."),
          \ 'col': col("."),
          \ 'word': expand("<cword>")
          \ }

  call LanguageClient#textDocument_definition({'handle': v:true}, function('MyGoToDefinitionCallback', [pos]))
endfunction

function! MyGoToDefinitionCallback(pos, ...) abort
  " Get the original position
  let l:fname = a:pos['fname']
  let l:line = a:pos['line']
  let l:col = a:pos['col']
  let l:word = a:pos['word']

  " Get the position of definition
  let l:jump_fname = expand('%:p')
  let l:jump_line = line(".")
  let l:jump_col = col(".")

  " If the position is the same as previous, ignore the jump action
  if l:fname == l:jump_fname && l:line == l:jump_line
    return
  endif

  " Workaround: Jump to origial file. If the function is in rust, there is a
  " way to ignore the behaviour
  if &modified
    exec 'hide edit'  l:fname
  else
    exec 'edit' l:fname
  endif
  call cursor(l:line, l:col)

  " Write a temp tags file
  let l:temp_tags_fname = tempname()
  let l:temp_tags_content = printf("%s\t%s\t%s", l:word, l:jump_fname,
      \ printf("call cursor(%d, %d)", l:jump_line, l:jump_col))
  call writefile([l:temp_tags_content], l:temp_tags_fname)

  " Store the original setting
  let l:ori_wildignore = &wildignore
  let l:ori_tags = &tags

  " Set temporary new setting
  set wildignore=
  let &tags = l:temp_tags_fname

  " Add to new stack
  execute ":tjump " . l:word

  " Restore original setting
  let &tags = l:ori_tags
  let &wildignore = l:ori_wildignore

  " Remove temporary file
  if filereadable(l:temp_tags_fname)
    call delete(l:temp_tags_fname, "rf")
  endif
endfunction

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants