76

I have my source code for copy operators written as follows.

foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;

I'd like to line things up as follows (more human readable, isn't it?).

foo    = rhs.foo;
foobar = rhs.foobar;
bar    = rhs.bar;
toto   = rhs.toto;

Is there a VIM magic insert-up-to-column-N, or something like that that would allow me to line things up using a couple of keystrokes per line?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Didier Trosset
  • 36,376
  • 13
  • 83
  • 122
  • 6
    No, it's not more human readable. I use a proportional font so it will be just worse. Also it's hard to maintain and it becomes hell to merge. – Yakov Galka May 27 '11 at 15:35
  • 3
    Be careful: If you've got 20 of these in a row and you decide the longest name needs to be changed to something longer, you'll either need to modify all of them, or accept some irregularity. That's a lot of extra work. :) – Bill May 27 '11 at 15:37
  • 29
    I choose to accept this additional work to layout the code on the grounds that the code is far more often read than written. Given the compiler can read both and really don't care at all, I decided to concentrate on the code being readable by humans rather than by compilers. And it *is* easier to read for a human (using fixed width fonts). – Didier Trosset May 27 '11 at 15:44
  • 1
    There is a relatively universal alignment trick that works without plugins, see it in action in [the answer](http://stackoverflow.com/a/7538363/254635) to the question "[Inserting indentation for columns in Vim](http://stackoverflow.com/q/7529029/254635)". – ib. Dec 03 '11 at 03:31
  • off topic for stackoverflow.com. there is a dedicated stackexchange for vim/vi questions: https://vi.stackexchange.com/ – Trevor Boyd Smith Oct 19 '18 at 18:16
  • 1
    @TrevorBoydSmith Perfectly true. However, vi.so.com was inexistent in '11. It only started 4 years later. That's the reason this question ended up here. What about automatic links from vi.so.com to all questions of so.com tagged with vim? – Didier Trosset Nov 14 '18 at 11:06
  • @DidierTrosset very true. my comment is not really helpful then! sry about that. – Trevor Boyd Smith Nov 14 '18 at 14:41
  • @YakovGalka you say it's not more readable but you use a proportional font? I didn't know there was anyone who would actually use a proportional font for coding! Isn't that the single most unreadable thing you can do to your code? – NeilG Jul 01 '20 at 23:36
  • @NeilG: A quick search would reveal that some people do. Why would it make it less readable? The only problem is when others try to align text horizontally assuming monospace. But then horizontal alignment frequently breaks even with monospace fonts, so that's a reason not to align things rather than not using better fonts. – Yakov Galka Jul 02 '20 at 00:38
  • Thanks for your interesting response, @YakovGalka. I wasn't asking a question about whether people use proportional fonts when coding, I was just stating my surprise when I discovered you! I never even thought to ask that as a question, it seems so foreign to me. That's why I asked "isn't that the most unreadable thing...". I think that while there may be some elements of readability that can be established as factually significant (e.g. colour contrast just as an easy example) there are clearly some matters that are opinion. – NeilG Jul 03 '20 at 01:10

7 Answers7

133

The other answers here are great, especially @nelstrom's comment for Tabular.vim and his excellent screencast.

But if I were feeling too lazy to install any Vim plugins, yet somehow willing to use Vim macros, I'd use macros.

The algorithm:

For each line,
    Add tons of spaces before the symbol =
    Go to the column you want to align to
    Delete all text up to =, thereby shifting the = into the spot you want.

For your example,

foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;

Position the cursor anywhere on the first line and record the macro for that line by typing, in normal mode:

qa0f=100i <Esc>8|dwjq

Which translates to:

  1. qa -- Record a macro in hotkey a
  2. 0 -- Go to the beginning of the line
  3. f= -- Go to the first equals sign
  4. 100i <Esc> -- (There's a single space after the i, and the <Esc> means press escape, don't type "<Esc>".) Insert 100 spaces
  5. 8| -- Go to the 8th column (sorry, you'll have to manually figure out which column to align to)
  6. dw -- Delete until the next non-space character
  7. j -- Go to the next line
  8. q -- Stop recording.

Then run the macro stored at hotkey a, 3 times (for the 3 remaining lines), by putting the cursor on the second line and pressing:

3@a
Numeri
  • 1,027
  • 4
  • 14
  • 32
TalkLittle
  • 8,866
  • 6
  • 54
  • 51
  • 12
    Annoyed that I didn't think of this algorithm for macros. I prefer this solution to a plugin :) – TimCinel Jun 16 '12 at 16:11
  • 2
    @TalkLittle: excellent use of a plugin-free solution that does not require any additional addons. Excellent use of a commented vim command to help new users. All good practice in supporting and explaining the power of native vim. – dreftymac Mar 30 '16 at 17:24
  • 1
    I just discovered the pipe command! Using vim's protocol you can also do `d8|` instead of `8|dw`. – NeilG Jul 01 '20 at 23:58
  • This macro can cause strange behavior for longer lines if you have `wrap` set, because it then inserts spaces up to column X for the second 'line'. So if you want to make this macro into a mapping in your `.vimrc`, make sure that you enclose it with `:set nowrap` and `:set wrap`, like so: `nnoremap \e :set nowrap0f=100i 8dwj:set wrap` to avoid this behavior – flipjacob Oct 26 '20 at 10:19
29

If you are using a unix-like environment, you can use the command line tool column. Mark your lines using visual mode, then:

:'<,'>!column -t

This pastes the selected text into the stdin of the command after '<,'>!. Note that '<,'>! is inserted automatically when you hit : in visual mode.

evnu
  • 6,450
  • 2
  • 27
  • 38
  • Is there a way to narrow the spaces between columns? I've tried here and it inserts two spaces after the `=` sign. – freitass May 27 '11 at 15:45
  • @freitass I think I would go with `sed`: `:'<,'>!column -tx -s ' ' | sed 's/= /= /g'` but there probably exists an easier solution. – evnu May 27 '11 at 16:05
  • 2
    @freitass, I know this is old, but `:'<,'>!column -t -o ' '` works. See the man page for column. `-o` is the column separator arg. – jgon Aug 03 '16 at 17:30
  • I never knew about `column`, and this is a great use of Vim's shell call feature. I use `sort` and others all the time. It's like a suite of plugins available from your shell just by typing `:!`. – NeilG Jul 01 '20 at 23:49
24

There is a nice plugin which does exactly that and more, called Align.vim

For you case, you would need to select your expression and then type :Align =. It will align everything, using = as a separator and reference.

(There is a lots of options to align, left, right, cyclically, etc)

You can also check Tabular.vim which provides similar features. See the screencast there for a demo.

Xavier T.
  • 40,509
  • 10
  • 68
  • 97
  • 12
    There's not a great deal of difference between Align.vim and [Tabular.vim](https://github.com/godlygeek/tabular), but after trying out both, I ended up settling for the latter. I made a screencast about [aligning text with Tabular.vim](http://vimcasts.org/episodes/aligning-text-with-tabular-vim/), which covers some of my favourite features. – nelstrom May 28 '11 at 12:39
  • 1
    I have switched to Tabular as well. – Xavier T. Apr 18 '12 at 08:50
  • I've used Tabular for quite some time and love it for exactly this kind of thing! And @TalkLittle's approach with native vim was well done. – JESii Feb 15 '23 at 21:00
18

A quick, simple way to proceed is to add X spaces and then delete back to column X. For example, if X=40, type

40a<Space><Esc>d40|
Patrick Sanan
  • 2,375
  • 1
  • 23
  • 25
  • 2
    Macro with X=200: `q200ad200|q`. Then just type `@a` whenever you want spaces up to column 200. (simplest & best answer imho :) – roblogic Feb 13 '18 at 23:05
  • 1
    Typo in the above: `qa200ad200|q` – Vlas Sokolov Dec 19 '18 at 11:06
  • 2
    So cool. This is by fare the best solution here. When you understand it once, you remember it forever, because it is logical. It works out of the box with simple vim commands. Thanks Patrick. I also used it, to create dotted lines within a macro: `j04W40i-d40|` (Goto next line, Goto line start, Go 4 full words forward (4th column), insert dotted line (40 times ' -'), delete dotted line back to column 40. – Thomas Steinbach Aug 23 '19 at 08:30
  • No plugins, can be bound to a command easily, simple and works. thank you! – 9a3eedi Jun 01 '21 at 11:49
2

An alternative solution is to perform two consecutive substitutions:

%s/=/     =/
%s/\%>7c *//

The trick is the column pattern \%>7c that matches white spaces * only after the 7th column. Here foobar is the longest variable name with 6 characters so we need 7 in the regex.

builder-7000
  • 7,131
  • 3
  • 19
  • 43
1

We can use these two functions that I described in the below path for the same scenario : https://stackoverflow.com/a/32478708/3146151

simply put those two functions in your .vimrc or .gvimrc and call the functions as normal function call in your editor whenever you want.

The functions I have posted it here : https://github.com/imbichie/vim-vimrc-/blob/master/MCCB_MCCE.vim

We need to call this function in vim editor and give the Number of Occurrence of the Character or Space that you wants to move and the character inside the '' and the column number.

The number of occurrence can be from the starting of each line (MCCB function) or can be at the end of each line (MCCE function).

for the above example mentioned in the question we can use the MCCB function and the character we can use '=', so the usage will be like this in the vim editor.

:1,4call MCCB(1,'=',8)

So this will move the first = sign to the 8th column from line number 1 to 4.

These are the functions :

" MCCB - Move the Character to the Column from the Begin of line
" This is a function for Moving the specified Character 
" in a given range of lines to a the specified Column from the Begin of the line
" NOTE 1 :- If the specified character and the first character of the line are same
"           then the number of Occurance (num_occr) will be one less than the actual
" NOTE 2 :- Maximum space between the specified character with in the range 
"           of lines should be less than or equal to 80, if we need more than 80
"           then we need to insert more spaces by increasing the value 80 in the 
"           "nmap s 80i <ESC>" line inside the function
" Usage :-  in command mode do it like below
" Eg 1:-    :5,11call MCCB(1, '=', 8)
"           The above command will move the 1st Occurance from the begin of Character =
"           to the 8th Column of the lines from 5 to 11
" Eg 2 :-   :7,10call MCCB(2, '+', 12)
"           The above command will move the 2nd Occurance of Character = to the 12th
"           Column of the lines from 7 to 10
    function! MCCB (num_occr, mv_char, col_num) range
        if (a:firstline <= a:lastline)
            nmap s 80i <ESC>
            let line_num = a:firstline
            while line_num <= a:lastline
                execute "normal " . line_num . "G0" . a:num_occr . "f" . a:mv_char . "s" . a:col_num . "|dw"
                let line_num = line_num + 1
            endwhile
            nunmap s
        else
            execute printf('ERROR : Start line %d is higher thatn End line %d, a:firstline, a:lastline)
        endif
    endfunction

" MCCE - Move the Character to the Column from the End of line
" This is a function for Moving the specified Character 
" in a given range of lines to a the specified Column from the End of the line
" NOTE 1 :- If the specified character and the last character of the line are same
"           then the number of Occurance (num_occr) will be one less than the actual
" NOTE 2 :- Maximum space between the specified character with in the range 
"           of lines should be less than or equal to 80, if we need more than 80
"           then we need to insert more spaces by increasing the value 80 in the 
"           "nmap s 80i <ESC>" line inside the function
" Usage :-  in command mode do it like below
" Eg 1:-    :5,11call MCCE(1, ';', 20)
"           The above command will move the 1st Occurance from the End of Character ;
"           to the 20th Column of the lines from 5 to 11
" Eg 2 :-   :7,10call MCCE(5, 'i', 26)
"           The above command will move the 5th Occurance from the End of Character i
"           to the 26th Column of the lines from 7 to 10
    function! MCCE (num_occr, mv_char, col_num) range
        if (a:firstline <= a:lastline)
            nmap s 80i <ESC>
            let line_num = a:firstline
            while line_num <= a:lastline
                execute "normal " . line_num . "G$" . a:num_occr . "F" . a:mv_char . "s" . a:col_num . "|dw"
                let line_num = line_num + 1
            endwhile
            nunmap s
        else
            execute printf('ERROR : Start line %d is higher thatn End line %d, a:firstline, a:lastline)
        endif
    endfunction
Community
  • 1
  • 1
imbichie
  • 1,510
  • 3
  • 13
  • 23
0

I know this is old but I thought @talklittle had the right idea, the answer just got verbose. A quicker way is to insert spaces after the = and then remove all spaces after the 10th column like this:

   :1,4 s/^\(.*=\) *\(.*\)$/\1                           \2/
   :1,4 s/^\(.\{10\}\) *\(.*\)$/\1\2/
Pete D
  • 1
  • 1