AutoHotKey w PowerShellu
Dziś nieco o lenistwie i wszelkich jego konsekwencjach. O tym, jak korzystając nieustannie z konsoli – pisać (paradoksalnie) jak najmniej. Dlaczego? Otóż wszyscy, którzy mnie znają wiedzą, że marny ze mnie pisarczyk. I nie chodzi tu o poziom moich wypocin na blogu czy w innych mediach (tu chyba mimo wszystko wychodzę obronną ręką, głównie dzięki sprawdzaniu pisowni w Wordzie i Live Writerze) – raczej o literówki, którymi strzelam na lewo i prawo w konsoli. W PowerShellu na ogół ratuje mnie tabulator, ale…
Tuż przed świętami wykupiłem kurs gita od Maćka Aniserowicza. Niby gita znam dość dobrze, ale stwierdziłem, że nie zaszkodzi wiedzę nieco uporządkować i rozszerzyć. Kurs okazał się strzałem w dziesiątkę z różnych względów, ale jednym z nich było ukłucie zazdrości, gdy zobaczyłem jak Maciek pisze „gc” a jego konsola uczynnie zamienia to na soczyste „git commit”. Od razu nabrałem przekonania, że PowerShella też powinno się dać do tego zmusić. Ale najpierw przyszły święta, potem wakacje z rodzinką i na eksperymenty zabrakło czasu. Dopiero wczoraj wieczorem mnie olśniło. Oczywiście, rozwiązanie nasuwa się samo – PSReadLine!
Możliwości implementacji tego rozwiązania jest kilka. W końcu zdecydowałem się na najprostszy: odtworzenie działania AutoHotKey bez żadnych kompromisów. Chcę napisać „ad”, nacisnąć spację i zobaczyć „git add „, znak zachęty i – hulaj dusza, piekła nie ma. Listę makr ograniczyłem do kilku zaledwie poleceń, ale myślę, że każdy „dośpiewa” sobie resztę.
Zanim przedstawię sam kod, składniki:
- tablica skrótów, w której kluczami będą nasze makra, a odpowiadające im wartości użyte będą w czasie „podmianki”
- metoda GetBufferState pobierającą aktualną zawartość linii
- metoda Replace, która podmieni to, co wpisałem na to, co chcę zobaczyć
- metoda SetCursorPosition, która przesunie kursor na koniec tego, co właśnie „wkleiła” metoda uprzednia
Cały kod:
Set-PSReadlineKeyHandler -Chord Spacebar -ScriptBlock { | |
param ($key, $arg) | |
$psReadLine = [Microsoft.PowerShell.PSConsoleReadLine] | |
$convert = @{ | |
'co' = 'git commit ' | |
'ad' = 'git add ' | |
'pu' = 'git push ' | |
'me' = 'git merge ' | |
} | |
$line = $null | |
$cursor = $null | |
$psReadLine::GetBufferState( | |
[ref]$line, [ref]$cursor | |
) | |
if ($convert.ContainsKey($line)) { | |
$psReadLine::Replace( | |
0, | |
$line.Length, | |
$convert.$line | |
) | |
$psReadLine::SetCursorPosition($convert.$line.Length) | |
} else { | |
$psReadLine::Insert(' ') | |
} | |
} |
Przy okazji: to nie jest pierwsze podejście do problemu: jak gitować bez pisania. Już jakiś czas temu zaimplementowałem w swoim profilu mechanizm, który sprawiał, że aliasy w gicie stawały się niemal pełnoprawnymi komendami w PowerShellu. Jak? Wykorzystując CommandNotFoundAction. Od wersji trzeciej sami możemy obsłużyć sytuację, gdy PowerShell nie znajdzie wpisanej przez nas komendy. Wystarczy więc pobrać listę aliasów, sprawdzić czy wpisana komenda się pokrywa z jednym z nich, przekazać pozostałe parametry i już:
$gitAliases = | |
(git config --global -l).Where{ | |
$_ -match '^alias\.' | |
}.ForEach{ | |
$_ -replace '^alias\.(\w+).*', '$1' | |
} | |
$ExecutionContext.InvokeCommand.CommandNotFoundAction = { | |
param ($name, $eventArgs) | |
if ($name -in $gitAliases) { | |
$alias = $name | |
} elseif ($aliases = $gitAliases -match [regex]::Escape($name)) { | |
$alias = $aliases | | |
Sort-Object -Property Length | | |
Select-Object -First 1 | |
} | |
if ($alias) { | |
$eventArgs.CommandScriptBlock = { git $alias @args }.GetNewClosure() | |
} | |
} |
Biorąc pod uwagę, że mam wiele aliasów opartych o LOLCODE, moja historia poleceń wygląda dość komicznie:
Wcześniej próbowałem podobnego rozwiązania z innym zestawem aliasów, ale historia wygląda z nim mało cenzuralnie, uznałem więc, że to jednak troszkę nierozsądne.