1

One can find several examples of formatting one sentence per line such as right here: How to set up Vim to work with one sentence per line?

The question states that: My current solution is to join a paragraph with J, then run :'<,'>s/\. /.\r/g.

However if there are hard breaks in the text, the provided solution still requires joining the paragraph (or selected text) with J before gq.

I tried unsucessfully incorporating this into the function like this among other attempts:

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

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

Is there a way we can do this in one shot?

1
0

One issue with your current approach is that after joining the lines, what you have is a single long line, so the range on the second command doesn't make sense anymore. You could fix that fairly easily by dropping it from the second command.

I'd also match a final \s*\ze\S in the substitute regex, both to remove all trailing/leading spaces and also to prevent it from adding an extra blank line at the end of the block (if it ends with a period.)

This 'formatexpr' is also pretty incompatible with a non-zero 'textwidth'. As Vim tries to reformat lines after the 'textwidth' is reached, whenever this ends up splitting it into two or more lines, the cursor will end up at the start of the last line and you'll be most probably inserting text at the wrong location.

You can work around that by simply setting 'textwidth' to 0 whenever you use this 'formatexpr' or by actually making it ignore a 'textwidth' setting and refuse to break lines while you're inserting text. You can do the latter by checking mode() and seeing if you're in an Insert or Replace mode. (The docs at :help 'formatexpr' detail this option.)

In any case above, this will only trigger when you use a gq command, such as gqip to reflow the current paragraph, at one sentence per line.

This is the final function I got that does the job:

function! OneSentencePerLine()
  if mode() =~# '^[iR]'
    return
  endif
  let start = v:lnum
  let end = start + v:count - 1
  execute start.','.end.'join'
  s/[.!?]\zs\s*\ze\S/\r/g
endfunction
set formatexpr=OneSentencePerLine()

It's actually possible to have it reformat the text on-the-fly and have 'textwidth' trigger it. (In that case, you might even want to use a very short 'textwidth' to trigger it early enough to catch even short sequences.) But the tricky part of that is keeping track of the cursor position and ensuring the cursor will be at the same location (on the same word, same insertion position) after reformatting the text. That's far from trivial. Possibly not something for an answer here... (But we'll see, I've seen answers with long pieces of code before. Or someone might take inspiration and write a plug-in to implement such a smart format expression.)

1
  • 1
    That is super awesome!!! Thank you so much for the detailed answer. It works perfectly. Hopefully that will be useful for the asciidoc/latex and other git users who need to keep track of sentence changes! FYI I set textwidth=0 anyways for asciidoc. (with wrap,showbreak,breakindent) – Boyd Jan 2 at 11:56

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.