We use our editors in order to write stuff. May it be code, prose or the occasional haiku, we tend to use the same software for it. For me, as a developer, this is vim. As you may know, it is entirely beneficial to use a US-style keyboard layout when programming.

Why, you ask? While programming we use parentheses and brackets - a lot. The US QWERTY layout has parentheses placed in easy to reach places. Of course you can optimize on that by using your custom mapping (e.g. dvorak), but we won't go into that today.

But if you are european, like me, you might notice that your native language uses special characters which extend the number of vowels in your language. The german language uses ä, ü, and ö. These characters are called umlauts. In spanish and french there are even more characters like that (e.g. ë, á and so on...).

So when i'm writing a letter (in german) to someone i'll need to use these umlauts. But when utilizing a US-layout keyboard i can't do that. I could setup my system to use the US-international layout with dead keys which will allow me to press "a and get an ä in return. However, when programming we'll use " quite often when writing Strings (e.g. "Hello World!"). Waiting for the " to drop out of dead-key mode can be really annoying, especially when writing a string that actually starts with an a, as - more often than not - i am writing ä instead of "a, noticing the error, then deleting the character and trying to write "a again.

This is tedious.

on the search for a better solution

So with vim being an expert in editing files, i started my search for a more optimal solution. If you know vim, chances are that you've heard of abbreviations (:h :ab). Usually they are being used to expand, guess what, abbreviations or to correct common spelling errors. Basically they consist of a mapping from input-text to output-text. But you can't really decide to not execute a abbreviation if you do not want it right now.

gif of the usage of abbreviations in vim

From this we can conclude that abbrevs are not really the right way to go here. However there still is the big brother of abbreviations: mappings.

Mappings can be most easily compared to motion-combination commands. For example when i'm pressing cw in normal mode (the default mode, also called ESC-mode, for the key you press to enter it), i actually press c (which is a stand-in for change) to put vim into operator-pending mode. This means that vim is waiting for me to enter a motion-command in order to complete it — actually it can also take an ordinal command beforehand in order to know how many times to execute the motion-command. In this case the motion-command is w (which is a stand-in for word).

Vim usually waits indefinitely in operator-pending mode (which is what the pending stands for and the reason why it is a mode).

Mappings are very similar in their effect (as they also perform one action) but they are grouped. A mapping is a specific sequence of key-strokes which will map to a specific action. Vim will not enter operator-pending mode when resolving a mapping, but it will wait a certain amount of time for the completion of a potential mapping. This wait-time can be configured but usally equals a fraction of a second. As mappings can be defined for pretty much any mode we can actually define mappings for the insert mode as well, which is the one we're interested in when trying to get umlauts to work.

So considering this, we should get what we want, if we were to add the following to our .vimrc:

inoremap "a ä
inoremap "u ü
inoremap "o ö
inoremap "A Ä
inoremap "U Ü
inoremap "O Ö

This will map german umlauts in small and big characters by pressing " and then the corresponding base-character. inoremap basically means that this is an insert mode mapping which disallows nested mappings - so the right-hand side of the mapping will not be tested for possible mappings to be executed.

However all this effectively did, was building US-international (wrt. to german umlauts) on our own. We still have the problem of waiting for vim to drop out of the wait-period in order to actually get a literal ". Of course we could always write <C-v>" which will place a literal " into the text - since this is what the Ctrl-v combination allows us to do. But this is neither elegant nor practical.

But this puts us at the threshold of what is possible in vim and what is not. We need to use the tools that are already implemented, unless we actually want to implement a completely new feature in vim. And that should really only be the last option.

As it was said in the beginning, the usefulness of being able to use umlauts — or any other special character — largely depends on the editor's intention when editing a file. Such an intention is usally paired with the type of the file. I usually only write german text when i'm editing LaTeX-, text-, markdown-, or other README-like files. So we could actually reduce the potential for problematic situations by only setting those mappings for filetypes when we are much more likely to use umlauts then the combination of quotes and other letters (e.g. strings in programming languages).

Our actual expectation of what is supposed to be possible is shown in the code-excerpt below.

" german umlauts, technically not abbreviations
let g:umlaut_mappings = [ ['"a', 'ä'], ['"o', 'ö'], ['"u', 'ü'], ['"A', 'Ä'], ['"O', 'Ö'], ['"U', 'Ü'] ]
let g:file_types = ['mkd', 'tex', 'text', 'mail', 'gitcommit']

for file_type in g:file_types
  call UmlautsForFileType(g:umlaut_mappings, file_type)
endfor

an approach to implementing a small plugin-like solution

For every listed filetype we want to establish our umlaut mappings. It is actually a little bit more complicated than that, but not by alot. But let's start with the actual implementation and work our way through the figurative call-graph.

" german umlauts, technically not abbreviations
let g:umlaut_mappings = [ ['"a', 'ä'], ['"o', 'ö'], ['"u', 'ü'], ['"A', 'Ä'], ['"O', 'Ö'], ['"U', 'Ü'] ]
let g:file_types = ['mkd', 'tex', 'text', 'mail', 'gitcommit']

call ImplementUmlauts()

This is the actual version where we only declare global variables and call our ImplementUmlauts function. One might be a little bit confused about the fact that we don't supply the arguments to the function directly. One could go that way, but for configuration a lot of plugins actually use the global-variable approach and it underlines the fact that we actually only want to configure something.

function! ImplementUmlauts()
  augroup umlauts
    call RemoveUmlautsOtherwise(g:umlaut_mappings)
    for file_type in g:file_types
      execute 'autocmd' 'FileType' file_type 'SetUmlautMappings' file_type
      execute 'autocmd' 'BufEnter,BufLeave' '*' 'SetUmlautMappings' file_type
    endfor
  augroup end
endfunction

A augroup can basically be considered a tag. It registers all the used autocommands under a common group-name which makes them easier to identify afterwards. Then we actually start by deleting mappings when we call RemoveUmlautsOtherwise. This is necessary to start with a clean slate.

function! RemoveUmlautsOtherwise(mappings)
  for pair in a:mappings
    execute 'autocmd' 'FileType,BufEnter,BufLeave' '*' 'call' "RemoveIMappingIfExists('". pair[0] ."')"
  endfor
endfunction

This registers an autocommand for filetype, the entering of a buffer — basically every time you open a file — and the leaving of a buffer. An autocommand is an action that is to be executed every time when a specific event occurs. In our case this is the RemoveIMappingIfExists function, which takes the actual mapping to be inserted in order to get the special character (e.g. "a) as its first and only argument.

function! RemoveIMappingIfExists(mapping)
  try
    execute 'iunmap' a:mapping
  catch /E31/
  endtry
endfunction

There is actually no function to just remove a mapping and not caring if it actually existed in vim. That is why we write our own one. We execute the iunmap ex-command, which will remove a mapping. If the mapping did not exist in the first place an E31 error will be displayed. The vim documentation (:h E31) describes the error as

No such mapping

Using try and catch allows us to ignore the error, which is actually what we want in this case.

Now we move back to the ImplementUmlauts function:

for file_type in g:file_types
  execute 'autocmd' 'FileType' file_type 'SetUmlautMappings' file_type
  execute 'autocmd' 'BufEnter,BufLeave' '*' 'SetUmlautMappings' file_type
endfor

In the next line we actually see our expected iteration over every filetype, where we add an autocommand for the corresponding filetype and when we enter or leave any buffer. As opposed to the clearing of any umlaut-mapping we actually care for the specific file-type in this instance, which is why we provide it to the autommand definition for FileType. We then execute SetUmlautMappings for the given filetype.

command! -nargs=1 SetUmlautMappings call SetUmlautMappings(<f-args>)

SetUmlautMappings is actually a command instead of a function. This command is specified to have exactly one argument (-nargs=1) and will call a function by the same name.

function! SetUmlautMappings(filetype)
  for [map_from, map_to]  in g:umlaut_mappings
    call SetUmlautMapping(a:filetype, map_from, map_to)
  endfor
endfunction

To keep it easy this is just an abstract mapping function that will call the specific mapping function with the correct filetype and the start and endpoint of the mapping. The then called SetUmlautMapping function does the heavy lifting.

function! SetUmlautMapping(filetype, map, mapped)
  let current_filetype = &filetype
  if current_filetype == a:filetype
    execute 'inoremap' a:map a:mapped
  endif
endfunction

This functions defines a local variable which will have the filetype that is actually currently set as its value. Settings can be retrieved by prefixing their name with a &-symbol. If the current filetype matches the wanted filetype, the mapping will be applied.

And this is it. This will allow you to use german-umlauts with an US-style keyboard layout for only those filetypes you actually need them for. And if you take a closer look at the variables you will see that it is actually quite easy to customize it for your native tongue.

As always the code can be viewed in its entirety on github. This time it is easiest to take a look at my dotfiles.

a last word on the naming scheme

Some of you might have wondered why this article is titled: Vim-02: Umlauts in vim. The reason for this might be odd but nevertheless quite simple. This article is part of a series i am starting on vim. Specifically on getting to know vimscript and vim customization in small steps. And this is number two because number one, the real intro to everything, isn't quite finished yet. I'm still polishing a few things and fixing a few kinks here and there. But this should soon be done and then the one will follow the two.