Add shell completion support
This commit is contained in:
21
README.md
21
README.md
@@ -53,6 +53,7 @@ bin/gsp.exe
|
||||
.\bin\gsp.exe graph page.lottery.main --root examples\lottery --format md --out .gsp\graph.md
|
||||
.\bin\gsp.exe graph page.lottery.main --root examples\lottery --format canvas --out .gsp\graph.canvas
|
||||
.\bin\gsp.exe stage-check --root examples\lottery --stage implement --out .gsp\stage-report.json
|
||||
.\bin\gsp.exe completion powershell
|
||||
```
|
||||
|
||||
输出文件:
|
||||
@@ -139,6 +140,7 @@ gsp version
|
||||
gsp init
|
||||
gsp ai-init
|
||||
gsp version
|
||||
gsp completion install powershell
|
||||
gsp validate
|
||||
gsp index
|
||||
gsp flatten <id>
|
||||
@@ -174,3 +176,22 @@ project/
|
||||
```bash
|
||||
gsp init path/to/project --force
|
||||
```
|
||||
|
||||
## 命令补全
|
||||
|
||||
输出补全脚本:
|
||||
|
||||
```bash
|
||||
gsp completion powershell
|
||||
gsp completion bash
|
||||
gsp completion zsh
|
||||
gsp completion fish
|
||||
```
|
||||
|
||||
安装 PowerShell 补全:
|
||||
|
||||
```powershell
|
||||
gsp completion install powershell
|
||||
```
|
||||
|
||||
安装后重新打开 PowerShell,`gsp <Tab>` 会补全子命令,`gsp graph --format <Tab>` 会补全格式,`gsp graph <Tab>` 会读取当前工程的 GSP id。
|
||||
|
||||
@@ -27,4 +27,5 @@ page.lottery.main
|
||||
.\bin\gsp.exe graph page.lottery.main --root examples\lottery --format mermaid
|
||||
.\bin\gsp.exe graph page.lottery.main --root examples\lottery --format md --out .gsp\lottery-graph.md
|
||||
.\bin\gsp.exe graph page.lottery.main --root examples\lottery --format canvas --out .gsp\lottery-graph.canvas
|
||||
.\bin\gsp.exe completion install powershell
|
||||
```
|
||||
|
||||
@@ -49,6 +49,20 @@ gsp version
|
||||
gsp version --json
|
||||
```
|
||||
|
||||
## completion
|
||||
|
||||
Print or install shell completion scripts.
|
||||
|
||||
```bash
|
||||
gsp completion powershell
|
||||
gsp completion bash
|
||||
gsp completion zsh
|
||||
gsp completion fish
|
||||
gsp completion install powershell
|
||||
```
|
||||
|
||||
Completion covers subcommands, common flags, enum values, and GSP ids from `gsp index --root .`.
|
||||
|
||||
## validate
|
||||
|
||||
Validate GSP files and references.
|
||||
|
||||
@@ -172,6 +172,7 @@ gsp init
|
||||
gsp validate
|
||||
gsp ai-init
|
||||
gsp version
|
||||
gsp completion powershell
|
||||
gsp index
|
||||
gsp trace <id>
|
||||
gsp flatten <id>
|
||||
@@ -192,6 +193,7 @@ go build -o ../bin/gsp ./cmd/gsp
|
||||
../bin/gsp graph page.lottery.main --root ../examples/lottery --format mermaid --out ../.gsp/graph.mmd
|
||||
../bin/gsp graph page.lottery.main --root ../examples/lottery --format md --out ../.gsp/graph.md
|
||||
../bin/gsp graph page.lottery.main --root ../examples/lottery --format canvas --out ../.gsp/graph.canvas
|
||||
../bin/gsp completion install powershell
|
||||
```
|
||||
|
||||
## 5. 输出
|
||||
|
||||
198
toolkit/cmd/gsp/completion.go
Normal file
198
toolkit/cmd/gsp/completion.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
func powerShellCompletionScript() string {
|
||||
return `# GSP PowerShell completion
|
||||
$script:GspSubcommands = @(
|
||||
'init',
|
||||
'ai-init',
|
||||
'version',
|
||||
'completion',
|
||||
'validate',
|
||||
'index',
|
||||
'trace',
|
||||
'flatten',
|
||||
'pack',
|
||||
'graph',
|
||||
'stage-check',
|
||||
'help'
|
||||
)
|
||||
|
||||
$script:GspFlags = @{
|
||||
'init' = @('--name', '--entry', '--force')
|
||||
'ai-init' = @('--root', '--agents', '--skill', '--all', '--force')
|
||||
'version' = @('--json')
|
||||
'completion' = @('powershell', 'bash', 'zsh', 'fish', 'install')
|
||||
'validate' = @('--root', '--out')
|
||||
'index' = @('--root', '--out')
|
||||
'trace' = @('--root', '--depth', '--out')
|
||||
'flatten' = @('--root', '--depth', '--include-type', '--exclude-type', '--out')
|
||||
'pack' = @('--root', '--depth', '--budget', '--include-type', '--exclude-type', '--out')
|
||||
'graph' = @('--root', '--depth', '--format', '--out')
|
||||
'stage-check' = @('--stage', '--root', '--out')
|
||||
}
|
||||
|
||||
function script:Get-GspIds {
|
||||
try {
|
||||
$json = & gsp index --root . 2>$null
|
||||
if (-not $json) { return @() }
|
||||
$items = $json | ConvertFrom-Json
|
||||
return @($items | ForEach-Object { $_.id })
|
||||
} catch {
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function script:New-GspCompletion($value, $tooltip = $null) {
|
||||
if (-not $tooltip) { $tooltip = $value }
|
||||
[System.Management.Automation.CompletionResult]::new($value, $value, 'ParameterValue', $tooltip)
|
||||
}
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName gsp -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$words = @($commandAst.CommandElements | ForEach-Object { $_.ToString() })
|
||||
if ($words.Count -le 1) {
|
||||
return $script:GspSubcommands |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
|
||||
$command = $words[1]
|
||||
if ($words.Count -eq 2 -and $wordToComplete -ne '') {
|
||||
return $script:GspSubcommands |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
|
||||
$previous = ''
|
||||
if ($wordToComplete -eq '' -and $words.Count -ge 1) {
|
||||
$previous = $words[$words.Count - 1]
|
||||
} elseif ($words.Count -ge 2) {
|
||||
$previous = $words[$words.Count - 2]
|
||||
}
|
||||
|
||||
switch ($previous) {
|
||||
'--format' {
|
||||
return @('json', 'mermaid', 'md', 'canvas') |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
'--stage' {
|
||||
return @('design', 'integrate', 'implement', 'bind', 'release') |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
'--skill' {
|
||||
return @('generic', 'codex') |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
}
|
||||
|
||||
if ($command -eq 'completion') {
|
||||
if ($words -contains 'install') {
|
||||
return @('powershell') |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
return @('powershell', 'bash', 'zsh', 'fish', 'install') |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
|
||||
if ($wordToComplete -like '-*') {
|
||||
return @($script:GspFlags[$command]) |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
|
||||
if (@('trace', 'flatten', 'pack', 'graph') -contains $command) {
|
||||
return Get-GspIds |
|
||||
Where-Object { $_ -like "$wordToComplete*" } |
|
||||
ForEach-Object { New-GspCompletion $_ }
|
||||
}
|
||||
|
||||
return @()
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
func bashCompletionScript() string {
|
||||
return `# GSP bash completion
|
||||
_gsp_completion() {
|
||||
local cur prev cmd
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
cmd="${COMP_WORDS[1]}"
|
||||
local commands="init ai-init version completion validate index trace flatten pack graph stage-check help"
|
||||
case "$prev" in
|
||||
--format) COMPREPLY=( $(compgen -W "json mermaid md canvas" -- "$cur") ); return ;;
|
||||
--stage) COMPREPLY=( $(compgen -W "design integrate implement bind release" -- "$cur") ); return ;;
|
||||
--skill) COMPREPLY=( $(compgen -W "generic codex" -- "$cur") ); return ;;
|
||||
esac
|
||||
if [[ $COMP_CWORD -eq 1 ]]; then
|
||||
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
||||
return
|
||||
fi
|
||||
if [[ "$cur" == -* ]]; then
|
||||
case "$cmd" in
|
||||
init) COMPREPLY=( $(compgen -W "--name --entry --force" -- "$cur") ) ;;
|
||||
ai-init) COMPREPLY=( $(compgen -W "--root --agents --skill --all --force" -- "$cur") ) ;;
|
||||
version) COMPREPLY=( $(compgen -W "--json" -- "$cur") ) ;;
|
||||
validate|index) COMPREPLY=( $(compgen -W "--root --out" -- "$cur") ) ;;
|
||||
trace) COMPREPLY=( $(compgen -W "--root --depth --out" -- "$cur") ) ;;
|
||||
flatten) COMPREPLY=( $(compgen -W "--root --depth --include-type --exclude-type --out" -- "$cur") ) ;;
|
||||
pack) COMPREPLY=( $(compgen -W "--root --depth --budget --include-type --exclude-type --out" -- "$cur") ) ;;
|
||||
graph) COMPREPLY=( $(compgen -W "--root --depth --format --out" -- "$cur") ) ;;
|
||||
stage-check) COMPREPLY=( $(compgen -W "--stage --root --out" -- "$cur") ) ;;
|
||||
completion) COMPREPLY=( $(compgen -W "powershell bash zsh fish install" -- "$cur") ) ;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
case "$cmd" in
|
||||
trace|flatten|pack|graph)
|
||||
local ids
|
||||
ids=$(gsp index --root . 2>/dev/null | sed -n 's/.*"id": "\([^"]*\)".*/\1/p')
|
||||
COMPREPLY=( $(compgen -W "$ids" -- "$cur") )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
complete -F _gsp_completion gsp
|
||||
`
|
||||
}
|
||||
|
||||
func zshCompletionScript() string {
|
||||
return `#compdef gsp
|
||||
# GSP zsh completion
|
||||
_gsp() {
|
||||
local -a commands
|
||||
commands=(
|
||||
'init'
|
||||
'ai-init'
|
||||
'version'
|
||||
'completion'
|
||||
'validate'
|
||||
'index'
|
||||
'trace'
|
||||
'flatten'
|
||||
'pack'
|
||||
'graph'
|
||||
'stage-check'
|
||||
'help'
|
||||
)
|
||||
_describe 'command' commands
|
||||
}
|
||||
_gsp "$@"
|
||||
`
|
||||
}
|
||||
|
||||
func fishCompletionScript() string {
|
||||
return `# GSP fish completion
|
||||
complete -c gsp -f -n '__fish_use_subcommand' -a 'init ai-init version completion validate index trace flatten pack graph stage-check help'
|
||||
complete -c gsp -n '__fish_seen_subcommand_from graph' -l format -a 'json mermaid md canvas'
|
||||
complete -c gsp -n '__fish_seen_subcommand_from stage-check' -l stage -a 'design integrate implement bind release'
|
||||
complete -c gsp -n '__fish_seen_subcommand_from ai-init' -l skill -a 'generic codex'
|
||||
complete -c gsp -n '__fish_seen_subcommand_from trace flatten pack graph' -a '(gsp index --root . 2>/dev/null | string match -r ''"id": "([^"]+)"'' | string replace -r ''.*"id": "([^"]+)".*'' ''$1'')'
|
||||
`
|
||||
}
|
||||
@@ -36,6 +36,8 @@ func run(args []string) error {
|
||||
return runAIInit(args[1:])
|
||||
case "version":
|
||||
return runVersion(args[1:])
|
||||
case "completion":
|
||||
return runCompletion(args[1:])
|
||||
case "validate":
|
||||
return runValidate(args[1:])
|
||||
case "index":
|
||||
@@ -65,6 +67,8 @@ Usage:
|
||||
gsp init [path] [--name project-name] [--entry project.entry] [--force]
|
||||
gsp ai-init [--root .] [--agents] [--skill generic|codex] [--all] [--force]
|
||||
gsp version [--json]
|
||||
gsp completion powershell|bash|zsh|fish
|
||||
gsp completion install powershell
|
||||
gsp validate [--root .] [--out report.json]
|
||||
gsp index [--root .] [--out index.json]
|
||||
gsp trace <id> [--root .] [--depth 3] [--out trace.json]
|
||||
@@ -75,6 +79,79 @@ Usage:
|
||||
`)
|
||||
}
|
||||
|
||||
func runCompletion(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("completion requires powershell, bash, zsh, fish, or install powershell")
|
||||
}
|
||||
if args[0] == "install" {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("completion install requires one shell")
|
||||
}
|
||||
if args[1] != "powershell" {
|
||||
return fmt.Errorf("completion install currently supports powershell")
|
||||
}
|
||||
return installPowerShellCompletion()
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("completion accepts one shell")
|
||||
}
|
||||
switch args[0] {
|
||||
case "powershell":
|
||||
return writeText("", powerShellCompletionScript())
|
||||
case "bash":
|
||||
return writeText("", bashCompletionScript())
|
||||
case "zsh":
|
||||
return writeText("", zshCompletionScript())
|
||||
case "fish":
|
||||
return writeText("", fishCompletionScript())
|
||||
default:
|
||||
return fmt.Errorf("unsupported completion shell %q", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func installPowerShellCompletion() error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
completionDir := filepath.Join(home, ".gsp", "completion")
|
||||
completionFile := filepath.Join(completionDir, "gsp.ps1")
|
||||
if err := os.MkdirAll(completionDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(completionFile, []byte(powerShellCompletionScript()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profilePath := filepath.Join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1")
|
||||
if err := os.MkdirAll(filepath.Dir(profilePath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
sourceLine := fmt.Sprintf(". '%s'", strings.ReplaceAll(completionFile, "'", "''"))
|
||||
existing, err := os.ReadFile(profilePath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(existing), sourceLine) {
|
||||
var builder strings.Builder
|
||||
if len(existing) > 0 {
|
||||
builder.Write(existing)
|
||||
if !strings.HasSuffix(string(existing), "\n") {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
builder.WriteString(sourceLine)
|
||||
builder.WriteString("\n")
|
||||
if err := os.WriteFile(profilePath, []byte(builder.String()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Printf("Installed PowerShell completion to %s\n", completionFile)
|
||||
fmt.Printf("Updated PowerShell profile %s\n", profilePath)
|
||||
fmt.Println("Open a new PowerShell terminal or run the profile file to enable completion.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAIInit(args []string) error {
|
||||
fs := flag.NewFlagSet("ai-init", flag.ContinueOnError)
|
||||
root := commonRoot(fs)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
param(
|
||||
[string]$InstallDir = "$HOME\.gsp\bin"
|
||||
[string]$InstallDir = "$HOME\.gsp\bin",
|
||||
[switch]$NoCompletion
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -36,3 +37,7 @@ else {
|
||||
}
|
||||
|
||||
& $TargetExe version
|
||||
|
||||
if (-not $NoCompletion) {
|
||||
& $TargetExe completion install powershell
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user