6

I write screenplays with fountain. Dialogue syntax looks like this:

THE TERMINATOR
Hasta la vista. Baby.

I would like to be able to select the sentence: Hasta la vista., without also selecting THE TERMINATOR with a sentence motion, like with vis or das.

vis works exactly like I want when there is an empty line between THE TERMINATOR and Hasta la vista. But that breaks the fountain syntax.

What do I need to do, to make this work? The plugin reedes/vim-textobj-sentence doesn't work.

4

If you tried reedes/vim-textobj-sentence, that (hopefully) means you have kana/vim-textobj-user installed. You can (mostly) trivially use this to define your own sentence. Treat this as a starting point for experimentation - it seems to work well enough, but clobbers search etc; I'm sure it can be handled more cleanly:

function! LinewiseSentenceI()
  let ini_line = line('.')
  call search('\(^\|\.\_s\|\!\_s\|?\_s\)','b')
  call cursor(ini_line,getpos('.')[2])
  if getpos('.')[2] != 1
    normal! w
  endif
  let head_pos = getpos('.')
  normal! n
  let tail_pos = getpos('.')
  return ['v',head_pos,tail_pos]
endfunction

This searches for sentence bounds by looking for the start of the line (^), or a period, exclamation point, or question mark followed by whitespace or EOL (|\.\_s\|\!\_s\|\?\_s\). If that position is not column one, then it's on the punctuation and we do a w to get to the beginning of the sentence. Search in the other direction for tail boundary. Then we use kana/vim-textobj-user:

:call textobj#user#plugin('lwsent',{'lwsent':{'select-i-function':'LinewiseSentenceI','select-i':'is',},})

Read the documentation on kana/vim-textobj-user for ways to expand on this idea - this barebones demo only handles is and not as. And again, the function could likely be a lot cleaner, but this should be a good jumping-off point as to how to make kana/vim-textobj-user accomplish what you want.

  • thanks! Can you tell me where I have to put the snippet and the function? I get an error when I put it into .vimrc – ctholho May 29 '18 at 20:06
  • 1
    Ideally you'd make a plugin or something for it, and if I get the chance I'll tidy the answer up later accordingly. But I know why you're getting an error, a big mistake on my part - the line normal! ?\(^\|\.\_s\|\!\_s\|\?\_s\) needs a literal return after it - press Ctrl-v Enter at the end of that line (in insert mode), or else the search will never be executed. Forgot that wouldn't translate in copy/paste. Depending on settings the line will look like normal! ?\(^\|\.\_s\|\!\_s\|\?\_s\)^M or normal! ?\(^\|\.\_s\|\!\_s\|\?\_s\)<0d>. (Also be sure to omit the colon before call). – brhfl May 29 '18 at 20:42
  • 2
    Consider using the search() function instead – D. Ben Knoble May 29 '18 at 21:55
  • thanks. this works. only is there still a small bug: when I'm on the first letter of Hasta la vista. the function selects THE TERMINATOR and H. otherwise it works perfectly. – ctholho May 29 '18 at 22:22
  • 1
    @brhfl fountain shouldn't hard-wrap because every line break counts and eventually generating the pdf is done by a different program. I use soft-wrap. Thanks a bunch, btw. – ctholho May 30 '18 at 2:14
2

Update: Well, I made a plugin. Get it from ctholho/vim-textobj-sentence-line.

The plugin supports is and as motions. I didn't test all possible conditions but it does the job reliably. It also remaps the ) and ( motion to behave in the same way. Thanks to brhfl whose answer provided the building blocks for this.

Based on the answer of brhfl I cobbled something together that supports is and as. It still doesn't behave exactly like the old sentence text object and I post it here merely for completeness.

Install kana/vim-textobj-user and put this into your .vimrc.

function! NewSentence_inner()
  call search('\(^\|\.\s\w\|\!\s\w\|?\s\w\)', 'bce')
  let head_pos = getpos('.')
  call search('\($\|\.\|\!\|?\)', 'ce')
  let tail_pos = getpos('.')
  return ['v',head_pos,tail_pos]
endfunction

function! NewSentence_outer()
  call search('\(^\|\.\s\w\|\!\s\w\|?\s\w\)', 'bce')
  let head_pos = getpos('.')
  call search('\($\|\.\s\|\!\s\|?\s\)', 'e')
  let tail_pos = getpos('.')
  " handle last sentence of line, except if it's the only one
  if col('.') == col('$') && getline('.') =~ '\(.\+\(\.\|\!\|?\)\)\{2,}'
    let head_pos[2] = head_pos[2] - 1
    let tail_pos[2] = tail_pos[2] - 1
  endif
  return ['v',head_pos,tail_pos]
endfunction

call textobj#user#plugin('fsent',{
\  'fsent':{
\     'select-i-function':'NewSentence_inner',
\     'select-i':'is',
\     'select-a-function':'NewSentence_outer',
\     'select-a':'as',
\   },
\})

NewSentence_inner() looks for the beginning of a line, the end of a sentence. The flags bce mean that it looks in reverse, accepts matches under the cursor and moves the cursor to the end of the match. To get tail_pos it looks for punctiation.

NewSentence_outer() does the same but gives extra care to whitespace of the last sentence of a line. if col('.') == col('$') && getline('.') =~ '\(.\+\(\.\|\!\|?\)\)\{2,}' checks if it is at the last position of the line and if the line has at least two sentences. If that's the case the selection shifts one position to the left.

  • You can tell I was rushed - I had the search() documentation open and still hacked together that keep-cursor-from-going-up-a-line deal instead of just using the c flag! Oops. I would note that I used \_s instead of \s to ensure that the last sentence on a line works - \s looks for a space or tab, but doesn't include a line break. This more accurately mimics vim's inbuilt concept of a sentence, and adding the whitespace to your tail call will skip through the middle of abbreviations at least. – brhfl May 30 '18 at 14:14
  • yes, there was a problem with \_s and then my limited understanding of regex etc prevented me from adding it again. I will eventually take a look at it but in the meantime I am happy that your answer opened the world of vim script to me. I should stop procrastinating, tho. :p – ctholho May 30 '18 at 15:11
  • Without removing the link, I would recommend editing as much of the code back in to your answer as possible... it's not terribly lengthy, and if anything happens to the link there would still be a serviceable answer here. It's generally preferable on SE to not rely on a link as an answer (which isn't exactly what's going on here, but still...) – brhfl May 31 '18 at 16:22
0

For screenwriting formats you can use

vipoj

to select all the dialogue or

vipojvf.

or

{jjvf.

if you really only want the first sentence.

0

In normal mode use the visual area over the first sentence on the second line:

vis

This area now includes the the line above (as you know) and the cursor is at the bottom right of the area.

Now while in visual mode, move the curser to the beginning (other end) of the visual area and then move the cursor down to the beginning of the sentence you want:

oj

The first sentence on the second line is now in the visual area. So combined the commands are:

visoj

No plug in required.

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.