Identifying Identical Blocks of Code - vim

Say I have multiple blocks of the following code in a file (spaces is irrelavent):
sdgfsdg dfg
dfgdfgf ddfg
dfgdfgdfg dfgfdg
How do you find/highlight all the occurrences?
What I ideally want to do is to visually select the code block and then press search to find all occurrences.

Maybe you should look at :
Search for visually selected text
I've taken it from here

Try this. Include this script somewhere in your runtimepath (see :help runtimepath). A simple option would be to put it in your vimrc. Visually select the thing you want to search for and press ,/ (the comma key and then the forward-slash key).
" Search for other instances of the current visual range
" This works by:
" <ESC> Cancel the visual range (it's location is remembered)
" / Start the search
" <C-R>= Insert the result of an expression on
" the search line (see :help c_CTRL-R_= )
" GetVisualRange()<CR> Call the function created below
" <CR> Run the search
vmap ,/ <ESC>/<C-R>=GetVisualRange()<CR><CR>
" Create the function that extracts the contents of the visual range
function! GetVisualRange()
" Get the start and end positions of the current range
let StartPosition = getpos("'<")
let EndPosition = getpos("'>")
" Prefix the range with \V to disable "magic"
" See :help \V
let VisualRange = '\V'
" If the start and end of the range are on the same line
if StartPosition[1] == EndPosition[1]
" Just extract the relevant part of the line
let VisualRange .= getline(StartPosition[1])[StartPosition[2]-1:EndPosition[2]-1]
else
" Otherwise, get the end of the first line
let VisualRange .= getline(StartPosition[1])[StartPosition[2]-1:]
" Then the all of the intermediate lines
for LineNum in range(StartPosition[1]+1, EndPosition[1]-1)
let VisualRange .= '\n' . getline(LineNum)
endfor
" Then the start of the last line
let VisualRange .= '\n' . getline(EndPosition[1])[:EndPosition[2]-1]
endif
" Replace legitimate backslashes with double backslashes to prevent
" a literal \t being interpreted as a tab
let VisualRange = substitute(VisualRange, '\\[nV]\#!', '\\\\', "g")
" Return the result
return VisualRange
endfunction

The text being searched for is stored in the / register. You can't yank or delete directly into this register, but you can assign to it using `let'.
Try this:
Use visual mode to highlight the code you want to search for
Type "ay to yank that highlighted selection into register a
Type :let #/ = #a to copy register a into the search register /
At this point, all code matching your selection will be highlighted, and you can navigate through occurrences using n/N just as you would a regular search.
Of course, you can use any temporary register instead of a. And it shouldn't be too difficult to get this command sequence mapped for easy use.

Quick and dirty partial solution:
:set hlsearch
*
The hlsearch option (on by default in some vim configs, but I always turn it off) makes vim highlight all found instances of the current search. Pressing * in normal mode searches for the word under the cursor. So this will highlight all instances of the word under the cursor.

Related

How to run commands against block-wise visual mode selection?

I have a testing file with the content:
var a = f
ff
fff
Then I moved the cursor on the f character in line 1, ctrl+v selected the two f below it (■ means selection).
var a = ■
■f
■ff
I want to change the text to this:
var a = "f"
"ff"
"fff"
So I executed this command:
:normal i"ctrl+vEscA"
But the quotes were added to the whole line. Is it possible to do operations on only block-wise visual selection (not the whole line)? Note that this example was made only for discussing Vim skills. I'm not trying to solving any problems, just want to learn Vim skills.
The problem is that all :-commands can take linewise range only. Recall that after pressing : in Visual mode you got :'<,'>_ which is a hint strong enough (see :h mark-motions in case you don't remember about backtick vs. single-quote).
So you can't process visually selected text directly but have to yank it into register first.
One solution is to yank-put the text into the lines of its own. Then you can apply your command(s) and move the new text where it should be.
This is how it's done in that "vis" plugin mentioned in the comment by #phd and the linked answer. But it's so big and bloated that we'd better implement such functionality ourselves.
" apply command to a visual selection
" a:cmd - any "range" command
" a:mods - :h command-modifiers
" a:trim - true if we need to trim trailing spaces from selection text
function s:block(cmd, mods, trim) abort
" save last line no
let l:last = line('$')
" yank selected text into the register "9"
silent normal! gv"9y
" put it at buffer's end
call append(l:last, getreg(9, 1, 1))
" trim trailing spaces
if a:trim
execute 'keepj keepp' l:last..'+,$s/\s\+$//e'
endif
" apply our command
" note: we assume that the command never enters Visual mode
execute a:mods l:last..'+,$' a:cmd
" get the changed text back into the register "9"
call setreg(9, getline(l:last + 1, '$'), visualmode())
" clean up
call deletebufline('%', l:last + 1, '$')
" replace the selection
" note: the old text goes into the register "1", "1" into "2", and so on.
" old register "9" is lost anyway, see :h v_p
silent normal! gv"9p
endfunction
" our interface is a user command
command! -bang -range -nargs=1 -complete=command B call s:block(<q-args>, <q-mods>, <bang>0)
Now you can select your block (do not forget about extending it until lines' ends!), and execute:
:'<,'>B! normal! i"^CA"
Note: ^C stands for Ctrl-VCtrl-C
On possible solution:
:'<,'>norm $ciw"ctrl-v ctrl-r""
OBS: Ctrl-v Ctrl-r should be typed literally
Another solution:
:'<,'>s/\w\+$/"&"

Surround visually selected texted with x at beginning and y at end

I know I can surround visually selected text with this macro:
qa " record a macro in buffer a
x " cut the selected text
i " enter insert mode
prefix " type the desired prefix
<esc> " exit insert mode
p " paste the cut text
a " enter insert mode after the pasted text
postfix " type the desired postfix
<esc> " exit insert mode
q " stop recording
I used it to surround a few words with the prefix {{c1:: and postfix :}}. I was then able to repeat the macro with #a.
My question is, how can I permanently map this macro to a command, so that surrounding text in this way will be available to me across sessions? What would I add to .vimrc or surround.vim?
This seems to be more complicated than other related questions in that I want to surround the text with different strings at the beginning and end of the selected text, and also that the selected text will be unique - I am not wanting to surround each instance of a particular string.
I would create a function and map it to some key (F3 in my example below):
vnoremap <F3> :call Surround("prfx_", "_psfx")<Enter>
function! Surround(prefix, postfix)
" get the selection
let selection = #*
" remove selected text
normal gv"xx
" inserting text with prefix and postfix
execute "normal i" . a:prefix . selection . a:postfix
endfunction
So function Surround accepts two arguments:
1st - prefix (the default is currently set to "prfx_")
2nd - postfix (the default is set to "_psfx")
If you want to be able to enter function arguments each time you press F3 then just remove <Enter> from key mapping:
vnoremap <F3> :call Surround("prfx_", "_psfx")

How do I change the join character in Vim?

In Vim, one can join two lines by typing capital J.
However, these are usually joined by a space.
I seem to remember there was a way to change the character used for the joining by setting some variable, but I can't seem to find it again.
I'd appreciate it if anyone could remind me, or confirm that it can't be done.
When I want to join just a few lines I use a 3 keys combo (normal mode):
Jr,
being , the joining character.
In case I want to join more lines or even join lines in groups, I use the previous combo with a macro.
For example, to transform 3 lines in a 3 columns CSV table, I record this macro (assigned to letter j of course):
qjJr,Jr,jq
So, using #j joins 3 lines using , and goes to the next line.
10#j converts 10 lines.
There isn't a setting that allows you to do this directly, see:
:help J
in particular, the text below the list of commands.
A couple of ways you could do this:
:nnoremap J gJi.<ESC>
" or
let joinchar = ';'
nnoremap J :s/\n/\=joinchar/<CR>
The latter option allows you to change it on the fly by changing the joinchar option.
Try something like this in your .vimrc:
nnoremap Y Jxi*<Esc>
It'll remap Y to join the lines with a *.
From http://vim.wikia.com/wiki/Remap_join_to_merge_comment_lines
put this in your .vimrc:
function! JoinWithLeader(count, leaderText)
let l:linecount = a:count
" default number of lines to join is 2
if l:linecount < 2
let l:linecount = 2
endif
echo l:linecount . " lines joined"
" clear errmsg so we can determine if the search fails
let v:errmsg = ''
" save off the search register to restore it later because we will clobber
" it with a substitute command
let l:savsearch = #/
while l:linecount > 1
" do a J for each line (no mappings)
normal! J
" remove the comment leader from the current cursor position
silent! execute 'substitute/\%#\s*\%('.a:leaderText.'\)\s*/ /'
" check v:errmsg for status of the substitute command
if v:errmsg=~'Pattern not found'
" just means the line wasn't a comment - do nothing
elseif v:errmsg!=''
echo "Problem with leader pattern for JoinWithLeader()!"
else
" a successful substitute will move the cursor to line beginning,
" so move it back
normal! ``
endif
let l:linecount = l:linecount - 1
endwhile
" restore the #/ register
let #/ = l:savsearch
endfunction
nnoremap <space> :<C-U>call JoinWithLeader(v:count, '"')<CR>
This also allows you to remap J to something else.
It will quicker if you replace the end of line with a comma (or join character)
:%s/$/,
and then joining multiple lines either by providing a range, or by selecting lines in visual mode and using the join command
10J
It's mapping. You can read the tutorial in vim wikia :
Mapping keys in vim
Try the command below in command mode, and try to press . This should work :)
:map <space> J

Escape characters during paste in vim

I copy stuff from output buffers into C++ code I'm working on in vim.
Often this output gets stuck into strings. And it'd be nice to be able to escape all the control characters automatically rather than going back and hand editing the pasted fragment.
As an example I might copy something like this:
error in file "foo.dat"
And need to put it into something like this
std::string expected_error = "error in file \"foo.dat\""
I'm thinking it might be possible to apply a replace function to the body of the last paste using the start and end marks of the last paste, but I'm not sure how to make it fly.
UPDATE:
Joey Mazzarelli sugested using
`[v`]h:%s/\%V"/\\"/g
after a paste.
Since no explaination was given for what that was going and I initially found it a bit terse, but hard to explain in the comments I thought I'd put an explaination of what I think that does here:
`[ : Move to start of last paste
v : Start visual mode
`] : Move to end of last paste
h : adjust cursor one position left
:% : apply on the lines of the selection
s/ : replace
\%V : within the visual area
" : "
/ : with
\\" : \"
/g : all occurrences
This seems like a good approach, but only handles the one character, ", I'd like it to handle newlines, tabs, and other things that might be expected to fall in text. (Probably not general unicode though) I understand that may not have been clear in the problem definition.
Here are a couple of vimscript functions that should do what you want.
EscapeText() transforms arbitrary text to the C-escaped equivalent. It converts newline to \n, tab to \t, Control+G to \a, etc., and generates octal escapes (like \o032) for special characters that don't have a friendly name.
PasteEscapedRegister() escapes the contents of the register named by v:register, then inserts it into the current buffer. (The register is restored when the function returns, so the function can be called repeatedly without escaping the register contents multiple times.)
There are also a couple of key mappings included to make PasteEscapedRegister() easy to use interactively. <Leader>P pastes escaped register contents before the cursor position, and <Leader>p pastes after. Both can be prefixed with a register specification, like "a\P to paste the escaped contents of register a.
Here's the code:
function! EscapeText(text)
let l:escaped_text = a:text
" Map characters to named C backslash escapes. Normally, single-quoted
" strings don't require double-backslashing, but these are necessary
" to make the substitute() call below work properly.
"
let l:charmap = {
\ '"' : '\\"',
\ "'" : '\\''',
\ "\n" : '\\n',
\ "\r" : '\\r',
\ "\b" : '\\b',
\ "\t" : '\\t',
\ "\x07" : '\\a',
\ "\x0B" : '\\v',
\ "\f" : '\\f',
\ }
" Escape any existing backslashes in the text first, before
" generating new ones. (Vim dictionaries iterate in arbitrary order,
" so this step can't be combined with the items() loop below.)
"
let l:escaped_text = substitute(l:escaped_text, "\\", '\\\', 'g')
" Replace actual returns, newlines, tabs, etc., with their escaped
" representations.
"
for [original, escaped] in items(charmap)
let l:escaped_text = substitute(l:escaped_text, original, escaped, 'g')
endfor
" Replace any other character that isn't a letter, number,
" punctuation, or space with a 3-digit octal escape sequence. (Octal
" is used instead of hex, since octal escapes terminate after 3
" digits. C allows hex escapes of any length, so it's possible for
" them to run up against subsequent characters that might be valid
" hex digits.)
"
let l:escaped_text = substitute(l:escaped_text,
\ '\([^[:alnum:][:punct:] ]\)',
\ '\="\\o" . printf("%03o",char2nr(submatch(1)))',
\ 'g')
return l:escaped_text
endfunction
function! PasteEscapedRegister(where)
" Remember current register name, contents, and type,
" so they can be restored once we're done.
"
let l:save_reg_name = v:register
let l:save_reg_contents = getreg(l:save_reg_name, 1)
let l:save_reg_type = getregtype(l:save_reg_name)
echo "register: [" . l:save_reg_name . "] type: [" . l:save_reg_type . "]"
" Replace the contents of the register with the escaped text, and set the
" type to characterwise (so pasting into an existing double-quoted string,
" for example, will work as expected).
"
call setreg(l:save_reg_name, EscapeText(getreg(l:save_reg_name)), "c")
" Build the appropriate normal-mode paste command.
"
let l:cmd = 'normal "' . l:save_reg_name . (a:where == "before" ? "P" : "p")
" Insert the escaped register contents.
"
exec l:cmd
" Restore the register to its original value and type.
"
call setreg(l:save_reg_name, l:save_reg_contents, l:save_reg_type)
endfunction
" Define keymaps to paste escaped text before or after the cursor.
"
nmap <Leader>P :call PasteEscapedRegister("before")<cr>
nmap <Leader>p :call PasteEscapedRegister("after")<cr>
This might at least get you started...
After pasting it in:
`[v`]h:%s/\%V"/\\"/g
You can obviously map that to something easier to type.
While Joeys solution looks like it might be extensible to cover all the cases that I need, I thought I'd share my partial solution using vims python integration (Since I'm more familiar at python than vim script)
# FILE : tocstring.py
import vim
def setRegister(reg, value):
vim.command( "let #%s='%s'" % (reg, value.replace("'","''") ) )
def getRegister(reg):
return vim.eval("#%s" % reg )
def transformChar( map, c):
if c in map:
return map[c]
return c
def transformText( map, text ):
return ''.join( [ transformChar(map,c) for c in text ] )
cmap={}
cmap["\\"]="\\\\"
cmap["\n"]="\\n"
cmap["\t"]=r"\\t"
cmap['"']="\\\""
def convertToCString( inregister, outregister ):
setRegister(outregister, transformText( cmap, getRegister(inregister) ) )
Then in my .vimrc or other conf file I can put
# FILE cpp.vim
python import tocstring
# C-Escape and paste the currently yanked content
nmap <Leader>P :python tocstring.convertToCString("#","z")<CR>"zP
# C-Escape and paste the current visual selection
vmap <Leader>P "zd:python tocstring.convertToCString("z","z")<CR>"zP
It would be nice if I could the first function to work so that "a\P pasted the transformed contents of the "a" register, and I assume this is doable using v:register somehow, but it escapes me.
A version of this that works in the same way as Joeys solution could be crafted as
nmap <Leader>P `[v`]"zd:python tocstring.convertToCString("z","z")<CR>"zP
Acknowledgement : This uses code from Can you access registers from python functions in vim for interacting with registers from vims python
for Java/JavaScript type of escaping one can use json_encode
nmap <leader>jp :call setreg('e', json_encode(#+))\| normal "ep<CR>
json_encode(#+) - json encode content of register + (mapped to clipboard)
setreg('e',...) - write it to register e
normal "ep - paste content of register e

Vim search in C/C++ code lines

Is there any way to search a string in a C/C++ source file while skipping commented lines?
This is an intriguing question.
I think #sixtyfootersdude has the right idea -- let Vim's syntax highlighting tell you what's a comment and what's not, and then search for matches within the non-comments.
Let's start with a function that mimics Vim's built-in search() routine, but also provides a "skip" parameter to let it ignore some matches:
function! SearchWithSkip(pattern, flags, stopline, timeout, skip)
"
" Returns true if a match is found for {pattern}, but ignores matches
" where {skip} evaluates to false. This allows you to do nifty things
" like, say, only matching outside comments, only on odd-numbered lines,
" or whatever else you like.
"
" Mimics the built-in search() function, but adds a {skip} expression
" like that available in searchpair() and searchpairpos().
" (See the Vim help on search() for details of the other parameters.)
"
" Note the current position, so that if there are no unskipped
" matches, the cursor can be restored to this location.
"
let l:matchpos = getpos('.')
" Loop as long as {pattern} continues to be found.
"
while search(a:pattern, a:flags, a:stopline, a:timeout) > 0
" If {skip} is true, ignore this match and continue searching.
"
if eval(a:skip)
continue
endif
" If we get here, {pattern} was found and {skip} is false,
" so this is a match we don't want to ignore. Update the
" match position and stop searching.
"
let l:matchpos = getpos('.')
break
endwhile
" Jump to the position of the unskipped match, or to the original
" position if there wasn't one.
"
call setpos('.', l:matchpos)
endfunction
Here are a couple of functions that build on SearchWithSkip() to implement syntax-sensitive searches:
function! SearchOutside(synName, pattern)
"
" Searches for the specified pattern, but skips matches that
" exist within the specified syntax region.
"
call SearchWithSkip(a:pattern, '', '', '',
\ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "' . a:synName . '"' )
endfunction
function! SearchInside(synName, pattern)
"
" Searches for the specified pattern, but skips matches that don't
" exist within the specified syntax region.
"
call SearchWithSkip(a:pattern, '', '', '',
\ 'synIDattr(synID(line("."), col("."), 0), "name") !~? "' . a:synName . '"' )
endfunction
Here are commands that make the syntax-sensitive search functions easier to use:
command! -nargs=+ -complete=command SearchOutside call SearchOutside(<f-args>)
command! -nargs=+ -complete=command SearchInside call SearchInside(<f-args>)
That was a long way to go, but now we can do stuff like this:
:SearchInside String hello
That searches for hello, but only within text that Vim considers a string.
And (finally!) this searches for double everywhere except comments:
:SearchOutside Comment double
To repeat a search, use the #: macro to execute the same command repeatedly, like pressing n to repeat a search.
(Thanks for asking this question, by the way. Now that I've built these routines, I expect to use them a lot.)
This pattern searches for a string that is not preceded by the two C++ commenting conventions. I've also excluded '*' as the first non-whitespace character, as that's a common convention for multi-line comments.
/\(\(\/\*\|\/\/\|^\s*\*[^/]\).*\)\#<!foo
Only the first and fourth foo are matched.
foo
/* foo
* baz foo
*/ foo
// bar baz foo
Putting \v at the beginning of the pattern eliminates a bunch of backslashes:
/\v((\/\*|\/\/|^\s*\*[^/]).*)#<!foo
You can bind a hotkey to this pattern by putting this in your .vimrc
"ctrl+s to search uncommented code
noremap <c-s> <c-o>/\v((\/\*\|\/\/\|^\s*\*[^/]).*)#<!
Not sure if this is helpful but when you type :syn it has all the formatting that is used in your file type. Maybe you can refer to that somehow. You could say something like:
map n betterN
function betterN{
n keystroke
while currentLine matches comment class
do another n keystroke
}
Here is how I would proceed:
Delete all C/C++ comments (using the replace command %s)
Proceed to the search using regular search command /
Set a mark at the position using m a (to set the mark "a")
Undo the deletion of the comments using u
Jump to the mark "a" using ``a`
Eventually deleting the mark using delm a (it would be overwritten in the case you don't delete it, so no big deal)
Of course you can do that in one big operation/function. I do not master Vim scripting good enough to give an example of that though.
I admit my solution is a bit "lazy" (and you can probably do it way better) but that's all I came to.
Hope it helps.

Resources