argument_completer.ps1
# 获取具有补全脚本的 Shell 列表。 function Get-FountShellListWithCompleter { param([string]$Username) # 获取指定用户或所有用户的可用 Shell 列表。 Get-FountPartList -parttype shells -Username $Username | # 筛选出存在 argument_completer.ps1 脚本的 Shell。 Where-Object { $shellDir = Get-FountPartDirectory -Username $Username -parttype shells -partname $_ Test-Path (Join-Path -Path $shellDir -ChildPath "argument_completer.ps1") -PathType Leaf } } # 移除哈希表中的指定键及其子键(递归)。 function Remove-ArgumentNode { param([hashtable]$Node, [string]$Key) # 如果哈希表中存在指定的键,则移除该键。 if ($Node.ContainsKey($Key)) { $Node.Remove($Key) } # 遍历哈希表中的所有键。 foreach ($mapperkey in $Node.Keys) { # 如果当前键对应的值是哈希表,则递归调用 Remove-ArgumentNode 函数。 if ($Node[$mapperkey] -is [hashtable]) { Remove-ArgumentNode -Node $Node[$mapperkey] -Key $Key } } } # 处理 'runshell' 子命令的参数补全。 function Invoke-RunshellCompletion { param( [string]$WordToComplete, [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition, [int]$runshellIndex, # 'runshell' 命令在 CommandAst 中的索引。 [int]$Argindex # 当前参数在 CommandAst 中的索引。 ) # 提取用户名和 shellname,处理它们可能还不存在的情况。 # $runshellIndex + 1 是用户名的可能位置。 # $runshellIndex + 2 是 shellname 的可能位置。 # 如果索引在 CommandAst 的范围内,并且元素是字符串常量,则提取其值。 $username = if ($runshellIndex + 1 -lt $CommandAst.CommandElements.Count -and $CommandAst.CommandElements[$runshellIndex + 1] -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $CommandAst.CommandElements[$runshellIndex + 1].Value } $shellname = if ($runshellIndex + 2 -lt $CommandAst.CommandElements.Count -and $CommandAst.CommandElements[$runshellIndex + 2] -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $CommandAst.CommandElements[$runshellIndex + 2].Value } # 补全用户名。 # 如果当前参数是 'runshell' 之后的第一个参数,则补全用户名。 if (($ArgIndex - $runshellIndex) -eq 1) { return Get-FountUserList | Where-Object { $_.StartsWith($WordToComplete) } } # 补全 shellname。 # 如果当前参数是 'runshell' 之后的第二个参数,则补全 shellname。 if (($ArgIndex - $runshellIndex) -eq 2) { return Get-FountShellListWithCompleter $username | Where-Object { $_.StartsWith($WordToComplete) } } # 委托给 shell 的 argument_completer.ps1 脚本处理后续参数。 # 获取 shell 目录的路径。 $shellDir = Get-FountPartDirectory -Username $username -parttype shells -partname $shellname # 如果 shell 目录存在,并且包含 argument_completer.ps1 脚本(-PathType Leaf 检查是否为文件),则执行该脚本。 if (Test-Path (Join-Path -Path $shellDir -ChildPath "argument_completer.ps1") -PathType Leaf) { # 使用 '&' 调用操作符执行脚本,并传递必要的参数。 & (Join-Path -Path $shellDir -ChildPath "argument_completer.ps1") $username $WordToComplete $CommandAst $CursorPosition $runshellIndex $Argindex } } # 定义 Fount 工具的参数结构。 $ArgumentStructure = @{ Root = @{ # 根节点包含的参数。 Parameters = 'background', 'geneexe', 'init', 'keepalive', 'runshell', 'shutdown' # 'background' 参数的子参数。 background = @{ Parameters = 'geneexe', 'init', 'keepalive', 'runshell', 'shutdown' # 'geneexe' 参数的处理程序(仅限 Windows)。 geneexe = { if ($IsWindows) { Get-ChildItem -Path "$($WordToComplete)*" -File | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'File', $_.FullName) } } } init = $null # 没有补全逻辑。 # 'keepalive' 参数的子参数和处理程序。 keepalive = @{ Parameters = 'debug', 'runshell'; debug = @{ Parameters = 'runshell'; runshell = ${function:Invoke-RunshellCompletion} }; runshell = ${function:Invoke-RunshellCompletion} } # 'runshell' 参数的处理程序。 runshell = ${function:Invoke-RunshellCompletion} shutdown = $null } geneexe = { if ($IsWindows) { Get-ChildItem -Path "$($WordToComplete)*" -File | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_.FullName, $_.Name, 'File', $_.FullName) } } } init = $null keepalive = @{ Parameters = 'debug', 'runshell'; debug = @{ Parameters = 'runshell'; runshell = ${function:Invoke-RunshellCompletion} }; runshell = ${function:Invoke-RunshellCompletion} } debug = @{ Parameters = 'runshell'; runshell = ${function:Invoke-RunshellCompletion} } runshell = ${function:Invoke-RunshellCompletion} shutdown = $null } } # 如果不是 Windows 系统,则移除 'geneexe' 参数。 if (-not $IsWindows) { Remove-ArgumentNode -Node $ArgumentStructure.Root -Key 'geneexe' } # Fount 工具的主参数补全函数。 function Get-FountArgumentCompletion { param( [string]$WordToComplete, [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) try { # 改进的提取 $realWordToComplete 的逻辑。 这段代码尝试更准确地确定用户正在输入的单词。 $WordToComplete = "$CommandAst" # 首先将整个命令字符串赋值给 $WordToComplete。 # 循环添加空格,直到 $WordToComplete 的长度大于等于光标位置。这确保了后续的字符串分割可以正确工作,即使光标后面有空格。 while ($CursorPosition -gt $WordToComplete.Length) { $WordToComplete += ' ' } # 将 $WordToComplete 截取到光标位置,然后按空格分割,得到一个单词数组。 $CommandWords = $WordToComplete.Substring(0, [Math]::Min($WordToComplete.Length, $CursorPosition)) -split '\s+' # $Argindex 是当前参数的索引。 $Argindex = $CommandWords.Count - 1 # $WordToComplete 现在是用户正在输入的单词。 $WordToComplete = $CommandWords[-1] # 从参数结构的根节点开始。 $currentLevel = $ArgumentStructure.Root # 跳过命令名称(例如 'fount')。 $ArgCommandElements = $CommandAst.CommandElements | Select-Object -Skip 1 $rootIndex = 0 # 用于跟踪runshell参数的索引 # 遍历命令参数。 foreach ($element in $ArgCommandElements) { # 如果元素是字符串常量。 if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $argValue = $element.Value # 如果当前级别是哈希表,并且包含该参数值。 if ($currentLevel -is [hashtable] -and $currentLevel.ContainsKey($argValue)) { # 移动到下一级。 $currentLevel = $currentLevel[$argValue] $rootIndex += 1 } # 否则,如果当前级别是哈希表,并且包含 "Parameters" 键,并且 "Parameters" 包含该参数值, 则不做任何事情,因为我们已经处理了有效的参数。 elseif ($currentLevel -is [hashtable] -and $currentLevel.ContainsKey("Parameters") -and ($currentLevel.Parameters -contains $argValue)) { } # 否则,参数无效,退出循环。 else { break } } # 否则,参数不是字符串常量,退出循环。 else { break } } # 如果当前级别是哈希表。 if ($currentLevel -is [hashtable]) { # 如果包含 "Parameters" 键。 if ($currentLevel.ContainsKey("Parameters")) { # 获取匹配的参数。 $parameters = $currentLevel.Parameters $matchingParameters = $parameters | Where-Object { $_.StartsWith($WordToComplete) } # 返回匹配的参数。 $matchingParameters return } return } # 否则,如果当前级别是脚本块。 elseif ($currentLevel -is [scriptblock]) { # 执行脚本块,并传递必要的参数。 & $currentLevel $WordToComplete $CommandAst $CursorPosition $rootIndex $Argindex | ForEach-Object { $_ } return } } catch { # 捕获并显示错误信息。 Write-Host "Error in Get-FountArgumentCompletion: $_" -ForegroundColor Red } } # 注册参数补全函数。 # 为 'fount'、'fount.ps1'、'fount.cmd' 和 'fount.exe' 命令注册 Get-FountArgumentCompletion 函数。 Register-ArgumentCompleter -CommandName fount -ScriptBlock ${function:Get-FountArgumentCompletion} -Native Register-ArgumentCompleter -CommandName fount.ps1 -ScriptBlock ${function:Get-FountArgumentCompletion} -Native Register-ArgumentCompleter -CommandName fount.cmd -ScriptBlock ${function:Get-FountArgumentCompletion} -Native Register-ArgumentCompleter -CommandName fount.exe -ScriptBlock ${function:Get-FountArgumentCompletion} -Native |