From d12ce0fb5730009ec545e3721d43cfd256f47ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=84=B1?= <1877685089@qq.com> Date: Mon, 8 Jun 2026 21:01:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(interactive):=20=E5=91=BD=E4=BB=A4=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E9=80=89=E4=B8=AD=E5=B8=A6=E5=8F=AF=E9=80=89=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E7=9A=84=E5=91=BD=E4=BB=A4=E4=B9=9F=E5=BC=B9=E5=87=BA?= =?UTF-8?q?=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:updatePalette 只在有缺失的 Required flag 时才弹参数表单。但很多 命令的参数是'至少一个'关系(如 branch +batch-protect 的 --names/--from 二选一),无法单独标记 Required,导致 missingRequiredFlags 返回空, 表单被跳过,命令直接执行后报 'no branch names provided'。 修复:把表单触发条件从'有缺失必填参数'改为'命令有任何 flag'。这样 选中任何带参数的命令都弹表单,用户能填可选参数(留空即跳过)。 无 flag 的命令(如 wiki +list、repo +recommend)仍直接执行。 回归测试 TestPaletteSelectionOpensFormForOptionalFlags 验证模拟的 batch-protect(全可选参数)选中后进入 stateForm 而非直接执行。 --- .../palette_optional_flags_test.go | 57 +++++++++++++++++++ cmd/interactive/repl.go | 14 +++-- 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 cmd/interactive/palette_optional_flags_test.go diff --git a/cmd/interactive/palette_optional_flags_test.go b/cmd/interactive/palette_optional_flags_test.go new file mode 100644 index 0000000..fcbcf54 --- /dev/null +++ b/cmd/interactive/palette_optional_flags_test.go @@ -0,0 +1,57 @@ +package interactive + +import ( + "testing" + + tea "github.com/charmbracelet/bubbletea" + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +// TestPaletteSelectionOpensFormForOptionalFlags 验证:选中一个"只有可选参数、 +// 无必填参数"的命令(如 branch +batch-protect 的 names/from 二选一)时, +// 仍然弹出参数表单,而非直接执行导致 "no ... provided" 错误。 +// +// 回归:之前 updatePalette 只在有缺失的 Required flag 时才弹表单, +// 导致 names/from 这类"至少一个"型参数(无法单独标 Required)的命令 +// 被直接执行,用户没机会输入参数。 +func TestPaletteSelectionOpensFormForOptionalFlags(t *testing.T) { + // 构造一个模拟 branch +batch-protect 的 shortcut:所有 flag 都非必填。 + shortcuts := map[string][]*common.Shortcut{ + "branch": { + { + Name: "batch-protect", + Description: "批量保护分支", + Flags: []common.Flag{ + {Name: "names", Usage: "分支名(逗号分隔)"}, // 无 Required + {Name: "from", Usage: "文件路径"}, // 无 Required + }, + Run: func(ctx *common.RuntimeContext) error { return nil }, + }, + }, + } + + m := newReplModel("owner", "repo") + // 注入测试 registry + m.shortcuts = shortcuts + + // 1) 输入 / 触发命令面板 + m2, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'/'}}) + repl := m2.(replModel) + if repl.state != statePalette { + t.Fatalf("输入 / 后应在 statePalette,实际 %v", repl.state) + } + + // 2) 模拟用户选中 branch 组 + repl.palette.result = &PaletteResult{ + Group: "branch", + Shortcut: shortcuts["branch"][0], + } + + // 3) 派发一条任意消息触发 updatePalette 的结果检查 + m3, _ := repl.Update(tea.KeyMsg{Type: tea.KeyEnter}) + final := m3.(replModel) + + if final.state != stateForm { + t.Fatalf("选中带可选参数的命令应进入 stateForm,实际 %v(命令被直接执行了,用户没机会填参数)", final.state) + } +} diff --git a/cmd/interactive/repl.go b/cmd/interactive/repl.go index e688907..207f82d 100644 --- a/cmd/interactive/repl.go +++ b/cmd/interactive/repl.go @@ -250,17 +250,19 @@ func (m replModel) updatePalette(msg tea.Msg) (tea.Model, tea.Cmd) { result := m.palette.result s := result.Shortcut - // Only open the form when required flags are missing. - // Commands with only optional flags execute directly — users can pass - // flags via the direct-command syntax if needed. - missing := missingRequiredFlags(s, map[string]string{}) - if len(missing) > 0 { + // Open the form whenever the command has ANY flags — not just required + // ones. Many commands have "at-least-one-of" parameters (e.g. branch + // +batch-protect needs --names OR --from) that can't be marked Required + // individually, so skipping the form for "optional-only" commands left + // users unable to supply them and the command failed at runtime. + // Commands with no flags at all execute directly. + if len(s.Flags) > 0 { m.form = newFormModel(result.Group, s, m.width, m.height, nil) m.state = stateForm return m, nil } - // No required flags missing → execute directly + // No flags → execute directly m.state = stateExecuting return m, tea.Batch(m.executeCommand(result.Group, s, map[string]string{}), m.spinner.Tick) }