1

I have the following text

Plan based ................................. 32  
Iron based in Compound .............................. 34  
Gallium Arsnide based III-V ............ 36  
...
Indium phosphide based III-V ............ 200

but there are about 100 lines not 4 or so.

I wish to pad this text by changing the sequence of dots " ...... " so the index text is ordered as shown below

Plan based ................................... 32  
Iron based in Compound ....................... 34  
Gallium Arsnide based III-V .................. 36  
...
Indium phosphide based III-V ................ 200

I can visually select the range using 'a v 'b with the a mark being at the top of the selection and the b mark being at the bottom.

Is it possible to pad it as desired with a macro or other way

(the triple digit 200 is one space to the left!)

3
  • 1
    If you want to consider plug-ins that might help with this task, I suggest looking at vim-easy-align and tabular, which are popular plug-ins that do alignment and tables. (Sorry, I'm not a user of either, so not sure if any covers your exact use case. Just thought I'd mention them, in case you'd like to take a closer look at them.) – filbranden Apr 23 at 2:03
  • 1
    Useful screen cast on tabular vim here vimcasts.org/episodes/aligning-text-with-tabular-vim – Kes Apr 25 at 11:48
  • 1
    Tabularize vim plugin works with commands like <,'>:*Tabularize /\d\d$. It does seem to align the index numbers to the same column, but does not pad out the "..." dots. – Kes Apr 25 at 12:10
2

One option that I find quite flexible and intuitive is to use the :normal command with a range, in order to repeat a sequence of Normal mode commands on multiple lines of text.

In this particular case, you can use that sequence to:

  • Add more dots to all lines, ensuring the dots will go past a specific column
  • Then, delete from a specific column until the end of the current word (which should truncate the dots), left-aligning the numbers.
  • Finally, remove one extra dot from the lines which have 3-digit numbers, in order to right-align them.

Assuming this block of text is present in the middle of a file that has unrelated contents, you can start by visually selecting only the block that you want to reformat. Move to the first line of the block, then use V to start linewise Visual mode, then } to jump to the next blank line (right after the end of the block), then h to go up to the previous line, which will be the end of the block. At this point, you'll have a Visual selection around the block you want to reformat.

Then, you can use a :normal command to insert more dots to every line (so you have enough on every line to truncate at a specific column.) You can use f. to move the cursor to the first . in the line, followed by 20i. to insert twenty dots at that location. This assumes that there is no .s in the items and that 20 dots are enough to insert. You might need to adapt this command if this assumption is invalid in your case. But assuming it's correct, the command would be:

:'<,'>norm f.20i.

Note that you don't need to type the '<,'> part! That's added automatically by Vim when you type : from a Visual selection. That part instructs Vim to execute the following command (in this case, :normal) only in the Visual range.

You also don't need an <Esc> to terminate the insertion, the :normal command will implicitly leave Insert mode at the end of its execution.

After you run this command, you'll have 20 extra dots on every line in your block.

The execution of this command will deactivate your Visual selection, you can now select it back again using the gv command.

Now it's time to truncate the dots at a specific column. Let's say you want to truncate them at column 42. Then you can execute the 42l Normal-mode command to move right 42 characters (which should put you on column 43 and in the middle of the string of dots), followed by the dE command to delete until the end of the current word (which should be the end of the dots, since they're followed by a space.) Putting it all together:

:'<,'>norm 42ldE

Again, you don't need to type the '<,'> part, since you had a Visual selection (from the earlier gv command.)

If everything went well, this sequence will have aligned the numbers and at this point we only need a tweak for the lines which have 3-digit numbers. If you remove a single dot from those lines, you'll have right-aligned all numbers.

Move to the first line with a 3-digit number, then start linewise Visual selection with V, followed by } and h to select the block of lines that have 3-digit numbers.

Here, you'll want to use Normal-mode command f. to move to the beginning of the string of dots, followed by x to delete a single character. So:

:'<,'>norm f.x

Done! Everything is properly aligned.

This procedure makes quite a few assumptions about your text, such as which characters are in the items (no dots), that adding 20 extra dots is enough, which column to truncate on, that you only have 2- and 3-digit numbers and that they're in order, so that all the 3-digit numbers are at the end.

But the point of this approach is that you can use some of Vim's powerful commands (such as :normal) in order to repeat simple operations on many lines of text.

If you manage to come up with a suitable strategy (such as adding more dots, then truncating at a specific location, etc.) and manage to come up with simple operations you can use on blocks of lines, it can help you come up with very efficient ways to handle one-off formatting tasks (for which a plug-in is probably overkill) without making them overly repetitive.

You can of course mix and match other commands. For example, :s with a pattern might be useful to add more dots at the proper location, or drop one from the lines with 3-digit numbers. :g is also very useful. Visual-Block mode is particularly useful when dealing with columns. Recording and replaying macros are also a very useful tool.

Hopefully you'll find these suggestions useful and be able to incorporate some of these techniques into your Vim tool belt to help you tackle editing and formatting tasks with very efficient operations.

9
  • 2
    No need for gv:, use :* – D. Ben Knoble Apr 23 at 12:08
  • 1
    @Ben Nice! I didn't know about that one. For reference, help on :* can be found under :help :star-visual-range. Also, it only works under 'nocompatible', but I don't imagine anyone is using Vim under 'compatible' anymore anyways... – filbranden Apr 23 at 16:07
  • 1
    @Filbranden Thank you ! :) A very comprehensive answer. I will give it a moment or two to digest and will be sure to read it again, and probarly again, since compounding helps learning! Appreciated & awsome. I've definatley added some ideas to my vim tool belt with this – Kes Apr 24 at 6:42
  • 1
    gv is very handy! To align the 1 digit page number numbers and the 2 digit page numbers with the 3 digit page numbers I used '<,'>s/ \([0-9]\{1\}\)$/ \1/c and '<,'>s/ \([0-9]\{2\}\)$/ \1/c – Kes Apr 24 at 7:55
  • 1
    Somehow :norm had fallen out of my tool kit vocabulary. It is extremely handy! gv is a great way to re-select visual selection, as is :norm 'aV'b for selecting between markers a and b. – Kes Apr 24 at 7:59
2

I would define a function like

function DotsToCol(col) abort
  " get rid of dots
  exe 'substitute/\.//' . (&gdefault ? '' : 'g')
  " find first digit
  normal! $bh
  " insert the rest of the dots
  execute 'normal!' (a:col - col('$') + (col('$')-col('.'))).'i.'
endfunction

And do, e.g., :%call DotsToCol(50). I get (note it didn't handle the 3rd digit quite right):

Plan based ...................................... 32
Iron based in Compound .......................... 34
Gallium Arsnide based III-V ..................... 36
Indium phosphide based III-V .................... 200
9
  • I think there's a bug: when I run this with no range (or . range) and cursor anywhere on second line of the example text, param value 50, it inserts 3 dots at the start of the preceding line. Same thing works fine on the other three lines. Perhaps that's too much of an edge case but FYI. – B Layer Apr 22 at 22:41
  • @BLayer that’s very odd indeed. (Also the col parameter is a bit buggy because of a fix I made while developing, so using 50 gets you 51… it’s a WIP I don’t have more time for tonight) – D. Ben Knoble Apr 22 at 22:51
  • 1
    Actually, it's just the use of substitute g flag. Breaks for people who have 'gdefault' enabled (which I believe is fairly common). Temporarily disabling that setting is one quick workaround. – B Layer Apr 22 at 23:12
  • 1
    Added one line fix. :) – B Layer Apr 22 at 23:22
  • 1
    @Kes you can add the e flag to the substitute to suppress the message. Good point about single digits—to make it clearer you could use execute 'normal! $F '. The last bit is building a normal command to insert the dots (i.) the right number of times. The math is (supposed to) build a count that gives the right number of dots. – D. Ben Knoble Apr 28 at 13:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.