diff --git a/cmd/interactive/palette.go b/cmd/interactive/palette.go index 03549d6..5dabf29 100644 --- a/cmd/interactive/palette.go +++ b/cmd/interactive/palette.go @@ -176,14 +176,29 @@ func (m *paletteModel) filterItems(query string) { m.list.ResetSelected() } -// matchesQuery checks if title or description contains the query (case-insensitive). +// matchesQuery checks if the query is a subsequence (fzy-style fuzzy match) +// of either the title or the description, case-insensitively. An empty query +// matches everything. func matchesQuery(title, description, query string) bool { + return subsequenceMatch(query, title) || subsequenceMatch(query, description) +} + +// subsequenceMatch returns true if every character of query appears in target +// in the same order (not necessarily contiguously), ignoring case. An empty +// query always matches. +func subsequenceMatch(query, target string) bool { + query = strings.ToLower(query) + target = strings.ToLower(target) if query == "" { return true } - q := strings.ToLower(query) - return strings.Contains(strings.ToLower(title), q) || - strings.Contains(strings.ToLower(description), q) + i := 0 + for j := 0; j < len(target) && i < len(query); j++ { + if target[j] == query[i] { + i++ + } + } + return i == len(query) } // Update handles messages for the palette. @@ -196,8 +211,9 @@ func (m paletteModel) Update(msg tea.Msg) (paletteModel, tea.Cmd) { m.backToGroups() return m, nil } + // levelGroup: signal the outer REPL to return to stateInput, not quit m.quitting = true - return m, tea.Quit + return m, nil case tea.KeyEnter: selected := m.list.SelectedItem() diff --git a/cmd/interactive/repl.go b/cmd/interactive/repl.go index 6bd7e99..2acb8f1 100644 --- a/cmd/interactive/repl.go +++ b/cmd/interactive/repl.go @@ -225,17 +225,19 @@ func (m replModel) updatePalette(msg tea.Msg) (tea.Model, tea.Cmd) { result := m.palette.result s := result.Shortcut - // Check if there are flags that need to be filled + // 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 || len(s.Flags) > 0 { + if len(missing) > 0 { m.form = newFormModel(result.Group, s, m.width, m.height, nil) m.state = stateForm return m, nil } - // Execute directly + // No required flags missing → execute directly m.state = stateExecuting - return m, m.executeCommand(result.Group, s, map[string]string{}) + return m, tea.Batch(m.executeCommand(result.Group, s, map[string]string{}), m.spinner.Tick) } return m, cmd @@ -269,12 +271,13 @@ func (m replModel) updateExecuting(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case execResultMsg: if msg.err != nil { - m.lastError = fmt.Sprintf("Error: %v", msg.err) + m.lastError = fmt.Sprintf("命令执行失败: %s", msg.err) m.lastOutput = "" m.state = stateError return m, nil } - m.lastOutput = msg.output + display := formatCommandDisplay(msg.group, msg.shortcut, msg.args) + m.lastOutput = fmt.Sprintf(" ✓ 执行: %s\n\n%s", display, msg.output) m.lastError = "" m.state = stateInput m.input.Focus()