I am trying to search and replace only in non-commented line.
By non commented line, I mean lines that are not greyed out by the vim syntax highlighting.
For example:
/*
...
DO NOT REPLACE
....
*/
Should not be searched and replaced.
My problem is specific to C++ but could be expanded to any language if there is a way to use the "syntax engine" of vim.
Edit : The link is only about searching, nothing about replacing.
I've finally done it myself using Bill Odom code as a base :
function Repl(pattern, patternSub)
" Move to top of the file
normal 1G
" Search Pattern, no wrap
while search(a:pattern, "W", "", "") > 0
" If found pattern is a comment, skip
if eval(synIDattr(synID(line("."), col("."), 0), "name") =~? "Comment")
continue
endif
" If found pattern is a string, skip
if eval(synIDattr(synID(line("."), col("."), 0), "name") =~? "String")
continue
endif
" Replace pattern by it's substitute
exec '.s/'.a:pattern.'/'.a:patternSub.'/'
" Restore cursor position
normal ``
endwhile
endfunction
command! -nargs=+ -complete=command Repl call Repl(<args>)
It does not replace any occurence of pattern which is a Comment or a String.
It actually is language independent.
You have to use it this way ( eg: removing space after all non-commented parenthesis ):
:Repl '( *','('
Related
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\+$/"&"
Often times when reviewing log files in vim, I'll highlight interesting lines using marks. At some point, I'd like to be able to copy all of the interesting lines (either all marked lines, or a list of marks) to either a register or another file (it doesn't really matter which; the goal is to facilitate writing a summary). I haven't been able to find any built in way to do this; is it possible in vim?
I suppose it's probably a fairly straightforward function; probably looking something like this, but my vimscript abilities are very weak:
for cur_mark in list_of_marks
goto mark
yank current line and append to register
Has anyone ever written anything similar that they can point me to?
Thanks
EDIT: I posted the accepted solution at https://github.com/mikeage/vim-yankmarks
As always, there are few things that are more motivating than asking for help. Here's what I came up with; feedback welcome.
function! Yankmark()
let save_cursor = getpos(".")
let n = 0
" I should really make this a parameter...
let marks_to_yank="abcdefghijklmnopqrstuvwxyz"
let nummarks = strlen(marks_to_yank)
" Clear the a register
let #a=''
while n < nummarks
let c = strpart(marks_to_yank, n, 1)
" Is the mark defined
if getpos("'".c)[2] != 0
" using g' instead of ' doesn't mess with the jumplist
exec "normal g'".c
normal "Ayy
endif
let n = n + 1
endwhile
call setpos('.', save_cursor)
endfunction
Mikeage had a great idea; here's a more refined version of his function turned into a command:
":YankMarks [{marks}] [{register}]
" Yank all marked (with [a-z] / {marks} marks) lines into
" the default register / {register} (in the order of the
" marks).
function! s:YankMarks( ... )
let l:marks = 'abcdefghijklmnopqrstuvwxyz'
let l:register = '"'
if a:0 > 2
echohl ErrorMsg
echomsg 'Too many arguments'
echohl None
return
elseif a:0 == 2
let l:marks = a:1
let l:register = a:2
elseif a:0 == 1
if len(a:1) == 1
let l:register = a:1
else
let l:marks = a:1
endif
endif
let l:lines = ''
let l:yankedMarks = ''
for l:mark in split(l:marks, '\zs')
let l:lnum = line("'" . l:mark)
if l:lnum > 0
let l:yankedMarks .= l:mark
let l:lines .= getline(l:lnum) . "\n"
endif
endfor
call setreg(l:register, l:lines, 'V')
echomsg printf('Yanked %d line%s from mark%s %s',
\ len(l:yankedMarks),
\ len(l:yankedMarks) == 1 ? '' : 's',
\ len(l:yankedMarks) == 1 ? '' : 's',
\ l:yankedMarks
\) . (l:register ==# '"' ? '' : ' into register ' . l:register)
endfunction
command! -bar -nargs=* YankMarks call <SID>YankMarks(<f-args>)
A different way of accomplishing this might be using the :global command. The global command takes the form :g/{pattern}/{cmd}. The command, {cmd}, will be executed on all lines matching {pattern}.
Append lines matching a pattern to a register:
:g/pattern/yank A
Append matching line to a log file:
:g/pattern/w >> file.log
Of course if you want to find line matching a mark you can match it in your pattern. The following pattern matches a line with mark m.
:g/\%'m/w >> file.log
To do something like this. (Note: I am using \v to turn on very magic)
:g/\v(%'a|%'b|%'m)/yank A
Of course if a pattern won't work you can do this by hand. Instead of marking the lines just build up the lines as you go. Just yank a line to an uppercase register to append.
"Ayy
Or do a write append with a range of a single line
:.w >> file.log
For more help see
:h :g
:h :w_a
:h /\%'m
:h /\v
You can do something like:
:redir #a
:silent marks XYZN
:redir END
"ap
That way the output of the :marks command will be redirected to the a register. Note, that it will only lists (in the above case) the X, Y, Z and N marks (as the arguments), and if there was an a register, it will be deleted/overwritten.
Also note, that it might not give the desired output, but gives you a starting point...
I like the solution from Mikeage, though I would probably solve this with the multiselect - Create multiple selections and operate plugin. This also has the benefit that you don't run out of marks.
With the plugin, you can select lines with <Leader>msa or :MSAdd. Finally, yank all lines with:
:let #a=''
:MSExecCmd yank A
If you use an upper-case register name when yanking into a specific register, Vim will append the yanked content instead of overwriting the register's value.
So, for example:
"ayy - yank current line to register a, overwriting
[move]
"Ayy - append this line to register a
[move]
"ap - paste all yanked material
See :help quotea for more details.
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
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.
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.