vim - how to select multiple lines? - vim

Say I have this text:
#!/bin/bash
# Get host
db_host=$(echo "dbhost")
# Get DB name
db_name=$(echo "dbname")
# Get user
db_user=$(echo "dbuser")
# Get password
db_pass=$(echo "dbpass")
and I want to select every variable name and produce this output below the text:
echo "db_host: $db_host"
echo "db_name: $db_name"
echo "db_user: $db_user"
echo "db_pass: $db_pass"
On sublime for example I'd highlight =$( and hit ⌘+d multiple times, go back to the start of the line, copy with SHIFT+→, go to the last line and create a new one, paste, highlight all new lines, hit ⌘+SHIFT+l and then add whatever I want.
I'm using vim multiple cursors but I'm not sure this is the way to go. Any directions?

There's many ways you could do it. Here's one: Starting with cursor at start of first db_host:
yGGp Yank everything, paste at the bottom
:.,$g/^#/d<CR> In the pasted part, remove all comment lines
<C-O> Back to start of the pasted part
<C-V>GI Select the first column, prepend
echo "<Esc>
f=<C-V> Then select the block from equals to quote, change
f"Gc
: $<Esc>
f)<C-V> Then select the block at the closing brace, change
G$c
;<Esc>
Another approach, using macros and registers, starting at the same position:
qqq Clear register q
qwq Clear register w
qq Start recording macro in q
yaw yank a word (db_name) to default register
o open a new line below, and start insert
echo "
<C-R>" insert the content of the default register (db_name)
: $
<C-R>" insert the content of the default register (db_name) again
"<Esc>
"Wdd the line is done; yank-append to register w
j0 skip the comment line, position at the start of the next variable
#q execute the q macro (which is currently empty, but not for long)
q save the q macro
#q execute the q macro (which will recurse, and slurp up all lines)
"wp paste all the accumulated lines at the bottom
Third one, using regexp:
:.,$v/^#/t$<CR> copy all non-comment lines to the end
:-3,.s/\(\w\+\).*/echo "\1: $\1"/<CR>
I didn't set a mark so manual range from 3 lines above:
capture the first word, discard everything else,
replace with what we want (obviously, could have done
visual selection instead of manually setting range)

Here's how I'd do it:
First, create the echo lines with a substitution:
:%s/\(db_\w\+\)=\$(echo "\w\+")/&\recho "\1: $\1"
Note the & in the replace, which is the whole matched line. Followed by \r for a new line.
Then, empty a register you will use for it (a in my case):
qaq
Then cut all the echo lines in the said register:
:g/^echo /d A
Finally, go where you want them to be, and paste the content of the register:
"ap

Maybe I missed what you are trying to do, but if I understood correctly, you just want to update the format of your output.
Try this:
:g/[(]echo "/s/echo "\([^"]*\)"\(.*\)$/echo "\1"\2^Mecho "\1: $\1"/
NOTE: that ^M is a single control character, NOT a carat and then an M.
See below.
Before -
#!/bin/bash
# Get host
db_host=$(echo "dbhost")
# Get DB name
db_name=$(echo "dbname")
# Get user
db_user=$(echo "dbuser")
# Get password
db_pass=$(echo "dbpass")
After -
#!/bin/bash
# Get host
db_host=$(echo "dbhost")
echo "dbhost: $dbhost"
# Get DB name
db_name=$(echo "dbname")
echo "dbname: $dbname"
# Get user
db_user=$(echo "dbuser")
echo "dbuser: $dbuser"
# Get password
db_pass=$(echo "dbpass")
echo "dbpass: $dbpass"
Explanation
:g/[(]echo "/s/echo "\([^"]*\)"\(.*\)$/echo "\1"\2^Mecho "\1: $\1"/
in pieces -
:g/pat/cmd
This says "globally" on every line matching "pat" do "cmd".
My "pat" was your (echo ", and my "cmd" is a substitution.
s/echo "\([^"]*\)"\(.*\)$/echo "\1"\2^Mecho "\1: $\1"/
I said to substitute the echo "..."... and remember what was between the quotes as 1, and anything after as 2.
The replace part says to put back whatever was there (echo "\1"\2) followed by a bit of chicanery.
By hitting CTRL-V I entered quote-mode, which allowed me to hit CTRL-M to insert a carriage return, which vim converts on execution to a newline, at least on the versions I have used.
It's a trick. It can be handy to know and use, but always bear in mind that such things are basically hackery. Caveat Scriptor.
I followed that with a formatted echo "\1: $\1" to add the lines you wanted.

A slightly different solution:
:g/echo/t$
:-3,.normal! ywccecho "<Ctrl-v><Ctrl-r>0: $<Ctrl-v><Ctrl-r0"
Explanation
:g ................... global command
/echo/ ............... applied on each line that has "echo"
t$ ................... copy to the end
The cursor will move to the end, so we set the interval to the minus three lines until the current line -3,.
yw ................... copy the first word
cc ................... start chnanging the whole line
echo " ............... inserts a literal `echo "`
Ctrl-v Ctrl-r 0 ...... inserts the word we copied
Note: To insert the register zero 0 we must type Ctrl-vCtrl-r0
Using a substitute command instead of a normal one
The second command could be a substitution
:-3,.s/\v(^\w+).*/echo "\1: $\1"
How the vim solution could be a smarter solution?
If you have hundreds or thousands of lines to change, I think multiple cursors will not help that much, specially if the pattern interleaved over multiple lines.

Related

Vim advanced multiline edits

I'm trying to getting into using more advanced vim features.
How would people go about for the following edit?
from this:
ssn=token_payload.fnr,
fname=token_payload.displayName,
email=token_payload.email,
login=token_payload.username,
to this:
ssn=token_payload['fnr'],
fname=token_payload['displayName'],
email=token_payload['email'],
login=token_payload['username'],
Command line :norm command
I would apply the following normal commands to all lines in the file:
" note that in the real command, <Esc> would be a literal
" press of the escape key (see explanation below)
:%norm f.s['<Esc>f,i']
apply to the whole file: %
the following normal mode commands:norm
move to the period: f.
substitute with opening square bracket and quote: s['
escape insert mode (press ctrl+v to enter a literal character, then escape -
you'll see a gray symbol appear): ^[
move to the comma: f,
insert the quote and closing square bracket: i']
I started using the command line way instead of macros recently since I find
that you can think it over more easily (particularly if you compose the command
in the command buffer with q: - see :help command-buffer).
Use a macro
Another way is to record a macro:
qa0f.s['<Esc>f,i']<Esc>jq
Which you can then deploy on the current line with #a and repeat with ##.
Or use :%norm #a to run the macro on each line.
It's basically the same as above, but instead of :%norm you use qa to
record into the a register (you can use any letter). Then perform the edit. I
added a drop down one line with j before stopping the recording with q.
You can edit the macro after recording it by pasting the contents of the
register ("ap), edit them, and yank them back ("ay$) before replaying it.
Using an external tool
If I wanted to perform multiple substitutions with a single command, I would
filter the text through an external program like sed:
:%!sed "s/\./['/; s/,$/'],/"
One more g[ood] thing
An extremely powerful tool is the :g[lobal] command! (see :help :g) I've
been using it a lot in combination with the norm command. For example, if I
wanted to get all the paragraphs in a document formatted nicely, but not affect
indented text (which could be code blocks, or tables etc.) I would do:
:%g/^\w/norm gqap
This means, for any line with a letter at the very start of the line, apply the
command gqap which applies the normal mode command gq to 'a paragraph'.
You might also want to capitalise the first word and increase the header level
of all the markdown headings like so:
:%g/^#/norm w~I#
This would change this:
# a heading
some text.
## another heading
some more text
```sh
# and a comment in some code will be unaffected
print('hello world')
```
## a further heading
some text
# conclusion
into this:
## A heading
some text.
### Another heading
some more text
```sh
# and a comment in some code will be unaffected
print('hello world')
```
### A further heading
some text
## Conclusion
see these videos for 'advanced' vim stuff
I'd implement this as an :s command. For example, this command would make the requested changes:
:%s/\.\(.*\),/['\1'],/
That operates on all lines %, matches the dot and comma and puts everything in between into a group (\(.*\)), and then replaces it with the desired value, matching the first group (\1).
If you want to operate on a different set of lines, you can write :1,4 instead of :%, or write :'<,'> to operate on the visual selection.

Vim: delete empty lines around cursor

Suppose I'm editing the following document (* = cursor):
Lions
Tigers
Kittens
Puppies
*
Humans
What sequence can I use to delete the surrounding white space so that I'm left with:
Lions
Tigers
Kittens
Puppies
*
Humans
Note: I'm looking for an answer that handles any number of empty lines, not just this exact case.
EDIT 1: Line numbers are unknown and I only want to effect the span my cursor is in.
EDIT 2: Edited example to show I need to preserve leading whitespace on edges
Thanks
Easy. In normal mode, dipO<Esc> should do it.
Explanation:
dip on a blank line deletes it and all adjacent blank lines.
O<Esc> opens a new empty line, then goes back to normal mode.
Even more concise, cip<Esc> would roll these two steps into one, as suggested by #Lorkenpeist.
A possible solution is to use the :join command with a range:
:?.?+1,/./-1join!
Explanation:
[range]join! will join together a [range] of lines. The ! means with out inserting any extra space.
The starting point is to search backwards to the first character then down 1 line, ?.?+1
As the 1 in +1 can be assumed this can be abbreviated ?.?+
The ending point is to search forwards to the next character then up 1 line, /./-1
Same as before the 1 can be assumed so, /./-
As we are using the same pattern only searching forward the pattern can be omitted. //-
The command :join can be shorted to just :j
Final shortened command:
:?.?+,//-j!
Here are some related commands that might be handy:
1) to delete all empty lines:
:g/^$/d
:v/./d
2) Squeeze all empty lines into just 1 empty line:
:v/./,//-j
For more help see:
:h :j
:h [range]
:h :g
:h :v
Short Answer: ()V)kc<esc>
In normal mode, if you type () your cursor will move to the first blank line. ( moves the cursor to the beginning of the previous block of non-blank lines, and ) moves the cursor to the end (specifically, to the first blank line after said block). Then a simple d) will delete all text until the beginning of the next non-blank line. So the complete sequence is ()d).
EDIT: You're right, that deletes the whitespace at the beginning of the next non-blank line. Instead of d) try V)kd. V puts you in visual line mode, ) jumps to the first non-blank line (skipping the whitespace at the beginning of the line), k moves the cursor up one line. At this point you've selected all the blank lines, so d deletes the selection.
Finally, type O (capital O) followed by escape to crate a new blank line to replace the ones you deleted. Alternatively, replacing dO<Escape> with c<Escape> does the same thing with one less keystroke, so the entire sequence would be ()V)kc<Esc>.
These answers are irrelevant after the updated question:
This may not be the answer you want to hear, but I would make use of ranges. Take a look at the line number for the first empty line (let's say 55 for example) and the second to last empty line (perhaps 67). Then just do :55,67d.
Or, perhaps you only want there to ever be one empty line in your whole file. In that case you can match any occurrence of one or more empty lines and replace them with one empty line.
:%s/\(^$\n\)\+/\r/
This answer works:
If you just want to use normal mode you could search for the last line with something on it. For instance,
/.<Enter>kkVNjd
I didn't test so much, but it should work for your examples. There maybe more elegant solutions.
function! DelWrapLines()
while match(getline('.'),'^\s*$')>=0
exe 'normal kJ'
endwhile
exe 'silent +|+,/./-1d|noh'
exe 'normal k'
endfunction
source it and try :call DelWrapLines()
I know this question has already been resolved, but I just found a great solution in "sed & awk, 2nd Ed." (O'Reilly) that I thought was worth sharing. It does not use vim at all, but instead uses sed. This script will replace all instances of one or more blank lines (assuming there is no whitespace in those lines) with a single blank line. On the command line:
sed '/ˆ$/{
N
/ˆ\n$/D
}' myfile
Keep in mind that sed does not actually edit the file, but instead prints the edited lines to standard output. You can redirect this input to a file:
sed '/ˆ$/{
N
/ˆ\n$/D
}' myfile > tempfile
Be careful though, if you try to write it directly to myfile, it will just delete the entire contents of the file, which is clearly not what you want! After you write the output to tempfile, you can just mv tempfile myfile and tada! All instances of multiple blank lines are replaced by a single blank line.
Even better:
cat -s myfile > temp
mv temp myfile
cat is awesome, yes?
Bestest:
If you want to do it inside vim, you can replace all instances of multiple blank lines with a single blank line by using vim's handy feature of executing shell commands on a range of lines within vim.
:%!cat -s
That's all it takes, and your entire file is reformatted all nice!

Add Padded Incremental Number to End of Each Line in VIM

I have a text file with a list in it:
dateformatfile.ext
dateformatfile.ext
dateformatfile.ext
...
I need to add a padded number to the end of each, like so:
dateformatfile.ext 00001
dateformatfile.ext 00002
dateformatfile.ext 00003
...
There are a lot, so I need to have a command to do this somehow.
Thanks in advance.
Assuming you want to do this for every line in your file, you can use the line number like this:
:execute "% normal A \<C-R>=printf(\"%05d\", line(\".\"))\<CR>"
where
execute(...) runs the string as a command
% normal runs a normal command on every line of the file
A appends to the line
<C-R>= inserts the result of a command
printf("%05d", ...) formats the second parameters as a five-digit number
line(".") gets the number of the current line
<CR> completes the <C-R>= insertion
if your text block is sitting at the beginning of the file. which means the line you want to append "00001" is the first line of your file, try this command, I just simply check the line ending with ext, you could change it to right regex if it is needed:
:%s/ext$/\="ext ".printf("%05d", line("."))/g
if the text block is not at the beginning of the file. You just check the first line (the line you want to append 00001) of the block and get the line number, for example, line number 5:
:let b=5|%s/ext$/\="ext ".printf("%05d", line(".")-b+1)/g
Here is my take.
Position cursor on first line where you want to add the first number.
:let i=0 Define a variable to hold the count.
qm Start to record a macro into register m.
A <C-R>=printf("%05d", i)<CR><ESC> Add a space and the ouput from printf.
:let i+=1 Increment the count for the next macro execution.
q End the recording of the macro.
jVG Visual select the rest of the document where we want to add numbers.
:normal #m Execute the macro to add the numbers to the selected lines.
I think this approach has some advantages:
No ugly escaping necessary.
The count is not tied to the line number. Allowing for offsets.
Using a macro can be easily combined with the :global command. For example:
:g/ext$/ normal #m Execute macro stored in register m on lines ending in ext.
One could very easily do this using awk. The NR variable gives you the record number, and records map to lines unless the RS variable is redefined. So the following:
awk -e '{ print $0 NR }' filename should do the trick. Padding them is an exercise left up to the reader.
I would do this using macros (I like macros :D).
First, let's take care of the numbers (we'll pad them later).
Add manually the number 1 at the end of the first line.
Then record this macro on the first line :
qq - record the macro q
$ - go at the end of the line
F<space> - go backward to the last space
"ay$ - copy till the end of the line in the buffer a
j$ - go at the end of the line below
"ap - copy the buffer content
<ctrl+A> - increment the number
q - stop recording the macro
Now you can apply it a bunch of times with 1000#q (it will stop at the end of the file).
This is not really pretty but it does the job.
For the padding, I would use another ugly trick. First, use a regex to match 3 digits numbers and add a 0 before, then do the same with 2 digits numbers (add two 0 this time) and so on...
vim macros are pretty ugly but they are useful to me when I am too tired to write a oneliner (I should learn awk though). Also, they can help you remember some obscure, yet useful vim shortcuts.
If you have PERL in your environment, you can run a PERL one-liner inside your VIM session.
:%! perl -pe " $count++ ; s/$/$count/"
The caveat is that you might have to use double quotes around your perl script. On my PC, PERL will run if I use single quotes. But I cannot address variables with the dollar sign.

What is the best way to refactor a Ruby ‘if’ statement into one-line shorthand form in Vim?

I have the following Ruby code:
if some_cond && another
foo_bar
end
and I want to change it to:
foo_bar if some_cond && another
What are the most idiomatic ways to do that in Vim?
Assuming that the cursor is located at the if-line prior to
refactoring (not necessarily at the beginning of that line),
I would use the following sequence of Normal-mode commands:
ddjVpkJ<<
or this chain of Ex commands:
:m+|-j|<|+d
Here the if-line is first moved down one line by the :move + command.
The :move command cuts a given range of lines (the current line, if
not specified) and pastes it below the line addressed by the argument.
The + address is a shorthand for .+1 referring to the next line
(see :help {address}).
Second, the line containing the body of the conditional statement is
joined with the just moved if-line. The :join command concatenates
a given range of lines into a single line. The - range is a shortened
form of the .-1 address referring to the line just above the cursor
(see :help {address}).
Third, the newly joined line is unindented by one shiftwidth using
the :< command.
Finally, the remaining end-line, which can now be addressed as +,
is removed by the :delete command.
I see few (probably non-optimal) solutions:
cursor in first character in first line:
D - remove if condition but leave cursor in same position (don't delete line)
J - join next line to current
A <Space> <ESC> - append space and exit to Normal mode
p - paste if condition
and then remove remaining end with jdd
cursor in first character in first line, as previously:
j - move to next line
dd - remove this line
k - move back to if condition
P - paste removed line before actual line, cursor should be placed to pasted line
J - join next line to current
== or << - unindent current line
and then remove remaining end with jdd
another solution:
j - move to second line
JD - join line with next, remove what was joined
dd - remove current line
k - step to previous line
PJ<< - paste, join and unshift
It's probably not optimal, but I do it without thinking, because most of this commands are in my muscle memory (you don't think how to move around you, how to yank/delete and paste most of the time, and joining line is also helpful to remember).
If you have virtualedit enabled in config, instead of A <Space> <Esc> you can $ <Space>, but I find $ harder to use than A followed by Ctrl-[ (it's simmilar to ESC).
As an advice: if you use some upper letter commands, try to chain them if it's possible, so you only need to keep Shift pressed and then execute some commands, instead of mixing upper and lower letter commands and pressing two keys at a time (upper letter is 2 key press, one is Shift). Once I found combo helpful for restarting server in console Ctrl+cpj, which sends Ctrl+c, Ctrl+p (previous command) and Ctrl+j (Enter key) with single Ctrl press. Since then I try to find simmilar time-saving combination in Vim too mostly with Shift, as Ctrl is not much used in Vim.
Yet another way:
ddpkJjdd
ddp swap the two lines
kJ move up and join the lines
== re-indent the line
jdd move down and delete the last line
There are probably 30 ways to do this.
Here is one, assuming you are starting from the end of the word end in normal mode:
dd (delete last line)
"aD (delete and copy foo_bar to buffer a)
dd (delete now-empty line 2)
"aP (paste from buffer a before caret)
aSpaceEsc (insert space and return to normal mode)
Again, "properly" rarely applies in Vim because there are so many ways to accomplish something. This is so small a change that even re-typing foo_bar could be justifiable.

Delete first word of each line

How do I delete first word of each line in Vim?
How about a pattern on each line?
:normal to the rescue:
:%norm dw
It basically replays the arguments as if you were typing them in normal ('non-edit') mode.
From :help :
:norm[al][!] {commands}
Execute Normal mode commands {commands}.
This makes it possible to execute Normal mode commands typed on the
command-line. {commands} is executed like it is typed.
Going for cryptic here, in true vi style:
1Gq10dwjq100000#1
Randy fixed this up in the comments to work on more than 100000 lines:
ggqqdwj#qq#q
For those starting out with Vim, this breaks down to:
gg - Go to first line
qq - Record a macro into register 'q'
dwj#q - The macro:
dw - delete word at cursor
j - go down one line
#q - run the macro in register 'q'
q - Stop recording the macro
#q - Execute the macro in register 'q'
In essence, the macro is recursive - it deletes a word, moves down a line, then calls itself again, repeating for each line until end of file. The final '#q' is the initial (manual) call needed to set the macro off on every line.
I would use something like the following:
:%s/^\w+\s+//
The regular expression will match one or more "word" characters starting at the beginning of the line followed by at least one whitespace character. It will remove the word and any following whitespace. If a line can contain only a single word -- and you still want it removed -- you could use alternation to match either whitespace or the end of line.
:%s/^\w+(\s+|$)//
First word (where word is defined as no whitespace)
:%s/^\s*[^ ]* //g
Delete pattern:
:%s/< insert pattern here >//g
What about this?
:%!cut -s -d' ' -f2-
:%s,^[^ ]*,,
From the beginning of the line match anything, but not a space and replace with none.
Although this is an old question, if someone else is looking to do this you could do use visual block.
press ctrl+v
select all the lines you would want to edit
now use arrow keys to select the entire word
press 'x'
That would delete the first word in all the lines. This method is especially very handy to edit log files

Resources