Using printf and substitute changes visual selection - vim

I've been working on implementing a simple utility into Vim which allows me to visually select some text, calculate the result, then replace the original text with the result. I've run into a problem, however: it appears that somehow using substitute in conjunction with printf changes my visual selection.
Here's the code:
vnoremap <leader>C y<ESC>:call setreg( '"', printf( '%f', eval( substitute( substitute( getreg( '"' ), '\n', '', 'g' ), '\(\d\+\.\=\d*\)\s*\^\s*\(\d\+\.\=\d*\)', 'pow( \1, \2 )', 'g' ) ) ), getregtype( '"' ) )<CR>gvp
Now, it's a bit lengthy, but I'm working on fixing that. Basically, it takes the visually selected text, yanks it, then sets the " register to contain the evaluated response, then re-selects the last visual selection, then pastes it, replacing the visual selection with the result.
As an example, say I have this line: a = 32 - 2. Visually select 32 - 2, then hit <leader>C. The line ends up being a = 30.0000002 - 2.
The problem is in the last bit of the last substitution: when the '\(\d\+\.\=\d*\)\s*\^\s*\(\d\+\.\=\d*\)'
bit doesn't have the last \.\=\d*, the code runs fine (given the exponent/index isn't a float). Without printf, it works fine given there are no floats. With printf and the last bit, only the first character is replaced, but the output is correct.
I need to use printf, though, because that allows me to use floats. I also need to do that substitution, because it allows me to use exponents.
What's happening, and how do I fix it? It doesn't make since that the visual area would be changed just by using that code inside of printf. Am I just missing something really obvious?

Apparently the unnamed register doesn't survive mode changes. However, according to the manual:
Writing to the "" register writes to register "0.
And indeed, changing gvp to gv"0p makes your macro work. However, I suggest using a slightly safer version instead:
function! s:expr_eval()
let old_expr = #"
let reg_type = getregtype('"')
let expr = substitute(old_expr, '\n', '', 'g')
let expr = substitute(expr, '\v(\d+%(\.\d*)?)\s*\^\s*(\d+%(\.\d*)?)', 'pow(\1, \2)', 'g')
try
let expr = printf('%f', eval(expr))
catch
let expr = old_expr
endtry
call setreg('"', expr, reg_type)
endfunction
vnoremap <silent> <leader>C y:<C-u>call <SID>expr_eval()<CR>gv"0p

Related

Vim Multi-Line If-Statements into Single-Line If-Statements

Is it possible to have a vim binding that turns multi-line if-statements into single-line if-statements and vice-versa?
turn this
if () {
statement_1;
statement_2;
} else if () {
statement_3; statement_4;
} else if () {
statement_5;
} else {
}
into this
if () { statement_1; statement_2 }
else if () { statement_3; statement_4; }
else if () { statement_5; }
else { }
or anything close to the above behavior? I was thinking of having the command execute upon visually selecting the block to convert and then use searches for else if and entering new lines, etc. But my problem was determining how many else if were in the code
Join all the lines to a single line by selecting them in visual mode V and pressing J; then add a newline before every else with :s/else/\relse/. You'll end up with:
if () { statement_1; statement_2; }
else if () { statement_3; statement_4; }
else if () { statement_5; }
else { }
The \r in the replacement pattern is a newline (you need to use \n and search and \r in replace; don't ask me why).
Next step is to put all the start braces in the same column. I'd use the tabular plugin for this, which makes that very easy:
:%Tabularize /{/
With % we operate on the entire buffer, in a "real" file you'll probably want to use a more restrictive range or visual mode. There are some other plugins which do something similar as well.
You should now have the output you want.
If you don't want to use a plugin, you can use the column command:
:%!column -ts{ -o{
If you want a "Vim-only" solution, then it's a bit more complex:
:let b:column = 10
:%s/^\(.\{-}\) {/\=submatch(1) . repeat(' ', b:column - len(submatch(1))) . ' {'/
Breaking that down:
I used the b:column variable to specify the column to align to. You don't need this but it make it a bit easier to edit this number later on.
^\(.\{-}\) { puts everything before { in a subgroup.
In the replace we used an expression (as indicated with \=). See :help sub-replace-\=.
First we put the if ... back with submatch(1)
Then we insert as many spaces as we need with repeat(' ', b:column - len(submatch(1)))
Finally we insert the literal { back.
I told you it was a bit more complex ;-) If you don't want tabular. Personally, I'd just start insert mode to insert spaces, which will be a lot faster than writing & debugging this (relevant xkcd).
Note that I didn't make some "magic" command which re-arranges all the text with just a stroke of a key. I don't think such a command would be a good idea. In practice there will be a lot of edge cases that such a command won't handle. Fully "parsing" a programming language with ad-hoc editing commands and/or regexps doesn't really work all that great.
Where Vim really shines is giving the user powerful text editing commands, which can be applied and combined with minimal effort, which is exactly what I did above. There are several other ways one can use to get the same effect.
But if you really want to, you can of course combine all of the above in a command:
fun! s:reformat(line1, line2)
" Remember number of lines for later
let l:before = line('$')
" Join the lines
execute 'normal! ' . (a:line2 - a:line1 + 1) . 'J'
" Put newline before else
:s/else/\relse/
" Run tabular; since the number of lines change we need to calculate the range.
" You could also use one of the other methods here, if you want.
let l:line2 = a:line2 - (l:before - line('$'))
execute a:line1 . ',' . l:line2 . 'Tabularize /{/'
endfun
command! -range Reformat :call s:reformat(<line1>, <line2>)

Quickest way to switch order of comma-sparated list in Vim

Supose I have a function such as
myfunc(arg1 = whatever, arg2 = different)
I would like to transform it to
myfunc(arg2 = different, arg1 = whatever)
What is the quickest command sequence to achieve this? suppose the cursor is on the first "m". My best attempt is fadt,lpldt)%p.
There is a vim plugin: vim-exchange
visual select arg1 = whatever
press Shiftx
visual select arg2 = different
press Shiftx
I would recommend you change it a bit so it will work from wherever the cursor is and so that it will work on any arguments:
0f(ldt,lpldt)%p
All I changed from your method was I added 0 to move the cursor to the beginning and I changed fa to f(l so that it will work regardless of argument name.
Now you can either put this into a macro, or, if you use it a lot, you can make it a mapping:
nnoremap <C-k> 0f(ldt,lpldt)%p
I arbitrarily chose Ctrl-k here put you can use whatever you like.
I wrote a plugin for manipulating function arguments called Argumentative. With it you just execute >, and the argument your cursor is on will shift to the right. It also provides argument text object in the form of i, and a,.
With pure vim, with your cursor at the start of the line:
%3dB%pldt,lp
This is the quickest I could think of on the spot (12 strokes).
This should work for all names as long as there is always a space around the equal signs.
% " Jump to closing brace
3dB " Delete to the beginning of 3 WORDS, backwards
% " Jump to the beginning brace
p " Paste the deleted text from the default register
l " Move right one character
dt, " Delete until the next comma
l " Move right one character
p " paste the deleted text from the default register
You could also turn this into a Macro to use at any time.

Vim (vimscript) get exact character under the cursor

I am getting the character under the cursor in vimscript the following way:
getline('.')[col('.')-1]
It works exactly like it should, however there is something I dislike. consider this [] the cursor. When there is a bracket next to the cursor like so:
}[] , ][] , )[] or {[] the cursor actually returns the bracket. What do I have to set so it will always return the character exactly under the cursor or atleast ignore if there is a bracket to it's left?
Note: I suspect that it might have to do with the brackets highlight, though I am not sure.
Note2: for the situation to occur there has to be a matching bracket.
Though I cannot reproduce the problem you're describing, there's another problem with your code: Because of the string indexing (and this is one of the uglier sides of Vimscript), it only works with single-byte characters, but will fail to capture chars like Ä or 𠔻 (depending on the encoding used). This is a better way of capturing the character under the cursor:
:echo matchstr(getline('.'), '\%' . col('.') . 'c.')
Edit: Since about Vim 7.4.1742, Vim has new strgetchar() and strcharpart() functions that work with character indexes, not byte addressing. This is helpful in many circumstances, but not here, because you still can only get the byte-index position of the cursor (or the screen column with virtcol(), but that's not the same as character index).
nr2char(strgetchar(getline('.')[col('.') - 1:], 0))
or
strcharpart(getline('.')[col('.') - 1:], 0, 1)
Another way to get the character index under cursor that deal with both ASCII and non-ASCII characters is the like the following:
function! CharAtIdx(str, idx) abort
" Get char at idx from str. Note that this is based on character index
" instead of the byte index.
return strcharpart(a:str, a:idx, 1)
endfunction
function! CursorCharIdx() abort
" A more concise way to get character index under cursor.
let cursor_byte_idx = col('.')
if cursor_byte_idx == 1
return 0
endif
let pre_cursor_text = getline('.')[:col('.')-2]
return strchars(pre_cursor_text)
endfunction
Then if you want to get char under cursor, use the following command:
let cur_char_idx = CursorCharIdx()
let cur_char = CharAtIdx(getline('.'), cur_char_idx)
See also this post on how to get pre-cursor char.

Case insensitive f key in vim?

Does anyone know of any way to get the 'f' key in vim normal command mode to operate case insensitive
Example
function! OpenCurrentFileInMarked()
In the above line, if I am at the start of the line I want to be able to type 'fi'and get the first 'i' and then ';' to toggle to the capital 'I' in the middle of the function name. I would prefer that the 'f' key is always bound to case insensitivity. Would work much better for me as a default.
The easy answer here is: use a plugin. Others have had the idea before.
Fanf,ingTastic;: Find a char across lines
The main purpose of this plugin is to make f and t cross the line boundary.
To make them ignore case you need to let g:fanfingtastic_ignorecase = 1 in your vimrc.
ft_improved: improved f/t command
Same here.
To make it ignore case you again need to set a variable in your vimrc, this time let g:ft_improved_ignorecase = 1.
The first part of this (case insensitive f) is actually in the reference manual as an example of how to use the getchar() function:
This example redefines "f" to ignore case:
:nmap f :call FindChar()<CR>
:function FindChar()
: let c = nr2char(getchar())
: while col('.') < col('$') - 1
: normal l
: if getline('.')[col('.') - 1] ==? c
: break
: endif
: endwhile
:endfunction
See :help getchar().
You'll need to save the character returned and write a similar map for ; if you want that to work too.
You can't do this with regular vim commands. However, you can write your own function and bind the f key to it.

vim - non rectangular visual block

Is there a way to select the second column in the following code,
which turns out to be non rectangular.
I tried "CTRLv 3jE" , but that doesn't work.
int var_one = 1;
int var_two = 2;
int var_three = 3;
int var_very_long = 4;
You could use one of the Align plugins to align your column, select and copy it and afterwards undo the alignment (or leave it aligned)
Based on the comments, I think the way to go is writing a custom function that
passes the task to awk. It could be done with some regex also, splitting each
line on spaces, but awk should be easier. Here is my first try:
function! ExtractColToRegister(reg, ...) range
let input = join(getline(a:firstline, a:lastline), "\n")
if a:1 | let column = a:1
else | let column = 1 | endif
exec "let #". a:reg . " = system(\"awk '{ print $" .
\ column . " }'\", input)"
endfunction
You should have no trouble understanding it if you're already writing Vim
scripts :-) however let me know if some part of it is unclear, and if there is
something to improve as well.
Basically what the function does is saving a specific column to a register. If
you visually select the example code given in the question, and then:
:'<,'>call ExtractColToRegister("a", 2)
Register a will now contain:
var_one
var_two
var_three
var_very_long
And you can easily "ap somewhere else. Notice the column defaults to 1 if the
argument was omitted.
Creating a custom command "Column to Register" makes the process even nicier to
use outside of Vim scripts:
:command! -range -nargs=+ CTR <line1>,<line2>call ExtractColToRegister(<f-args>)
use the CopyMatches function from http://vim.wikia.com/wiki/Copy_the_search_results_into_clipboard
then select the lines and do something like
:'<,'>CopyMatches .*=

Resources