diff options
Diffstat (limited to 'internal/hexaiaction')
| -rw-r--r-- | internal/hexaiaction/cmdentry.go | 26 | ||||
| -rw-r--r-- | internal/hexaiaction/cmdentry_runcommand_test.go | 8 | ||||
| -rw-r--r-- | internal/hexaiaction/cmdentry_test.go | 22 | ||||
| -rw-r--r-- | internal/hexaiaction/prompts.go | 7 | ||||
| -rw-r--r-- | internal/hexaiaction/run.go | 16 | ||||
| -rw-r--r-- | internal/hexaiaction/tui.go | 3 | ||||
| -rw-r--r-- | internal/hexaiaction/types.go | 1 |
7 files changed, 52 insertions, 31 deletions
diff --git a/internal/hexaiaction/cmdentry.go b/internal/hexaiaction/cmdentry.go index 78c315b..d60f172 100644 --- a/internal/hexaiaction/cmdentry.go +++ b/internal/hexaiaction/cmdentry.go @@ -10,17 +10,16 @@ import ( "codeberg.org/snonux/hexai/internal/llm" "codeberg.org/snonux/hexai/internal/tmux" - "golang.org/x/term" ) // Options configures the command-line orchestration for hexai-tmux-action. type Options struct { - Infile string - Outfile string - UIChild bool - TmuxTarget string - TmuxSplit string // "v" or "h" - TmuxPercent int // 1-100 + Infile string + Outfile string + UIChild bool + TmuxTarget string + TmuxPopupWidth string // popup width, e.g. "80%" or "120" + TmuxPopupHeight string // popup height, e.g. "80%" or "40" } // RunCommand is the CLI orchestrator used by cmd/hexai-tmux-action. It runs in tmux @@ -30,14 +29,13 @@ func RunCommand(ctx context.Context, opts Options, stdin io.Reader, stdout, stde if opts.UIChild { return runChild(ctx, opts.Infile, opts.Outfile, stdout, stderr) } - // Always use tmux path - return runInTmuxParent(ctx, stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent) + // Always use tmux popup path + return runInTmuxParent(ctx, stdin, stdout, opts.TmuxTarget, opts.TmuxPopupWidth, opts.TmuxPopupHeight) } // seams for unit tests var ( - isTTYFn = func(fd uintptr) bool { return term.IsTerminal(int(fd)) } - splitRunFn = tmux.SplitRun + popupRunFn = tmux.PopupRun osExecutableFn = os.Executable runFn = Run ) @@ -99,7 +97,7 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri return os.Rename(tmp, outfile) } -func runInTmuxParent(ctx context.Context, stdin io.Reader, stdout io.Writer, target, split string, percent int) error { +func runInTmuxParent(ctx context.Context, stdin io.Reader, stdout io.Writer, target, popupWidth, popupHeight string) error { dir, err := os.MkdirTemp("", "hexai-tmux-action-") if err != nil { return err @@ -115,8 +113,8 @@ func runInTmuxParent(ctx context.Context, stdin io.Reader, stdout io.Writer, tar return err } argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath} - opts := tmux.SplitOpts{Target: target, Vertical: split != "h", Percent: percent} - if err := splitRunFn(opts, argv); err != nil { + opts := tmux.PopupOpts{Target: target, Width: popupWidth, Height: popupHeight} + if err := popupRunFn(opts, argv); err != nil { return err } if err := waitForFile(ctx, outPath, 60*time.Second); err != nil { diff --git a/internal/hexaiaction/cmdentry_runcommand_test.go b/internal/hexaiaction/cmdentry_runcommand_test.go index b139bb3..ac6106d 100644 --- a/internal/hexaiaction/cmdentry_runcommand_test.go +++ b/internal/hexaiaction/cmdentry_runcommand_test.go @@ -33,12 +33,10 @@ func TestRunCommand_UIChild(t *testing.T) { } func TestRunCommand_Tmux(t *testing.T) { - oldTTY := isTTYFn oldExec := osExecutableFn - oldSplit := splitRunFn - isTTYFn = func(_ uintptr) bool { return false } + oldPopup := popupRunFn osExecutableFn = func() (string, error) { return "/bin/hexai-tmux-action", nil } - splitRunFn = func(_ tmux.SplitOpts, argv []string) error { + popupRunFn = func(_ tmux.PopupOpts, argv []string) error { for i := 0; i < len(argv)-1; i++ { if argv[i] == "-outfile" && i+1 < len(argv) { _ = os.WriteFile(argv[i+1], []byte("OUT"), 0o600) @@ -47,7 +45,7 @@ func TestRunCommand_Tmux(t *testing.T) { } return nil } - defer func() { isTTYFn = oldTTY; osExecutableFn = oldExec; splitRunFn = oldSplit }() + defer func() { osExecutableFn = oldExec; popupRunFn = oldPopup }() var out bytes.Buffer if err := RunCommand(context.Background(), Options{}, bytes.NewBufferString("X"), &out, io.Discard); err != nil { t.Fatalf("RunCommand tmux: %v", err) diff --git a/internal/hexaiaction/cmdentry_test.go b/internal/hexaiaction/cmdentry_test.go index 054c78c..b9d5e9b 100644 --- a/internal/hexaiaction/cmdentry_test.go +++ b/internal/hexaiaction/cmdentry_test.go @@ -78,9 +78,9 @@ func TestRunInTmuxParent_Stubbed(t *testing.T) { // capture stdout rout, wout, _ := os.Pipe() oldExec := osExecutableFn - oldSplit := splitRunFn + oldPopup := popupRunFn osExecutableFn = func() (string, error) { return "/bin/hexai-tmux-action", nil } - splitRunFn = func(opts tmux.SplitOpts, argv []string) error { + popupRunFn = func(opts tmux.PopupOpts, argv []string) error { for i := 0; i < len(argv)-1; i++ { if argv[i] == "-outfile" && i+1 < len(argv) { _ = os.WriteFile(argv[i+1], []byte("OUT:"+strings.Join(argv, ",")), 0o600) @@ -89,8 +89,8 @@ func TestRunInTmuxParent_Stubbed(t *testing.T) { } return nil } - t.Cleanup(func() { osExecutableFn = oldExec; splitRunFn = oldSplit }) - if err := runInTmuxParent(context.Background(), r, wout, "", "v", 33); err != nil { + t.Cleanup(func() { osExecutableFn = oldExec; popupRunFn = oldPopup }) + if err := runInTmuxParent(context.Background(), r, wout, "", "", ""); err != nil { t.Fatalf("runInTmuxParent: %v", err) } _ = wout.Close() @@ -108,22 +108,22 @@ func TestRunInTmuxParent_ExecutableError(t *testing.T) { r, w, _ := os.Pipe() _, _ = w.Write([]byte("x")) _ = w.Close() - if err := runInTmuxParent(context.Background(), r, io.Discard, "", "v", 33); err == nil { + if err := runInTmuxParent(context.Background(), r, io.Discard, "", "", ""); err == nil { t.Fatal("expected error from missing executable") } } -func TestRunInTmuxParent_SplitError(t *testing.T) { +func TestRunInTmuxParent_PopupError(t *testing.T) { oldExec := osExecutableFn osExecutableFn = func() (string, error) { return "/bin/hexai-tmux-action", nil } - oldSplit := splitRunFn - splitRunFn = func(_ tmux.SplitOpts, _ []string) error { return fmt.Errorf("split failed") } - t.Cleanup(func() { osExecutableFn = oldExec; splitRunFn = oldSplit }) + oldPopup := popupRunFn + popupRunFn = func(_ tmux.PopupOpts, _ []string) error { return fmt.Errorf("popup failed") } + t.Cleanup(func() { osExecutableFn = oldExec; popupRunFn = oldPopup }) r, w, _ := os.Pipe() _, _ = w.Write([]byte("x")) _ = w.Close() - if err := runInTmuxParent(context.Background(), r, io.Discard, "", "v", 33); err == nil { - t.Fatal("expected split error") + if err := runInTmuxParent(context.Background(), r, io.Discard, "", "", ""); err == nil { + t.Fatal("expected popup error") } } diff --git a/internal/hexaiaction/prompts.go b/internal/hexaiaction/prompts.go index 03a9441..4b2d8be 100644 --- a/internal/hexaiaction/prompts.go +++ b/internal/hexaiaction/prompts.go @@ -82,6 +82,13 @@ func runSimplify(ctx context.Context, cfg actionConfig, client chatDoer, selecti return runOnce(ctx, client, sys, user, reqOptsFrom(cfg)) } +func runFixTypos(ctx context.Context, cfg actionConfig, client chatDoer, selection string) (string, error) { + prompts := cfg.PromptSection() + sys := prompts.PromptCodeActionFixTyposSystem + user := Render(prompts.PromptCodeActionFixTyposUser, map[string]string{"selection": selection}) + return runOnce(ctx, client, sys, user, reqOptsFrom(cfg)) +} + func runGoTest(ctx context.Context, cfg actionConfig, client chatDoer, funcCode string) (string, error) { prompts := cfg.PromptSection() sys := prompts.PromptCodeActionGoTestSystem diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go index 2a3ade1..0330354 100644 --- a/internal/hexaiaction/run.go +++ b/internal/hexaiaction/run.go @@ -251,6 +251,7 @@ func codeActionHandlers() map[ActionKind]CodeActionHandler { ActionDocument: codeActionHandler{build: buildDocumentPlan}, ActionGoTest: codeActionHandler{build: buildGoTestPlan}, ActionSimplify: codeActionHandler{build: buildSimplifyPlan}, + ActionFixTypos: codeActionHandler{build: buildFixTyposPlan}, ActionCustomPrompt: codeActionHandler{build: buildCustomPromptPlan}, } } @@ -304,6 +305,15 @@ func buildSimplifyPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io }, true } +func buildFixTyposPlan(parts InputParts, cfg actionConfig, client chatDoer, _ io.Writer) (actionPlan, bool) { + return actionPlan{ + fallback: parts.Selection, + run: func(ctx context.Context) (string, error) { + return handleFixTyposAction(ctx, parts, cfg, client) + }, + }, true +} + func buildCustomPromptPlan(parts InputParts, cfg actionConfig, client chatDoer, stderr io.Writer) (actionPlan, bool) { return actionPlan{ fallback: parts.Selection, @@ -348,6 +358,12 @@ func handleSimplifyAction(ctx context.Context, parts InputParts, cfg actionConfi }) } +func handleFixTyposAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer) (string, error) { + return runWithTimeout(ctx, timeout20s, func(cctx context.Context) (string, error) { + return runFixTypos(cctx, cfg, client, parts.Selection) + }) +} + func handleCustomAction(ctx context.Context, parts InputParts, cfg actionConfig, client chatDoer, selectedCustom *appconfig.CustomAction) (string, error) { if selectedCustom == nil { return parts.Selection, nil diff --git a/internal/hexaiaction/tui.go b/internal/hexaiaction/tui.go index 549a6ab..749b30c 100644 --- a/internal/hexaiaction/tui.go +++ b/internal/hexaiaction/tui.go @@ -31,6 +31,7 @@ func newModel() model { item{title: "Simplify and improve", desc: "", kind: ActionSimplify, hotkey: 'i'}, item{title: "Document code", desc: "", kind: ActionDocument, hotkey: 'c'}, item{title: "Generate Go unit test(s)", desc: "", kind: ActionGoTest, hotkey: 't'}, + item{title: "Fix typos and improve grammar and clarity", desc: "", kind: ActionFixTypos, hotkey: 'f'}, item{title: "Custom prompt", desc: "", kind: ActionCustomPrompt, hotkey: 'p'}, item{title: "Skip", desc: "", kind: ActionSkip, hotkey: 's'}, } @@ -81,7 +82,7 @@ func handleKey(m model, msg tea.KeyMsg) (tea.Model, tea.Cmd) { if n := len(m.list.Items()); n > 0 { m.list.Select(n - 1) } - case "s", "r", "c", "t", "i", "p": + case "s", "r", "c", "t", "i", "f", "p": items := m.list.Items() for i := 0; i < len(items); i++ { if it, ok := items[i].(item); ok && strings.ToLower(string(it.hotkey)) == low { diff --git a/internal/hexaiaction/types.go b/internal/hexaiaction/types.go index 8b4ba28..eb1f3f4 100644 --- a/internal/hexaiaction/types.go +++ b/internal/hexaiaction/types.go @@ -10,6 +10,7 @@ const ( ActionDocument ActionKind = "document" ActionGoTest ActionKind = "gotest" ActionSimplify ActionKind = "simplify" + ActionFixTypos ActionKind = "fix_typos" // ActionCustom represents a configured custom action from the submenu. ActionCustom ActionKind = "custom" // ActionCustomPrompt is the free-form prompt opened in the editor (hotkey 'p'). |
