VIM command or expression to select text from a function - vim

In my .vimrc file, I would like to configure a minimalist function to perform a smart selection.
For example try to select inner (), if selection is empty try inner [], if still empty try inner {}, etc.
But I am stuck at the very beginning: to call / execute a command / expression to select text from a function.
function! SelectInnerBrackets():
" failed attempts
call visualmode()
execute "vi("
execute "visual! i("
endfunction
Fyi: I actually use neovim, but it probably does not make difference to this issue.
EDIT: based on solution proposed by #Ingo Karkat, I share my final piece of code. Note that it does not work perfectly with cross nested delimiters.
function! SelectInner(delimiter)
" We have to switch to normal mode to compare positions
execute "normal! vi".a:delimiter."\<C-\>\<C-n>"
return getpos("'<") != getpos("'>")
endfunction
function! TrySelectInner(delimiters)
for delimiter in a:delimiters
if SelectInner(delimiter)
normal! gv
break
endif
endfor
endfunction
" quickly select a word, expression or brackets content
nnoremap W viw
nnoremap E :call TrySelectInner(["'", '"'])<CR>
nnoremap R :call TrySelectInner(['(', '[', '{'])<CR>

If you read :help visualmode(), you'll notice that the (non-argument version of the) function is a query that has no side effects. Just :calling doesn't do any good, and you don't need the current / previous visual mode, as you build the selection yourself.
Commands like vi( are normal mode commands. To invoke them from a Vimscript function, you need the :normal! command. :execute is used on Ex commands, in order to interpolate variable values (this is called eval() in many other languages), or use special :help key-notation (we'll use that later).
In order to test whether a selection was made, Vim conveniently has two special marks ('< and '>) that specify the boundaries of the selection. Unfortunately, they are only set after visual mode has been left (by operating on it, or via <Esc>). Within a plugin, it's better to use <C-\><C-n> keys instead of <Esc>; it will return to normal mode, too, but doesn't beep if we're already in normal mode. I use a separate :normal! command for that (with :execute to use the special key notation) to ensure that it will also execute when the previous command sequence aborts because no such selection can be made.
Taken together, here's the corrected version of your attempt:
function! SelectInnerBrackets()
echomsg "trying (...)"
normal! vi(
execute "normal! \<C-\>\<C-n>"
if getpos("'<") != getpos("'>") | return 1 | endif
echomsg "trying [...]"
normal! vi[
execute "normal! \<C-\>\<C-n>"
if getpos("'<") != getpos("'>") | return 1 | endif
echomsg "trying {...}"
normal! vi{
execute "normal! \<C-\>\<C-n>"
if getpos("'<") != getpos("'>") | return 1 | endif
echomsg "nothing found"
return 0
endfunction
In order to re-select, you can use gv afterwards, e.g. via this mapping:
nnoremap <Leader>V :if SelectInnerBrackets() <Bar> execute "normal! gv" <Bar> endif<CR>

Related

count of command get lost when do key mapping in vim

I have a key mapping for p in my vimrc file as below:
noremap p <ESC>:set paste<CR>p:set nopaste<CR>
The purpose of this key mappinng is to make sure the content from outside of vim can be pasted in its original format in paste mode. And restore it to nopaste mode after paste complete.
But when I run the following commands:
yy
5p
p is only executed once other than 5 times.
It looks like the count is missed from the key mapping.
Any way to pass the count in the key mapping?
Or how to fix this problem?
The fisrt <ESC> drops the count so it is no available later. Instead of <ESC> we would ignore and save count with <c-u> and access it later with v:count1 variable like :<c-u>set paste <CR>... v:count1 ....
But there is another problem: count will be lost after the first <CR>, so we want to rewrite the mapping as a single command. Chaining of commands may be done with |, but in mappings we should write <BAR> instead of |.
Here is the final mapping:
:noremap p :<c-u>set paste <BAR> :exe "normal! " . v:count1 . "p" <BAR> :set nopaste<CR>
:exe "normal! " is a fancy way to execute a command from evaluated string.

Programmatically equalize windows inside a function

I've got the following vimscript function:
function! WindowCommand(cmd)
execute a:cmd
if !g:golden_ratio_enabled
normal <C-w>=
endif
endfunction
And I use it like so:
map <space>w/ :call WindowCommand(':vs')<cr>
It's supposed to equalize the windows, but only if g:golden_ratio_enabled is 0, otherwise it should do nothing.
It doesn't work, though, and I'm not sure why, because the following DOES work:
map <space>w/ :vs<cr><C-w>=
What am I doing wrong here?
There are a couple fixes. Thankfully, the fix is really simple
For whatever reason, normal <C-w>foo does not work; You must use wincmd instead. From :h wincmd
*:winc* *:wincmd*
These commands can also be executed with ":wincmd":
:[count]winc[md] {arg}
Like executing CTRL-W [count] {arg}. Example: >
:wincmd j
Moves to the window below the current one.
This command is useful when a Normal mode cannot be used (for
the |CursorHold| autocommand event). Or when a Normal mode
command is inconvenient.
The count can also be a window number. Example: >
:exe nr . "wincmd w"
This goes to window "nr".
So in this case, you should do
wincmd =
Alternatively, you could enter a literal <C-w> character, by typing <C-v><C-w>. In your vim session, this character will be displayed as ^W.
To execute actions with <notation>, use instead:
:exe "normal \<notation>"
I use it a lot to debug mappings.
But in this case, prefer indeed wincmd.

A non-executing version of :execute

Is there a command or function like :execute that allows you to send text to the Vim cmdline but that doesn't actually execute it? I want to have the cursor left at the end of the cmdline. I've been going through the help docs for half an hour, can't find anything.
It depends on how that gets triggered. If it's through a mapping, you can simply use :help :map-expr:
fun! MyText()
return (localtime() % 2 ? ':echo ' : ':echomsg ')
endfun
:nnoremap <expr> <Leader>e MyText()
Else (i.e. through a custom command or by :autocmd), you have to use the feedkeys() function to insert the typed characters into Vim's input event loop:
:command! MyText call feedkeys(MyText(), 'n')
You cannot just use :normal, because that assumes a full command, and will abort if it's incomplete.

Vim script: run an execute when mapping?

So I'm not sure how to go about running some some code in my mappings, like:
nnoremap <Return> :execute "normal! if 1 echo('one') endif"<cr>
Also tried it without the 'normal' - tried different combinations with separating the commands via '\' and '|' but nothing worked - keep getting variable not defined errors.
Any idea how?
EDIT:
So here's what I'm actually doing:
" Quickly toggle between insert/normal modes
nnoremap <A-e> i
inoremap <silent><A-e> <esc>:call GoRightIfNotBOL()<cr>
" Returns 1 if the cursor is at the beginning of a line "
function! IsBOL()
return col('.') == 1
endfu
function! GoRightIfNotBOL()
if !IsBOL()
execute "normal l"
endif
endfu
So instead of calling GoRightIfNotBOL I thought I could inline its code cause really, I can't think of another location where I would be using this function, and it's pretty small.
you are looking for <expr> mapping
read :h <expr> there you'll find examples.
If your codes were a bit long, put them in a function, and call that function in your mapping. It is more readable if you later want to do some change on it.
An example with inoremap <expr>:
inoremap <expr> <YourKeys> "<esc>".(col('.')>1?'l':'')

Vim script: check if the current word is/isn't a C/C++ keyword

I'm working on a small vim plugin which should highlight current word occurrences after cursor idle.
I have a highlight part, and what I need is to check if the current word is not a C/C++ keyword.
You can do that depending on current syntax highlight script. Say, this function returns if your cursor is on some access modifier (public, protected, etc), or some C++ type (say, bool, etc) :
function! IsCppAccessOrType()
return match(synIDattr(synID(line("."), col("."), 1), "name"), '\v\CcppAccess|cppType') >= 0
endfunction
But, again, this will completely depend on current syntax script. Example above depends on standard syntax/cpp.vim provided with Vim 7.3 .
By the way, there is very useful trick to deal with syntax highlight stuff:
" Show syntax highlighting groups for word under cursor
nnoremap <silent> <F10> :call <SID>SynStack()<CR>
function! <SID>SynStack()
if !exists("*synstack")
return
endif
echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
endfunc
Add this to your vimrc, and when you press F10, stack of syntax items will be echoed ( see :help synstack for more details ).
I have found this trick somewhere on http://vimbits.com .

Resources