Last active
June 6, 2024 07:20
-
-
Save cdcd72/4e5f35d222fab88baed6a81b71452b01 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Management.Automation | |
using namespace System.Management.Automation.Language | |
if ($host.Name -eq 'ConsoleHost') | |
{ | |
Import-Module PSReadLine | |
} | |
Set-PSReadLineOption -PredictionSource History | |
Set-PSReadLineOption -PredictionViewStyle ListView | |
Set-PSReadLineOption -EditMode Windows | |
# 設定按下 Ctrl+d 可以退出 PowerShell 執行環境 | |
Set-PSReadlineKeyHandler -Chord ctrl+d -Function ViExit | |
# 設定按下 Ctrl+w 可以刪除一個單字 | |
Set-PSReadlineKeyHandler -Chord ctrl+w -Function BackwardDeleteWord | |
# 設定按下 Ctrl+e 可以移動游標到最後面(End) | |
Set-PSReadlineKeyHandler -Chord ctrl+e -Function EndOfLine | |
# 設定按下 Ctrl+a 可以移動游標到最前面(Begin) | |
Set-PSReadlineKeyHandler -Chord ctrl+a -Function BeginningOfLine | |
#Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward | |
#Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward | |
# This key handler shows the entire or filtered history using Out-GridView. The | |
# typed text is used as the substring pattern for filtering. A selected command | |
# is inserted to the command line without invoking. Multiple command selection | |
# is supported, e.g. selected by Ctrl + Click. | |
Set-PSReadLineKeyHandler -Key F7 ` | |
-BriefDescription History ` | |
-LongDescription 'Show command history' ` | |
-ScriptBlock { | |
$pattern = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$pattern, [ref]$null) | |
if ($pattern) | |
{ | |
$pattern = [regex]::Escape($pattern) | |
} | |
$history = [System.Collections.ArrayList]@( | |
$last = '' | |
$lines = '' | |
foreach ($line in [System.IO.File]::ReadLines((Get-PSReadLineOption).HistorySavePath)) | |
{ | |
if ($line.EndsWith('`')) | |
{ | |
$line = $line.Substring(0, $line.Length - 1) | |
$lines = if ($lines) | |
{ | |
"$lines`n$line" | |
} | |
else | |
{ | |
$line | |
} | |
continue | |
} | |
if ($lines) | |
{ | |
$line = "$lines`n$line" | |
$lines = '' | |
} | |
if (($line -cne $last) -and (!$pattern -or ($line -match $pattern))) | |
{ | |
$last = $line | |
$line | |
} | |
} | |
) | |
$history.Reverse() | |
$command = $history | Out-GridView -Title History -PassThru | |
if ($command) | |
{ | |
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert(($command -join "`n")) | |
} | |
} | |
# F1 for help on the command line - naturally | |
Set-PSReadLineKeyHandler -Key F1 ` | |
-BriefDescription CommandHelp ` | |
-LongDescription "Open the help window for the current command" ` | |
-ScriptBlock { | |
param($key, $arg) | |
$ast = $null | |
$tokens = $null | |
$errors = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor) | |
$commandAst = $ast.FindAll( { | |
$node = $args[0] | |
$node -is [CommandAst] -and | |
$node.Extent.StartOffset -le $cursor -and | |
$node.Extent.EndOffset -ge $cursor | |
}, $true) | Select-Object -Last 1 | |
if ($commandAst -ne $null) | |
{ | |
$commandName = $commandAst.GetCommandName() | |
if ($commandName -ne $null) | |
{ | |
$command = $ExecutionContext.InvokeCommand.GetCommand($commandName, 'All') | |
if ($command -is [AliasInfo]) | |
{ | |
$commandName = $command.ResolvedCommandName | |
} | |
if ($commandName -ne $null) | |
{ | |
Get-Help $commandName -ShowWindow | |
} | |
} | |
} | |
} | |
Set-PSReadLineKeyHandler -Key '"',"'" ` | |
-BriefDescription SmartInsertQuote ` | |
-LongDescription "Insert paired quotes if not already on a quote" ` | |
-ScriptBlock { | |
param($key, $arg) | |
$quote = $key.KeyChar | |
$selectionStart = $null | |
$selectionLength = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength) | |
$line = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) | |
# If text is selected, just quote it without any smarts | |
if ($selectionStart -ne -1) | |
{ | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $quote + $line.SubString($selectionStart, $selectionLength) + $quote) | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2) | |
return | |
} | |
$ast = $null | |
$tokens = $null | |
$parseErrors = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$parseErrors, [ref]$null) | |
function FindToken | |
{ | |
param($tokens, $cursor) | |
foreach ($token in $tokens) | |
{ | |
if ($cursor -lt $token.Extent.StartOffset) { continue } | |
if ($cursor -lt $token.Extent.EndOffset) { | |
$result = $token | |
$token = $token -as [StringExpandableToken] | |
if ($token) { | |
$nested = FindToken $token.NestedTokens $cursor | |
if ($nested) { $result = $nested } | |
} | |
return $result | |
} | |
} | |
return $null | |
} | |
$token = FindToken $tokens $cursor | |
# If we're on or inside a **quoted** string token (so not generic), we need to be smarter | |
if ($token -is [StringToken] -and $token.Kind -ne [TokenKind]::Generic) { | |
# If we're at the start of the string, assume we're inserting a new string | |
if ($token.Extent.StartOffset -eq $cursor) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$quote$quote ") | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
return | |
} | |
# If we're at the end of the string, move over the closing quote if present. | |
if ($token.Extent.EndOffset -eq ($cursor + 1) -and $line[$cursor] -eq $quote) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
return | |
} | |
} | |
if ($null -eq $token -or | |
$token.Kind -eq [TokenKind]::RParen -or $token.Kind -eq [TokenKind]::RCurly -or $token.Kind -eq [TokenKind]::RBracket) { | |
if ($line[0..$cursor].Where{$_ -eq $quote}.Count % 2 -eq 1) { | |
# Odd number of quotes before the cursor, insert a single quote | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($quote) | |
} | |
else { | |
# Insert matching quotes, move cursor to be in between the quotes | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$quote$quote") | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
} | |
return | |
} | |
# If cursor is at the start of a token, enclose it in quotes. | |
if ($token.Extent.StartOffset -eq $cursor) { | |
if ($token.Kind -eq [TokenKind]::Generic -or $token.Kind -eq [TokenKind]::Identifier -or | |
$token.Kind -eq [TokenKind]::Variable -or $token.TokenFlags.hasFlag([TokenFlags]::Keyword)) { | |
$end = $token.Extent.EndOffset | |
$len = $end - $cursor | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($cursor, $len, $quote + $line.SubString($cursor, $len) + $quote) | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($end + 2) | |
return | |
} | |
} | |
# We failed to be smart, so just insert a single quote | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($quote) | |
} | |
Set-PSReadLineKeyHandler -Key '(','{','[' ` | |
-BriefDescription InsertPairedBraces ` | |
-LongDescription "Insert matching braces" ` | |
-ScriptBlock { | |
param($key, $arg) | |
$closeChar = switch ($key.KeyChar) | |
{ | |
<#case#> '(' { [char]')'; break } | |
<#case#> '{' { [char]'}'; break } | |
<#case#> '[' { [char]']'; break } | |
} | |
$selectionStart = $null | |
$selectionLength = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength) | |
$line = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) | |
if ($selectionStart -ne -1) | |
{ | |
# Text is selected, wrap it in brackets | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $key.KeyChar + $line.SubString($selectionStart, $selectionLength) + $closeChar) | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2) | |
} else { | |
# No text is selected, insert a pair | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)$closeChar") | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
} | |
} | |
Set-PSReadLineKeyHandler -Key ')',']','}' ` | |
-BriefDescription SmartCloseBraces ` | |
-LongDescription "Insert closing brace or skip" ` | |
-ScriptBlock { | |
param($key, $arg) | |
$line = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) | |
if ($line[$cursor] -eq $key.KeyChar) | |
{ | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
} | |
else | |
{ | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)") | |
} | |
} | |
Set-PSReadLineKeyHandler -Key Backspace ` | |
-BriefDescription SmartBackspace ` | |
-LongDescription "Delete previous character or matching quotes/parens/braces" ` | |
-ScriptBlock { | |
param($key, $arg) | |
$line = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) | |
if ($cursor -gt 0) | |
{ | |
$toMatch = $null | |
if ($cursor -lt $line.Length) | |
{ | |
switch ($line[$cursor]) | |
{ | |
<#case#> '"' { $toMatch = '"'; break } | |
<#case#> "'" { $toMatch = "'"; break } | |
<#case#> ')' { $toMatch = '('; break } | |
<#case#> ']' { $toMatch = '['; break } | |
<#case#> '}' { $toMatch = '{'; break } | |
} | |
} | |
if ($toMatch -ne $null -and $line[$cursor-1] -eq $toMatch) | |
{ | |
[Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor - 1, 2) | |
} | |
else | |
{ | |
[Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar($key, $arg) | |
} | |
} | |
} | |
# winget parameter completion | |
Register-ArgumentCompleter -Native -CommandName winget -ScriptBlock { | |
param($wordToComplete, $commandAst, $cursorPosition) | |
[Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new() | |
$Local:word = $wordToComplete.Replace('"', '""') | |
$Local:ast = $commandAst.ToString().Replace('"', '""') | |
winget complete --word="$Local:word" --commandline "$Local:ast" --position $cursorPosition | ForEach-Object { | |
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) | |
} | |
} | |
# PowerShell parameter completion shim for the dotnet CLI | |
Register-ArgumentCompleter -Native -CommandName dotnet -ScriptBlock { | |
param($commandName, $wordToComplete, $cursorPosition) | |
dotnet complete --position $cursorPosition "$wordToComplete" | ForEach-Object { | |
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) | |
} | |
} | |
# PowerShell parameter completion shim for the npm CLI | |
Register-ArgumentCompleter -Native -CommandName npm -ScriptBlock { | |
param($wordToComplete, $commandAst, $cursorPosition) | |
$Local:ast = $commandAst.ToString().Replace(' ', '') | |
if ($Local:ast -eq 'npm') { | |
$command = 'run install start' | |
$array = $command.Split(' ') | |
$array | | |
Where-Object { $_ -like "$wordToComplete*" } | | |
ForEach-Object { | |
New-Object -Type System.Management.Automation.CompletionResult -ArgumentList $_ | |
} | |
} | |
if ($Local:ast -eq 'npmrun') { | |
$scripts = (Get-Content .\package.json | ConvertFrom-Json).scripts | |
$scripts | | |
Get-Member -MemberType NoteProperty | | |
Where-Object { $_.Name -like "$wordToComplete*" } | | |
ForEach-Object { | |
New-Object -Type System.Management.Automation.CompletionResult -ArgumentList $_.Name | |
} | |
} | |
} | |
# 移除兩個不實用的 Cmdlet Aliases | |
If (Test-Path Alias:curl) {Remove-Item Alias:curl} | |
If (Test-Path Alias:wget) {Remove-Item Alias:wget} | |
# 快速開啟 c:\windows\system32\drivers\etc\hosts 檔案 | |
function hosts { notepad c:\windows\system32\drivers\etc\hosts } | |
# 快速產生一組亂數密碼 (預設會產生 10 個字元的密碼) | |
function New-Password { | |
<# | |
.SYNOPSIS | |
Generate a random password. | |
.DESCRIPTION | |
Generate a random password. | |
.NOTES | |
Change log: | |
27/11/2017 - faustonascimento - Swapped Get-Random for System.Random. | |
Swapped Sort-Object for Fisher-Yates shuffle. | |
17/03/2017 - Chris Dent - Created. | |
#> | |
[CmdletBinding()] | |
[OutputType([String])] | |
param ( | |
# The length of the password which should be created. | |
[Parameter(ValueFromPipeline)] | |
[ValidateRange(8, 255)] | |
[Int32]$Length = 10, | |
# The character sets the password may contain. A password will contain at least one of each of the characters. | |
[String[]]$CharacterSet = ('abcdefghijklmnopqrstuvwxyz', | |
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', | |
'0123456789', | |
'!$%&^.#;'), | |
# The number of characters to select from each character set. | |
[Int32[]]$CharacterSetCount = (@(1) * $CharacterSet.Count), | |
[Parameter()] | |
[switch]$ConvertToSecureString | |
) | |
begin { | |
$bytes = [Byte[]]::new(4) | |
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() | |
$rng.GetBytes($bytes) | |
$seed = [System.BitConverter]::ToInt32($bytes, 0) | |
$rnd = [Random]::new($seed) | |
if ($CharacterSet.Count -ne $CharacterSetCount.Count) { | |
throw "The number of items in -CharacterSet needs to match the number of items in -CharacterSetCount" | |
} | |
$allCharacterSets = [String]::Concat($CharacterSet) | |
} | |
process { | |
try { | |
$requiredCharLength = 0 | |
foreach ($i in $CharacterSetCount) { | |
$requiredCharLength += $i | |
} | |
if ($requiredCharLength -gt $Length) { | |
throw "The sum of characters specified by CharacterSetCount is higher than the desired password length" | |
} | |
$password = [Char[]]::new($Length) | |
$index = 0 | |
for ($i = 0; $i -lt $CharacterSet.Count; $i++) { | |
for ($j = 0; $j -lt $CharacterSetCount[$i]; $j++) { | |
$password[$index++] = $CharacterSet[$i][$rnd.Next($CharacterSet[$i].Length)] | |
} | |
} | |
for ($i = $index; $i -lt $Length; $i++) { | |
$password[$index++] = $allCharacterSets[$rnd.Next($allCharacterSets.Length)] | |
} | |
# Fisher-Yates shuffle | |
for ($i = $Length; $i -gt 0; $i--) { | |
$n = $i - 1 | |
$m = $rnd.Next($i) | |
$j = $password[$m] | |
$password[$m] = $password[$n] | |
$password[$n] = $j | |
} | |
$password = [String]::new($password) | |
if ($ConvertToSecureString.IsPresent) { | |
ConvertTo-SecureString -String $password -AsPlainText -Force | |
} else { | |
$password | |
} | |
} catch { | |
Write-Error -ErrorRecord $_ | |
} | |
} | |
} | |
Import-Module -Name Terminal-Icons | |
oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH\neil.omp.json" | Invoke-Expression |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment