17

I'm currently writing a lot of plain text (and LaTeX with minimal formatting in paragraphs), and it would be nice if I could set up vim to keep each sentence (for simplicity, text terminated by a '. ', '! ', or '? '; that is a terminating punctuation followed by a space so as to avoid breaking on decimal numbers) on its own line so the VCS diffs will be more useful.

Minimally, I'd like gq to format text from:

He lay flat on the brown, pine-needled floor of the forest, his chin on his
folded arms, and high overhead the wind blew in the tops of the pine trees.
The mountainside sloped gently where he lay; but below it was steep and he
could see the dark of the oiled road winding through the pass. There was a
stream alongside the road and far down the pass he saw a mill beside the
stream and the falling water of the dam, white in the summer sunlight. 

To:

He lay flat on the brown, pine-needled floor of the forest, his chin on his folded arms, and high overhead the wind blew in the tops of the pine trees.
The mountainside sloped gently where he lay; but below it was steep and he could see the dark of the oiled road winding through the pass.
There was a stream alongside the road and far down the pass he saw a mill beside the stream and the falling water of the dam, white in the summer sunlight.

But it would be nice if vim would also perform this formatting as I type (like it does with textwidth reflow). Is this possible?

My current solution is to join a paragraph with J, then run :'<,'>s/\. /.\r/g which is pretty good as there are few exclamation and question marks, but it would be much better if I could make gq slightly smarter.

23

The 'formatexpr' option

You can achieve this using the 'formatexpr' option. From the help:

Expression which is evaluated to format a range of lines for the gq operator or automatic formatting (see 'formatoptions'). When this option is empty 'formatprg' is used.

Setting your 'formatexpr' correctly will work both when using gq and while inserting text.

A simple example

Here's a simple VimScript function and corresponding 'formatexpr' value that should do the job. I haven't tested it around all the edge cases, but it worked nicely for my simple tests.

function! MyFormatExpr(start, end)
    silent execute a:start.','.a:end.'s/[.!?]\zs /\r/g'
endfunction

set formatexpr=MyFormatExpr(v:lnum,v:lnum+v:count-1)

Explanation

The expression is a call to the function MyFormatExpr(), passing in the starting and ending lines over which the formatting will be applied. These lines are computed using the automatically populated variables v:lnum (the first line to be formatted) and v:count (the number of lines to be formatted).

The MyFormatExpr() function constructs a :substitute command with the passed in range, and replaces a whitespace following an end-of-sentence punctuation mark (., !, or ?) with a newline (\r).

Caveats

  • The MyFormatExpr() function doesn't preserve leading indentation. Some more sophisticated logic would be needed to fix that.
  • There may be other edge cases where this falls apart. Give it a test drive and tweak as necessary!
  • Oh, yeah this totally the way to go, ignore my answer. – Sukima Apr 8 '15 at 2:39
  • This is awesome! What I wish I could do is have the function run a macro, like "move forward to the next sentence ) and insert a newline; repeat until you get to the end of the range." Then it would presumably apply any indentation rules. But my Vim-fu is too weak for that. :) – Nathan Long May 21 '16 at 11:13
  • This splits on e.g. e.g. blah, and Blah et al. 2018 which is a little annoying. You can un-match patterns like that with something like 's/\(e\.g\|\<al\)\@<![.!?]\zs /\r/g'. Just add more patterns in to the first group to avoid other things you don't want to split on. Otherwise, this is super useful, thanks. I hadn't come across gq before, and gqip is super useful for formatting text that co-author's text editors have mangled. – naught101 Feb 14 '18 at 2:18
  • Is there a way to add this behaviour to the default behaviour? I would like to be able to wrap at a given text width, as well as splitting at the end of sentences.. – naught101 Jan 15 at 1:17
1

Well you can setup up some maps and autocommands:

function! AddSentenceMaps()
  imap .<Space> .<CR>
  imap !<Space> !<CR>
  imap ?<Space> ?<CR>
  exec "nmap gq vipJ:s/\\([.!?]\\)\\s\\+/\\1\\r/g<CR>"
endfunction

autocmd Filetype customtxt call AddSentenceMaps()

Then just set the file type to customtxt or call the AddSentenceMaps function manually.

0

I want to add my thoughts about this issue. Usually people want this kind of feature because of jumping over wrapped lines when ones navigating through the code with j and k commands which is default behaviour of Vim. For the navigation through wrapped lines I use gj - move down and gk - move up. Also there is possibility remap standard j and k command on the g-prefixed counterparts, but IMHO it would be overkill.

0

It doesn't answer your specific question about reformatting, but 'set wrap' together with 'set linebreak' can make editing long lines much more pleasant. Combine this with gj and gk to navigate within wrapped lines. I map the up and down arrows to gj and gk respectively so that I have a choice between moving up and down real lines or visual lines. Like wise for Home / End to g0 / g$.

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.