6

I have a file that looks like this:

# Section 1
#
# new: a.txt
# new: b.txt
#
#
# Section 2
#
# [n]ew: c.txt
# new: d.txt
# new: e.txt
#

My cursor is on the first new of the second section and I want to select all the "paragraph", i.e. the 3 new lines starting at this one. But because the paragraph is defined as a set of non-empty lines, vip selects all the file.

How can I select only those 3 lines? This should apply for any number of lines and the cursor should be placed anywhere in the "paragraph", i.e. vip/equivalent should select the current new paragraph.

My question is the following:

How can I omit a column of text when visually selecting a paragraph?

  • 6
    I don't have a solution for this exact problem, but I can tell you of a good way to approach it: install textobj-user and write a plugin for it. There are many such plugins listed at the wiki, including one for comments (but this one doesn't do what you want though). Writing textobj plugins is reasonably straightforward. – Sato Katsura Jul 11 '16 at 15:13
  • You can keep the text in any line and search for previous empty line and mark it as start of paragraph. Do the same for end of paragraph. Now, apply indentation or whatever in this selected block. You can write a function to fulfill the above needs. You can also check whether it is inside comment or quotes and do accordingly. – SibiCoder Jul 11 '16 at 15:36
  • You could try to mess with paragraphs option. It uses nroff macros which I'm not familiar with. I tried to mess with it a bit but never got working solution. Still, I feel that there might be a way to do it with this. – Tumbler41 Jul 11 '16 at 16:00
  • 1
    I posted a possible implementation of a textobj-user plugin below. For what its worth. – Sato Katsura Jul 11 '16 at 20:05
  • 1
    @Tumbler41 I think you're misreading the documentation (and the reason I think that is because I spent ages believing it meant what you're suggesting it does). If I now understand it correctly, the 'paragraphs' option does not allow you to arbitrarily define different types of "paragraph" by using nroff macros. It's an incredibly outdated option that allows you to change which nroff macro strings count as paragraph breaks when editing a file that already contains them. i.e. It's intended to make editing nroff files easier, not as a general purpose paragraph customization tool. – Rich Jul 13 '16 at 9:41
5

Using Kana Natsuno's textobj-user: install textobj-user, then put the following in a file textobj-comment-lines/plugin/textobj/comment_lines.vim:

if exists('g:loaded_textobj_comment_lines')
    finish
endif
let g:loaded_textobj_comment_lines = 1

let s:save_cpo = &cpo
set cpo&vim

call textobj#user#plugin('commentlines', {
    \   '-': {
    \     'select-a': "a#",  '*select-a-function*': 'CommentLinesSelectA',
    \     'select-i': "i#",  '*select-i-function*': 'CommentLinesSelectI'
    \   }
    \ })

function! CommentLinesSelectA() " {{{1
    if getline(line('.')) !~# '\m^#.*\S'
        return 0
    endif

    let head_pos = search('\m\%(\%^\|^\n\|^[^#].*\n\|^#\s*\n\)\zs#.*\S', 'cbnW')
    let tail_pos = search('\m\%#.*\n\%(^#.*\S.*\n\)*\%(^#\s*\n\)*\zs\%(^#\s*\n\)', 'cnW')
    if tail_pos > 0
        if getline(tail_pos) !~# '\m^#'
            let tail_pos -= 1
        endif
    else
        let tail_pos = search('\m^#.*\S.*\n\ze\%(\%$\|^\n\|^[^#].*\n\|^#\s*\n\)', 'cnW')
    endif
    return ['V', [0, head_pos, 1, 0], [0, tail_pos, 1, 0]]
endfunction " }}}1

function! CommentLinesSelectI() " {{{1
    if getline(line('.')) !~# '\m^#.*\S'
        return 0
    endif

    let head_pos = search('\m\%(\%^\|^\n\|^[^#].*\n\|^#\s*\n\)\zs#.*\S', 'cbnW')
    let tail_pos = search('\m^#.*\S.*\n\ze\%(\%$\|^\n\|^[^#].*\n\|^#\s*\n\)', 'cnW')
    if !tail_pos
        let tail_pos = line('$')
    endif
    return ['V', [0, head_pos, 1, 0], [0, tail_pos, 1, 0]]
endfunction " }}}1

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: set sw=4 sts=4 fdm=marker:

This adds two mappings, vi# and va#, and the first one does what you want. The gist is the regexp searches, the rest is mostly boilerplate.

The comments are hard-coded to # anchored at the beginning of line. Dealing with other kinds of comments (f.i. /* ... */) would be possible, but not trivial. You can, of course, refine this in many ways.

Reference: textobj-user user manual.

  • Thanks for the effort, it seems to work nicely on my machine. I'm not using the plugin at the moment, it may be the occasion to take a deeper look at it. – nobe4 Jul 12 '16 at 8:00
  • 1
    @nobe4 Yes, you already made it clear you don't want a solution with plugins. It's a pretty crude first write anyway. I'm mainly posting it to show how such a thing looks. – Sato Katsura Jul 12 '16 at 12:01
3

You could delete the first column, then select the paragraph with vip, then restore the deleted #'s and reselect the last visual selection with gv. Basically something like this mapped to the Q key for example:

nnoremap Q :%s/^#// \| exe 'norm!``vip"9y' \| silent %s/^/#/ \| nohl \| norm!gv<CR>

It yanks the paragraph into register 9 just so vim will remember the selection for the gv command.

2

An alternative is ?#$<cr>jVNk:

?#$ jumps back to the previous occurrence of # on its own line ($ in regex means "end of line"), j moves to the start of the paragraph to select, we start Visual mode and move to the Next occurrence of # by itself, and finally move back up one line with k.

This is relatively short, so you can type it manually when necessary to avoid having to bind a key to it, but it does have the disadvantage that it clobbers the search history. If you do make it a mapping, though, you can keep search history intact with

:execute "keeppatterns normal! ?#$\<cr>jV/#$\<cr>k"

or the shorthand

:exe "keepp norm! ?#$\<cr>jV/#$\<cr>k"
  • 2
    What happens if the last section ends abruptly with a # text, rather than an empty line #? What if it starts with # text at the top of the file? – Sato Katsura Jul 12 '16 at 4:39
1

If you do not mind leaving dirty marks behind you can use them to mark the paragraph. i.e. you can:

  • mark your position,
  • select the entire commented text,
  • return to the mark
  • vip to select the paragraph
  • use marks again to mark the paragraph itself
  • undo the change
  • use the marks to select the single paragraph

Here is the code:

nnoremap <leader>vip mqvip:s/^./<cr>`qvip<esc>`<mw`>meu`w0v`e$

Explanation:

  • mq marks the current position
  • vip selects the problematic paragraph
  • s/^./<cr> removes the problematic characters (can be adapted to other forms of comments)
  • `q returns the cursor to the original position
  • vip now selects the desired paragraph
  • <esc> forces the current visual to be the latest selection
  • `<mw marks the beginning of the desired paragraph
  • `>me marks the end of the desired paragraph
  • u undoes the change, puts the comments back
  • `w0 goes to the beginning of the desired paragraph
  • v enters visual mode
  • `e$ moves to the end of the desired paragraph completing the selection.

This is a little dirty since the marks end one character off after the undo, that is the reason to use 0 and $ in the final selection.

1

Unfortunately, vim doesn't yet support custom-defined paragraphs (from :h todo).

  • Add regex for 'paragraphs' and 'sections': 'parare' and 'sectre'. Combine the two into a regex for searching. (Ned Konz)

Not all hope is lost though, regex to the rescue!!!

EDIT: A function to do it!

function! SelectBlock()
  execute "normal! ?#\\s*$\<cr>" "go to top of current block
  execute "normal! j" " go down one line
  execute "normal! v/\\_.#\\s*$\<cr>" "visually select to end of block
endfunction

You can type :call SelectBlock() or map it to something to make this happen. It works wherever your cursor is. To do it, type v/\_.#\s*$, which will match all the lines under the current line (including the current line) until a line with a # followed by non-whitespace characters.

Explanation of the last command:

v - visual select mode

/ - select until the following search result (our regex)

\_. - select any characters, including newlines

#\s*$ - end the regex match if there's a line with a # followed by nothing but whitespace

  • 2
    This is useful, but only expands forward. If you're in the middle of the paragraph it won't select the whole thing, which is what vip normally does. Also, if you have a paragraph that ends at the bottom of the file without a following # it has some unintended behavior. – Tumbler41 Jul 11 '16 at 16:06
  • @Tumbler41 That's a good point - it's better than nothing though. I guess you could search upwards until a line not matching #.*$ was found, then run the command to achieve that functionality. – DoYouEvenCodeBro Jul 11 '16 at 16:10
  • the command for that would be ?#\s*$, but that matches the header line as well. I'm not sure how to make it only match to the header line, but its a start – DoYouEvenCodeBro Jul 11 '16 at 16:16
  • @f41lurizer, I think it may be possible to write a function around this principle, I'll have a look tonight but I feel it may be a way around :) – nobe4 Jul 11 '16 at 16:23
  • @nobe4 edited my post to include a function :) – DoYouEvenCodeBro Jul 11 '16 at 20:24

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.