Improving TeX Editing in Vim with a Smart 'Select Inside Any Pair' Function
Improving Your TeX Workflow in Vim with a Smart “Select Inside Any Pair” Function
One of the reasons I love working in Vim is how easily I can extend it to match the way I write and edit. When dealing with LaTeX files—especially long derivations, nested parentheses, or math environments—I often need to quickly select the text inside {…}, (…), or even inside $…$ blocks. Doing this manually wastes time, interrupts my flow, and becomes frustrating when there are many levels of nesting.
To fix this, I wrote a custom function that intelligently selects whatever “pair” I am currently inside: curly braces, brackets, parentheses, math mode, quotes—anything. This small function has massively improved how I work inside TeX files.
Why This Function Matters
•Instant selection in TeX math: When working between $…$, a single keystroke selects the content cleanly—even in long expressions.
•Nested structures handled automatically: For { { ( … ) } }, the function detects the correct level around the cursor instead of selecting the wrong one.
•Works across all common pairs: Parentheses, brackets, braces, math mode, quotes, and even symmetric delimiters are supported.
•Better than built-in text objects: Vim’s default vi( and friends work only when your cursor is right next to the delimiter. This custom function works even if you're deep inside the content.
The Function That Does the Magic
function! SelectInsideAnyPair()
let l:savepos = getpos('.')
let l:pairs = [
\ ['{', '}'],
\ ['[', ']'],
\ ['(', ')'],
\ ['$', '$'],
\ ['"', '"'],
\ ["'", "'"]
\ ]
for l:pair in l:pairs
let l:open = l:pair[0]
let l:close = l:pair[1]
if l:open ==# l:close
" symmetric delimiters
let l:start = searchpos('\V' . l:open, 'bnW')
let l:end = searchpos('\V' . l:close, 'nW')
else
" asymmetric pairs (handles nesting)
let l:start = searchpairpos('\V' . l:open, '', '\V' . l:close, 'bnW')
let l:end = searchpairpos('\V' . l:open, '', '\V' . l:close, 'nW')
endif
if l:start[0] > 0 && l:end[0] > 0
let l:cur = getpos('.')
let l:start_before_cursor = (l:start[0] < l:cur[1]) || (l:start[0] == l:cur[1] && l:start[1] <= l:cur[2])
let l:end_after_cursor = (l:end[0] > l:cur[1]) || (l:end[0] == l:cur[1] && l:end[1] >= l:cur[2])
if l:start_before_cursor && l:end_after_cursor
" start just after opening delimiter
call setpos('.', [0, l:start[0], l:start[1] + 1, 0])
normal! v
" end just before closing delimiter
call setpos('.', [0, l:end[0], l:end[1], 0])
normal! h
return
endif
endif
endfor
" --- fallback: select whole line (without trailing $) ---
let l:lnum = line('.')
let l:line = getline(l:lnum)
" compute start and end columns
let l:start_col = 1
let l:end_col = len(l:line)
" if line ends with $, exclude it
if l:end_col > 0 && l:line[-1:] ==# '$'
let l:end_col -= 1
endif
if l:end_col >= l:start_col
call setpos('.', [0, l:lnum, l:start_col, 0])
normal! v
call setpos('.', [0, l:lnum, l:end_col, 0])
else
call setpos('.', l:savepos)
echo "Nothing to select"
endif
endfunction
nnoremap <silent> <leader>v :call SelectInsideAnyPair()<CR>
This tiny Vim function eliminates the repetitive hassle of manually selecting content inside brackets, quotes, or math delimiters. For LaTeX-heavy workflows, it’s a real productivity booster.