7

Have been reading vim scripting posts for an hour, but I'm afraid I don't have the skills to apply these posts to my problem. Editing math in latex documents, I'm frequently wanting to replace math symbols such as +, -, = with \Pad{+}, \Pad{-}, \Pad{=}, where \Pad is a latex macro I've defined to put space around the symbol. I'd like be able to put my cursor on such a symbol, type a couple of letters, say ;P and have this action replace the symbol with

\Pad{whatever the symbol under the cursor is}

Any help would be most appreciated!

`

5
function! s:PadMacro(type) abort
    if a:type ==# 'char'
        normal! `[v`]d
    elseif a:type ==# 'line'
        normal! '[V']d
    elseif a:type ==# 'v'
        normal! `<v`>d
    else
        return
    endif
    let string = '\Pad{' . @" . '}'
    silent execute "normal! i\<c-r>=string\r"
endfunction
nnoremap <silent> ;P :set operatorfunc=<sid>PadMacro<cr>g@
vnoremap <silent> ;P :<c-u>call <sid>PadMacro(visualmode())<cr>

If you put the previous code in your vimrc, it should do what you want.

To use it, hit ;P{motion} and the PadMacro() function should enclose the characters covered by {motion} with \Pad{ and }.

Usage examples:

  • ;Pl to enclose the character under the cursor (because l is a motion which covers the character under the cursor)
  • ;Piw to enclose the word under the cursor (because iw is a text object which covers all the characters inside the word under the cursor )
  • ;Pis to enclose the sentence in which the cursor is (because is is a text object which covers all the characters inside a sentence)
  • ;P$ to enclose the characters between your cursor and the end of the line
  • ;P to enclose the characters visually selected

Everything done by the PadMacro() function is considered as a single edition which means it's dot repeatable.
In other words, if you have several similar portions of text to enclose with \Pad{} there's no need to hit the mapping every time, only the first time (after that you can hit . to repeat your last edition).

For a complete list of text-objects on which you can apply your enclosure, see:
:help text-objects.

|improve this answer|||||
  • Fabulous, thanks very much @saginaw. I actually have a few different spacing commands I use, like \pad, \ppad, in addition to Pad. Obviously I could duplicate the macro for each of these, but for instructional purposes, I presume there's a way to pass an argument to the macro, modify the "let string" line to insert the argument instead of Pad, and then define a different noremap for each option, e.g., ;p would insert \pad, ;pp would insert \ppad? Thanks again! – Leo Simon Dec 29 '15 at 17:04
  • @LeoSimon You can define a whole group of mappings with a single for loop and the :execute command. See here for an example: github.com/romainl/dotvim/blob/master/vimrc#L307-L312 It should not be difficult to do the same thing for your mappings. However, I don't know how to pass "pad", "ppad", ... as an argument to the function. I have an idea (after reading :h g@, it seems you could add an optional group of arguments with ... and access the first argument of this group with a:1), but I haven't succeeded in writing a working function. If I can, I'll let you know. – saginaw Dec 29 '15 at 18:00
  • Thanks @saginaw, I started on the for loop, but sadly, couldn't get past first base. I tried the following as a beginning step: within your macro, – Leo Simon Dec 29 '15 at 20:40
  • @LeoSimon I hope the name PadMacro is not too misleading, because it's just a vimscript function. I called it like that because you said \Pad{} was interpreted as a latex macro. But if you prefer, you can call it anything else (PadEnclosure for example). For the mappings, let's say you have 3 functions MyFunctionA, MyFunctionB and MyFunctionC, each one applying a certain transformation on your text. And you want to map them to ;Pa, ;Pb and ;Pc. Then the following code should be able to do it for you: paste.debian.net/plain/356949 Not tested, so I'm not sure it works. – saginaw Dec 29 '15 at 21:19
  • Thanks! @saginaw . PadMacro is perfectly fine. Your loop worked beautifully. Everything would be a bit more elegant if I could include the actual script function in the loop. It's easy to modify the line \let string = so that it accepts a list item instead of a Pad; the problem part is to define the macro name as a list item instead of a literal string, i.e., the following doesn't work (not surprisingly) function! s: . func_name . (type) abort. Maybe there's some way of creating the function with a generic name, then within the loop copying the generic function to func_name? – Leo Simon Dec 29 '15 at 22:45
5

The idea is simple:

  1. you cut the text you want to surround and enter insert mode,
  2. you insert the first part,
  3. you insert the content of the unnamed register,
  4. you insert the last part,
  5. you get out of insert mode.

For your example you would do:

s\Pad{<C-r>"}<Esc>

Which can be mapped for your convenience:

" normal mode, for single characters
nnoremap ;P s\Pad{<C-r>"}<Esc>

" visual mode, for multiple characters
xnoremap ;P s\Pad{<C-r>"}<Esc>

Note that it's possible to use a different register if you don't want to clutter the unnamed register. Here I use the z register:

"zs\Pad{<C-r>z}<Esc>
|improve this answer|||||
3

Just one more solution for case when surround not only one symbol (sample from my config for surround with "[" & "]"

vmap Q[ O<Esc>i[<Esc>gvo<Esc>la]<Esc>
nnoremap Q[ veO<Esc>i[<Esc>gvo<Esc>la]<Esc>

Key is to use "o" & "O" to move to start/end of selection

|improve this answer|||||

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.