Improve TwiddleCase function to work in visual block mode - vim

The following snippet can be found on the vim wiki:
function! TwiddleCase(str)
if a:str ==# toupper(a:str)
let result = tolower(a:str)
elseif a:str ==# tolower(a:str)
let result = substitute(a:str,'\(\<\w\+\>\)', '\u\1', 'g')
else
let result = toupper(a:str)
endif
return result
endfunction
vnoremap ~ ygv"=TwiddleCase(#")<CR>Pgv
This, however, fails in visual block mode. Given the following
input:
foo alpha
bar beta
Having the cursor positioned on the f of foo and pressing
ctrl-v je~ yields the following result.
Foo
Bar alpha
beta
instead of the expected
Foo alpha
Bar beta
How can this function be improved to properly work in visual block mode?

The problem is that the use of the expression register ("=) forces a characterwise operation.
To fix that, manipulate the default register (that contains the original yanked text, anyway), and keep the original register type via getregtype():
vnoremap ~ y:call setreg('', TwiddleCase(#"), getregtype(''))<CR>gv""Pgv

Related

Check if a selection exists in vimscript

I want to write a function in vimscript that echoes the selected text or, if no text is selected, the entire buffer.
How can I distinguish between these two cases?
Define two mappings, an :nmap using the entire buffer, and a :vmap for the selected text. Both can invoke the same function, passing an isVisual boolean flag or a mode argument.
Anything else (custom commands, direct function :call) would require an explicit hint, because in order to invoke them, visual mode as already been left (for command-line mode). You also cannot use the '<,'> marks for the detection, because they will keep the last selection even after it has been removed.
I write a function to get visually selected text.
I hope it can help you.
function! GetSelected()
" save reg
let reg = '"'
let reg_save = getreg(reg)
let reg_type = getregtype(reg)
" yank visually selected text
silent exe 'norm! gv"'.reg.'y'
let value = getreg(reg)
" restore reg
call setreg(reg,reg_save,reg_type)
return value
endfun
" viusal map
vnoremap gs :<C-U>echo GetSelected()<CR>
" normal map
nnoremap gs :<C-U>echo join(getline(1, '$'), "\n")<CR>

Can you do interactive macros or recordings in vim?

I would like to define a vim macro that breaks for user input at certain times, is this possible?
EDIT: Turns out I ment recordings (q), not macros
It is possible to use the input command in a recording, but it's more trouble than it's worth.
I first mapped insert input escape to a key
:map <F2> a<C-R>=input('input: ')<CR>
then I made this recording in the q register
name:
and pasted it into a new tab
iname: ^[
And after the final escape I pressed <C-V><F2> making the line:
iname ^[^[OQ
That I yanked back to the q buffer then used the macro, letting me use the input function.
It works, but terribly.
Yes. See the function input({prompt}, [, {text} [, {completion}] ]). There is even
inputdialog({prompt} [, {text} [, {cancelreturn}]]), for a dialog popup.
If you use input() inside a mapping or macro, the remaining characters will be taken as input, which is not what you want. Vim offers the inputsave() and inputrestore() functions to temporarily suspend reading from the mapping character stream.
Based on mogelbrod's answer, this doesn't work; the itest is read in as input:
oBEFORE ^R=input('prompt> ')^Mitest
But this does:
function! Input()
call inputsave()
let text = input('prompt> ')
call inputrestore()
return text
endfunction
oBEFORE ^R=Input()^Mitest
Unfortunately, because <C-R> takes an expression, we cannot put the commands inline, but have to define a separate Input() function.
Unfortunately it doesn't seem to be possible. You can trigger input() inside a macro, but continuing on afterwards doesn't seem to be possible as any additional input recorded is inserted into the input prompt.
Yank the line into a named register ("qY) and run it (#q) to try it out.
Note: replace ^R and ^M with Ctrl-V Ctrl-R/M (see :help i_CTRL-V).
oBEFORE ^R=input('prompt> ') - works
oBEFORE ^R=input('prompt> ')^Mitest - works, but inserts itest into the prompt
oBEFORE ^R=input('prompt> ')<CR>test - fails
I have collected information from this and other threads and written this script:
function! MacroInterrupt()
"call inputsave()
if strlen(reg_recording()) == 0
if mode() == 'n'
call inputsave()
let tmp_col = col('.')
let tmp_line = line('.')
let text = input('input:')
let line = getline('.')
call setline('.', strpart(line, 0, col('.') - 1) . text . strpart(line, col('.') - 1))
call cursor(tmp_line, tmp_col + strlen(text))
call inputrestore()
return text
else
call inputsave()
let text = input('input:')
call inputrestore()
return text
endif
else
echo "Interrupt added to macro"
call setreg(reg_recording(), getreg(reg_recording()) . "\<F2>")
"echo getreg("q")
endif
"call inputrestore()
endfunction
map <F2> :call MacroInterrupt() <CR>
inoremap <buffer><expr> <F2> MacroInterrupt()
I hope this can help especially people attempting the same.

How to define a new Vim operator with a parameter?

I have been looking to map a new operator in Vim that takes an extra parameter.
For example, we know that ciw will “cut inside word” and will put you into Insert mode. What I am looking for is having a custom action to replace c (for example, s) that takes movements like iw, but requires an extra parameter.
A trivial example would be:
Given a line in a text file
Execute siw* in Normal mode (assuming the cursor is on the first column) for it to surround the first word with * like so:
*Given* a line in a text file
I know, this is what the most excellent surround.vim plugin does. But I am just giving an example here, and looking for an answer as to how to get the mappings so that the above work.
I tried playing with onoremap and opfunc, but can’t seem to get them to play the way I want.
So, what I am looking for is a combination of motions plus operator pending mappings.
Here is an example implementation of the command described
in the question, for illustrative purposes.
nnoremap <silent> s :set opfunc=Surround<cr>g#
vnoremap <silent> s :<c-u>call Surround(visualmode(), 1)<cr>
function! Surround(vt, ...)
let s = InputChar()
if s =~ "\<esc>" || s =~ "\<c-c>"
return
endif
let [sl, sc] = getpos(a:0 ? "'<" : "'[")[1:2]
let [el, ec] = getpos(a:0 ? "'>" : "']")[1:2]
if a:vt == 'line' || a:vt == 'V'
call append(el, s)
call append(sl-1, s)
elseif a:vt == 'block' || a:vt == "\<c-v>"
exe sl..','..el 's/\%'..sc..'c\|\%'..ec..'c.\zs/\=s/g|norm!``'
else
exe el 's/\%'..ec..'c.\zs/\=s/|norm!``'
exe sl 's/\%'..sc..'c/\=s/|norm!``'
endif
endfunction
To get user input, the function InputChar() is used, assuming that
the required argument is a single character.
function! InputChar()
let c = getchar()
return type(c) == type(0) ? nr2char(c) : c
endfunction
If it is necessary to accept a string argument, change the call to
InputChar() in Surround() to the call to input(), instead.
The title of the question might cause misunderstanding. What you want to do is to define a new operator like y, d and c, neither motions nor text objects, isn't it?
:help :map-operator describes how to define a new operator. To take a parameter like the surround plugin, use getchar() in your 'operatorfunc'.
Though :help :map-operator describes the basics, it's a bit troublesome to deal with arguments passed to 'operatorfunc'. You can use vim-operator-user to simplify the handling of arguments. With this plugin, surround-like operator can be written as follows:
function! OperatorSurround(motion_wise)
let _c = getchar()
let c = type(_c) == type(0) ? nr2char(_c) : _c
if c ==# "\<Esc>" || c == "\<C-c>"
return
endif
let bp = getpos("'[")
let ep = getpos("']")
if a:motion_wise ==# 'char'
call setpos('.', ep)
execute "normal! \"=c\<Return>p"
call setpos('.', bp)
execute "normal! \"=c\<Return>P"
elseif a:motion_wise ==# 'line'
let indent = matchstr(getline('.'), '^\s*')
call append(ep[1], indent . c)
call append(bp[1] - 1, indent . c)
elseif a:motion_wise ==# 'block'
execute bp[1].','.ep[1].'substitute/\%'.ep[2].'c.\zs/\=c/'
execute bp[1].','.ep[1].'substitute/\%'.bp[2].'c\zs/\=c/'
call setpos('.', bp)
else
endif
endfunction
call operator#user#define('surround', 'OperatorSurround')
map s <Plug>(operator-surround)
If you really want to define your own text objects, please consider vim-textobj-user.
Consider one of the plugins for writing custom text objects. For example:
https://github.com/kana/vim-textobj-user

CamelCase Expansion in Vim like Intellij Idea?

In Intellij Idea, there's a feature. Let's say I have used a variable myCamelCase somewhere in my code. Then if I type mCC and press Ctrl-Enter or some such key combination, it expands to myCamelCase. Is there something similar in Vim?
Okay, forgive me for answering twice, but since my first attempt missed the point, I'll have another go. This is more complicated than I thought, but possibly not as complicated as I have made it (!).
This is now modified to suggest all matching variable names.
First of all, here's a function to generate the 'mCC' abbreviation from the 'myCamelCase' string:
function! Camel_Initials(camel)
let first_char = matchstr(a:camel,"^.")
let other_char = substitute(a:camel,"\\U","","g")
return first_char . other_char
endfunction
Now, here's a function that takes an abbreviation ('mCC') and scans the current buffer (backwards from the current line) for "words" that have this abbreviation. A list of all matches is returned:
function! Expand_Camel_Initials(abbrev)
let winview=winsaveview()
let candidate=a:abbrev
let matches=[]
try
let resline = line(".")
while resline >= 1
let sstr = '\<' . matchstr(a:abbrev,"^.") . '[a-zA-Z]*\>'
keepjumps let resline=search(sstr,"bW")
let candidate=expand("<cword>")
if candidate != a:abbrev && Camel_Initials(candidate) == a:abbrev
call add( matches, candidate )
endif
endwhile
finally
call winrestview(winview)
if len(matches) == 0
echo "No expansion found"
endif
return sort(candidate)
endtry
endfunction
Next, here's a custom-completion function that reads the word under the cursor and suggests the matches returned by the above functions:
function! Camel_Complete( findstart, base )
if a:findstart
let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '[A-Za-z_]'
let start -= 1
endwhile
return start
else
return Expand_Camel_Initials( a:base )
endif
endfunction
To make use of this, you must define the "completefunc":
setlocal completefunc=Camel_Complete
To use insert-mode completion, type CTRL-X CTRL-U, but I usually map this to CTRL-L:
inoremap <c-l> <c-x><c-u>
With this code in your vimrc you should find that typing mCC followed by CTRL-L will make the expected replacement. If no matching expansion is found, the abbreviation is unchanged.
The code isn't water-tight, but it works in all the simple cases I tested. Hope it helps. Let me know if anything needs elucidating.
There is a plugin for this in Vim called vim-abolish. Use the map crc to expand to camel case.

Get the current line in visual mode from a function

I have a simple vim script that takes a visual block of text and stores it as a list. The problem with the function VtoList() is that it executes after the cursor returns to the start of the visual block, not before it. Because of this, I have no way of getting the line where the visual block ends.
nn <F2> :call VtoList()<CR>
func! VtoList()
firstline = line('v') " Gets the line where the visual block begins
lastline = line('.') " Gets the current line, but not the one I want.
mylist = getline(firstline, lastline)
echo mylist
endfunc
The problem is with line('.'). It should return the current line of the cursor, but before the function is called, the cursor has already returned to the start of the visual block. Thus, I'm only getting a single line instead of a range of lines.
I put together a solution that sets a mark everytime the user hits V and sets another mark before the function is called.
nnoremap V mV
nnoremap <F2> mZ:call VtoList()<CR>
The function works fine if I substitute line('v') and line('.') with line("'V") and line("'Z"), but I want to avoid this solution if I can because it could conflict with a user's mappings.
Is there a way I can get current line of a visual block within a function before the cursor has returned to the start of the visual block?
Don't use :, use <expr>:
function! s:NumSort(a, b)
return a:a>a:b ? 1 : a:a==a:b ? 0 : -1
endfunction
func! VtoList()
let [firstline, lastline]=sort([line('v'), line('.')], 's:NumSort')
let mylist = getline(firstline, lastline)
echo mylist
return ""
endfunc
vnoremap <expr> <F2> VtoList()
Note other changes: let (you forgot it), sort (line where selection starts may be after the line where selection ends), vnoremap (line("v") works only in visual mode), return (expr mappings return value is executed, but you don't need it, you need only side effects). You can replace the second line with
if mode()=~#"^[vV\<C-v>]"
let [firstline, lastline]=sort([line('v'), line('.')], 's:NumSort')
else
let [firstline, lastline]=sort([line("'<"), line("'>")], 's:NumSort')
endif
The reason why your solution is not working is that when : occurs in the mapping, you immediately exit visual mode and enter command mode. line("v") works only in visual mode.
Other note: vnoremap {lhs} : will produce command line already filled with '<,'>. You may have added range to the function definition and use let [firstline, lastline]=sort([a:firstline, a:lastline], 's:NumSort'). But you nevertheless will exit visual mode with :.

Resources