How does one navigate Ruby methods in VIM? - ruby

I'm learning VIM for Rails development and would like to easily navigate methods in a file. So far I see several options:
Find 'def' by using
/def<space>
Create a macro that corresponds to a key using q and record
use VIM marks? (not even sure what they do, they just sound promising
Anyone have any better ideas?

:help ]m
I think it requires vim-ruby for ruby support.

you'll want a feature called ctags
see exuberant ctags, it works for many languages included Ruby and is v simple to use.
from VIM :help ctags
ctags will create an index of all identifiers in a source tree. You can then use the tag commands to navigate around your source tree. see :help tag-commands. The easiest is to place the cursor over a keyword and press CTRL-]. To get back to where you came from press CTRL-T
Beyond this you might want to look at this page which documents how to use VIM as a more full featured Ruby IDE: Using Vim as a Complete Ruby On Rails IDE

Best solution for Vim: use ctags. Read Vim documentation about how to navigate in TAGS files, also install plugin like CtrlP which allows you to visually browse tags.
Warning: Exuberant ctags does not work well with Ruby, the parser is not in good condition and it has not been changed 4 years now.
ctags doesn't deal with: module A::B
ctags doesn't tag (at least some of) the operator methods like ==
ctags doesn't support qualified tags, -type=+
ctags doesn't output tags for constants or attributes.
Unfortunately all the others (I found 2) Ruby ctags generators are either outdated (no Ruby 1.9+ support) or very slow.
There is one solution tho. Ripper-ctags: https://github.com/tmm1/ripper-tags It is fast and it works as expected. It is based on Ruby 1.9+ feature called "Ripper" which allows us to build on top of (fast) Ruby original parser. It is the most accurate ctags generator today.
Ripper CLI options are almost identical to ctags, so if you already know ctags, you will find ripper-tags easy to learn. It's as easy as:
ripper-tags -R .
This creates TAGS file which vim automatically reads by default (must be the directory where you open your vim instance in, or manually change path setting in vim if you start it in a different directory - more in the Vim manual).
If you like this, you can go step further and install my project which automatically creates TAGS for all the gems you install: https://github.com/lzap/gem-ripper-tags
Usage is very simple (note again, only Ruby 1.9+):
gem install gem-ripper-tags
Then generate tags for all already installed gems:
gem ripper_tags
Anytime you install a gem now, tags will be automatically created.
gem instal some_gem ...
I go one additional step further - I have a git template which is regenerating my project TAGS after every git pull or merge automatically (using ripper-tags):
https://github.com/lzap/bin-public/blob/master/git-hooks-reinstall
Note you will need directory files/git_template as well from the very same git repository.
I hope this is good starting point for navigating in Ruby codebases :-)

A couple of ideas:
Firstly, make a mapping to use the C function searching keys in ~/.vim/after/ftplugin/ruby.vim:
:nmap [[ ?def <CR>
:nmap ]] /def <CR>
Then you can use [[ and ]] to go forward and back a function, just like in C/Perl/Java code etc.
Another way that might help:
In .vimrc, add the line:
:let ruby_fold = 1
Then use zj, zk, z[ and z] to navigate by folds. You could also install this plugin so that you can easily delete folds using daz.
For finding specific functions (rather than just navigating around them) you'll want to use ctags (as mentioned by chillitom). The taglist plugin makes it much easier to navigate to a specific function, but (as chillitom said) Ctrl-] and Ctrl-T are useful for following keywords under the cursor.
For more information, see:
:help [[
:help ft-ruby-syntax
:help z[
:help after-directory

I recently found that Ruby.vim (per one of the answers above) comes with pretty useful key-bindings:
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\<Bar>rubyClass','b','n')<CR>
nnoremap <silent> <buffer> ]] :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>','rubyModule\<Bar>rubyClass','','n')<CR>
nnoremap <silent> <buffer> [] :<C-U>call <SID>searchsyn('\<end\>','rubyModule\<Bar>rubyClass','b','n')<CR>
nnoremap <silent> <buffer> ][ :<C-U>call <SID>searchsyn('\<end\>','rubyModule\<Bar>rubyClass','','n')<CR>
xnoremap <silent> <buffer> [[ :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>','rubyModule\<Bar>rubyClass','b','v')<CR>
xnoremap <silent> <buffer> ]] :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>','rubyModule\<Bar>rubyClass','','v')<CR>
xnoremap <silent> <buffer> [] :<C-U>call <SID>searchsyn('\<end\>','rubyModule\<Bar>rubyClass','b','v')<CR>
xnoremap <silent> <buffer> ][ :<C-U>call <SID>searchsyn('\<end\>','rubyModule\<Bar>rubyClass','','v')<CR>

One trick is to just search using '/f methodName'.
You should also look at turning on code folding by adding this line to your .vimrc:
:let ruby_fold
See :help ft-ruby-syntax for more details.

Usually I just type the name of the method on the incremental search.

I wrote a small shell script using ag (The Silver Searcher). Define the following functions in your .vimrc
This method will expand the word under the cursor.
function! SearchForDeclarationCursor()
let searchTerm = expand("<cword>")
call SearchForDeclaration(searchTerm)
endfunction
Then declare the SearchForDeclaration method
function! SearchForDeclaration(term)
let definition = 'def ' . a:term
cexpr system('ag -w ' . shellescape(definition))
endfunction
Note that we are explicitly searching for def keyword. (You can use your language method signature syntax)
We then map the above function to a Leader command.
map <Leader>cd :call SearchForDeclarationCursor()<CR>
Now if you place your cursor anywhere on a method that is defined "in your project", and press <Leader>cd, it will navigate you to where the method is defined.
Note that a method can be defined in multiple classes. You can cycle using <Leader>n for next or <Leader>p for prev.
If you want a more detailed explanation for the above, I've written a blog post here: http://pradyumna.io/2017/12/17/search-ruby-method-declarations-in-vim.html
Hope this helps!

Related

insert bash script output in vim file

I'm writing markdown in Vim and using this shortcut to set correct markdown image syntax:
nnoremap <leader>p :s/.*/![](\0)/ <CR>
when pressing ,p, /path/to/image/img.jpg (in my vim text file) becomes
![](/path/to/image/img.jpg)
But I want to add after that this { width = *variable*% }, like this
![](img.jpg){ width = *variable*% }
I made this little bash script (img.sh) that gives me the variable according to the image size:
#!/bin/bash
VAR=$(identify -format '%h' $1)
echo "scale=3; 300 * (100/$VAR)" | bc
If I do this in vim :r !img.sh /path/to/image/img.jpg I get a number in this case 32
I would want to launch this script with the shortcut above, I tried this:
nnoremap <leader>p :s/.*/![](\0){ width=/ <CR> :r !img.sh /path/to/image/img.jpg <CR> % }
I want you to help me to find a way to not type path to image to execute the script. Path to image is already written in the text if i could find a way to indicate to vim to place it in the shortcut after img.sh it would be great !
You can use a Vimscript expression on the replacement side of your :s command, that way you can refer to the text in the match (which is the path to the image) using submatch(0). You can use an expression on the replacement of a :s by beginning it with \=, see :help sub-replace-expression for more details.
Using a replacement expression, you can also use the system() function to call the external command, instead of using a separate :r !... command to read its output into the current buffer.
Putting it all together:
:s/.*/\='![]('.submatch(0).']{ width='.trim(system('img.sh '.submatch(0))).' }'/
Or to add a mapping to it:
nnoremap <leader>p :s/.*/\='![]('.submatch(0).']{ width='.trim(system('img.sh '.submatch(0))).' }'/<CR>
(NOTE: You might also want to add a :noh command to the end of your mapping, otherwise it will keep highlighting the search for .* which matches everything.)
nnoremap <leader>p :s/.*/\='![]('.submatch(0).']{ width='.trim(system('img.sh '.submatch(0))).' }'/\|noh<CR>

Vim Command Mapping "Copy to Clipboard" Not Working

I recently began using Vim as my primary editor instead of programs like Atom/VSCode. I added a number of leader mappings to simplify tasks I do quite often but I'm having trouble with a few of them.
In Visual mode, I would like to be able to press <Space>y to copy the current selection to the clipboard (+ register). I've verified that I can do this manually by entering visual mode, selecting the text I want, and pressing "+y. However, my mapping doesn't seem to work:
vmap <Leader>y "+y
I set my leader the following way:
map <Space> <Leader>
I do it this way so that when showcmd is set, I get a visual indicator in operator-pending mode. By looking at that indicator, I can tell that when I press <Space>, I do enter operator pending mode on the \ key as expected. Then, when I press y, I am no longer in operator pending mode, but I am still in visual mode and haven't yanked the selection to the register.
To make sure there wasn't a plugin colliding with my mapping, I backed up my .vimrc and replaced it with one that only has the following contents:
set showcmd
map <Space> <Leader>
vmap <Leader>y "+y
Does one of these keys need to be escaped? Or am I doing something else wrong?
(I'm currently running Ubuntu Bash on Windows. Vim is version 7.4)
For reference, I got the idea from this article (And use the exact same command):
https://sheerun.net/2014/03/21/how-to-boost-your-vim-productivity/
Thankfully, the fix is pretty simple.
map <Space> <Leader>
is incorrect. The right way is
let mapleader=" "
From :help mapleader
*<Leader>* *mapleader*
To define a mapping which uses the "mapleader" variable, the special string
"<Leader>" can be used. It is replaced with the string value of "mapleader".
If "mapleader" is not set or empty, a backslash is used instead. Example: >
:map <Leader>A oanother line<Esc>
Works like: >
:map \A oanother line<Esc>
But after: >
:let mapleader = ","
It works like: >
:map ,A oanother line<Esc>
Note that the value of "mapleader" is used at the moment the mapping is
defined. Changing "mapleader" after that has no effect for already defined
mappings.
How to define the "leader" key is explained under :help mapleader. If you want to use <Space> as "leader" you are supposed to do:
let mapleader = "\<Space>"
Note that the "leader" key is not a special key at all. With <Space> as "leader", the two mappings below are strictly equivalent:
vmap <leader>y "+y
vmap <Space>y "+y

Can't use CTRL-<Space> with Cscope plugin in gvim (windows)

Configuration
Windows 7 Service Pack 1 (64-bit)
VIM 7.4 (2013 Aug 10), 32-Bit GUI version
cscope_macros.vim plugin from www.vim.org, version 2.0.0
The problem
The plugin maps several cscope find functions to open in horizontal or vertical splits using 'CTRL-spacebar' or <CTRL-#>, as this is how VIM recognises it according to the plugin documentation. here is a snippet from the plugin:
" Using 'CTRL-spacebar' (intepreted as CTRL-# by vim) then a search type
" makes the vim window split horizontally, with search result displayed in
" the new window.
"
" (Note: earlier versions of vim may not have the :scs command, but it
" can be simulated roughly via:
" nmap <C-#>s <C-W><C-S> :cs find s <C-R>=expand("<cword>")<CR><CR>
nmap <C-#>s :scs find s <C-R>=expand("<cword>")<CR><CR>
nmap <C-#>g :scs find g <C-R>=expand("<cword>")<CR><CR>
However, 'CTRL-spacebar' does not work. When I look at what has been mapped <C-#> is actually translated as <nul>. For example, if I use the command :map, this is the result for the cscope plugin mapped keys.
n <nul>d :scs find d <C-R>=expand("<cword>")<CR><CR><Tab>
n <nul>i :scs find i <C-R>=expand("<cfile>")<CR><CR><Tab>
n <nul>f :scs find f <C-R>=expand("<cfile>")<CR><CR><Tab>
n <nul>e :scs find e <C-R>=expand("<cword>")<CR><CR><Tab>
The only thing I can find 'CTRL-spacebar'/<CTRL-#> (:help index) is
tag char action in Insert mode ~
-----------------------------------------------------------------------
i_CTRL-# CTRL-# insert previously inserted text and stop
insert
But this is not the behaviour I observe.
When I try using 'CTRL-spacebar' in insert mode all that happens is that a space is inserted at the cursor. When I use it in normal mode it seems to move the cursor to the beginning of the next word, or the next line if it is blank.
So, how do I map 'CTRL-spacebar' in VIM on windows?
In Windows GVIM, use the straightforward <C-Space> as the left-hand side of the mapping. <C-#> or the equivalent <Nul> is a workaround for the (Linux) terminal, which in general offers fewer mappable keys. The instructions were presumably aimed at that only.

VIM : Trouble mapping <c-/> and re-selecting visual selection?

I am using a vim plugin called tComment
It allows me to comment a line by pressing gc or <c-_><c-_>
Also, it works on the shortcut <c-/><c-/> but the visual selection is lost.
So, I tried:
To make it work on single <c-/>
To retain the visual selection.
My attempts :
inoremap <c-/> gc
vnoremap <c-/> gc gv
nnoremap <c-/> gc
=========
imap <c-/> gc
vmap <c-/> gc gv
nmap <c-/> gc
=========
imap <c-/> gc$
vmap <c-/> gc$ gv
nmap <c-/> gc$
=========
inoremap <c-/> <c-_><c-_>
vnoremap <c-/> <c-_><c-_> gv
nnoremap <c-/> <c-_><c-_>
=========
imap <c-/> <c-_><c-_>
vmap <c-/> <c-_><c-_> gv
nmap <c-/> <c-_><c-_>
( Non of the above seems to work )
Note:
I have not done any other customizations from my side.
My attempts are listed above
Installing tComment on native vim (Ubuntu) lands you to my setup.
If you want to map keys to another mapping, you need to use :map, not :noremap.
For most plugins, this shouldn't be necessary; they usually provide either configuration variables or <Plug>PluginName... for that. Read :help g:tcommentMaps for instructions for this particular plugin, then place your overrides into your ~/.vimrc.
If I understand you correctly, you want to have one map (in i, n, & v-mode) that either comments the current line or the visual selection. This is what tcomment's <c-_><c-_> map does now (with the exception that you want to maintain the visual selection). In order to use <c-/> you have to set g:tcommentMapLeader1 = '' (or some other map, since <c-/> seems to be the same as <c-_> as echristopherson pointed out) in vimrc and then define your maps for <c-/>.
This should work (add these lines to .vimrc):
let g:tcommentMapLeader1 = ''
noremap <silent> <c-/> :TComment<cr>
vnoremap <silent> <c-/> :TCommentMaybeInline<cr>gv
inoremap <silent> <c-/> <c-o>:TComment<cr>
You might have to replace <c-/> with <c-_> to make this work. Since you reported that tcomment already worked when typing <c-/><c-/>, the <c-_> map should work.
Anyway, I'd also recommend to use the operator maps since those fit better the way vim works. I don't think using a single key is still a good idea though.

Jumping to a Ruby bang method using Ctags in Vim

I'm having a problem with jumping to a Ruby bang method using Exhuberant Ctags. I have searched for others having a similar problem and am unable to find anything. An example of the problem can be shown using the following small Ruby class:
class Hello
def start
method!
end
def method
# Blah
end
def method!
# Blah
end
end
When ctags -R . is run on this file the resulting tags file contains the following 2 lines demonstrating that both methods are discovered at generation:
method test.rb /^ def method$/;" f class:Hello
method! test.rb /^ def method!$/;" f class:Hello
However, if I place my cursor on the call to method! on line 3 and press ^] then the cursor jumps to the method definition rather than to the correct bang version. It seems as if the exclamation mark is not being included in the identifier that is searched for.
Is there a way to fix this so the correct method is jumped to?
I realize this is super-old, but I ran into the same thing in both Vim 8.0 and Neovim. If I enter :tag mymethod! from vim's command-line, it finds the relevant tag, but if I try <C-]> with my cursor on the method name, it errors E426: tag not found: mymethod (note the lack of ! in the name it searched for).
You can fix this by adding ! to the list of characters recognized as keyword characters in Ruby syntax:
:set iskeyword+=!
You could add this to ~/.vim/after/syntax/ruby.vim to apply it in any Ruby file you open. I haven't tested this though, so can't say whether it will adversely affect anything else. I know it will change word jumping behavior. w will, for instance, treat the ! as part of the "small" word.
On second thought, it will definitely mishandle things like !some_test. If you were to hit <C-]> with the cursor anywhere in there, it would search for a method named !some_test, which is definitely not what you want. A better solution would be to write a wrapper function around the tag lookup for Ruby files. I'm actually working on something for that, so I'll post when I have something that presentable.
Update: I found a surprisingly simple workaround:
nnoremap <buffer><silent> <C-]> :tag <C-R><C-W><CR>
For some reason, the behavior of <C-R><C-W> in command-line mode differs from that of expand('<cword>'), and arguably from the documentation. Even though ! is not an 'iskeyword' character, and expand('<cword>') results in mymethod, <C-R><C-W> results in mymethod!. The same applies to is_this_your_method?. You could apply this workaround by putting the following in ~/.vim/ftplugin/ruby.vim:
nnoremap <buffer><silent> <C-]> :tag <C-R><C-W><CR>
nnoremap <buffer><silent> g] :tselect <C-R><C-W><CR>
nnoremap <buffer><silent> g<C-]> :tjump <C-R><C-W><CR>
Update 2
It turns out the special behavior of <C-R><C-W> was provided by vim-ruby (and included in Vim's runtime files by default). That script customizes <C-R><C-W> and also adds a <Plug><cword> mapping to correctly identify the Ruby cursor identifier. I only ran into the mishandling of ! because I had inadvertently clobbered the mappings already provided by vim-ruby when adding what I find to be more a comfortable keybinding:
nnoremap <C-.> <C-]>
If I'd done nmap instead, vim-ruby's mapping could have done its job. Alternatively, you could leverage what vim-ruby provides by doing (in a ruby ftplugin file):
nnoremap <buffer><silent> <C-]> :<C-U>exe v:count1."tag <Plug><cword>"<CR>
nnoremap <buffer><silent> g] :<C-U>tselect <Plug><cword><CR>
nnoremap <buffer><silent> g<C-]> :<C-U>tjump <Plug><cword><CR>
You can always use :tag:
:tag method!
Or visual mode - if you highlight any text (with v + movement) before you hit ^], it will use the highlighted text as the tag instead of trying to find an 'identifier' under the cursor. So if your cursor is on the m in method!, then
vE^]
should do the trick. If your cursor is elsewhere in the word, then hit b first.
I was using MacVim snapshot 63 at the time I posted this question. I'm now using snapshot 72 and the problem has gone. The only advice I can give here is to upgrade the version of Vim that you are using.

Resources