7

Suppose I have a defined text object. For example, I have a ii text block which defines all the contiguous lines around the cursor at the same level of indentation or deeper. I know what it means to, say, yank or delete this text block.

As a consequence, as I move through the document, there is a well defined "start of this type of block" and "end of this type of block". I would like to have a motion which would take me to these positions. For example, given:

a piece of 
  indented
  text
    with
  some words
in it
  and some
  more

if I were to do something like geii ("go end inner indent?") from indented, I would end up at "some words".

It would similarly be nice to be able to go to the "next start" of a block. In this case, the "next start" of an indentation block would be any time the indentation increases. So from the top, you would move to "indented", then "with", then to "and some".

Is there a plugin that achieves this? All the better if it's independent of the specific kind of text block.

3 Answers 3

5

For the general case, you can try creating new operators, gs and ge:

function! GoStart(type) abort
  normal! `[
endfunction

function! GoEnd(type) abort
  normal! `]
endfunction

nnoremap <silent> gs :set opfunc=GoStart<CR>g@
nnoremap <silent> ge :set opfunc=GoEnd<CR>g@

When Vim invokes the operator functions after you type your text-object, it sets the '[ and '] marks to be the start and end of that text-object. The functions themselves simply jump to the locations of these marks by using the :normal command to execute the `[ and `] motions.

I haven't tested these with your ii text object as you don't say how you've defined that, but they work fine with the various text objects I have tested them with, e.g. gsis, geip, gsa(, gea{.

However, note this proviso as described by the author of the related ninja-feet plugin, Tom McDonald:

It's easy to make an operator that does nothing but move the cursor. This gives us free compatibility with all motions (built-in and user-defined), but it has an unacceptable side-effect: as all operators are repeatable, it overrides the previous operation to be repeated by [the dot command] .. This would be quite jarring to a user.

7
  • 1
    This sounds like it's in the right direction! The motions come to me from plugins, I believe all defined through textobj-user (which is itself a plugin). What does norm! do? Commented Jan 15, 2020 at 15:31
  • norm! allows you to execute normal mode commands from Vimscript. When Vim invokes the operator function it sets the '[ and '] marks to the start and end of the text object, so we're just jumping to them using the normal mode commands
    – Rich
    Commented Jan 15, 2020 at 15:44
  • I understand! In this case, I think `[ and `] by themselves are enough for what I mean by "moving to the start/end" (I prefer not to write custom functions if there's something vanilla that's close enough). I'm going to check out ninja-feet, too. Commented Jan 16, 2020 at 11:43
  • @preferred_anon `[ and `] will only work specifically within the context of the opfunc described in my answer. In regular normal mode they instead move to the first/last character of the previously changed/yanked text: it's the use of opfunc and g@ that sets the marks to point instead to the start and end of the text object.
    – Rich
    Commented Jan 16, 2020 at 11:47
  • Right, I think I understand. What I meant by my comment is that I don't object doing something like yii`[ or similar to set the markers and then just use them normally. I'll consider automating it, though. Commented Jan 16, 2020 at 11:53
4

This doesn't answer your more general question about text objects, but you can achieve the behaviour in your specific indentation-based example with folds:

setlocal foldmethod=indent
setlocal shiftwidth=2
setlocal foldlevel=99

Setting 'foldmethod' tells Vim that you want to use indentation based folding. Setting 'shiftwidth' tells Vim the size of each indentation level, and setting 'foldlevel' tells Vim always to display the folds as open. (You can omit this if you actually want to use the folding feature, instead of just borrowing its motions.)

Then you can use ]z as your "go end inner indent" command and for your "next start" command, you can use zj. You can also use [z and zk to move up the file in a similar manner.

3

Plugin vim-ninja-feet by tommcdo

Currently, this plugin provides following square bracket mappings (do not overlook the square in the definition):

{operator}[{text object}
                        Perform {operator} from the cursor to the beginning
                        of the text object defined by {text object}.
{operator}]{text object}
                        Perform {operator} from the cursor to the end of the
                        text object defined by {text object}.

z[{text object}              Enter |Insert| mode at the beginning of {text object}.

z]{text object}              Enter |Insert| mode at the end of {text object}.

You are probably interested in issue #3 Make it work in Normal mode: you can change z[{text object} and z]{text object} to not automatically switch to insert mode by following diff yourself:

~/.vim/pack/tommcdo/opt/ninja-feet/plugin master*
❯ git diff
diff --git a/plugin/ninja-feet.vim b/plugin/ninja-feet.vim
index 4a2a9f1..ee07408 100644
--- a/plugin/ninja-feet.vim
+++ b/plugin/ninja-feet.vim
@@ -28,13 +28,11 @@ function! s:ninja_strike(mode)
 endfunction

 function! s:ninja_insert(mode)
-       let op = a:mode == 'line' ? 'O' : 'i'
-       call feedkeys('`['.op, 'n')
+       call feedkeys('`[', 'n')
 endfunction

 function! s:ninja_append(mode)
-       let op = a:mode == 'line' ? 'o' : 'a'
-       call feedkeys('`]'.op, 'n')
+       call feedkeys('`]', 'n')
 endfunction

 function! s:map_expr(sid, type, direction, count)

Your Answer

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

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