1

I'd like to define a text object for tab-delimited fields in a tsv file. So far, I have

vnoremap af :<C-U>silent! normal! T<Tab>vf<Tab><CR>
omap af :normal Vat<CR>
vnoremap if :<C-U>silent! normal! T<Tab>vt<Tab><CR>
omap if :normal Vit<CR>

The problem is with the first and the last field because those lack a tab on one side. I've experimented with ?[\t\n]?e+1 and search() instead of T<Tab> (and analogously for the ending), using various patterns for defining text objects that I've found online, but nothing seems to work.

EDIT: Eventually I came up with this, but I can't help the feeling it's an overkill. If someone comes up with a nicer solution, I'll be very happy to learn.

function! TsvField (inner)
    " first field
    if getline('.')[:col('.')-1] !~ "\t[^[:blank:]]"
        execute a:inner ? "normal ^vt\t" : "normal ^vf\t"
    " last field
    elseif getline('.')[col('.')-1:] !~ "\t"
        execute a:inner ? "normal T\tv$h" : "normal F\tv$h"
    " middle field
    else
        execute a:inner ? "normal T\tvt\t" : "normal T\tvf\t"
    endif
endfunction
vnoremap if :<C-U>call TsvField(1)<CR>
omap if :normal vit<CR>
vnoremap af :<C-U>call TsvField(0)<CR>
omap af :normal vat<CR>

EDIT 2: After some more testing I discovered that [^\s] does not match the letter s, so the first if fails when one of the fields begins with this letter. Replacing [^\s] with \S fixes this particular case – while breaking all the others. It seems [^[:blank:]] might be the answer.

7
  • Something like ?^\|\t<CR> might be able to replace T<Tab>, and then you would do something like lv/$\|\t<CR>h. Untested, but maybe enough to help you get the rest of the way there? – D. Ben Knoble Oct 29 '20 at 16:15
  • PS Vat and Vit looks wrong; probably you mean vat/vit (though the actual at/it mappings throw away the original visual mode, so it probably works) – D. Ben Knoble Oct 29 '20 at 16:17
  • @D.BenKnoble Thanks but I must be still doing something wrong because this doesn't work. How do I even use search in a mapping? I'm more confused now than when I started. – Kamil S. Oct 29 '20 at 19:55
  • I would put the search commands after normal!, since that's how you structured the original. ? and / are normal commands (derived from the original use for denoting ranges) – D. Ben Knoble Oct 29 '20 at 20:21
  • 1
    Welcome to Vi and Vim! – filbranden Oct 29 '20 at 22:25
0

One option to consider is to use the vim-textobj-user plug-in, which allows you to easily define your own text objects from simple patterns.

In your case, you can use the following to define the it and at patterns:

call textobj#user#plugin('vimrc', {
\   'tab-a': {
\     'pattern': '[^\t]\+\t*',
\     'select': 'at',
\   },
\   'tab-i': {
\     'pattern': '[^\t]\+',
\     'select': 'it',
\   },
\ })

(This of course assumes you have the plug-in properly installed.)

The plug-in will define both a visual mapping and an operator-pending mapping for these bindings.

The at mapping above will select a number of tabs following a field. If you want to limit it to a single one, use '[^\t]\+\t\?' for the pattern instead.

There are some limitations, such as you can't really use these patterns with a count (e.g. v5at to select 5 fields) and you can't use repeated its or ats to extend the selection. So there's a merit to using functions to define your mappings in that they can process counts and extend existing selections, but perhaps something as simple as this solution is all you need.

(Please note that at and it are already valid motions, to select around HTML/XML tags, so consider whether you want to override those mappings or use different ones. vim-textobj-user also supports defining mappings per filetype, in which case you can define these only for your particular filetype that contains tab-separated fields, see the full documentation for detais on how to define those.)

3
  • Thanks. Tbh, I'm not entirely happy with how tabs are treated in this solution. When the cursor is between the first and second field, vit selects the second field while vat selects the first. Perhaps this would be easy to fix, but as a general rule, I'd rather use pure Vim, without any plugins. Sorry I didn't think to mention this in the question. – Kamil S. Oct 30 '20 at 7:49
  • 2
    I wonder about reminding the OP that it and at are already text-objects (perhaps iT/aT are better here?) – D. Ben Knoble Oct 30 '20 at 13:18
  • 1
    I didn't know that. I don't normally work with tags, but you're right, I'll remap to if/af for 'field' – Kamil S. Nov 5 '20 at 22:14

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.