Die vier Tasten h j k l sind das ikonischste Element der Vim-Bedienung — eine Erbschaft aus den 1970er Jahren, als Bill Joys vi auf einem ADM-3A-Terminal entwickelt wurde, das die Pfeile genau auf diese vier Tasten gedruckt hatte. Heute haben Tastaturen eigene Pfeil-Tasten, und trotzdem ist hjkl die richtige Wahl: die vier Buchstaben liegen direkt unter der Heimreihe der rechten Hand, weshalb jede Cursor-Bewegung ohne Hand-Verschiebung passiert. Dieser Artikel zeigt die hjkl-Basis, den Unterschied zwischen logischen und visuellen Zeilen (gj/gk), die vier wichtigen Zeilen-Anker und warum die meisten Vim-Lehrer empfehlen, die Pfeiltasten in der Lernphase explizit zu deaktivieren.
hjkl — die Heimreihen-Navigation
Die vier Grundbewegungen:
| Taste | Richtung | Mnemonik / Historik |
|---|---|---|
| h | ein Zeichen nach links | „h" ist links auf der Tastatur |
| j | eine Zeile nach unten | „j" wie der Buchstabe, der nach unten zeigt |
| k | eine Zeile nach oben | „k" wie der Buchstabe, der nach oben zeigt |
| l | ein Zeichen nach rechts | „l" ist rechts auf der Tastatur |
Mit Counts multipliziert: 5h ist fünf Zeichen nach links, 12j zwölf Zeilen nach unten. Wer in einer fünfzigzeiligen Datei zur Mitte will, drückt 25j — keine Pfeil-Reibung, kein Maus-Wechsel.
Die historische Begründung — der ADM-3A — ist 2026 nur noch eine Anekdote, aber die ergonomische Begründung gilt unverändert: hjkl spart Hand-Bewegung. Die rechte Hand bleibt auf der Heimreihe (j, k, l, ;), Cursor-Bewegung passiert mit den Fingern, die ohnehin dort sind. Pfeiltasten erzwingen eine Hand-Verschiebung um etwa 5-10 cm — eine kleine Bewegung, die sich aber bei hundertfacher Wiederholung pro Stunde addiert.
gj/gk — visuelle vs. logische Zeilen
Bei langen, umgebrochenen Zeilen wird der Unterschied zwischen logischen und visuellen Zeilen relevant:
- Eine logische Zeile ist eine durch
\ngetrennte Texteinheit — das, was beim Lesen einer Datei mitcatals eine Zeile gilt. - Eine visuelle Zeile ist das, was am Bildschirm als eine Zeile erscheint — bei langen logischen Zeilen mit
set wrapüber mehrere visuelle Zeilen verteilt.
j und k springen über logische Zeilen. Bei einem 200-Zeichen-Paragraph, der auf einem 80-Spalten-Bildschirm in drei visuelle Zeilen gewrappt ist, ist j ein Sprung um den ganzen Paragraphen nach unten — nicht um eine visuelle Zeile.
Für visuelle Zeilen-Navigation gibt es gj und gk:
| Taste | Bewegung |
|---|---|
j / k | eine logische Zeile (\n-Sprung) |
gj / gk | eine visuelle Zeile (Bildschirm-Sprung) |
In Prosa- und Markdown-Workflows fühlt sich gj/gk natürlicher an — der Cursor bewegt sich so, wie das Auge es erwartet. In Code-Workflows, wo Zeilen typisch kurz sind und nicht wrappen, ist der Unterschied irrelevant.
Wer immer visuelle Zeilen will, kann j/k auf gj/gk mappen:
" j und k folgen visuellen Zeilen statt logischen.
" Bei Counts wieder logische Zeilen — damit `5j` wie erwartet wirkt.
nnoremap <expr> j v:count == 0 ? 'gj' : 'j'
nnoremap <expr> k v:count == 0 ? 'gk' : 'k'Die <expr>-Variante prüft den Count: ohne Count (visuelle Zeile, „nach unten/oben"), mit Count (logische Zeile, „springe N Zeilen"). Damit 5j weiterhin „fünf logische Zeilen runter" bedeutet, was bei Code-Block-Sprüngen die richtige Semantik ist.
Zeilen-Anker: 0, ^, $, g_
Innerhalb einer Zeile gibt es vier Anker-Positionen:
| Taste | Position |
|---|---|
| 0 | Spalte 1 — echter Zeilenanfang (vor Whitespace) |
| ^ | erstes Nicht-Whitespace-Zeichen der Zeile |
| $ | Zeilenende (Cursor auf dem letzten Zeichen) |
| g_ | letztes Nicht-Whitespace-Zeichen (Zeilenende ohne Trailing-Whitespace) |
Der Unterschied zwischen 0 und ^ wird klar bei eingerücktem Code:
" Beispiel-Zeile (mit 4 Leerzeichen Einrückung):
" console.log("Hello");
0 " springt in Spalte 1 (zwischen den Whitespaces, vor dem ersten)
^ " springt zu "c" — dem ersten echten ZeichenIn der Praxis ist ^ fast immer die nützlichere Variante — Code beginnt selten in Spalte 1, eingerückte Zeilen sind die Norm. 0 ist sinnvoll, wenn man tatsächlich den Whitespace-Bereich braucht (z. B. zum Bearbeiten der Einrückung).
Analog ist g_ die nützlichere Variante von $: viele Editoren produzieren Trailing-Whitespace ohne Absicht, und g_ springt zum letzten bedeutungstragenden Zeichen, nicht zum letzten White-Char. Bei sauberen Dateien sind beide gleichwertig.
Anker mit Operatoren kombiniert
Diese Anker kombinieren sich mit Operatoren zu sehr häufigen Edit-Befehlen:
| Befehl | Bedeutung |
|---|---|
d$ / D | bis Zeilenende löschen |
d0 | bis Spalte 1 löschen |
d^ | bis erstem Nicht-Whitespace löschen |
c$ / C | bis Zeilenende ändern (= load + Insert) |
y$ / Y | bis Zeilenende kopieren (Y so in modernem Vim als Default) |
>$ | von hier bis Zeilenende einrücken (selten gebraucht) |
gU$ | bis Zeilenende uppercase |
D, C und Y sind klassische Abkürzungen für d$, c$, y$ — sie sparen jeweils einen Tastendruck. D und C sind in jeder Vim-Version Default, Y (als Synonym für y$) ist in modernem Vim und Neovim als Default gesetzt; in alten Versionen ist es noch yy, weshalb manche Konfigs explizit mappen:
" Y soll wie D und C funktionieren: bis Zeilenende (nicht ganze Zeile)
nnoremap Y y$Counts mit hjkl und Anker
Counts wirken auf alle Motions, auch hjkl:
10j " zehn Zeilen nach unten
25l " 25 Zeichen nach rechts
100k " 100 Zeilen nach oben
" Auf den Anker-Tasten haben Counts oft keine sinnvolle Bedeutung —
" "5$" springt zur fünften Zeile darunter und dann zum Zeilenende,
" was selten gewollt ist.Praktisch ist die Kombination Count + Zeilen-Sprung mit gg und G (siehe spätere Artikel zur Sprung-Navigation):
42G " zur Zeile 42 (G ohne Count = letzte Zeile)
42gg " zur Zeile 42 (alternativ-Syntax)
:42 " ebenfalls zur Zeile 42 (Ex-Variante)42G und :42 sind funktional identisch — beide springen zur Zeile 42. Wer die Zeile schon kennt (z. B. aus einem Compiler-Fehler), kann das direkt eingeben statt mit j/k zu zählen.
Was Vim bei l am Zeilenende tut
Eine kleine Eigenheit: Standardmäßig stoppt l am Zeilenende — der Cursor wandert nicht in die nächste Zeile. Wer das ändern will, gibt es die Option whichwrap:
" Default ist whichwrap=b,s
" Mit dieser Erweiterung: l und h wrappen über Zeilenende hinaus,
" Pfeiltasten ebenfalls.
set whichwrap+=<,>,h,l,[,]Im Default-Verhalten ist Vim strikt zeilenorientiert — der Cursor bleibt in der Zeile, in der er ist, bis man explizit mit j/k wechselt. Manche User mögen das nicht und mappen den Wrap-Modus ein; andere finden gerade diese Strenge nützlich, weil sie versehentliche Zeilenwechsel verhindert.
Eine analoge Option: set virtualedit=onemore lässt den Cursor in jeder Zeile eine Position hinter das letzte Zeichen, statt direkt auf dem letzten Zeichen zu stehen. Für viele Editor-Refugees natürlicher.
Pfeiltasten — warum disziplinär verbannen
Die häufigste Empfehlung in Vim-Tutorials lautet: Pfeiltasten in der Lernphase deaktivieren. Der Grund ist nicht religiös, sondern pragmatisch — solange die Pfeile funktionieren, nutzt man sie automatisch, weil sie vertraut sind. Das blockiert das Lernen der hjkl-Reflexe.
" Pfeile im Normal-Mode komplett blockieren
nnoremap <Up> <Nop>
nnoremap <Down> <Nop>
nnoremap <Left> <Nop>
nnoremap <Right> <Nop>
" Auch im Insert-Mode (umstrittener, aber konsequent)
inoremap <Up> <Nop>
inoremap <Down> <Nop>
inoremap <Left> <Nop>
inoremap <Right> <Nop>
" Auch in Visual
vnoremap <Up> <Nop>
vnoremap <Down> <Nop>
vnoremap <Left> <Nop>
vnoremap <Right> <Nop>Drei Tage schmerzhaftes Umlernen, danach ist hjkl unter den Fingern automatisch. Wer es radikaler will, klebt sich für eine Woche Pflaster über die Pfeil-Tasten auf der physischen Tastatur — eine erstaunlich wirksame Methode.
Nach dem Lernprozess kann man die Mappings wieder entfernen — die Pfeile sind dann nur noch verfügbar, werden aber kaum noch gebraucht.
Pfeile im Command-Line-Mode behalten
Eine Ausnahme: im Command-Line-Mode (nach :, /, ?) sind Pfeile nützlich für History-Navigation (Up/Down) und für Cursor-Bewegung im Befehl (Left/Right). Sie sollten nicht disabled werden:
" NICHT mappen — Command-Line lassen Pfeile intakt
" (cnoremap würde sie auch dort blocken — bewusst nicht!)Praxis-Workflows
Drei typische Navigations-Szenarien:
Workflow 1: zur Funktion-Definition springen
" In Zeile 200 eines Codes, Funktion ist in Zeile 50
50G " direkt zur Zeile 50
^ " zum ersten Nicht-Whitespace-ZeichenWorkflow 2: am Zeilenende ein Semikolon ergänzen
" Cursor irgendwo in der Zeile
$a;<Esc> " ans Ende, append, ; tippen, zurück in Normal
" Oder kompakter mit A (siehe Insert-Mode-Artikel):
A;<Esc> " A = Insert am Zeilenende — zwei Tasten gespartWorkflow 3: zwischen Zeilen-Anfang und -Ende pendeln
" Schnell die Einrückung kontrollieren
^ " erstes Nicht-Whitespace
g_ " letztes Nicht-Whitespace
^ " zurück zum AnfangInteressantes
hjkl ist eine ADM-3A-Erbschaft, kein Mythos
Bill Joys Tastatur in den 1970er Jahren hatte die Pfeil-Symbole tatsächlich auf den Tasten h, j, k, l aufgedruckt. Das Layout ist seitdem aus historischen Gründen geblieben — und hat sich durch die Heimreihen-Position als ergonomisch überlegen erwiesen. Die Geschichte ist gut belegt, kein Internet-Mythos.
`gj`/`gk` sind in Markdown- und Prosa-Workflows essentiell
Bei langen Sätzen, die über mehrere visuelle Zeilen wrappen, ist j ein Sprung über den ganzen Paragraph — was beim Lesen verwirrt. gj/gk folgen der Anzeige. Wer viel mit Prosa arbeitet, mappt sich typischerweise j/k auf gj/gk.
`^` ist `0` für gut erzogene Programmierer
Wer in eingerücktem Code arbeitet, will praktisch nie die Spalte-1-Position (0), sondern das erste sinnvolle Zeichen (^). ^ ist im Alltag deutlich öfter die richtige Wahl — und sollte als Reflex aufgebaut werden.
`D` = `d$`, `C` = `c$`, `Y` sollte `y$` sein
Die Großbuchstaben-Varianten sparen einen Tastendruck und sind ein verbreitetes Idiom. Y als Default-yy ist in alten Vims eine Inkonsistenz — in modernem Vim ist Y = y$, in alten Versionen lohnt das explizite Mapping.
Pfeile blockieren bringt mehr als 10 Cheat-Sheets
Mappen der Pfeile auf <Nop> ist eine erstaunlich wirksame Lern-Maßnahme. Drei bis fünf Tage Unbequemlichkeit, danach ist hjkl automatisch — und der Editor fühlt sich anders an. Wer es nicht aushält, kann auf Insert-Mode-Pfeile verzichten und nur Normal-Mode mappen.
`set virtualedit=onemore` für IDE-Reflexe
In den meisten Editoren steht der Cursor hinter dem letzten Zeichen einer Zeile. In Vim direkt auf dem letzten Zeichen. Wer das nicht mag, schaltet virtualedit=onemore ein — und kann den Cursor eine Position weiter setzen. Im Alltag eher Geschmacks- als Notwendigkeitsfrage.
Weiterführende Ressourcen
Externe Quellen
- Vim Help: left-right-motions — h, l und Zeilen-Anker.
- Vim Help: up-down-motions — j, k, gj, gk.
- Vim Help: whichwrap — Cursor-Wrap-Verhalten.
- Vim Help: virtualedit — virtuelle Cursor-Positionen.
- Why vi uses the hjkl keys for movement — die ADM-3A-Geschichte mit Foto-Beweis.