view runtime/ftplugin/ruby.vim @ 32782:abf161ce0c77 v9.0.1707

patch 9.0.1707: Cannot wrap around in popup_filter_menu() Commit: https://github.com/vim/vim/commit/badeedd913d9d6456ad8087911d024fd36800743 Author: Christian Brabandt <cb@256bit.org> Date: Sun Aug 13 19:25:28 2023 +0200 patch 9.0.1707: Cannot wrap around in popup_filter_menu() Problem: Cannot wrap around in popup_filter_menu() Solution: Allow to wrap around by default Currently, it is not possible, to wrap around at the end of the list using e.g. down (and go back to the top) or up at the beginning of the list and go directly to the last item. This is not consistent behaviour with e.g. how the pum-menu currently works, so let's just allow this. Also adjust tests about it. closes: #12689 closes: #12693 Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sun, 13 Aug 2023 19:30:04 +0200
parents e3d6184b89fa
children 828bcb1a37e7
line wrap: on
line source

" Vim filetype plugin
" Language:		Ruby
" Maintainer:		Tim Pope <vimNOSPAM@tpope.org>
" URL:			https://github.com/vim-ruby/vim-ruby
" Release Coordinator:	Doug Kearns <dougkearns@gmail.com>
" Last Change:		2022 Mar 21

if (exists("b:did_ftplugin"))
  finish
endif
let b:did_ftplugin = 1

let s:cpo_save = &cpo
set cpo&vim

if has("gui_running") && !has("gui_win32")
  setlocal keywordprg=ri\ -T\ -f\ bs
else
  setlocal keywordprg=ri
endif

" Matchit support
if exists("loaded_matchit") && !exists("b:match_words")
  let b:match_ignorecase = 0

  let b:match_words =
	\ '{\|\<\%(if\|unless\|case\|while\|until\|for\|do\|class\|module\|def\|=\@<!begin\)\>=\@!' .
	\ ':' .
	\ '\<\%(else\|elsif\|ensure\|when\|rescue\|break\|redo\|next\|retry\)\>' .
	\ ':' .
        \ '}\|\%(^\|[^.\:@$=]\)\@<=\<end\:\@!\>' .
        \ ',^=begin\>:^=end\>,' .
	\ ',\[:\],(:)'

  let b:match_skip =
	\ "synIDattr(synID(line('.'),col('.'),0),'name') =~ '" .
	\ "\\<ruby\\%(String\\|.\+Delimiter\\|Character\\|.\+Escape\\|" .
        \ "Regexp\\|Interpolation\\|Comment\\|Documentation\\|" .
	\ "ConditionalModifier\\|RepeatModifier\\|RescueModifier\\|OptionalDo\\|" .
	\ "MethodName\\|BlockArgument\\|KeywordAsMethod\\|ClassVariable\\|" .
	\ "InstanceVariable\\|GlobalVariable\\|Symbol\\)\\>'"
endif

setlocal formatoptions-=t formatoptions+=croql

setlocal include=^\\s*\\<\\(load\\>\\\|require\\>\\\|autoload\\s*:\\=[\"']\\=\\h\\w*[\"']\\=,\\)
setlocal suffixesadd=.rb

if exists("&ofu") && has("ruby")
  setlocal omnifunc=rubycomplete#Complete
endif

" TODO:
"setlocal define=^\\s*def

setlocal comments=b:#
setlocal commentstring=#\ %s

if !exists('g:ruby_version_paths')
  let g:ruby_version_paths = {}
endif

function! s:query_path(root) abort
  let code = "print $:.join %q{,}"
  if &shell =~# 'sh' && empty(&shellxquote)
    let prefix = 'env PATH='.shellescape($PATH).' '
  else
    let prefix = ''
  endif
  if &shellxquote == "'"
    let path_check = prefix.'ruby --disable-gems -e "' . code . '"'
  else
    let path_check = prefix."ruby --disable-gems -e '" . code . "'"
  endif

  let cd = haslocaldir() ? 'lcd' : 'cd'
  let cwd = fnameescape(getcwd())
  try
    exe cd fnameescape(a:root)
    let path = split(system(path_check),',')
    exe cd cwd
    return path
  finally
    exe cd cwd
  endtry
endfunction

function! s:build_path(path) abort
  let path = join(map(copy(a:path), 'v:val ==# "." ? "" : v:val'), ',')
  if &g:path =~# '\v^%(\.,)=%(/%(usr|emx)/include,)=,$'
    let path = path . ',.,,'
  elseif &g:path =~# ',\.,,$'
    let path = &g:path[0:-4] . path . ',.,,'
  elseif &g:path =~# ',,$'
    let path = &g:path[0:-2] . path . ',,'
  else
    let path = substitute(&g:path, '[^,]\zs$', ',', '') . path
  endif
  return path
endfunction

if !exists('b:ruby_version') && !exists('g:ruby_path') && isdirectory(expand('%:p:h'))
  let s:version_file = findfile('.ruby-version', '.;')
  if !empty(s:version_file) && filereadable(s:version_file)
    let b:ruby_version = get(readfile(s:version_file, '', 1), '')
    if !has_key(g:ruby_version_paths, b:ruby_version)
      let g:ruby_version_paths[b:ruby_version] = s:query_path(fnamemodify(s:version_file, ':p:h'))
    endif
  endif
endif

if exists("g:ruby_path")
  let s:ruby_path = type(g:ruby_path) == type([]) ? join(g:ruby_path, ',') : g:ruby_path
elseif has_key(g:ruby_version_paths, get(b:, 'ruby_version', ''))
  let s:ruby_paths = g:ruby_version_paths[b:ruby_version]
  let s:ruby_path = s:build_path(s:ruby_paths)
else
  if !exists('g:ruby_default_path')
    if has("ruby") && has("win32")
      ruby ::VIM::command( 'let g:ruby_default_path = split("%s",",")' % $:.join(%q{,}) )
    elseif executable('ruby') && !empty($HOME)
      let g:ruby_default_path = s:query_path($HOME)
    else
      let g:ruby_default_path = map(split($RUBYLIB,':'), 'v:val ==# "." ? "" : v:val')
    endif
  endif
  let s:ruby_paths = g:ruby_default_path
  let s:ruby_path = s:build_path(s:ruby_paths)
endif

if stridx(&l:path, s:ruby_path) == -1
  let &l:path = s:ruby_path
endif
if exists('s:ruby_paths') && stridx(&l:tags, join(map(copy(s:ruby_paths),'v:val."/tags"'),',')) == -1
  let &l:tags = &tags . ',' . join(map(copy(s:ruby_paths),'v:val."/tags"'),',')
endif

if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
  let b:browsefilter = "Ruby Source Files (*.rb)\t*.rb\n" .
                     \ "All Files (*.*)\t*.*\n"
endif

let b:undo_ftplugin = "setl inc= sua= path= tags= fo< com< cms< kp="
      \."| unlet! b:browsefilter b:match_ignorecase b:match_words b:match_skip"
      \."| if exists('&ofu') && has('ruby') | setl ofu< | endif"

if get(g:, 'ruby_recommended_style', 1)
  setlocal shiftwidth=2 softtabstop=2 expandtab
  let b:undo_ftplugin .= ' | setl sw< sts< et<'
endif

" To activate, :set ballooneval
if exists('+balloonexpr') && get(g:, 'ruby_balloonexpr')
  setlocal balloonexpr=RubyBalloonexpr()
  let b:undo_ftplugin .= "| setl bexpr="
endif

function! s:map(mode, flags, map) abort
  let from = matchstr(a:map, '\S\+')
  if empty(mapcheck(from, a:mode))
    exe a:mode.'map' '<buffer>' a:flags a:map
    let b:undo_ftplugin .= '|sil! '.a:mode.'unmap <buffer> '.from
  endif
endfunction

cmap <buffer><script><expr> <Plug><ctag> substitute(RubyCursorTag(),'^$',"\022\027",'')
cmap <buffer><script><expr> <Plug><cfile> substitute(RubyCursorFile(),'^$',"\022\006",'')
let b:undo_ftplugin .= "| sil! cunmap <buffer> <Plug><ctag>| sil! cunmap <buffer> <Plug><cfile>"

if !exists("g:no_plugin_maps") && !exists("g:no_ruby_maps")
  nmap <buffer><script> <SID>:  :<C-U>
  nmap <buffer><script> <SID>c: :<C-U><C-R>=v:count ? v:count : ''<CR>
  cmap <buffer> <SID><cfile> <Plug><cfile>
  cmap <buffer> <SID><ctag>  <Plug><ctag>

  nnoremap <silent> <buffer> [m :<C-U>call <SID>searchsyn('\<def\>',['rubyDefine'],'b','n')<CR>
  nnoremap <silent> <buffer> ]m :<C-U>call <SID>searchsyn('\<def\>',['rubyDefine'],'','n')<CR>
  nnoremap <silent> <buffer> [M :<C-U>call <SID>searchsyn('\<end\>',['rubyDefine'],'b','n')<CR>
  nnoremap <silent> <buffer> ]M :<C-U>call <SID>searchsyn('\<end\>',['rubyDefine'],'','n')<CR>
  xnoremap <silent> <buffer> [m :<C-U>call <SID>searchsyn('\<def\>',['rubyDefine'],'b','v')<CR>
  xnoremap <silent> <buffer> ]m :<C-U>call <SID>searchsyn('\<def\>',['rubyDefine'],'','v')<CR>
  xnoremap <silent> <buffer> [M :<C-U>call <SID>searchsyn('\<end\>',['rubyDefine'],'b','v')<CR>
  xnoremap <silent> <buffer> ]M :<C-U>call <SID>searchsyn('\<end\>',['rubyDefine'],'','v')<CR>

  nnoremap <silent> <buffer> [[ :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>',['rubyModule','rubyClass'],'b','n')<CR>
  nnoremap <silent> <buffer> ]] :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>',['rubyModule','rubyClass'],'','n')<CR>
  nnoremap <silent> <buffer> [] :<C-U>call <SID>searchsyn('\<end\>',['rubyModule','rubyClass'],'b','n')<CR>
  nnoremap <silent> <buffer> ][ :<C-U>call <SID>searchsyn('\<end\>',['rubyModule','rubyClass'],'','n')<CR>
  xnoremap <silent> <buffer> [[ :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>',['rubyModule','rubyClass'],'b','v')<CR>
  xnoremap <silent> <buffer> ]] :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>',['rubyModule','rubyClass'],'','v')<CR>
  xnoremap <silent> <buffer> [] :<C-U>call <SID>searchsyn('\<end\>',['rubyModule','rubyClass'],'b','v')<CR>
  xnoremap <silent> <buffer> ][ :<C-U>call <SID>searchsyn('\<end\>',['rubyModule','rubyClass'],'','v')<CR>

  let b:undo_ftplugin = b:undo_ftplugin
        \."| sil! exe 'unmap <buffer> [[' | sil! exe 'unmap <buffer> ]]' | sil! exe 'unmap <buffer> []' | sil! exe 'unmap <buffer> ]['"
        \."| sil! exe 'unmap <buffer> [m' | sil! exe 'unmap <buffer> ]m' | sil! exe 'unmap <buffer> [M' | sil! exe 'unmap <buffer> ]M'"

  if maparg('im','x') == '' && maparg('im','o') == '' && maparg('am','x') == '' && maparg('am','o') == ''
    onoremap <silent> <buffer> im :<C-U>call <SID>wrap_i('[m',']M')<CR>
    onoremap <silent> <buffer> am :<C-U>call <SID>wrap_a('[m',']M')<CR>
    xnoremap <silent> <buffer> im :<C-U>call <SID>wrap_i('[m',']M')<CR>
    xnoremap <silent> <buffer> am :<C-U>call <SID>wrap_a('[m',']M')<CR>
    let b:undo_ftplugin = b:undo_ftplugin
          \."| sil! exe 'ounmap <buffer> im' | sil! exe 'ounmap <buffer> am'"
          \."| sil! exe 'xunmap <buffer> im' | sil! exe 'xunmap <buffer> am'"
  endif

  if maparg('iM','x') == '' && maparg('iM','o') == '' && maparg('aM','x') == '' && maparg('aM','o') == ''
    onoremap <silent> <buffer> iM :<C-U>call <SID>wrap_i('[[','][')<CR>
    onoremap <silent> <buffer> aM :<C-U>call <SID>wrap_a('[[','][')<CR>
    xnoremap <silent> <buffer> iM :<C-U>call <SID>wrap_i('[[','][')<CR>
    xnoremap <silent> <buffer> aM :<C-U>call <SID>wrap_a('[[','][')<CR>
    let b:undo_ftplugin = b:undo_ftplugin
          \."| sil! exe 'ounmap <buffer> iM' | sil! exe 'ounmap <buffer> aM'"
          \."| sil! exe 'xunmap <buffer> iM' | sil! exe 'xunmap <buffer> aM'"
  endif

  call s:map('c', '', '<C-R><C-F> <Plug><cfile>')

  cmap <buffer><script><expr> <SID>tagzv &foldopen =~# 'tag' ? '<Bar>norm! zv' : ''
  call s:map('n', '<script><silent>', '<C-]>       <SID>:exe  v:count1."tag <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', 'g<C-]>      <SID>:exe         "tjump <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', 'g]          <SID>:exe       "tselect <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', '<C-W>]      <SID>:exe v:count1."stag <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', '<C-W><C-]>  <SID>:exe v:count1."stag <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', '<C-W>g<C-]> <SID>:exe        "stjump <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', '<C-W>g]     <SID>:exe      "stselect <SID><ctag>"<SID>tagzv<CR>')
  call s:map('n', '<script><silent>', '<C-W>}      <SID>:exe v:count1."ptag <SID><ctag>"<CR>')
  call s:map('n', '<script><silent>', '<C-W>g}     <SID>:exe        "ptjump <SID><ctag>"<CR>')

  call s:map('n', '<script><silent>', 'gf           <SID>c:find <SID><cfile><CR>')
  call s:map('n', '<script><silent>', '<C-W>f      <SID>c:sfind <SID><cfile><CR>')
  call s:map('n', '<script><silent>', '<C-W><C-F>  <SID>c:sfind <SID><cfile><CR>')
  call s:map('n', '<script><silent>', '<C-W>gf   <SID>c:tabfind <SID><cfile><CR>')
endif

let &cpo = s:cpo_save
unlet s:cpo_save

if exists("g:did_ruby_ftplugin_functions")
  finish
endif
let g:did_ruby_ftplugin_functions = 1

function! RubyBalloonexpr() abort
  if !exists('s:ri_found')
    let s:ri_found = executable('ri')
  endif
  if s:ri_found
    let line = getline(v:beval_lnum)
    let b = matchstr(strpart(line,0,v:beval_col),'\%(\w\|[:.]\)*$')
    let a = substitute(matchstr(strpart(line,v:beval_col),'^\w*\%([?!]\|\s*=\)\?'),'\s\+','','g')
    let str = b.a
    let before = strpart(line,0,v:beval_col-strlen(b))
    let after  = strpart(line,v:beval_col+strlen(a))
    if str =~ '^\.'
      let str = substitute(str,'^\.','#','g')
      if before =~ '\]\s*$'
        let str = 'Array'.str
      elseif before =~ '}\s*$'
        " False positives from blocks here
        let str = 'Hash'.str
      elseif before =~ "[\"'`]\\s*$" || before =~ '\$\d\+\s*$'
        let str = 'String'.str
      elseif before =~ '\$\d\+\.\d\+\s*$'
        let str = 'Float'.str
      elseif before =~ '\$\d\+\s*$'
        let str = 'Integer'.str
      elseif before =~ '/\s*$'
        let str = 'Regexp'.str
      else
        let str = substitute(str,'^#','.','')
      endif
    endif
    let str = substitute(str,'.*\.\s*to_f\s*\.\s*','Float#','')
    let str = substitute(str,'.*\.\s*to_i\%(nt\)\=\s*\.\s*','Integer#','')
    let str = substitute(str,'.*\.\s*to_s\%(tr\)\=\s*\.\s*','String#','')
    let str = substitute(str,'.*\.\s*to_sym\s*\.\s*','Symbol#','')
    let str = substitute(str,'.*\.\s*to_a\%(ry\)\=\s*\.\s*','Array#','')
    let str = substitute(str,'.*\.\s*to_proc\s*\.\s*','Proc#','')
    if str !~ '^\w'
      return ''
    endif
    silent! let res = substitute(system("ri -f rdoc -T \"".str.'"'),'\n$','','')
    if res =~ '^Nothing known about' || res =~ '^Bad argument:' || res =~ '^More than one method'
      return ''
    endif
    return res
  else
    return ""
  endif
endfunction

function! s:searchsyn(pattern, syn, flags, mode) abort
  let cnt = v:count1
  norm! m'
  if a:mode ==# 'v'
    norm! gv
  endif
  let i = 0
  call map(a:syn, 'hlID(v:val)')
  while i < cnt
    let i = i + 1
    let line = line('.')
    let col  = col('.')
    let pos = search(a:pattern,'W'.a:flags)
    while pos != 0 && index(a:syn, s:synid()) < 0
      let pos = search(a:pattern,'W'.a:flags)
    endwhile
    if pos == 0
      call cursor(line,col)
      return
    endif
  endwhile
endfunction

function! s:synid() abort
  return synID(line('.'),col('.'),0)
endfunction

function! s:wrap_i(back,forward) abort
  execute 'norm! k'
  execute 'norm '.a:forward
  let line = line('.')
  execute 'norm '.a:back
  if line('.') == line - 1
    return s:wrap_a(a:back,a:forward)
  endif
  execute 'norm! jV'
  execute 'norm '.a:forward
  execute 'norm! k'
endfunction

function! s:wrap_a(back,forward) abort
  execute 'norm '.a:forward
  if line('.') < line('$') && getline(line('.')+1) ==# ''
    let after = 1
  endif
  execute 'norm '.a:back
  while getline(line('.')-1) =~# '^\s*#' && line('.')
    -
  endwhile
  if exists('after')
    execute 'norm! V'
    execute 'norm '.a:forward
    execute 'norm! j'
  elseif line('.') > 1 && getline(line('.')-1) =~# '^\s*$'
    execute 'norm! kV'
    execute 'norm '.a:forward
  else
    execute 'norm! V'
    execute 'norm '.a:forward
  endif
endfunction

function! RubyCursorIdentifier() abort
  let asciicode    = '\%(\w\|[]})\"'."'".']\)\@<!\%(?\%(\\M-\\C-\|\\C-\\M-\|\\M-\\c\|\\c\\M-\|\\c\|\\C-\|\\M-\)\=\%(\\\o\{1,3}\|\\x\x\{1,2}\|\\\=\S\)\)'
  let number       = '\%(\%(\w\|[]})\"'."'".']\s*\)\@<!-\)\=\%(\<[[:digit:]_]\+\%(\.[[:digit:]_]\+\)\=\%([Ee][[:digit:]_]\+\)\=\>\|\<0[xXbBoOdD][[:xdigit:]_]\+\>\)\|'.asciicode
  let operator     = '\%(\[\]\|<<\|<=>\|[!<>]=\=\|===\=\|[!=]\~\|>>\|\*\*\|\.\.\.\=\|=>\|[~^&|*/%+-]\)'
  let method       = '\%(\.[_a-zA-Z]\w*\s*=>\@!\|\<[_a-zA-Z]\w*\>[?!]\=\)'
  let global       = '$\%([!$&"'."'".'*+,./:;<=>?@\`~]\|-\=\w\+\>\)'
  let symbolizable = '\%(\%(@@\=\)\w\+\>\|'.global.'\|'.method.'\|'.operator.'\)'
  let pattern      = '\C\s*\%('.number.'\|\%(:\@<!:\)\='.symbolizable.'\)'
  let [lnum, col]  = searchpos(pattern,'bcn',line('.'))
  let raw          = matchstr(getline('.')[col-1 : ],pattern)
  let stripped     = substitute(substitute(raw,'\s\+=$','=',''),'^\s*[:.]\=','','')
  return stripped == '' ? expand("<cword>") : stripped
endfunction

function! RubyCursorTag() abort
  return substitute(RubyCursorIdentifier(), '^[$@]*', '', '')
endfunction

function! RubyCursorFile() abort
  let isfname = &isfname
  try
    set isfname+=:
    let cfile = expand('<cfile>')
  finally
    let isfname = &isfname
  endtry
  let pre = matchstr(strpart(getline('.'), 0, col('.')-1), '.*\f\@<!')
  let post = matchstr(strpart(getline('.'), col('.')), '\f\@!.*')
  if s:synid() ==# hlID('rubyConstant')
    let cfile = substitute(cfile,'\.\w\+[?!=]\=$','','')
    let cfile = substitute(cfile,'^::','','')
    let cfile = substitute(cfile,'::','/','g')
    let cfile = substitute(cfile,'\(\u\+\)\(\u\l\)','\1_\2', 'g')
    let cfile = substitute(cfile,'\(\l\|\d\)\(\u\)','\1_\2', 'g')
    return tolower(cfile) . '.rb'
  elseif getline('.') =~# '^\s*require_relative\s*\(["'']\).*\1\s*$'
    let cfile = expand('%:p:h') . '/' . matchstr(getline('.'),'\(["'']\)\zs.\{-\}\ze\1')
    let cfile .= cfile !~# '\.rb$' ? '.rb' : ''
  elseif getline('.') =~# '^\s*\%(require[( ]\|load[( ]\|autoload[( ]:\w\+,\)\s*\%(::\)\=File\.expand_path(\(["'']\)\.\./.*\1,\s*__FILE__)\s*$'
    let target = matchstr(getline('.'),'\(["'']\)\.\.\zs/.\{-\}\ze\1')
    let cfile = expand('%:p:h') . target
    let cfile .= cfile !~# '\.rb$' ? '.rb' : ''
  elseif getline('.') =~# '^\s*\%(require \|load \|autoload :\w\+,\)\s*\(["'']\).*\1\s*$'
    let cfile = matchstr(getline('.'),'\(["'']\)\zs.\{-\}\ze\1')
    let cfile .= cfile !~# '\.rb$' ? '.rb' : ''
  elseif pre.post =~# '\<File.expand_path[( ].*[''"]\{2\}, *__FILE__\>' && cfile =~# '^\.\.'
    let cfile = expand('%:p:h') . strpart(cfile, 2)
  else
    return substitute(cfile, '\C\v^(.*):(\d+)%(:in)=$', '+\2 \1', '')
  endif
  let cwdpat = '^\M' . substitute(getcwd(), '[\/]', '\\[\\/]', 'g').'\ze\[\/]'
  let cfile = substitute(cfile, cwdpat, '.', '')
  if fnameescape(cfile) !=# cfile
    return '+ '.fnameescape(cfile)
  else
    return cfile
  endif
endfunction

"
" Instructions for enabling "matchit" support:
"
" 1. Look for the latest "matchit" plugin at
"
"         http://www.vim.org/scripts/script.php?script_id=39
"
"    It is also packaged with Vim, in the $VIMRUNTIME/macros directory.
"
" 2. Copy "matchit.txt" into a "doc" directory (e.g. $HOME/.vim/doc).
"
" 3. Copy "matchit.vim" into a "plugin" directory (e.g. $HOME/.vim/plugin).
"
" 4. Ensure this file (ftplugin/ruby.vim) is installed.
"
" 5. Ensure you have this line in your $HOME/.vimrc:
"         filetype plugin on
"
" 6. Restart Vim and create the matchit documentation:
"
"         :helptags ~/.vim/doc
"
"    Now you can do ":help matchit", and you should be able to use "%" on Ruby
"    keywords.  Try ":echo b:match_words" to be sure.
"
" Thanks to Mark J. Reed for the instructions.  See ":help vimrc" for the
" locations of plugin directories, etc., as there are several options, and it
" differs on Windows.  Email gsinclair@soyabean.com.au if you need help.
"

" vim: nowrap sw=2 sts=2 ts=8: