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. ![]()


