What is the best way to refactor a Ruby ‘if’ statement into one-line shorthand form in Vim? - 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.

Related

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!

How to efficiently concatenate one set of lines with another set of lines in Vim?

Say, I have 10 consecutive lines followed by another 10 lines, e.g.:
1
2
⋮
10
a
b
⋮
j
I want to append the lines from the second range (a…j) to the lines in the first range (1…10), so that the above 20 lines turn into the following 10 lines, instead:
1a
2b
⋮
10j
Which Vim commands can I use to achieve this?
I would start going to line with a, then CTRL-V, 10j$d to blockwise delete everything.
Then :set virtualedit=all, goto first line, move cursor right by 10 characters for example, and press p. Now remove first sequence of spaces in your ten lines.
There is a second way, which is basically the same:
10dd
:call setreg('"', #", '^V') where ^V is typed with CTRL-V CTRL-V
(this will turn the register blockwise)
P
:,+10s/ //g
You can also do that programatically: enter Ex mode with Q, and type this
let i = 1
while i <= 10
call setline(i, getline(i) . getline(11))
11d
let i = i + 1
endwhile
vi
If you intend to reuse it put this into your vimrc :
function PasteLines(startline,numlines)
let i = 0
while i < a:numlines
call setline(a:startline+i, getline(a:startline+i) . getline(a:startline+a:numlines))
exec '' . (a:startline+a:numlines) . 'd'
let i = i + 1
endwhile
endfunction
And call it with :
:call PasteLines(1, 10)
where 1 is the first line, and 10 the number of lines. You need therefore 20 lines.
This would be my way:
qaG"aDdd9-$"apq
9#a
Explanation:
q # Begin recording typed characters...
a # to register 'a'
G # Set cursor in last line.
"aD # Delete the content from the beginning of line till the end and save it in register 'a'.
dd # Previous command deleted the content but left the line in blank, delete the complete line.
9- # Go back 9 lines.
$ # Set cursor at the end of current line (last number in your example).
"ap # Paste content of register 'a' (at end of line without newline character).
q # Stop recording.
---------------
9 # Run nine times.
#a # Commands of register 'a' (all previous commmands).
Register 'a' where I record commands is a different register of where I save content of each line, although they are named the same (letter 'a').
1. Assuming that the cursor is located on the first line of the
twenty-line block, let us consider the following short Ex command:
:,+9g/^/''+10m.|-j!
This is the :global command running on lines that belong to the
range of the next ten lines (starting from the current one). On every
of these lines, two Ex commands, ''+10m. and -j!, are sequentially
executed. The first command takes the tenth line under the line at
which the cursor has been positioned and inserts it just below the
line where the cursor is currently located, using the :move command.
The :join command, -j!, appends the just moved line to the one
just above it (without inserting or deleting whitespace in between,
due to the ! modifier).
There are two considerations that is necessary to take into account in
order to get the idea of that line movement. First, before the command
specified in a :global is executed on yet another line, the cursor
is positioned at the first column of that line. This way, the address
referenced in the aforementioned :move command as ., corresponds
to the latest line on which the command is currently being run.
Second, the number of the line that was the current one just before
a :global command was sent to execution, is added to the jump list.
Therefore, its address can be obtained in ranges through the
' pseudo-mark (see :help :range).
See also my answer to the question “Vim paste -d ' ' behaviour
out of the box?”.
2. The same effect can be achieved by means of Normal mode commands:
qqdd9+PgJ9-q9#q
This sequence of commands implements the same moving scheme, using a
macros to repeat a single-line transferring operation. The commands to
concatenate the first pair of lines, dd9+PgJ9-, are recorded in the
"q register using the q command. Similarly to the Ex command
proposed above, the macros deletes the current line (dd), moves the
cursor nine lines downward (9+), inserts the just cut line above the
new cursor position (P), joins that two lines without adding or
removing any spaces between them (gJ), and moves the cursor nine
lines upward (9-). Finally, these actions are automatically iterated
nine times using the # command to join the remaining nine pairs of
corresponding lines.

How to delete line(s) below current line in vim?

Is there a command to delete a line (or several lines) that is immediately below current line?
Currently I'm doing it as:
jdd and then . to repeat as needed.
Is there a command that would combine all these?
UPDATE: The reason I would like to have such command is that I don't like to move away from current position, yet be able to delete lines below.
The delete ex command will work nicely.
:+,$d
This will delete all the lines from current +1 till the end ($)
To delete the next 2 lines the follow range would work, +1,+2 or shorthand +,+2
:+,+2d
As #ib mentioned the :delete or :d command will move the cursor to the start of the line next to the deleted text. (Even with nostartofline set). To overcome this we can issue the `` normal mode command. `` will jump back to the exact position before the last jump, in this case the :d command. Our command is now
:+,+2denter``
Or as one ex command
:+,+2d|norm! ``
To make this easier we wrap this all up in a command:
command! -count=1 -register D :+,+<count>d <reg><bar>norm! ``
Now to delete the next following 3 lines:
:3D
This command can also take a {reg} like :delete and :yank do. So deleting the next 4 lines into register a would be:
:4D a
For more information
:h :d
:h :command
:h :command-register
:h :command-count
:h ``
dG should work.
This means delete all rows until end of file from current cursor.
This will delete ALL lines below the current one:
jdG
Unfortunately that will move the cursor to the beginning of current line after the deletion is made.
well, to do it simply you could use the xxdd command. Most of the time I know (at least have an idea) the size of the script I am editing. So, the command as below is usually more than enough :
99dd
999dd to remove 999lines starting at the cursor position.
9999dd
99999dd for very long script ;)
The other solutions are informative, but I feel it'd be simpler to use a macro for this:
qq (begins recording)
jddk (go down, delete the line, and go back up - i.e. the thing you want to do)
q (end recording)
Now you can do #q to perform this action, maintaining the cursor at the current position. You could also do something like 5#q to delete 5 lines below the cursor.
And finally, if you're repeating the action more than once, you could just type ## after the first time you run #q (this repeats the last used macro - in this case q)
This is a job for marks!
Try maj20dd`a
ma sets the file-specific mark 'a', j20dd does the deletion you want (20 lines in this case), and `a restores you to the mark's position (line and column).
Obviously this pattern can be extended to do anything you want before returning to the mark. If you use mA (or any other capital letter) the mark will actually be unique across files, so you can even edit elsewhere before returning. If you have a very frequent usage you could make it a macro as suggested above.
You could enter the number of lines to delete: j 20 dd k.
Just for the fun of it, you can define a little function that does
exactly what you described: deletes the next n lines below the
current line and restores the initial cursor position.
function! DeleteNextLines(n, reg)
let l = line('.')
let m = min([a:n, line('$')-l])
if m > 0
let c = col('.')
exe '+,+'.m 'd' a:reg
call cursor(l, c)
endif
endfunction
Also, you can define a command that accepts the number of lines
to delete (one, if omitted) and the register name to use as an
optional argument (just like the :delete command).
:command! -range=1 -register -bar D call DeleteNextLines(<count>, <q-reg>)
Additionally, you can define a mapping for triggering the above
:D command, if it is necessary.

Prepending a character followed by the line number to every line

I'm hand-editing CNC Gcode text files and need a way to reference locations in the file and on the toolpath.
I want to modify every line in the text file so that it begins with the the upper case letter N followed by the line number, incremented in tens for each successive line, then a whitespace followed by the original text on that line. How can I do this in Vim?
I'm not sure about vi, but (since you're using the vim tag) Vim allows you to accomplish your task as follows:
Adjust the first line by hand (insert a N10 at the beginning of the line), then put the cursor at the beginning of the next line.
Press qb to start recording a macro (the b names the register used to store the macro; feel free to use a different letter -- and definitely do use a different letter if you've got something useful stashed away in b).
Move the cursor upward to the beginning of the previous line (which you have adjusted by hand). Press v to start visual selection mode, then f to move the cursor to the next space on the line (if you use a single space as your whitespace separator, that is; adjust this step if you're using a tab or multiple spaces).
Press y to yank the selected text. This will also remove the visual selection.
Move the cursor to the beginning of the next line. Press P to insert the previously yanked text before the cursor, that is, on the very beginning of the line.
Move the cursor to the numeric part of the line header. Press 10 C-a (1, 0, control + A) to increment that number by 10.
Move the cursor to the beginning of the next line. Press q to stop recording the macro.
Press 10000000 #b to execute the macro 10000000 times or until it hits the end of the file. This should be enough to take care of all the lines in your file, unless it is really huge, in which case use a bigger number.
...or use Vim to write a simple script to do the job in whichever language you like best, then run it from a terminal (or from withing Vim with something like :!./your-script-name). ;-)
The following command will prepend ‘N<line number * 10>’ to every line:
:g/^/exe 'normal! 0iN' . (line('.')*10) . ' '
You can do it easily in Vim with this:
:%s/^/\=line(".")*10 . " "/
This replaces the start of every line with the result of an expression that gives the line number times ten, followed by a space.
I have not timed it, but I suspect it might be noticeably faster than the other Vim solutions.
Cheating answer:
:%!awk '{print "N" NR "0", $0}'
There are two ways to implement that without resorting to external
tools: via a macro or by using Vimscript. In my opinion, the first way
is a little cumbersome (and probably not as effective as the solution
listed below).
The second way can be implemented like this (put the code into your
.vimrc or source it some other way):
function! NumberLines(format) range
let lfmt = (empty(a:format) ? 'N%04d' : a:format[0]) . ' %s'
for lnum in range(a:firstline, a:lastline)
call setline(lnum, printf(lfmt, lnum, getline(lnum)))
endfor
endfunction
The NumberLines function enumerates all lines of the file in a given
range and prepends to each line its number according to the provided
printf-format (N%04d, by default).
To simplify the usage of this function, it is convenient to create
a command that accepting a range of lines to process (the whole file,
by default) and a optional argument for the line number format:
command! -range=% -nargs=? NumberLines <line1>,<line2>call NumberLines([<f-args>])

How do I move to end of line in Vim?

I know how to generally move around in command mode, specifically, jumping to lines, etc. But what is the command to jump to the end of the line that I am currently on?
Just the $ (dollar sign) key. You can use A to move to the end of the line and switch to editing mode (Append). To jump to the last non-blank character, you can press g then _ keys.
The opposite of A is I (Insert mode at beginning of line), as an aside. Pressing just the ^ will place your cursor at the first non-white-space character of the line.
As lots of people have said:
$ gets you to the end of the line
but also:
^ or _ gets you to the first non-whitespace character in the line, and
0 (zero) gets you to the beginning of the line incl. whitespace
$ moves to the last character on the line.
g _ goes to the last non-whitespace character.
g $ goes to the end of the screen line (when a buffer line is wrapped across multiple screen lines)
The main question - end of line
$ goes to the end of line, remains in command mode
A goes to the end of line, switches to insert mode
Conversely - start of line (technically the first non-whitespace character)
^ goes to the start of line, remains in command mode
I (uppercase i) goes to the start of line, switches to insert mode
Further - start of line (technically the first column irrespective of whitespace)
0 (zero) goes to the start of line, remains in command mode
0i (zero followed by lowercase i) goes the start of line, switches to insert mode
For those starting to learn vi, here is a good introduction to vi by listing side by side vi commands to typical Windows GUI Editor cursor movement and shortcut keys.
vi editor for Windows users
If your current line wraps around the visible screen onto the next line, you can use g$ to get to the end of the screen line.
I can't see hotkey for macbook for use vim in standard terminal. Hope it will help someone.
For macOS users (tested on macbook pro 2018):
fn + ← - move to beginning line
fn + → - move to end line
fn + ↑ - move page up
fn + ↓ - move page down
fn + g - move the cursor to the beginning of the document
fn + shift + g - move the cursor to the end of the document
For the last two commands sometime needs to tap twice.
The dollar sign: $
Press A to enter edit mode starting at the end of the line.
The advantage of the 'End' key is it works in both normal and insert modes.
'$' works in normal/command mode only but it also works in the classic vi editor (good to know when vim is not available).
Also note the distinction between line (or perhaps physical line) and screen line. A line is terminated by the End Of Line character ("\n"). A screen line is whatever happens to be shown as one row of characters in your terminal or in your screen. The two come apart if you have physical lines longer than the screen width, which is very common when writing emails and such.
The distinction shows up in the end-of-line commands as well.
$ and 0 move to the end or beginning of the physical line or paragraph, respectively:
g$ and g0 move to the end or beginning of the screen line or paragraph, respectively.
If you always prefer the latter behavior, you can remap the keys like this:
:noremap 0 g0
:noremap $ g$
In many cases, when we are inside a string we are enclosed by a double quote, or while writing a statement we don't want to press escape and go to end of that line with arrow key and press the semicolon(;) just to end the line. Write the following line inside your vimrc file:
imap <C-l> <Esc>$a
What does the line say? It maps Ctrl+l to a series of commands. It is equivalent to you pressing Esc (command mode), $ (end of line), a (append) at once.
Or there's the obvious answer: use the End key to go to the end of the line.
Possibly unrelated, but if you want to start a new line after the current line, you can use o anywhere in the line.
The easiest option would be to key in $. If you are working with blocks of text, you might appreciate the command { and } in order to move a paragraph back and forward, respectively.
I was used to Home/End getting me to the start and end of lines in Insert mode (from use in Windows and I think Linux), which Mac doesn't support. This is particularly annoying because when I'm using vim on a remote system, I also can't easily do it. After some painful trial and error, I came up with these .vimrc lines which do the same thing, but bound to Ctrl-A for the start of the line and Ctrl-D for the end of the line. (For some reason, Ctrl-E I guess is reserved or at least I couldn't figure a way to bind it.) Enjoy.
:imap <Char-1> <Char-15>:normal 0<Char-13>
:imap <Char-4> <Char-15>:normal $<Char-13>
There's a good chart here for the ASCII control character codes here for others as well:
http://www.physics.udel.edu/~watson/scen103/ascii.html
You can also do Ctrl-V + Ctrl- as well, but that doesn't paste as well to places like this.

Resources