What is wrong with :s##// in vimrc? - vim

I just want a convenient note function:
map <silent> <nowait> <M-n> ^:call Note()<CR>
func! Note()
if &filetype == 'cpp'
if getline(".")[col(".") - 1] == '/'
execute 'normal! xx'
else
execute 's##//'
endif
elseif &filetype == 'vim'
if getline(".")[col(".") - 1] == '"'
silent :s#^"#
else
silent :s#^#"
endif
endif
endfunc
Vim said:
E488: Trailing characters
What is wrong with my code?

Lets expand this a bit for clarity:
:s##//#
An empty pattern in a substitution means to use the last used pattern, "/. If that pattern does not exist then you get an E488 error. I think it is more likely that you wanted your pattern to be ^ or .
Some more thoughts:
As a general rule it is best to supply a mode when making mappings
Use *noremap variants if able
For maintainability it can be helpful to use full names instead of shortened commands. e.g. function vs func
This handles comments for 2 languages, so it becomes brittle to expand
Consider using 'commentstring'
Should probably thing about a visual mappings and/or creating an operator
:s will set the last used search pattern which could be surprising if the search pattern is not restored
Commenting is a common pattern yet surprisingly tricky to get right. Best to stand the shoulders of giants and use a plugin. If you want a quick-and-dirty commenting script you can use this gist as a jumping off point

Related

Remove specific char in the beginning of the line if it present in vim

Just another vim source code comment question here. I have this mapping for my python source code files:
map <C-C> <Home>i#<Esc>
imap <C-C> <Home>#<Esc> i
On Ctrl-C it puts # in the beginning if the line to comment it out. This improves productivity a lot. But when I want to uncomment lines, I have to do this manually, meaning going to the first character of each commented line and remove it. This is very annoying. At the first glance, I can just bind Home-x to some key, but I can occasionally remove an innocent space or something else in case I misshit and do this on line that has no # character at the beginning of it. I first try to do some replacement with :%s// for a single line, but that has an unwanted affect - it triggers a search and highlights 'pattern' in other lines. In case of a single # character it is a fail.
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
I have created a simple function to Toggle comment in the line:
function! ToggleComment()
let l:pos = col('.')
if getline('.') =~ '\v(\s+|\t+)?#'
exec 'normal! _"_x'
let l:pos -= 1
else
exec 'normal! I#'
let l:pos += 1
endif
call cursor(line("."), l:pos)
endfunction
nnoremap <Leader>t :call ToggleComment()<CR>
inoremap <Leader>t <C-o>:call ToggleComment()<CR>
I recommend Tim Pope's plugin vim-commentary because is way more complete. But of course our idea gives you guys a glimpse how far we can get with vimscript.
Another approach, which does not need to save windowview and toggles comments in other languages can be seen here
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
A solution would be (assuming your cursor is anywhere to the right of # when using the map):
map <c-c> mmF#x`m
A more general solution would be to use a substitution and histdel() to delete the last search pattern:
function! DelComment()
s/^\( *\)#/\1/
call histdel("search", -1)
let #/ = histget("search", -1)
endfunction
After executing the function (by selecting it and typing :#") you can map it to <c-c>:
map <silent> <c-c> mm:silent! call DelComment()<cr>`m
I like using marks around functions to retain the cursor position after executing the map. Feel free to remove mm and `m in the above map.

Highlight all occurence of the selection

i'm creating a little function in order to highlight same term as you are currently selected in Visual mode.
that's my current work :
function CheckSameTerm()
let currentmode = mode()
" Check for (any) visual mode
if currentmode =~? '.*v'
exec printf('match IncSearch /\V\<%s\>/', escape(expand('<cword>'), '/\'))
"this method is for selection only and not full word(but don't really work)
"let select = getline("'<")[getpos("'<")[2]-1:getpos("'>")[2]-1]
"exec printf('match IncSearch /\V\<%s\>/', select)
endif
endfunction
au CursorMoved * exec "call CheckSameTerm()"
For now i'm quite stuck with 2 issues :
the code actually check full word > escape(expand(''), '/\') because I can't get the exact selected text.
I have to find a way to delete highlight on visual leave (but no event exist for that)
So have you some ideas for that ? And most important question : Do I go on the good way ? Maybe I miss something easier.
oh and by the way, this is to learn vim script, it is not life saver ;)
Edit
This plugin evolved, go check https://github.com/Losams/-VIM-Plugins/blob/master/checkSameTerm.vim
You're on the right way. This is far beyond trivial Vim customization, though, and to turn this into a robust plugin, a lot more work is required.
To address your questions:
The '<,'> marks unfortunately only contain the current selection once you've left visual mode (explicitly or after operating on it). Your code for extracting the text is fine, but the marks aren't set yet. Instead, you can yank the current selection and then restore it with gv. This clobbers the default register; additional code to save / restore its contents would be needed. (See ingo#selection#Get() from my ingo-library plugin for a robust implementation.)
As the visual selection is not necessarily delimited by keyword boundaries, you need to drop the \< ... \> from the pattern.
To handle selections across lines, embedded newline characters need to be converted into the \n atom. I do this with substitute() after escaping the special characters in the selection.
mode() returns v, V, or ^V for visual mode. No need for the .* and pattern matching; ^V also is a single character (and we cannot handle blockwise selections in a good way, anyway, so it's best to ignore it).
To turn off highlighting, just add an else block to the conditional. It needs an additional cursor move to apply, though.
You don't need :execute in the :autocmd; there are no variables here that need to be interpolated.
Here's what I have:
function! CheckSameTerm()
let currentmode = mode()
" Check for (any) visual mode
if currentmode ==? 'v'
normal! ygv
exec printf('match IncSearch /\V%s/', substitute(escape(#", '/\'), '\n', '\\n', 'g'))
else
match none
endif
endfunction
au! CursorMoved * call CheckSameTerm()
I hope this helps you with a further exploration of Vim scripting. Have fun!

don't stop mapping on not found

There is a important part of vim mapping:
:map <C-j>r f{^
I noticed, that when the { character is not found in the line, then the rest of the mapping is not executed.
Is there a way, how to force the mapping to continue even though the search character is not found? (In this case execute the return to the beginning of the file)
Yes, Vim aborts on an error in a mapping. You can use :silent! normal! ... to continue regardless of errors. With a sequence of :normal! commands, you can even check (e.g. whether the cursor moved) and react to it. Sketch:
:map <C-j>r :exe 'normal! maf{'<Bar>if getpos("'a") == getpos(".")<Bar>echo "no move"<Bar>endif<CR>
Note that this doesn't scale well. You're better of moving the commands into a :function.
Also, you should probably use :noremap; it makes the mapping immune to remapping and recursion.
The literal answer to this question would be:
:map <C-j>r :silent! exe "normal f{^"<CR>

Replacing a motion in operator mode

I'm trying to alter the ge motion's functionality when it's used with an operator. Instead of it operating on the last character of the target word, I'd like it to work exclusively only until the target word's last character similarly to w.
Example:
foo bar
^
Should lead to
fooar instead of foar when I use dge with the cursor at ^
I don't know anything about Vimscript and I'm pretty much relying on exe now. This quick attempt I wrote seems to be full of errors. The variables don't seem to be working right at all.
My leading idea is to escape when ge is typed in operator mode and call the function with hopefully the right arguments. Then set a mark on the starting position, move to the left one column (to ensure that it would work with repeated movements, even though this is operator mode only), move the set amount of ges and then move one column right (my main goal here) and delete till the set mark.
If anybody could point out the errors I've made, it would be greatly appreciated!
function! FixGE(count, operator)
exe 'normal m['
exe 'normal h'
exe count.'normal ge'
exe 'normal l'
exe operator.'normal `['
endfunction
onoremap ge <esc>:call FixGE(v:prevcount, v:operator)<cr>
This is a little closer (untested):
function! FixGE(count, operator)
exe 'normal m['
exe 'normal h'
exe 'normal ' . a:count . 'ge'
exe 'normal l'
exe 'normal ' . a:operator . '`['
endfunction
onoremap ge <esc>:call FixGE(v:prevcount, v:operator)<cr>
Function arguments have to use the :a prefix (or sigil, I think). I know that count is a reserved variable: :let count = 7 gives an error. I think it is OK to use it as a function argument, though.
I have also put :normal at the beginning of two lines, instead of in the middle.
Your example could be solved without changing the ge motion, by using the f and t operators:
dTo
dF (d + F + <space> )
If you still feels that you need to override the default behavior of ge you shoul check Ingo Karkat's plugin, CountJump : Create custom motions and text objects via repeated jumps:
DESCRIPTION
Though it is not difficult to write a custom movement (basically a :map
that executes some kind of search or jump) and a custom text-object (an
:omap that selects a range of text), this is too complex for a novice user
and often repetitive.

Vim inline remap to check first character

I am trying to do a comment remap in Vim with an inline if to check if it's already commented or not. This is what I have already and of course it's not working ha ha:
imap <c-c> <Esc>^:if getline(".")[col(".")-1] == '/' i<Delete><Delete> else i// endif
What I want to do is check the first character if it's a / or not. If it's a / then delete the first two characters on that line, if it's not a / then add two // in front of the line.
What I had originally was this:
imap <c-c> <Esc>^i//
And that worked perfectly, but what I want is to be able to comment/uncomment at a whim.
I completely agree with #Peter Rincker's answer warning against doing this in insert mode, and pointing you to fully-featured plugins.
However, I couldn't resist writing this function to do precisely what you ask for. I find it easier to deal with this kind of mapping with functions. As an added bonus, it returns you to insert mode in the same position on the line as you started (which has been shifted by inserting or deleting the characters).
function! ToggleComment()
let pos=getpos(".")
let win=winsaveview()
if getline(".") =~ '\s*\/\/'
normal! ^2x
let pos[2]-=1
else
normal! ^i//
let pos[2]+=3
endif
call winrestview(win)
call setpos(".",pos)
startinsert
endfunction
inoremap <c-c> <Esc>:call ToggleComment()<CR>
Notice the modifications to pos to ensure the cursor is returned to the correct column. The command startinsert is useful in this type of function to return to insert mode. It is always safer to use noremap for mappings, unless there is a very good reason not to.
This seems to work well, but it is not very Vim-like, and you might find other plugins more flexible in the long run.
There are many commenting plugins for vim:
commentary.vim
tComment
EnhCommentify
NERD Commenter
and many more at www.vim.org
I would highly suggest you take a look at some these plugins first before you decide to roll your own. It will save you great effort.
As a side note you typically would want to comment/uncomment in normal mode not insert mode. This is not only the vim way, but will also provide a nicer undo history.
If you are dead set on creating your own mappings I suggest you create a function to do all the hard work and have your mapping call that function via :call. If you think you can get by with simple logic that doesn't need a function then you can use an expression mapping (see :h map-<expr>). You may want organize into a plugin as it could be large. If that is the case look at :h write-plugin to give you a feel
for writing plugins the proper way.
Example of a simple expression mapping for toggling comments:
nnoremap <expr> <leader>c getline(".") =~ '\m^\s*\/\/' ? '^"_2x' : 'I//<esc>`['
there's also this vimtip! http://vim.wikia.com/wiki/Comment/UnComment_visually_selected_text
i use the bottom one with the
...
noremap <silent> ,c :<C-B>sil <C-E>s/^/<C-R>=escape(b:comment_leader,'\/')<CR>/<CR>:noh<CR>
noremap <silent> ,u :<C-B>sil <C-E>s/^\V<C-R>=escape(b:comment_leader,'\/')<CR>//e<CR>:noh<CR>
,c comments out a region
,u uncomments a region

Resources