3

When I use vim to write regular text (not source code), I often highlight a long line, or a paragraph, and do: !fmt to re-format the lines into proper length lines. I think they up up being 75 characters per line, or something like that.

The nice thing is that fmt also takes care of indentation, so if my original line started with one indentation level, all the lines will start with one indentation level.

The problem I recently noticed is that doing this, fmt replaces the leading whitespace (which in my text is ALWAYS spaces) with tabs. Even though I have:

set tabstop=4
set softtabstop=4
set expandtab

set in my vimrc, fmt still inserts tabs, rather than spaces.

Is there a way I can fix this? From reading the fmt manual, there's no option to tell it to use spaces instead of tabs. I suppose I could make a bash script wrapper around fmt that converts tabs to spaces, but installing that on every single system I use vim on would be a pain.

Update: Upon closer inspection, the GNU fmt binary works fine. It's the MacOS BSD fmt that turns spaces into tabs.

4
  • My read of the source code of fmt is that it will use tabs if you have any tabs in your input... Otherwise, it will use spaces.
    – filbranden
    Jan 6, 2021 at 19:10
  • 1
    I added an update. Turns out the problem is with the BSD fmt - not Linux's GNU fmt. It's funny, because the man page for BSD fmt actually says For instance, within visual mode of the ex(1) editor (e.g., vi(1)) the command !}fmt will reformat a paragraph, evening the lines.. So this is something it's actually meant to do, but doesn't appear to do properly. As a side-note, not sure why it says !}fmt. Why the }?
    – John
    Jan 6, 2021 at 20:59
  • Vim has built-in commands gq (and also gw, but maybe that's a recent addition in Vim) that can do formatting for you. It's a built-in, so it respects your Vim settings. I'll post an answer about it... But kinda busy right now, will do so later. } is a motion, to filter through the external fmt command until the next blank line ..
    – filbranden
    Jan 6, 2021 at 22:12
  • 1
    You could also use :%retab (possibly with a bang) to change out the tabs for spaces
    – D. Ben Knoble
    Jan 7, 2021 at 1:26

1 Answer 1

2

Instead of using an external fmt program to format your text by filtering it through the external command, consider using Vim built-in commands gq or gw.

Both commands take a motion or text object to determine which lines of text to act on (by the way, the same is true for the ! command, which also takes a motion, so you can use !}fmt to format from the current line until the next blank line.)

So you can use gq} to format from the current line to the next blank line, gqip to format the current paragraph (block delimited by blank lines), gqG to format from the current line to the end of the file, gggqG to format the whole buffer (first gg jumps to the first line.) Additionally, you can select lines in Visual mode and use just gq to format the lines in the Visual selection. Finally, there's gqq (or gqgq) and gww (or gwgw) to format the current line only.

These commands will respect the Vim settings you're using, such as 'textwidth' to determine how long lines can be, and 'expandtab' to determine whether to use tabs or spaces. Since they're built-ins, they can have easy access to the settings you're already using, which is not the case when you're filtering the text through an external command, which will most surely not know about the settings you had inside Vim.

The difference between gq and gw is that gq may be customized using Vimscript. You can write your own formatter function and set 'formatexpr' to call that function. This is quite useful when using it to format code, where the 'formatexpr' can be made aware of the language it's formatting and can then wrap it correctly while maintaining the correct syntax.

The gq command can also use a 'formatprg' pointing at an external binary to use as a filter. So you could even have it configured to actually use fmt under the hood if you wanted to!

The gw command on the other hand doesn't use any of the customizations, so it will always give you access to Vim's native formatter, even if you have a 'formatexpr' or 'formatprg' set. As a bonus, gw will preserve the cursor position across formatting the block (which is not often done by the custom formatters, since it's usually hard to do so in a function, and would be virtually impossible when using an external filter, where there's really no protocol to communicate that information of cursor position back to Vim.)

Hopefully you'll find these commands useful and see how they're much superior to filtering through an external fmt. Not to mention they should easily prevent the issue you're having with fmt adding tabs to your text.

3
  • 1
    Amazing. I never knew. I'll be retraining my brain to use this instead of fmt. Works incredibly well, especially as gwip or gqip. Thanks for the great explanation, even covering the difference between gw and gq, and the meaning of }. Super helpful!
    – John
    Jan 7, 2021 at 15:42
  • 1
    I believe it's formatprg, not formatprog (at least in vim). I tried to edit the answer but its < 6 characters which I can't do. Jan 8, 2021 at 10:27
  • Thanks @Andrew! I just edited it with the correction.
    – filbranden
    Jan 8, 2021 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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