diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 15:16:55 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 15:16:55 +0200 |
| commit | 0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe (patch) | |
| tree | 358e331945b8206ed4a72703e3aedfe8ea7cdcdb /cmd/krino/review.go | |
| parent | 1d3f2d1e4c59867024470d3444e12698b7ebb22e (diff) | |
| download | krino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.tar.gz krino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.zip | |
krino: 0.0.5 — keyword cache, t and d in reviewv0.0.5
Diffstat (limited to 'cmd/krino/review.go')
| -rw-r--r-- | cmd/krino/review.go | 99 |
1 files changed, 72 insertions, 27 deletions
diff --git a/cmd/krino/review.go b/cmd/krino/review.go index 66e17b9..29b3915 100644 --- a/cmd/krino/review.go +++ b/cmd/krino/review.go @@ -21,7 +21,7 @@ import ( // directory-level table does (root-relative inside root, ~-abbreviated // outside it) instead of always falling back to the abbreviated form. p // styles the prompts and the per-file steps (spec §8.2). -func reviewDir(out io.Writer, chains []plan.Chain, root string, p palette) (map[string]bool, rune, error) { +func reviewDir(out io.Writer, chains []plan.Chain, root string, p palette) (map[string]bool, map[string]plan.Kind, rune, error) { return reviewChains(keyReader{stdin}, out, chains, root, p) } @@ -47,17 +47,18 @@ func (k keyReader) Read(p []byte) (int, error) { // // menu, and, for [c], the per-file // -// [y] yes [n] no [a] yes to this and all remaining [d] done, apply chosen so far [q] quit, apply nothing +// [y] yes [n] no [a] yes to this and all remaining [t] trash [d] delete permanently [w] write, apply chosen so far [q] quit, apply nothing // // prompt. action is always one of 'a', 'c', 's' or 'q': a [c] session's own // [q] ("quit, apply nothing") folds into the same 'q' the caller already // handles for the top-level menu, and approved is emptied to match - even a // file already marked yes in that session is discarded, per spec §8.3's -// wording ("apply nothing"), unlike [d] ("apply chosen so far"), which -// keeps it. root is the directory being reviewed - passed only to +// wording ("apply nothing"), unlike [w] ("apply chosen so far"), which +// keeps it. replaced holds the files [t] or [d] chose to trash or delete +// instead of what the rules planned; replaceChains applies it. root is the directory being reviewed - passed only to // reviewPerFile's destination rendering (review finding 1, fix round // 2026-09-12); nothing here uses it directly. -func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (map[string]bool, rune, error) { +func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (map[string]bool, map[string]plan.Kind, rune, error) { fmt.Fprintln(out) for _, l := range wrapped("", "[a] apply all [c] choose per file [s] skip this directory [q] quit", 0, widthPolicy(out), p.keys) { fmt.Fprintln(out, l) @@ -65,24 +66,24 @@ func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, for { key, err := readKey(in) if err != nil { - return nil, 0, err + return nil, nil, 0, err } switch key { case 'a': - return approveAll(chains), 'a', nil + return approveAll(chains), nil, 'a', nil case 's': - return map[string]bool{}, 's', nil + return map[string]bool{}, nil, 's', nil case 'q': - return map[string]bool{}, 'q', nil + return map[string]bool{}, nil, 'q', nil case 'c': - approved, quit, err := reviewPerFile(in, out, chains, root, p) + approved, replaced, quit, err := reviewPerFile(in, out, chains, root, p) if err != nil { - return nil, 0, err + return nil, nil, 0, err } if quit { - return map[string]bool{}, 'q', nil + return map[string]bool{}, nil, 'q', nil } - return approved, 'c', nil + return approved, replaced, 'c', nil default: fmt.Fprintf(out, "%q is not a, c, s or q\n", key) } @@ -94,14 +95,18 @@ func reviewChains(in io.Reader, out io.Writer, chains []plan.Chain, root string, // Each file shows the same block body the plan shows (stepLines): every // step, its rule and its reason, wrapped to the terminal. [y]/[n] decide // just that file; [a] approves it and every remaining file without asking -// again; [d] stops asking and applies whatever was already chosen, +// again; [t] and [d] approve it with its chain replaced by one trash or +// permanent delete step, [d] only after a y to its own confirmation (any +// other key asks about the same file again); [w] stops asking and applies +// whatever was already chosen, // declining the rest; [q] aborts the review entirely, discarding even files // already marked yes - reported back to reviewChains via quit=true. root is // passed down so a destination inside root renders root-relative and one // outside it renders ~-abbreviated, exactly as in the plan (review finding // 1, fix round 2026-09-12). -func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (approved map[string]bool, quit bool, err error) { +func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string, p palette) (approved map[string]bool, replaced map[string]plan.Kind, quit bool, err error) { approved = map[string]bool{} + replaced = map[string]plan.Kind{} yesRest := false for i, c := range chains { if yesRest { @@ -113,14 +118,17 @@ func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string for _, l := range stepLines(c, 7, root, p, widthPolicy(out)) { fmt.Fprintln(out, l) } - for _, l := range wrapped(" ", perFileKeys, 2, widthPolicy(out), p.keys) { - fmt.Fprintln(out, l) - } - + showKeys := true for { + if showKeys { + for _, l := range wrapped(" ", perFileKeys, 2, widthPolicy(out), p.keys) { + fmt.Fprintln(out, l) + } + showKeys = false + } key, kerr := readKey(in) if kerr != nil { - return nil, false, kerr + return nil, nil, false, kerr } switch key { case 'y': @@ -130,23 +138,60 @@ func reviewPerFile(in io.Reader, out io.Writer, chains []plan.Chain, root string case 'a': approved[c.File.Rel] = true yesRest = true + case 't': + approved[c.File.Rel] = true + replaced[c.File.Rel] = plan.Trash case 'd': - return approved, false, nil + fmt.Fprintf(out, " delete %s permanently? [y/N] ", c.File.Rel) + confirm, kerr := readKey(in) + if kerr != nil { + return nil, nil, false, kerr + } + fmt.Fprintln(out) + if confirm != 'y' { + fmt.Fprintln(out, " not deleted") + showKeys = true + continue + } + approved[c.File.Rel] = true + replaced[c.File.Rel] = plan.DeletePermanent + case 'w': + return approved, replaced, false, nil case 'q': - return nil, true, nil + return nil, nil, true, nil default: - fmt.Fprintf(out, "%q is not y, n, a, d or q\n", key) + fmt.Fprintf(out, "%q is not y, n, a, t, d, w or q\n", key) continue } break } } - return approved, false, nil + return approved, replaced, false, nil } -// perFileKeys is the per-file prompt of spec §8.3, shared by review and -// undo, and wrapped to the terminal like every other long line. -const perFileKeys = "[y] yes [n] no [a] yes to this and all remaining [d] done, apply chosen so far [q] quit, apply nothing" +// perFileKeys is the per-file prompt of spec §8.3, wrapped to the terminal +// like every other long line. Undo's has no [t] or [d] (undoPerFileKeys). +const perFileKeys = "[y] yes [n] no [a] yes to this and all remaining [t] trash [d] delete permanently [w] write, apply chosen so far [q] quit, apply nothing" + +// reviewRule is the rule name a step chosen in review is planned and logged +// under, in parentheses so no configured rule can be mistaken for it. +const reviewRule = "(review)" + +// replaceChains returns chains with the chain of every file in replaced +// swapped for one step of the chosen kind on the file itself, under +// reviewRule: [t] and [d] set aside what the rules planned. chains itself +// is not modified. +func replaceChains(chains []plan.Chain, replaced map[string]plan.Kind) []plan.Chain { + out := make([]plan.Chain, len(chains)) + for i, c := range chains { + if k, ok := replaced[c.File.Rel]; ok { + c.Steps = []plan.Step{{Kind: k, Rule: reviewRule, Src: c.File.Path, Reason: "chosen in review"}} + c.Warnings = nil + } + out[i] = c + } + return out +} // readKey reads the single byte reviewChains treats as one keypress. Over // the real terminal that byte already came from tui.ReadKey (via |
