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
"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
ä instead of
"a, noticing the error, then deleting the character
and trying to write
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 (
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.
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
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
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
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
<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
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
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 ."')" 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
"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
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
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
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.