How to repeatedly add text on both sides of a word in vim? - vim

I have a bunch of local variable references in a Python script that I want to pull from a dictionary instead. So, I need to essentially change foo, bar, and others into env['foo'], env['bar'] and so on. Do I need to write a regular expression and match each variable name to transform, or is there a more direct approach that I could just repeat with the . command?

You can use a macro: type these commands in one go (with spacing just to insert comments)
" first move to start of the relevant word (ie via search)
qa " record macro into the a register.
ienv['<esc> " insert relevant piece
ea'] " move to end of word and insert relevant piece
q " stop recording
then, when you're on the next word, just hit #a to replay the macro (or even ## to repeat the last replay after that).

There's an easier way - you can use a regex search and replace. Go into cmdline mode by typing a colon and then run this command:
%s/\\(foo\|bar\|baz\\)/env['\1']/
Replacing foo, bar, and baz with whatever your actual variable names are. You can add as many additional variables as you'd like, just be sure to escape your OR pipes with a backslash. Hope that helps.

you could write a function that would do this pretty well, add this to your .vimrc file:
function! s:surround()
let word = expand("<cword>")
let command = "%s/".word."/env[\'".word."\']/g"
execute command
endfunction
map cx :call <SID>surround()<CR>
This will surround every occurance of the word currently under the cursor.
If you wanted to specify what went before and after each instance you could use this:
function! s:surround()
let word = expand("<cword>")
let before = input("what should go before? ")
let after = input("what should go after? ")
let command = "%s/".word."/".before.word.after."/g"
execute command
endfunction
map cx :call <SID>surround()<CR>
If you only want to confirm each instance of the variable you could use this:
function! s:surround()
let word = expand("<cword>")
let before = input("what should go before? ")
let after = input("what should go after? ")
let command = "%s/".word."/".before.word.after."/c"
execute command
endfunction
map cx :call <SID>surround()<CR>

I figured out one way to do what I need. Use q{0-9a-zA-Z"} to record key strokes into a buffer. Position the cursor at the begging of the variable name, then cw and type env['']. Next move the cursor back one space to the last quote and paste the buffer filled from the cw command with P. Finally, reuse the recording with #{0-9a-z".=*} for each variable.

Related

Resize vim split based on number of visible lines

I am trying to write a function in my vimrc which lets me create a small split which looks as though it is inside the current file but is, in fact, a different file.
To do this cleanly, I want this split to appear immediately below the cursor which means I need to resize my splits appropriately. Unfortunately, I cannot find a way to measure the number of visible lines between my cursor and the top of the window. In files where I have folded code, the approach of using line(".") - line("w0") ("." gives line number of cursor; "w0" gives line number of topmost visible line) does not work since this calculation includes the hidden lines inside the folds. Does anybody know how this could be achieved?
Images for reference before and after inserting the split:
line(".") - line("w0")
gives you the number of physical lines between the top of the window and the current line. As you figured out, this method doesn't account for folds.
You can count the number of folds within a range with something like this:
function! CountFolds(top_line, bottom_line)
let folded_lines = []
for line_nr in range(a:top_line, a:bottom_line)
if foldlevel(line_nr) == 0
call add(folded_lines, 0)
else
call add(folded_lines, 1)
endif
endfor
return count(uniq(folded_lines), 1)
endfunction
and then remove it from your initial line count:
let top_line = line("w0")
let bottom_line = line(".")
let physical_lines = bottom_line - top_line
let screen_lines = physical_lines - CountFolds(top_line, bottom_line)
Reference:
:help range()
:help foldlevel()
:help add()
:help uniq()
:help count()
Note that you may need your script to account for soft-wrapped lines as well, a topic that is well worth another question.

Placing cursor after lines appended via append / line

I use the following function to insert a break in comments of the following format:
Break:
# Notes -------------------------------------------------------------------
Function:
" Insert RStudio like section break
function! InsertSectionBreak()
let title = input("Section title: ") " Collect title
let title_length = strlen(title) " Number of repetitions
let times = 80 - (title_length + 1)
let char = "-" " Create line break
let sep_line = repeat(char, times)
let final_string = '\n#' . ' ' . title . ' ' . sep_line " Create final title string
call cursor( line('.')+1, 1)
call append(line('.')+1, final_string) " Get current line and insert string
endfunction
" Map function to keyboard shortcut ';s'
nmap <silent> ;s :call InsertSectionBreak()<CR>
Problem
After performing the operation I would like to place the cursor one line below the created section.
Desired behaviour:
# Notes -------------------------------------------------------------------
<cursor should land here>
Current behaviour
The cursors stays on the current line.
<some code I'm typing when I exit insert mode and call ;s - cursor stays here>
# Notes -------------------------------------------------------------------
<cursor lands here>
As a low-level function, append() is not affected by and also does not affect the cursor position. Therefore, you just need to adapt the cursor() arguments. I would also recommend to only change the cursor at the very end, to make the calculation based on line('.') easier:
function! InsertSectionBreak()
let title = input("Section title: ") " Collect title
let title_length = strlen(title) " Number of repetitions
let times = 80 - (title_length + 1)
let char = "-" " Create line break
let sep_line = repeat(char, times)
let final_string = '#' . ' ' . title . ' ' . sep_line " Create final title string
call append(line('.')+1, ['', final_string]) " Get current line and insert string
call cursor(line('.')+4, 1)
endfunction
Additional notes
The '\n#' string includes a literal \n, not a newline character. For that, double quotes would have to be used. However, even that won't work with append() because it always inserts as one text line, rendering the newline as ^#. To include a leading empty line, pass a List of lines instead, and make the first list element an empty string.
You're using mostly low-level functions (cursor(), append()); you could have used higher-level functions (:normal! jj or :execute lnum for cursor positioning, :put =final_string for adding lines) instead. There would be more side effects (like adding to the jumplist, :put depending on and positioning the cursor already, and having the change marks delimit the added text); usually this is good (but it depends on the use case).
Mappings that interactively query stuff from the user are not very Vim-like. I would have rather defined a custom command (e.g. :Break {title}) that takes the title as an argument, and maybe an additional (incomplete command-line) mapping for quick access :nnoremap ;s :Break<Space>. This way, you could easily repeat the last insertion with the same title via #:, for instance.

In vim, how to split a word and flip the halves? FooBar => BarFoo

I sometimes write a multi-word identifier in one order, then decide the other order makes more sense. Sometimes there is a separator character, sometimes there is case boundary, and sometimes the separation is positional. For example:
$foobar becomes $barfoo
$FooBar becomes $BarFoo
$foo_bar becomes $bar_foo
How would I accomplish this in vim? I want to put my cursor on the word, hit a key combo that cuts the first half, then appends it to the end of the current word. Something like cw, but also yanking into the cut buffer and then appending to the current word (eg ea).
Nothing general and obvious comes to mind. This is more a novelty question than one of daily practical use, but preference is given to shortest answer with fewest plugins. (Hmm, like code golf for vim.)
You can use this function, it swaps any word of the form FooBar, foo_bar, or fooBar:
function! SwapWord()
" Swap the word under the cursor, ex:
" 'foo_bar' --> 'bar_foo',
" 'FooBar' --> 'BarFoo',
" 'fooBar' --> 'barFoo' (keeps case style)
let save_cursor = getcurpos()
let word = expand("<cword>")
let match_ = match(word, '_')
if match_ != -1
let repl = strpart(word, match_ + 1) . '_' . strpart(word, 0, match_)
else
let matchU = match(word, '\u', 1)
if matchU != -1
let was_lower = (match(word, '^\l') != -1)
if was_lower
let word = substitute(word, '^.', '\U\0', '')
endif
let repl = strpart(word, matchU) . strpart(word, 0, matchU)
if was_lower
let repl = substitute(repl, '^.', '\L\0', '')
endif
else
return
endif
endif
silent exe "normal ciw\<c-r>=repl\<cr>"
call setpos('.', save_cursor)
endf
Mapping example:
noremap <silent> gs :call SwapWord()<cr>
Are you talking about a single instance, globally across a file, or generically?
I would tend to just do a global search and replace, e.g.:
:1,$:s/$foobar/$barfoo/g
(for all lines, change $foobar to $barfoo, every instance on each line)
EDIT (single occurrence with cursor on the 'f'):
3xep
3xep (had some ~ in there before the re-edit of the question)
4xea_[ESC]px
Best I got for now. :)
nnoremap <Leader>s dwbP
Using Leader, s should now work.
dw : cut until the end of the word from cursor position
b : move cursor at the beginning of the word
P : paste the previously cut part at the front
It won't work for you last example though, you have to add another mapping to deal with _ .
(If you don't know what Leader is, see :help mapleader)

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 - 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