PSReadLine
PowerShell w wersji trzeciej wprowadził kilka zmian, dzięki korzystanie z PowerShell.exe jest nieco przyjemniejsze: ulepszony tabulator, usunięcie usterki, która “czyściła” wszystko za kursorem po wciśnięciu tabulatora. Ale to wszystko jedynie czubek góry lodowej: największe usprawnienie nie jest oczywiste. Nie jest nawet domyślnie wykorzystywane. Możliwości jego użycia w chwili obecnej są dwie: stworzyć coś własną rękę, bądź skorzystać z modułu PSReadLine.
Cóż to za zwierz?
Jednym z ukrytych skarbów w PowerShell 3 jest możliwość definiowania własnej funkcji PSConsoleHostReadline, które jest wywoływana w trakcie pracy w “normalnej” konsoli, przy każdym wciśnięciu klawisza (chyba, że będzie on “przechwycony” przez inne aplikacje/ system). Łatwo to sprawdzić samemu:
Skorzystałem z funkcji “przetwarzającej” od razu całą linię. W przeciwnym wypadku musiałbym jakoś obsłużyć budowanie komendy znak po znaku. Szczęśliwie, podobnie jak to miało miejsce w przypadku rozbudowywania tabulatora, aby móc tworzyć swoje własne “regułki”, nie musimy budować wszystkiego od zera. Szczególnie, że moduł PSReadline daje nam to wszystko plus parę innych ciekawych funkcji:
- kolorowanie składni (
)
- informowanie o błędnej składni
- lepsze wsparcie dla tworzenie komend na wiele linii
- możliwość definiowania własnych kombinacji klawiszy
- rozbudowana konfiguracja
Pierwsze kroki
Moduł ten, podobnie jak opisywany przeze mnie miesiąc temu TabExpansion++, znajdziemy na GitHubie. Aby moduł zainstalować musimy ściągnąć odpowiedniego zipa, rozpakować do folderu z modułami i załadować go do bieżącej sesji (bądź dodać do profilu).
I znów, podobnie jak to miało miejsce w przypadku TabExpansion++, od razu zyskujemy całkiem sporo: kolorowanie składni, kilka standardowych skrótów klawiszowych, plus kilka poleceń, dzięki którym możemy sami wzbogacić konsolę o kilka nam przydatnych skrótów:
Oczywiście, kolory możemy sami dobrać. Wystarczy wywołać komendę Set-PSReadlineOption z odpowiednim TokenKind/ Foreground/ Backgroundcolor:
Jeśli chodzi o definiowanie skrótów klawiszowych: w “zestawie” dostajemy sporo gotowych funkcji, którym możemy przypisać skróty klawiszowe (lub nadpisać je, jeśli zostały już uprzednio przypisane innym funkcjom). Dodatkowo możemy stworzyć własne kombinacje, korzystając przy tym z bloku kodu i specjalnie w tym celu stworzonych metod (również stanowiących część modułu). Jakie więc funkcje mamy od razu zdefiniowane? Pełną listę “aktywnych” skrótów uzyskamy korzystając z polecenia Get-PSReadlineKeyHandler:
Get-PSReadlineKeyHandler -Bound | Out-GridView
Najciekawsze z mojego punktu widzenia to:
- Ctrl+Shift+C: kopiuje bieżącą linię, bądź jej fragment przez nas zaznaczony
- Ctrl+v: wkleja zawartość bufora
- ctrl+x: wycina zaznaczony fragment
- Shift+lewo/prawo: zaznacza fragment linii
- Ctrl+L: czyści ekran, przepisując wpisaną komendę
- Ctrl+Spacja: pokazuje możliwe dopełnienia (troszkę jak TAB w bashu)
- Ctrl+z: cofa wprowadzone zmiany
- Ctrl+y: ponawia zmiany uprzednio cofnięte
- PageUp/PageDown: przewija bufor (żegnaj myszko!)
Jak widać, moduł bez modyfikacji załatwia w zasadzie odwieczny problem z pracą w konsoli bez użycia myszy.
Krok dalej
Oprócz funkcji, którym domyślnie przypisano skróty klawiszowe, istnieje całkiem sporo, które nie są zagospodarowane. Przypisanie im skrótu jest stosunkowo najprostszą modyfikacją, jaką możemy przeprowadzić, tej możliwości więc przyjrzymy się w drugiej kolejności.
Ponownie, w celu wylistowania dostępnych funkcji, możemy skorzystać z polecenia Get-PSReadlineKeyHandler, tym razem z przełącznikiem –Unbound. Oczywiście, część z funkcji będzie w pewien sposób odpowiadać funkcjom już zdefiniowanym. Doskonałym tego przykładem jest funkcja “Complete”, która jest “bashową” implementacją tabulatora: zamiast wędrować po liście TAB dopełnia część wspólną wszystkich pasujących elementów, ponowne jego wciśnięcie wyświetli dostępne opcje. Inne interesujące funkcje:
- CaptureScreen: idealny kandydat na “efekt wow” – pozwala kopiować linie widoczne na ekranie wraz z odpowiednim formatowaniem:
- Enable/DisableDemoMode: przydatne w trakcie demonstrowania modułu: pokazuje jakie klawisze były używane
- HistorySearchBackward/Forward: przeszukuje historię w poszukiwaniu poleceń zaczynających się od wpisanego ciągu znaków
- Shell(Forward/Backward)Word: przesuwanie wprzód/ w tył w oparciu o rzeczywiste elementy poleceń (a więc traktowanie polecenia “Get-Command” jako całości)
Własne skróty
Zdarzyć się może oczywiście, że pewnych funkcji nie znajdziemy wśród wbudowanych. Może być też tak, że pewne “reakcje” na wciśnięcie klawisza zechcemy przypisać czynnościom dość luźno związanym z wprowadzonymi komendami. Co wtedy? Zawsze możemy skorzystać z możliwości samodzielnego zdefiniowania bloku skryptu, który zostanie uruchomiony po wciśnięciu wybranego przez nas skrótu klawiszowego.
Najłatwiej jest oczywiście stworzyć skróty nie wymagające integracji z samą konsolą. Dla przykładu, skrót uruchamiający wybrany edytor:
Set-PSReadlineKeyHandler -Chord CTRL+N -ScriptBlock { notepad }
Drugi poziom “wtajemniczenia” korzysta z wpisanego tekstu, ale go nie zmienia. Dla przykładu: zaprogramujmy obsługę klawisza F1:
Set-PSReadlineKeyHandler -Chord F1 -ScriptBlock { $line = $null $cursor = $null [PSConsoleUtilities.PSConsoleReadline]::GetBufferState( [ref]$line, [ref]$cursor ) $token = [Management.Automation.PSParser]::Tokenize( $line, [ref]$null) | Where-Object { $_.Start -le $cursor -and $_.EndColumn -ge $cursor } | Select -First 1 -ExpandProperty Content if ((Get-Command -Name $token -ErrorAction SilentlyContinue) -or $token -like "about_*") { Get-Help $token -ShowWindow } }
Korzystając z dostępnej w ramach modułu klasy PSConoleReadline możemy w prosty sposób sprawdzić, co użytkownik wprowadził w konsoli. Następnie przetwarzamy tokeny i sprawdzamy, czy kursor znajduje się w takim elemencie składni, który posiada pomoc. Jeśli tak – wyświetlamy ją w osobnym oknie. W podobny sposób możemy na przykład przekazać ścieżkę do uruchamianego skryptu do edytora.
Ostatni stopień “wtajemniczenia” to definiowanie skrótów, które będą zmieniać wpisywaną właśnie komendę. Świetny przykład znajdziemy na stronie projektu: dzięki kilku liniom kodu możemy sprawić, że wpisanie cudzysłowu spowoduje jego “dopełnienie” i ustawi kursor w odpowiedniej pozycji:
Set-PSReadlineKeyHandler -Chord 'Oem7','Shift+Oem7' { param($key, $arg) $line = $null $cursor = $null [PSConsoleUtilities.PSConsoleReadline]::GetBufferState( [ref]$line, [ref]$cursor ) if ($line[$cursor] -eq $key.KeyChar) { # Just move the cursor [PSConsoleUtilities.PSConsoleReadline]::SetCursorPosition( $cursor + 1 ) } else { # Insert matching quotes, move cursor to be in between the quotes [PSConsoleUtilities.PSConsoleReadline]::Insert( "$($key.KeyChar)" * 2 ) [PSConsoleUtilities.PSConsoleReadline]::GetBufferState( [ref]$line, [ref]$cursor ) [PSConsoleUtilities.PSConsoleReadline]::SetCursorPosition( $cursor - 1 ) } }
W oparciu o ten przykład, możemy podobną regułkę stworzyć dla nawiasów:
Set-PSReadlineKeyHandler -Chord '(', '{', '[' -ScriptBlock { param ($key, $arg) $line = $null $cursor = $null [PSConsoleUtilities.PSConsoleReadline]::GetBufferState( [ref]$line, [ref]$cursor ) $pair = switch ($key.KeyChar) { '(' { ')' } '[' { ']' } '{' { '}' } } [PSConsoleUtilities.PSConsoleReadline]::Insert( "$($key.KeyChar)$pair" ) [PSConsoleUtilities.PSConsoleReadline]::GetBufferState( [ref]$line, [ref]$cursor ) [PSConsoleUtilities.PSConsoleReadline]::SetCursorPosition( $cursor - 1 ) }
Jak widać: wykorzystujemy przy tym trzy metody: jedna oceni co zostało wpisane (GetBufferState), druga – wstawi znaki przez nas wymagane (Insert), trzecia (SetCursorPosition) – ustawi kursor na odpowiedniej pozycji. Korzystając z tych trzech funkcji możemy uzyskać bardzo ciekawe efekty i w efekcie, uprościć jeszcze bardziej pracę w “tradycyjnej” konsoli.
Podsumowując: PSReadline może być świetnym uzupełnieniem zestawu narzędzi, dzięki którym praca w PowerShellu staje się przyjemniejsza. I choć sam na ogół korzystam z ISE (gdzie modułu tego nie da się nawet zaimportować) to wydaje mi się, że warto mieć go “pod ręką”. Szczególnie jeśli często zdarza nam się korzystać z “gołej” konsoli.