completers/python_completer/python_completer.ps1
|
Set-StrictMode -Version 2.0 if (-not (Get-Variable -Name PythonModuleCache -Scope Script -ErrorAction Ignore)) { $script:PythonModuleCache = @{ RootsKey = $null Roots = @() NamesByDirectory = @{} } } function New-PythonCompletionResult { param( [string]$CompletionText, [string]$ResultType = 'ParameterValue', [string]$ToolTip, [string]$ListItemText ) if ([string]::IsNullOrWhiteSpace($ListItemText)) { $ListItemText = $CompletionText } if ([string]::IsNullOrWhiteSpace($ToolTip)) { $ToolTip = $ListItemText } [System.Management.Automation.CompletionResult]::new( $CompletionText, $ListItemText, $ResultType, $ToolTip ) } function Test-PythonStartsWith { param( [string]$Candidate, [string]$Prefix, [switch]$CaseSensitive ) if ([string]::IsNullOrEmpty($Prefix)) { return $true } $comparison = if ($CaseSensitive) { [System.StringComparison]::Ordinal } else { [System.StringComparison]::OrdinalIgnoreCase } $Candidate.StartsWith($Prefix, $comparison) } function New-PythonOptionSpec { param( [string[]]$Tokens, [string]$Description, [string]$ValueKind = '' ) [pscustomobject]@{ Tokens = @($Tokens) Description = $Description ValueKind = $ValueKind } } function New-PythonXOptionSpec { param( [string]$Name, [string]$Description, [string[]]$ValueHints = @(), [bool]$AllowBare = $true ) [pscustomobject]@{ Name = $Name Description = $Description ValueHints = @($ValueHints) AllowBare = $AllowBare } } function Get-PythonOptionSpecs { @( New-PythonOptionSpec @('-b') 'Issue warnings about str(bytes_instance), str(bytearray_instance), and bytes/int comparisons.' New-PythonOptionSpec @('-bb') 'Issue errors instead of warnings for bytes/str and bytes/int comparisons.' New-PythonOptionSpec @('-B') 'Do not write .pyc files on import.' New-PythonOptionSpec @('-c') 'Run the given command string.' 'CommandString' New-PythonOptionSpec @('-d') 'Turn on parser debugging output.' New-PythonOptionSpec @('-E') 'Ignore PYTHON* environment variables.' New-PythonOptionSpec @('-h', '-?', '--help') 'Show python command-line help.' New-PythonOptionSpec @('-i') 'Inspect interactively after running a script or command.' New-PythonOptionSpec @('-I') 'Isolated mode: imply -E and -s.' New-PythonOptionSpec @('-m') 'Run a library module as a script.' 'ModuleName' New-PythonOptionSpec @('-O') 'Remove assert statements and __debug__-dependent code.' New-PythonOptionSpec @('-OO') 'Also discard docstrings when optimizing.' New-PythonOptionSpec @('-P') 'Do not prepend a potentially unsafe path to sys.path.' New-PythonOptionSpec @('-q') 'Do not print the copyright and version messages on interactive startup.' New-PythonOptionSpec @('-s') 'Do not add the user site-packages directory to sys.path.' New-PythonOptionSpec @('-S') 'Do not imply import site on initialization.' New-PythonOptionSpec @('-u') 'Force stdout and stderr to be unbuffered.' New-PythonOptionSpec @('-v') 'Verbose import tracing.' New-PythonOptionSpec @('-V', '--version') 'Print the Python version number and exit.' New-PythonOptionSpec -Tokens @('-W') -Description 'Warning control (action:message:category:module:lineno).' -ValueKind 'WarningFilter' New-PythonOptionSpec @('-x') 'Skip the first line of the source.' New-PythonOptionSpec @('-X') 'Set an implementation-specific option.' 'XOption' New-PythonOptionSpec @('--check-hash-based-pycs') 'Control validation behavior for hash-based .pyc files.' 'HashPycsMode' New-PythonOptionSpec @('--help-env') 'Show help about Python environment variables.' New-PythonOptionSpec @('--help-xoptions') 'Show help about implementation-specific -X options.' New-PythonOptionSpec @('--help-all') 'Show complete help output.' ) } function Get-PythonOptionMap { # python's option parser is case-sensitive (-x and -X, -b and -B are different options). $map = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal) foreach ($spec in Get-PythonOptionSpecs) { foreach ($token in $spec.Tokens) { $map[$token] = $spec } } $map } function Get-PythonXOptionSpecs { @( New-PythonXOptionSpec -Name 'context_aware_warnings' -Description 'Enable or disable context-aware warnings.' -ValueHints @('0', '1') New-PythonXOptionSpec -Name 'cpu_count' -Description 'Override os.cpu_count() and related APIs.' -ValueHints @('default', '<N>') -AllowBare $false New-PythonXOptionSpec -Name 'dev' -Description 'Enable Python development mode.' New-PythonXOptionSpec -Name 'disable-remote-debug' -Description 'Disable remote debugging support.' New-PythonXOptionSpec -Name 'faulthandler' -Description 'Enable faulthandler.' New-PythonXOptionSpec -Name 'frozen_modules' -Description 'Control frozen module usage.' -ValueHints @('on', 'off') New-PythonXOptionSpec -Name 'importtime' -Description 'Show import timing information.' -ValueHints @('2') New-PythonXOptionSpec -Name 'int_max_str_digits' -Description 'Limit int-to-string conversion digit count.' -ValueHints @('<N>') -AllowBare $false New-PythonXOptionSpec -Name 'no_debug_ranges' -Description 'Disable extra location tables for tracebacks.' New-PythonXOptionSpec -Name 'perf' -Description 'Enable Linux perf profiler support.' New-PythonXOptionSpec -Name 'perf_jit' -Description 'Enable Linux perf JIT support.' New-PythonXOptionSpec -Name 'pycache_prefix' -Description 'Write bytecode caches under the given prefix path.' -ValueHints @('<PATH>') -AllowBare $false New-PythonXOptionSpec -Name 'showrefcount' -Description 'Display total reference count and memory blocks at shutdown.' New-PythonXOptionSpec -Name 'thread_inherit_context' -Description 'Control thread context inheritance.' -ValueHints @('0', '1') New-PythonXOptionSpec -Name 'tracemalloc' -Description 'Start tracing memory allocations.' -ValueHints @('1', '<N>') New-PythonXOptionSpec -Name 'utf8' -Description 'Enable or disable UTF-8 mode.' -ValueHints @('0', '1') New-PythonXOptionSpec -Name 'warn_default_encoding' -Description 'Enable EncodingWarning for default encodings.' ) } function Get-PythonCommandLineState { param( [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) $line = if ($null -eq $CommandAst) { '' } else { $CommandAst.Extent.Text } if ($null -eq $line) { $line = '' } if ($null -ne $CommandAst -and $CursorPosition -gt $CommandAst.Extent.EndOffset) { $line += [string]::new([char]32, ($CursorPosition - $CommandAst.Extent.EndOffset)) } $relativeCursor = if ($null -eq $CommandAst) { $CursorPosition } else { $CursorPosition - $CommandAst.Extent.StartOffset } $safeCursor = [Math]::Min([Math]::Max($relativeCursor, 0), $line.Length) $prefix = $line.Substring(0, $safeCursor) $tokens = New-Object System.Collections.Generic.List[string] $builder = New-Object System.Text.StringBuilder $quoteChar = [char]0 foreach ($character in $prefix.ToCharArray()) { if (($character -eq [char]34) -or ($character -eq [char]39)) { if ($quoteChar -eq [char]0) { $quoteChar = $character } elseif ($quoteChar -eq $character) { $quoteChar = [char]0 } [void]$builder.Append($character) continue } if ([char]::IsWhiteSpace($character) -and $quoteChar -eq [char]0) { if ($builder.Length -gt 0) { $tokens.Add($builder.ToString()) [void]$builder.Clear() } continue } [void]$builder.Append($character) } $hasTrailingSpace = $prefix -match '\s$' if ($builder.Length -gt 0) { $tokens.Add($builder.ToString()) } if ($hasTrailingSpace) { return [pscustomobject]@{ TokensBeforeCurrent = @($tokens) CurrentToken = '' } } if ($tokens.Count -gt 0) { return [pscustomobject]@{ TokensBeforeCurrent = @($tokens | Select-Object -First ($tokens.Count - 1)) CurrentToken = $tokens[$tokens.Count - 1] } } [pscustomobject]@{ TokensBeforeCurrent = @() CurrentToken = '' } } function Get-PythonArgumentState { param([pscustomobject]$CommandLineState) $tokensBeforeCurrent = @($CommandLineState.TokensBeforeCurrent) $currentArgument = if ($null -eq $CommandLineState.CurrentToken) { '' } else { $CommandLineState.CurrentToken } $argumentsBeforeCurrent = if ($tokensBeforeCurrent.Count -gt 0) { @($tokensBeforeCurrent | Select-Object -Skip 1) } else { @() } if ($tokensBeforeCurrent.Count -eq 0 -and $currentArgument -match '^(?i:python(?:\.exe)?)$') { $currentArgument = '' } [pscustomobject]@{ ArgumentsBeforeCurrent = $argumentsBeforeCurrent CurrentArgument = $currentArgument } } function Get-PythonCompletionContext { param([string[]]$ArgumentsBeforeCurrent) $optionMap = Get-PythonOptionMap $pendingValueKind = '' $terminalMode = '' foreach ($argument in @($ArgumentsBeforeCurrent)) { if (-not [string]::IsNullOrEmpty($terminalMode)) { continue } if ([string]::IsNullOrWhiteSpace($argument)) { continue } $consumedPendingValue = $false if (-not [string]::IsNullOrEmpty($pendingValueKind)) { switch ($pendingValueKind) { 'CommandString' { $pendingValueKind = '' $terminalMode = 'CommandTail' $consumedPendingValue = $true break } 'ModuleName' { $pendingValueKind = '' $terminalMode = 'ModuleTail' $consumedPendingValue = $true break } default { $pendingValueKind = '' $consumedPendingValue = $true break } } if ($consumedPendingValue) { continue } } if ($optionMap.ContainsKey($argument)) { $valueKind = $optionMap[$argument].ValueKind if (-not [string]::IsNullOrWhiteSpace($valueKind)) { $pendingValueKind = $valueKind } continue } if ($argument.StartsWith('-', [System.StringComparison]::Ordinal) -and $argument.Length -gt 1) { # Unknown or clustered switch: it is not the program operand. continue } $terminalMode = 'ScriptTail' } [pscustomobject]@{ PendingValueKind = $pendingValueKind TerminalMode = $terminalMode } } function Get-PythonUniqueResults { param( [object[]]$Results, [switch]$CaseSensitive ) $comparer = if ($CaseSensitive) { [System.StringComparer]::Ordinal } else { [System.StringComparer]::OrdinalIgnoreCase } $seen = [System.Collections.Generic.HashSet[string]]::new($comparer) $unique = New-Object System.Collections.Generic.List[object] foreach ($result in @($Results)) { if ($null -eq $result) { continue } if ($seen.Add($result.CompletionText)) { [void]$unique.Add($result) } } @($unique.ToArray()) } function Get-PythonOptionCompletions { param([string]$CurrentWord) $results = New-Object System.Collections.Generic.List[object] foreach ($spec in Get-PythonOptionSpecs) { foreach ($token in $spec.Tokens) { if (Test-PythonStartsWith -Candidate $token -Prefix $CurrentWord -CaseSensitive) { [void]$results.Add((New-PythonCompletionResult -CompletionText $token -ResultType 'ParameterName' -ToolTip $spec.Description)) } } } @(Get-PythonUniqueResults -Results $results.ToArray() -CaseSensitive) } function Get-PythonWarningFilterCompletions { param([string]$CurrentWord) $actions = @( @{ Name = 'default'; Description = 'Print the first occurrence of matching warnings for each location.' } @{ Name = 'error'; Description = 'Turn matching warnings into exceptions.' } @{ Name = 'always'; Description = 'Always print matching warnings.' } @{ Name = 'all'; Description = 'Alias for always.' } @{ Name = 'module'; Description = 'Print the first occurrence of matching warnings for each module.' } @{ Name = 'once'; Description = 'Print only the first occurrence of matching warnings.' } @{ Name = 'ignore'; Description = 'Never print matching warnings.' } ) $categories = @( 'Warning', 'DeprecationWarning', 'PendingDeprecationWarning', 'UserWarning', 'SyntaxWarning', 'RuntimeWarning', 'FutureWarning', 'ImportWarning', 'UnicodeWarning', 'BytesWarning', 'ResourceWarning', 'EncodingWarning' ) $results = New-Object System.Collections.Generic.List[object] $fields = @($CurrentWord.Split([char]':')) if ($fields.Count -eq 1) { foreach ($action in $actions) { if (Test-PythonStartsWith -Candidate $action.Name -Prefix $CurrentWord) { [void]$results.Add((New-PythonCompletionResult -CompletionText $action.Name -ResultType 'ParameterValue' -ToolTip $action.Description)) } } } elseif ($fields.Count -eq 3) { $prefix = ($fields[0], $fields[1]) -join ':' foreach ($category in $categories) { if (Test-PythonStartsWith -Candidate $category -Prefix $fields[2]) { [void]$results.Add((New-PythonCompletionResult -CompletionText "${prefix}:$category" -ResultType 'ParameterValue' -ToolTip "Warning category $category" -ListItemText $category)) } } } if ($results.Count -eq 0) { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<action:message:category:module:lineno>' -ToolTip 'Warning filter for -W.') } @(Get-PythonUniqueResults -Results $results.ToArray()) } function Get-PythonClosedValueCompletions { param( [string[]]$Values, [string]$CurrentWord, [string]$ToolTip ) $results = New-Object System.Collections.Generic.List[object] foreach ($value in @($Values)) { if (Test-PythonStartsWith -Candidate $value -Prefix $CurrentWord) { [void]$results.Add((New-PythonCompletionResult -CompletionText $value -ResultType 'ParameterValue' -ToolTip $ToolTip)) } } if ($results.Count -eq 0) { $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { '<value>' } else { $CurrentWord } [void]$results.Add((New-PythonCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip $ToolTip)) } @($results.ToArray()) } function Get-PythonPlaceholderCompletions { param( [string]$CurrentWord, [string]$Placeholder, [string]$ToolTip ) $completionText = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $Placeholder } else { $CurrentWord } @( New-PythonCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip $ToolTip ) } function Get-PythonXOptionCompletions { param([string]$CurrentWord) $results = New-Object System.Collections.Generic.List[object] $xOptions = @(Get-PythonXOptionSpecs) if ($CurrentWord -match '^(?<name>[^=]+)=(?<value>.*)$') { $typedName = $Matches.name $typedValue = $Matches.value foreach ($spec in $xOptions) { if (-not $spec.Name.StartsWith($typedName, [System.StringComparison]::OrdinalIgnoreCase)) { continue } foreach ($valueHint in @($spec.ValueHints)) { if ([string]::IsNullOrWhiteSpace($valueHint)) { continue } if (-not (Test-PythonStartsWith -Candidate $valueHint -Prefix $typedValue)) { continue } [void]$results.Add((New-PythonCompletionResult -CompletionText "$($spec.Name)=$valueHint" -ResultType 'ParameterValue' -ToolTip $spec.Description)) } } if ($results.Count -eq 0) { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<xoption>' -ToolTip 'Python -X option.') } return @(Get-PythonUniqueResults -Results $results.ToArray()) } foreach ($spec in $xOptions) { if ($spec.AllowBare -and (Test-PythonStartsWith -Candidate $spec.Name -Prefix $CurrentWord)) { [void]$results.Add((New-PythonCompletionResult -CompletionText $spec.Name -ResultType 'ParameterValue' -ToolTip $spec.Description)) } foreach ($valueHint in @($spec.ValueHints)) { if ([string]::IsNullOrWhiteSpace($valueHint)) { continue } $candidate = "$($spec.Name)=$valueHint" if (Test-PythonStartsWith -Candidate $candidate -Prefix $CurrentWord) { [void]$results.Add((New-PythonCompletionResult -CompletionText $candidate -ResultType 'ParameterValue' -ToolTip $spec.Description)) } } } if ($results.Count -eq 0) { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<xoption>' -ToolTip 'Python -X option.') } @(Get-PythonUniqueResults -Results $results.ToArray()) } function Get-PythonPathResults { param( [string]$CurrentWord, [string]$Placeholder = '<script-or-path>' ) $results = New-Object System.Collections.Generic.List[object] foreach ($item in [System.Management.Automation.CompletionCompleters]::CompleteFilename($CurrentWord)) { [void]$results.Add([System.Management.Automation.CompletionResult]::new( $item.CompletionText, $item.ListItemText, $item.ResultType, $item.ToolTip )) } if ($results.Count -eq 0) { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder $Placeholder -ToolTip 'Filesystem path.') } @($results.ToArray()) } function Get-PythonModuleRoot { # The import roots behind 'python -m', read from the interpreter's layout on disk; no process is started. $cache = $script:PythonModuleCache $key = "$env:PATH|$env:PYTHONNOUSERSITE" if ($cache.RootsKey -ceq $key) { return @($cache.Roots) } $roots = New-Object System.Collections.Generic.List[object] $command = Get-Command -Name python -CommandType Application -ErrorAction Ignore | Select-Object -First 1 $executable = if ($null -eq $command) { '' } else { $command.Source } if ($executable) { $targetFile = "$executable.__target__" if ([System.IO.File]::Exists($targetFile)) { # Python install manager shims record the real interpreter beside themselves. $executable = [System.IO.File]::ReadAllText($targetFile).Trim() } } $binDirectory = if ($executable) { [System.IO.Path]::GetDirectoryName($executable) } else { '' } if ($binDirectory) { $venvDirectory = [System.IO.Path]::GetDirectoryName($binDirectory) $venvConfig = if ([string]::IsNullOrEmpty($venvDirectory)) { '' } else { [System.IO.Path]::Combine($venvDirectory, 'pyvenv.cfg') } $baseDirectory = $binDirectory $siteDirectories = New-Object System.Collections.Generic.List[object] if ($venvConfig -and [System.IO.File]::Exists($venvConfig)) { $settings = @{} foreach ($configLine in [System.IO.File]::ReadAllLines($venvConfig)) { if ($configLine -match '^\s*([^=]+?)\s*=\s*(.*?)\s*$') { $settings[$Matches[1]] = $Matches[2] } } $baseDirectory = if ($settings.ContainsKey('home')) { $settings['home'] } else { '' } [void]$siteDirectories.Add(@([System.IO.Path]::Combine($venvDirectory, 'Lib', 'site-packages'), 'venv site-packages')) if ($settings.ContainsKey('include-system-site-packages') -and $settings['include-system-site-packages'] -eq 'true' -and $baseDirectory) { [void]$siteDirectories.Add(@([System.IO.Path]::Combine($baseDirectory, 'Lib', 'site-packages'), 'site-packages')) } } else { $versionDll = if ([System.IO.Directory]::Exists($baseDirectory)) { [System.IO.Directory]::GetFiles($baseDirectory, 'python3*.dll') | ForEach-Object { [System.IO.Path]::GetFileNameWithoutExtension($_) } | Where-Object { $_ -match '^python3\d+t?$' } | Select-Object -First 1 } if ($versionDll -and $env:APPDATA -and -not $env:PYTHONNOUSERSITE) { [void]$siteDirectories.Add(@([System.IO.Path]::Combine($env:APPDATA, 'Python', ('P' + $versionDll.Substring(1)), 'site-packages'), 'user site-packages')) } [void]$siteDirectories.Add(@([System.IO.Path]::Combine($baseDirectory, 'Lib', 'site-packages'), 'site-packages')) } $candidates = @() if ($baseDirectory) { $candidates += , @([System.IO.Path]::Combine($baseDirectory, 'DLLs'), 'stdlib extension') $candidates += , @([System.IO.Path]::Combine($baseDirectory, 'Lib'), 'stdlib') } $candidates += $siteDirectories.ToArray() foreach ($candidate in $candidates) { if ([System.IO.Directory]::Exists($candidate[0])) { [void]$roots.Add([pscustomobject]@{ Path = $candidate[0]; Kind = $candidate[1]; RequirePackageMarker = $false; IsSite = $candidate[1].EndsWith('site-packages') }) } } } $cache.Roots = @($roots.ToArray()) $cache.RootsKey = $key @($cache.Roots) } function Get-PythonPthDirectory { param([string]$SiteDirectory) # Each .pth line in a site directory appends that path to sys.path; 'import' lines run code and are skipped. $cache = $script:PythonModuleCache.NamesByDirectory $cacheKey = "$SiteDirectory|pth" $stamp = [System.IO.Directory]::GetLastWriteTimeUtc($SiteDirectory) if ($cache.ContainsKey($cacheKey) -and $cache[$cacheKey].Stamp -eq $stamp) { return @($cache[$cacheKey].Entries) } $directories = New-Object System.Collections.Generic.List[string] foreach ($pthFile in [System.IO.Directory]::GetFiles($SiteDirectory, '*.pth')) { foreach ($pthLine in [System.IO.File]::ReadAllLines($pthFile)) { $entry = $pthLine.Trim() if (-not $entry -or $entry.StartsWith('#') -or $entry -match '^import[ \t]') { continue } $directory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($SiteDirectory, $entry)) if ([System.IO.Directory]::Exists($directory) -and -not $directories.Contains($directory)) { [void]$directories.Add($directory) } } } $cache[$cacheKey] = [pscustomobject]@{ Stamp = $stamp; Entries = @($directories.ToArray()) } @($directories.ToArray()) } function Get-PythonDirectoryModule { param( [string]$Directory, [bool]$RequirePackageMarker ) # A directory's entry list only changes when its own LastWriteTime does, so that stamp keys the cache. $cache = $script:PythonModuleCache.NamesByDirectory $cacheKey = "$Directory|$RequirePackageMarker" $stamp = [System.IO.Directory]::GetLastWriteTimeUtc($Directory) if ($cache.ContainsKey($cacheKey) -and $cache[$cacheKey].Stamp -eq $stamp) { return @($cache[$cacheKey].Entries) } $modules = New-Object System.Collections.Generic.List[object] foreach ($entry in [System.IO.DirectoryInfo]::new($Directory).EnumerateFileSystemInfos()) { $isPackage = $entry -is [System.IO.DirectoryInfo] if ($isPackage) { if ($RequirePackageMarker -and -not [System.IO.File]::Exists([System.IO.Path]::Combine($entry.FullName, '__init__.py'))) { continue } $moduleName = $entry.Name } elseif ($entry.Extension -eq '.py' -or $entry.Extension -eq '.pyw') { $moduleName = [System.IO.Path]::GetFileNameWithoutExtension($entry.Name) } elseif ($entry.Extension -eq '.pyd') { $moduleName = $entry.Name.Substring(0, $entry.Name.IndexOf('.')) } else { continue } if ($moduleName -cnotmatch '^[A-Za-z_][A-Za-z0-9_]*$' -or $moduleName -ceq '__pycache__' -or $moduleName -ceq '__init__' -or $moduleName -ceq '__main__') { continue } [void]$modules.Add([pscustomobject]@{ Name = $moduleName; IsPackage = $isPackage }) } $cache[$cacheKey] = [pscustomobject]@{ Stamp = $stamp; Entries = @($modules.ToArray()) } @($modules.ToArray()) } function Get-PythonModuleCompletions { param([string]$CurrentWord) $quote = '' $word = $CurrentWord if ($word.Length -gt 0 -and ($word[0] -eq [char]39 -or $word[0] -eq [char]34)) { $quote = [string]$word[0] $word = $word.Substring(1) } $lastDot = $word.LastIndexOf('.') $parentName = if ($lastDot -ge 0) { $word.Substring(0, $lastDot) } else { '' } $leaf = $word.Substring($lastDot + 1) $parentValid = [string]::IsNullOrEmpty($parentName) -or $parentName -cmatch '^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$' $results = New-Object System.Collections.Generic.List[object] if ($parentValid) { # 'python -m' puts the current directory first on sys.path; there only real packages count. $searchRoots = New-Object System.Collections.Generic.List[object] [void]$searchRoots.Add([pscustomobject]@{ Path = $ExecutionContext.SessionState.Path.CurrentFileSystemLocation.ProviderPath; Kind = 'current directory'; RequirePackageMarker = $true }) foreach ($root in @(Get-PythonModuleRoot)) { [void]$searchRoots.Add($root) if ($root.IsSite) { foreach ($pthDirectory in @(Get-PythonPthDirectory -SiteDirectory $root.Path)) { [void]$searchRoots.Add([pscustomobject]@{ Path = $pthDirectory; Kind = "$($root.Kind) .pth"; RequirePackageMarker = $false }) } } } $relativePath = $parentName.Replace('.', [System.IO.Path]::DirectorySeparatorChar) $toolTips = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::Ordinal) $names = New-Object System.Collections.Generic.List[string] $includePrivate = $leaf.StartsWith('_', [System.StringComparison]::Ordinal) $namePrefix = if ($parentName) { "$parentName." } else { '' } foreach ($root in $searchRoots) { $directory = if ($relativePath) { [System.IO.Path]::Combine($root.Path, $relativePath) } else { $root.Path } if (-not [System.IO.Directory]::Exists($directory)) { continue } foreach ($module in @(Get-PythonDirectoryModule -Directory $directory -RequirePackageMarker $root.RequirePackageMarker)) { if (-not $module.Name.StartsWith($leaf, [System.StringComparison]::OrdinalIgnoreCase) -or (-not $includePrivate -and $module.Name.StartsWith('_', [System.StringComparison]::Ordinal))) { continue } $fullName = $namePrefix + $module.Name if (-not $toolTips.ContainsKey($fullName)) { $kind = if ($module.IsPackage) { 'package' } else { 'module' } $toolTips[$fullName] = "$($root.Kind) $kind ($directory)" [void]$names.Add($fullName) } } } $names.Sort([System.StringComparer]::OrdinalIgnoreCase) foreach ($name in $names) { [void]$results.Add([System.Management.Automation.CompletionResult]::new("$quote$name$quote", $name, 'ParameterValue', $toolTips[$name])) } } if ($results.Count -eq 0) { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<module>' -ToolTip 'Module name for python -m.') } @($results.ToArray()) } function Get-PythonFirstPositionalCompletions { param( [string]$CurrentWord, [bool]$IncludeRootOptions = $false ) $results = New-Object System.Collections.Generic.List[object] if ('-'.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) { [void]$results.Add((New-PythonCompletionResult -CompletionText '-' -ResultType 'ParameterValue' -ToolTip 'Read the Python program from standard input.')) } if ($IncludeRootOptions) { foreach ($item in @(Get-PythonOptionCompletions -CurrentWord $CurrentWord)) { [void]$results.Add($item) } } if (-not $CurrentWord.StartsWith('-')) { # The script operand is python's dominant invocation, so list files from an empty word too. foreach ($item in @(Get-PythonPathResults -CurrentWord $CurrentWord -Placeholder '<script-or-path>')) { [void]$results.Add($item) } } @(Get-PythonUniqueResults -Results $results.ToArray() -CaseSensitive) } function Get-PythonTailCompletions { param( [string]$CurrentWord, [string]$TerminalMode ) if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and -not $CurrentWord.StartsWith('-')) { return @(Get-PythonPathResults -CurrentWord $CurrentWord -Placeholder '<arg-path>') } switch ($TerminalMode) { 'CommandTail' { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<command-arg>' -ToolTip 'Argument passed after python -c.') } 'ModuleTail' { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<module-arg>' -ToolTip 'Argument passed after python -m.') } default { return @(Get-PythonPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<script-arg>' -ToolTip 'Argument passed to the Python program.') } } } function Complete-Python { param( [string]$wordToComplete, [System.Management.Automation.Language.CommandAst]$commandAst, [int]$cursorPosition ) $commandLineState = Get-PythonCommandLineState -CommandAst $commandAst -CursorPosition $cursorPosition $argumentState = Get-PythonArgumentState -CommandLineState $commandLineState $currentWord = if ($null -eq $argumentState.CurrentArgument) { '' } else { $argumentState.CurrentArgument } $argumentsBeforeCurrent = @($argumentState.ArgumentsBeforeCurrent) $context = Get-PythonCompletionContext -ArgumentsBeforeCurrent $argumentsBeforeCurrent if (-not [string]::IsNullOrEmpty($context.PendingValueKind)) { switch ($context.PendingValueKind) { 'HashPycsMode' { return @(Get-PythonClosedValueCompletions -Values @('always', 'default', 'never') -CurrentWord $currentWord -ToolTip 'Value for --check-hash-based-pycs.') } 'XOption' { return @(Get-PythonXOptionCompletions -CurrentWord $currentWord) } 'WarningFilter' { return @(Get-PythonWarningFilterCompletions -CurrentWord $currentWord) } 'ModuleName' { return @(Get-PythonModuleCompletions -CurrentWord $currentWord) } 'CommandString' { return @(Get-PythonPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<command-string>' -ToolTip 'Command string for python -c.') } } } if (-not [string]::IsNullOrEmpty($context.TerminalMode)) { return @(Get-PythonTailCompletions -CurrentWord $currentWord -TerminalMode $context.TerminalMode) } if ([string]::IsNullOrWhiteSpace($currentWord)) { return @(Get-PythonFirstPositionalCompletions -CurrentWord $currentWord -IncludeRootOptions $true) } if ($currentWord -eq '-') { return @(Get-PythonFirstPositionalCompletions -CurrentWord $currentWord -IncludeRootOptions $true) } if ($currentWord.StartsWith('-')) { return @(Get-PythonOptionCompletions -CurrentWord $currentWord) } @(Get-PythonFirstPositionalCompletions -CurrentWord $currentWord) } Register-ArgumentCompleter -Native -CommandName @('python', 'python.exe') -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) Complete-Python -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition } |