6

I have a LaTeX file with a bunch of commands of the form \label{<word>} where the <word> represents a string that contains special characters, namely á é í ó ú ý ð þ æ ö. I'm looking for a quick way to change the special characters into a corresponding ASCII character(s): a e i o u y dh th ae oe. To give an example, I want to change \label{orðabók} to \label{ordhabok}.

If I wanted just to change, for example, all þ's to th, I would visually select the word inside the \label{}, exit to normal mode and then use

:s/\%Vþ/th/ge

And I could give two such commands at the same time using |, e.g.

:s/\%Vþ/th/ge|s/\%Vð/dh/ge

I guess I could just string together all ten commands, but I was hoping for a more elegant way. In particular, I would like to not have to write the \%V and the g every time.

A follow-up question: I have to do this for every \label{} command in a very long file, and I plan to make a macro or commmand that automates it. My plan was to make a macro that searches for the next occurance of \label{, visually selects the word inside the {}, and performs the substitutions I described above. Then I would just count the \label commands and run the macro that many times. If I get help with the first question, then I know how to implement this. But is there another method that would be faster for a very long file?

3 Answers 3

5

How about ... a substitution inside another one, and all thrown in a loop? E.g.

function! s:replaceSpecialChars(name)
    let from = ['á','é','í','ó','ú','ý','ð','þ','æ','ö']
    let to = ['a','e','i','o','u','y','dh','th','ae','oe']

    let i = 0
    while i < len(from)
        exe '%s/' . a:name . '{\zs.\{-}\ze}/\=substitute(submatch(0),from[i], to[i], "g")/g'
        let i += 1
    endwhile
endfunction

command! -nargs=1 ReplaceSpecialChars call s:replaceSpecialChars(<f-args>)

Copy the above code in a vim file, and source the file (e.g. :w and so % if it's the current file). Then you can run it when you are in your LaTeX buffer like this:

:ReplaceSpecialChars label

Just make sure to keep those lists (from, to) matched (equal nr. of elements).

This function globally replaces, in turn, any number of occurences of one of the characters from the from[] list to their to[] correspondent, but only for text found inside the <name>{ } construct.

5
  • Thank you, this seems to work very well and is much more easily applied to similar situations than the mix of macros and registers that I had imagined. I have one suggestion to generalize your solution: Could the function take a string name as argument and then perform the substitutions inside name{ } instead of label{ }? Then I could call the function with argument label with the same effect as :call ReplaceInLabels(). How would I do that?
    – Peter
    Commented Jul 12, 2015 at 17:24
  • Yes, why not :) I edited the answer. I'll also probably add a command that takes a name as parameter (easier to use) - and ... done.
    – VanLaser
    Commented Jul 12, 2015 at 17:31
  • Shouldn't it be :call ReplaceSpecialChars('label') instead of :ReplaceSpecialChars label? The latter doesn't work for me.
    – Peter
    Commented Jul 12, 2015 at 19:14
  • I made it as a command, it's easier to run this way. It should work though, it just calls the original function with one argument. (I just retested it with a copy/paste on my machine and it runs alright)
    – VanLaser
    Commented Jul 12, 2015 at 19:21
  • Sorry, I missed that part. It works perfectly. Thanks!
    – Peter
    Commented Jul 12, 2015 at 19:23
7

Tim Pope's abolish.vim plugin provides a :Subvert (:S for short) command that lets you substitute a list of things for another list of things. For example, the following command will change a to x, b to y, and c to z:

:S/{a,b,c}/{x,y,z}/g

The plugin also provides a lot of other cool functionality that can't be easily articulated; I recommend checking it out.

3
  • Thanks, why reinvent the wheel? Note that these substitutions are not case-sensitive though. Commented Sep 1, 2017 at 13:59
  • 1
    It's not quite that it's not case sensitive, but that it also substitutes other case variants. For example, :S/foo/bar/g will convert foo to bar, Foo to Bar, and FOO to BAR. (It's good at matching case variants for multi-word terms as well.)
    – tommcdo
    Commented Oct 13, 2017 at 19:29
  • I was about to sit and write some utility for doing this... but, since tpope did it... Commented Jan 27, 2022 at 10:13
5

How about that.

  1. define your substitution dictionary:

    :let dict_subst = { 
         \ 'á': 'a', 
         \ 'é': 'e', 
         \ 'í': 'i', 
         \ 'ó': 'o', 
         \ 'ú': 'u', 
         \ 'ý': 'y', 
         \ 'ð': 'dh', 
         \ 'þ': 'th', 
         \ 'æ': 'ae', 
         \ 'ö': 'oe'} 
    
  2. Then call your substitution function like this:

    :exe ":s/".join(keys(dict_subst),'\|').'/\=dict_subst[submatch(0)]/g'
    
1
  • 1
    That's nicer than the 2 lists I used, but it ignores the "replace inside a label only" requirement.
    – VanLaser
    Commented Jul 12, 2015 at 20:46

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.