ShellServer.psm1
# # Set port to use. It shouldn't have collisions as Udp, but user might want to change it. # Also will have to change in __init__.py # # if you change the port number here, you must change below too $Port = 5432 Register-EngineEvent PowerShell.Exiting -action { $Sock = New-Object System.Net.Sockets.UdpClient # Change port number below if you changed above. Don't use variables. $Sock.Connect('127.0.0.1', 5432) $Sock.Send([System.Text.Encoding]::UTF8.GetBytes('2Exit')) } > $nul # # Create socket objects to interact with server # $LocalHost = '127.0.0.1' $Encoder = [System.Text.Encoding]::UTF8 $Sock = New-Object System.Net.Sockets.UdpClient $Sock.Client.ReceiveTimeout = 3000 $Sock.Connect($LocalHost, $Port) $Address = [System.Net.IpAddress]::Parse($LocalHost) $End = New-Object System.Net.IPEndPoint $Address, $Port $buffer = $Encoder.GetBytes('2Initpwsh') $Sock.Send($buffer) > $nul # # Define functions that will interact with server # function global:prompt { $origDollarQuestion = $global:? $origLastExitCode = $global:LASTEXITCODE # if venv > 0: venv == venv.length $venv = $env:VIRTUAL_ENV if ($venv) { $venv = ($venv.Substring($venv.LastIndexOf('\')+1)).Length + 3 } else { Write-Host '' $venv = [int] [bool] $venv } $width = $Host.UI.RawUI.BufferSize.Width - $venv $duration = (Get-History -Count 1).Duration if (-Not $duration) { $duration = 0.0 } else { $duration = [string]$duration.TotalSeconds } $buffer = $Encoder.GetBytes('1' + [int] $origDollarQuestion + (Get-Location).Path + ";$width;$duration") $Sock.Send($buffer) > $nul try {$response = Receive-Msg} catch [System.Net.Sockets.SocketException] { Write-Host "Server didn't respond in time." function global:prompt { "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " } Set-PSReadLineKeyHandler -Key Enter -ScriptBlock { [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() } "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " } $ExecutionContext.InvokeCommand.ExpandString($response) $global:LASTEXITCODE = $origLastExitCode if ($global:? -ne $origDollarQuestion) { if ($origDollarQuestion) { 1+1 } else { Write-Error '' -ErrorAction 'Ignore' } } } function p { param( [switch]$o, [string]$path ) if (-not $path) { Set-Location return } if ($args) { $path += ' ' + $args -join ' ' } $buffer = $Encoder.GetBytes("3$path") $Sock.Send($buffer) > $nul $response = Receive-Msg if (($response) -and ($o.IsPresent)) { Write-Output $response } elseif ($response) { Set-Location $response } elseif ($resPath = try {(Resolve-Path $path -ErrorAction 'Stop').Path} catch {}) { Set-Location $resPath } else { Write-Output "ShellServer: No match found." } } function pz { $buffer = $Encoder.GetBytes('4') $Sock.Send($buffer) > $nul $response = Receive-Msg $query = $args -Join ' ' if ($query) { $answer = Write-Output $response | fzf --height=~20 --layout=reverse -q $query } else { $answer = Write-Output $response | fzf --height=~20 --layout=reverse } # send the user choice to server to adjust precedence $Sock.Send($Encoder.GetBytes('4' + $answer)) > $nul Set-Location $answer } function Get-ServerListDir { param($opts, $path) if ($path) { try {$resPath = (Resolve-Path $path -ErrorAction 'Stop').Path} catch {$resPath = (Get-Error).TargetObject} } else { $resPath = (Get-Location).Path } $buffer = $Encoder.GetBytes("5$opts;$resPath") $Sock.Send($buffer) > $nul $response = Receive-Msg $ExecutionContext.InvokeCommand.ExpandString($response) } function ll { Get-ServerListDir '-cil' ($args -join ' ') } function la { Get-ServerListDir '-acil' ($args -join ' ') } function Switch-Theme { param( [Parameter()] [ArgumentCompletions('all', 'terminal', 'system', 'blue', 'readline')] $arg ) if ($arg -eq 'readline') { Switch-ReadlineTheme return } $buffer = $Encoder.GetBytes("6$arg") $Sock.Send($buffer) > $nul if (($arg -eq 'terminal') -Or (-Not $arg)) { Switch-ReadlineTheme } } function Search-History { param( # Is there a better way to do this? [switch]$c, [switch]$a, [switch]$ac, [switch]$ca ) $opt = '' if ($c.IsPresent) { $opt += 'c' } if ($a.IsPresent) { $opt += 'a' } if (($ac.IsPresent) -or ($ca.IsPresent)) { $opt = 'ac' } $width = $Host.UI.RawUI.BufferSize.Width $height = $Host.UI.RawUI.BufferSize.Height $arg = $args -join ' ' $buffer = $Encoder.GetBytes("7$arg;$width;$height;$opt") $Sock.Send($buffer) > $nul $response = Receive-Msg $ExecutionContext.InvokeCommand.ExpandString($response) } function Switch-ServerTimeout { param([Parameter(Mandatory=$true)]$milliSeconds) $Sock.Client.ReceiveTimeout = $milliSeconds } function Switch-ServerOpt { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ArgumentCompletions( 'timeit', 'no-timeit', 'fallback', 'no-fallback', 'watchdog', 'no-watchdog', 'disable-git', 'enable-git', 'use-gitstatus', 'use-git', 'use-pygit2', 'test-status', 'no-test-status', 'linear', 'no-linear', 'read-async', 'no-read-async', 'let-crash' )] $option ) $buffer = $Encoder.GetBytes("2Set$option") $Sock.Send($buffer) > $nul } function Receive-Msg { $answer = '' $iterativeAnswer = $Encoder.GetString($Sock.Receive([ref] $End)) while ($iterativeAnswer[0] -eq '1') { $answer += $iterativeAnswer.Substring(1) $iterativeAnswer = $Encoder.GetString($Sock.Receive([ref] $End)) } $answer += $iterativeAnswer.Substring(1) return $answer } # # Misc. Front-end only functions, etc. # $FirstEnterKeyPress = 1 $LastCmdId = (Get-History -Count 1).Id Set-PSReadLineKeyHandler -Key Enter -ScriptBlock { $line = $cursor = $nul [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) $venv = [int](Test-Path 'ENV:VIRTUAL_ENV') $currentCmdId = (Get-History -Count 1).Id if (($currentCmdId -ne $LastCmdId) -or (-not $line) -or ($FirstEnterKeyPress)) { $Script:LastCmdId = $currentCmdId # will treat the enter after an empty line as the first enter key press $Script:FirstEnterKeyPress = [int](-not $line) [Console]::SetCursorPosition(0, [Console]::GetCursorPosition().Item2 - (2 - $venv)) Write-Host -NoNewline "`e[J`e[34m❯`e[0m $line" } [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() } $LightThemeColors = @{ Command = "DarkYellow" Comment = "`e[90m" ContinuationPrompt = "DarkGray" Default = "DarkGray" Emphasis = "DarkBlue" InlinePrediction = "DarkGray" Keyword = "Green" ListPrediction = "DarkYellow" ListPredictionSelected = "`e[34;238m" Member = "`e[34m" Number = "`e[34m" Operator = "DarkGray" Parameter = "DarkGray" Selection = "`e[34;238m" String = "DarkCyan" Type = "`e[32m" Variable = "Green" } $DarkThemeColors = @{ Command = "`e[93m" Comment = "`e[32m" ContinuationPrompt = "`e[34m" Default = "`e[37m" Emphasis = "`e[96m" InlinePrediction = "`e[38;5;238m" Keyword = "`e[92m" ListPrediction = "`e[33m" ListPredictionSelected = "`e[48;5;238m" Member = "`e[97m" Number = "`e[97m" Operator = "`e[90m" Parameter = "`e[90m" Selection = "`e[30;47m" String = "`e[36m" Type = "`e[34m" Variable = "`e[92m" } $IsLightThemeSelected = 0 if ((Get-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize).SystemUsesLightTheme) { Set-PSReadLineOption -Colors $LightThemeColors $Script:IsLightThemeSelected = 1 } else { Set-PSReadLineOption -Colors $DarkThemeColors $Script:IsLightThemeSelected = 0 } function Switch-ReadlineTheme { if ($IsLightThemeSelected -eq 0){ Set-PSReadLineOption -Colors $LightThemeColors $Script:IsLightThemeSelected = 1 } else { Set-PSReadLineOption -Colors $DarkThemeColors $Script:IsLightThemeSelected = 0 } } # # The server will respond the first communication with the completions # for the 'p' function. It's safer to keep this in the end of file. # $raw = Receive-Msg $completions = @($raw -split ';') $scriptBlock = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $completions | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { $_ } } Register-ArgumentCompleter -CommandName p -ParameterName path -ScriptBlock $scriptBlock Export-ModuleMember -Function @("p", "pz", "ll", "la", "Search-History", "Switch-Theme", "Switch-ServerTimeout", "Switch-ServerOpt") |