diff --git a/internal/tui/diff_viewer_test.go b/internal/tui/diff_viewer_test.go index e01a0dba1..aeedddb10 100644 --- a/internal/tui/diff_viewer_test.go +++ b/internal/tui/diff_viewer_test.go @@ -332,23 +332,6 @@ func TestDiffViewerFallsBackForOversizedSourceLine(t *testing.T) { } } -func TestDiffViewerSyntaxHighlighterKeepsSpans(t *testing.T) { - requireDiffViewerTrueColor(t) - styled, ok := highlightCodeForPathWithSpans( - []string{"func greet(name string) string { return \"new\" }"}, - "example.go", - 200, - zeroTheme.addLine.GetBackground(), - []highlightSpan{{line: 0, start: 39, end: 44, background: zeroTheme.addLineWord.GetBackground()}}, - ) - if !ok || len(styled) != 1 { - t.Fatalf("highlightCodeForPathWithSpans = %#v, %v", styled, ok) - } - if !strings.Contains(styled[0], "46;101;77") { - t.Fatalf("highlighted span lost its word-diff background: %q", styled[0]) - } -} - func TestDiffViewerHighlightsEachFileWithItsOwnPath(t *testing.T) { requireDiffViewerTrueColor(t) rawLines := []string{ diff --git a/internal/tui/syntax_highlight.go b/internal/tui/syntax_highlight.go index 083a9fae3..f923b1186 100644 --- a/internal/tui/syntax_highlight.go +++ b/internal/tui/syntax_highlight.go @@ -206,111 +206,6 @@ func highlightCodeForPath(code []string, path string, measure int, bg color.Colo return highlightCodeWithLexer(cachedLexerForPath(path), code, measure, bg) } -// highlightShellCommand styles a one-line command for a tool-card heading. -// Command output is deliberately left untouched: it may be structured data, -// prose, or terminal control text rather than shell source. -func highlightShellCommand(command string) (string, bool) { - if command == "" { - return "", false - } - lexer := cachedLexer("bash") - if lexer == nil { - return "", false - } - iterator, err := lexer.Tokenise(nil, command) - if err != nil { - return "", false - } - var builder strings.Builder - expectingCommand := true - for _, token := range iterator.Tokens() { - if token.Type == chroma.Text || token.Type == chroma.TextWhitespace { - builder.WriteString(highlightShellText(token.Value, &expectingCommand)) - continue - } - builder.WriteString(tokenStyle(token.Type).Render(token.Value)) - if token.Type.InCategory(chroma.Operator) || (token.Type.InCategory(chroma.Punctuation) && strings.ContainsAny(token.Value, ";|")) { - expectingCommand = true - } - } - return builder.String(), true -} - -func highlightShellText(text string, expectingCommand *bool) string { - var builder strings.Builder - for start := 0; start < len(text); { - end := start - space := text[start] == ' ' || text[start] == '\t' - for end < len(text) { - isSpace := text[end] == ' ' || text[end] == '\t' - if isSpace != space { - break - } - end++ - } - part := text[start:end] - if space { - builder.WriteString(part) - start = end - continue - } - - clean := strings.Trim(part, "\"'") - style := tokenStyle(chroma.Text) - switch { - case isShellControlOperator(clean): - style = tokenStyle(chroma.Operator) - *expectingCommand = true - case *expectingCommand: - style = tokenStyle(chroma.NameFunction) - *expectingCommand = isShellCommandWrapper(clean) - case strings.HasPrefix(clean, "-"): - style = tokenStyle(chroma.NameAttribute) - case isShellNumber(clean): - style = tokenStyle(chroma.LiteralNumber) - case looksLikeShellPath(clean): - style = tokenStyle(chroma.LiteralString) - } - builder.WriteString(style.Render(part)) - start = end - } - return builder.String() -} - -func isShellControlOperator(value string) bool { - switch value { - case "&&", "||", "|", ";": - return true - default: - return false - } -} - -func isShellCommandWrapper(value string) bool { - switch value { - case "sudo", "env", "command", "time", "xargs": - return true - default: - return false - } -} - -func isShellNumber(value string) bool { - if value == "" { - return false - } - for _, r := range value { - if r < '0' || r > '9' { - return false - } - } - return true -} - -func looksLikeShellPath(value string) bool { - return strings.HasPrefix(value, "./") || strings.HasPrefix(value, "../") || strings.HasPrefix(value, "/") || strings.HasPrefix(value, "~/") || strings.Contains(value, "/") -} - // highlightSpan overlays a background on a rune range in one source line while // retaining the lexer-selected foreground. Diff rendering uses it to keep the // precise changed word visible inside a syntax-highlighted add/delete row. @@ -320,10 +215,6 @@ type highlightSpan struct { background color.Color } -func highlightCodeForPathWithSpans(code []string, path string, measure int, bg color.Color, spans []highlightSpan) ([]string, bool) { - return highlightCodeWithLexerAndSpans(cachedLexerForPath(path), code, measure, bg, spans) -} - // highlightCodeForPathWithLineBackgrounds applies syntax highlighting to a // single source unit while preserving a distinct background for every source // line. The diff viewer uses one lexer pass per hunk so multiline syntax keeps diff --git a/internal/tui/syntax_highlight_test.go b/internal/tui/syntax_highlight_test.go index 71cc3baf7..99296db3d 100644 --- a/internal/tui/syntax_highlight_test.go +++ b/internal/tui/syntax_highlight_test.go @@ -125,7 +125,7 @@ func TestDiffSyntaxKeepsThemeColorOverDiffSurface(t *testing.T) { } } -func TestInlineCodeAndShellCommandsUseTheActiveSyntaxPalette(t *testing.T) { +func TestInlineCodeUsesTheActiveSyntaxPalette(t *testing.T) { previous := zeroTheme defer func() { zeroTheme = previous }() _, zeroTheme = themeForMode("nord", true) @@ -134,26 +134,6 @@ func TestInlineCodeAndShellCommandsUseTheActiveSyntaxPalette(t *testing.T) { if want := inlineCodeStyle().Render("gofmt"); !strings.Contains(inline, want) { t.Fatalf("inline code should use the active palette:\n%s", inline) } - - command, ok := highlightShellCommand("gofmt -w calculator.go && go run . divide 9 2") - if !ok { - t.Fatal("bash command should have a cached syntax lexer") - } - if plain := ansiPattern.ReplaceAllString(command, ""); plain != "gofmt -w calculator.go && go run . divide 9 2" { - t.Fatalf("command highlight changed visible command to %q", plain) - } - if !strings.Contains(command, "\x1b[") { - t.Fatalf("shell command should be syntax styled, got %q", command) - } - for token, style := range map[string]lipgloss.Style{ - "gofmt": tokenStyle(chroma.NameFunction), - "-w": tokenStyle(chroma.NameAttribute), - "9": tokenStyle(chroma.LiteralNumber), - } { - if want := style.Render(token); !strings.Contains(command, want) { - t.Fatalf("shell token %q should use its semantic style:\n%s", token, command) - } - } } func styleForegroundHex(t *testing.T, style lipgloss.Style) string { diff --git a/internal/tui/theme.go b/internal/tui/theme.go index c16e471ad..1d30078a4 100644 --- a/internal/tui/theme.go +++ b/internal/tui/theme.go @@ -345,13 +345,6 @@ func colorChannel(value float64) int { return int(value*255 + 0.5) } -// buildSystemTheme is the dark-terminal default for package-level render helpers -// and tests. The running TUI resolves the matching contrast direction through -// buildSystemThemeForTerminal. -func buildSystemTheme() tuiTheme { - return buildSystemThemeForTerminal(true) -} - // buildSystemThemeForTerminal preserves the terminal canvas and its foreground // color. It uses ANSI role colors for semantic cues and confines fixed local // surfaces to selected and diff rows, so those states remain visible even in diff --git a/internal/tui/word_diff_test.go b/internal/tui/word_diff_test.go index b6ea1671c..3ef8bafa6 100644 --- a/internal/tui/word_diff_test.go +++ b/internal/tui/word_diff_test.go @@ -1,11 +1,9 @@ package tui import ( - "fmt" "strings" "testing" - "charm.land/lipgloss/v2" "github.com/alecthomas/chroma/v2" ) @@ -102,64 +100,3 @@ func TestMixedDiffSyntaxHighlightsCodeOnBothSides(t *testing.T) { t.Fatalf("mixed diff context code must retain syntax colors, got:\n%s", joined) } } - -func TestSystemThemeKeepsSyntaxOnAnAddedLineBand(t *testing.T) { - previous := zeroTheme - defer func() { zeroTheme = previous }() - zeroTheme = buildSystemTheme() - - diff := strings.Join([]string{ - "--- /dev/null", - "+++ b/main.go", - "@@ -0,0 +1,2 @@", - "+package main", - "+func main() {}", - }, "\n") - body := diffCardBody(diff, 80, cardRenderOptions{bodyCap: 20}) - joined := strings.Join(body.lines, "\n") - if !styleHasLocalBackground(zeroTheme.addLine) { - t.Fatal("system-theme added code must use a local line background") - } - if want := tokenStyle(chroma.Keyword).Background(zeroTheme.addLine.GetBackground()).Render("package"); !strings.Contains(joined, want) { - t.Fatalf("system-theme added code must retain syntax highlighting, got:\n%s", joined) - } - if !styleHasLocalBackground(zeroTheme.onSel(zeroTheme.ink)) { - t.Fatal("system-theme selected rows must use a local selection background") - } -} - -func TestSystemThemeUsesMutedLocalSurfaceColors(t *testing.T) { - theme := buildSystemTheme() - for _, test := range []struct { - name string - style lipgloss.Style - want string - }{ - {"added diff", theme.addLine, "#212922"}, - {"deleted diff", theme.delLine, "#3c170f"}, - {"selection", theme.onSel(theme.ink), "#2b2f2d"}, - } { - if got := styleBackgroundHex(t, test.style); got != test.want { - t.Errorf("%s background = %s, want %s", test.name, got, test.want) - } - } -} - -func styleHasLocalBackground(style lipgloss.Style) bool { - background := style.GetBackground() - if background == nil { - return false - } - _, noColor := background.(lipgloss.NoColor) - return !noColor -} - -func styleBackgroundHex(t *testing.T, style lipgloss.Style) string { - t.Helper() - background := style.GetBackground() - if background == nil { - t.Fatal("style has no background") - } - r, g, b, _ := background.RGBA() - return fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8) -} diff --git a/internal/update/replace_windows.go b/internal/update/replace_windows.go index 2aedfd4b1..cda93eea3 100644 --- a/internal/update/replace_windows.go +++ b/internal/update/replace_windows.go @@ -255,12 +255,6 @@ func openRecoveryCopy(path string) (*os.File, error) { var renameRecoveryFileByHandle = renameOpenFile -// clearOldBinaryPreserved models the operator accepting the installed binary -// by deleting the recovery marker. The recovery copy itself remains preserved. -func clearOldBinaryPreserved(oldPath string) { - _ = os.Remove(oldPath + oldBinaryPreservedSuffix) -} - // oldBinaryPreserved reports whether a failed restore marked oldPath as the last // known-good binary. // diff --git a/internal/update/stage_promote_windows_test.go b/internal/update/stage_promote_windows_test.go index d7f454795..0019335dc 100644 --- a/internal/update/stage_promote_windows_test.go +++ b/internal/update/stage_promote_windows_test.go @@ -255,7 +255,7 @@ func TestPromoteRefusesWhileRecoveryCopyIsMarked(t *testing.T) { // Clearing the marker is the operator accepting the installed binary; the // next promotion proceeds normally without destroying the recovery copy. - clearOldBinaryPreserved(oldPath) + _ = os.Remove(oldPath + oldBinaryPreservedSuffix) stubRandomStagingSuffix(t, "deadbeef") if err := installBinary(sourcePath, targetPath); err != nil { t.Fatalf("installBinary after the operator cleared the marker: %v", err) @@ -333,7 +333,7 @@ func TestPreflightRefusesWhenRecoveryMarkerIsDeletedButTrustedRecordRemains(t *t t.Fatalf("trusted records = %+v, want one unresolved entry", queue.Records) } - clearOldBinaryPreserved(recoveryPath) + _ = os.Remove(recoveryPath + oldBinaryPreservedSuffix) if oldBinaryPreserved(recoveryPath) { t.Fatal("marker deletion did not take effect") } @@ -427,7 +427,7 @@ func TestPreflightRefusesWhenRecordWriteFailsAndRecoveryMarkerIsDeleted(t *testi } } - clearOldBinaryPreserved(asidePath) + _ = os.Remove(asidePath + oldBinaryPreservedSuffix) if oldBinaryPreserved(asidePath) { t.Fatal("marker deletion did not take effect") } diff --git a/internal/zerogit/zerogit.go b/internal/zerogit/zerogit.go index 3011b77df..afbcd154e 100644 --- a/internal/zerogit/zerogit.go +++ b/internal/zerogit/zerogit.go @@ -1100,39 +1100,6 @@ func UpstreamRef(ctx context.Context, cwd, branch string, runGit Runner) string return strings.TrimSpace(out) } -// HasUpstream reports whether branch has a published upstream tracking the same -// branch name on a remote (the relationship `git push -u ` -// records). ensureFeatureBranch consults this when it is called again with a -// non-default current branch: a generated branch that just lost a -// force-with-lease race against a concurrent creator is left checked out -// locally without that relationship, so a retry must not treat it the same as -// an ordinary, already-published feature branch and drop the nonexistence -// lease. -// -// An inherited upstream to a different branch (branch.autoSetupMerge=inherit -// copies origin/main onto a new user/slug) is not publication state and reports -// false. Any failure to resolve the upstream (including "no upstream -// configured") also reports false so the caller keeps requiring the lease. -func HasUpstream(ctx context.Context, cwd, branch string, runGit Runner) (bool, error) { - branch = strings.TrimSpace(branch) - if branch == "" { - return false, nil - } - ref := UpstreamRef(ctx, cwd, branch, runGit) - if ref == "" { - return false, nil - } - // rev-parse --abbrev-ref prints "/". Both remote names - // (e.g. team/upstream) and branch names (e.g. user/slug) may contain - // slashes, so verify that the upstream ref ends with "/" + branch and - // has a non-empty remote prefix. - suffix := "/" + branch - if strings.HasSuffix(ref, suffix) && len(ref) > len(suffix) { - return true, nil - } - return false, nil -} - // UpstreamRemote returns the configured upstream remote name for branch (e.g. "origin"), // or "" if no upstream is configured. This alone is not proof of publication: // branch.autoSetupMerge=inherit copies branch..remote from the source diff --git a/internal/zerogit/zerogit_test.go b/internal/zerogit/zerogit_test.go index e61071a65..5ed20b1b7 100644 --- a/internal/zerogit/zerogit_test.go +++ b/internal/zerogit/zerogit_test.go @@ -1889,10 +1889,11 @@ func TestRemoteHasBranchSeesPushWithoutLocalUpstream(t *testing.T) { } } -// TestHasUpstreamRejectsInheritedMainUpstream covers branch.autoSetupMerge=inherit: -// checkout -b copies origin/main onto the new branch before any push -u. That -// must not count as a published upstream for the generated branch name. -func TestHasUpstreamRejectsInheritedMainUpstream(t *testing.T) { +// TestUpstreamRefTracksInheritedThenPublishedUpstream covers +// branch.autoSetupMerge=inherit: checkout -b copies origin/main onto the new +// branch before any push -u, so UpstreamRef must report that inherited ref +// as-is until an explicit push -u republishes it under the branch's own name. +func TestUpstreamRefTracksInheritedThenPublishedUpstream(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skipf("git unavailable: %v", err) } @@ -1922,39 +1923,11 @@ func TestHasUpstreamRejectsInheritedMainUpstream(t *testing.T) { if ref := UpstreamRef(context.Background(), repo, "user/slug", nil); ref != "origin/main" { t.Fatalf("UpstreamRef after inherit = %q, want origin/main", ref) } - has, err := HasUpstream(context.Background(), repo, "user/slug", nil) - if err != nil { - t.Fatalf("HasUpstream: %v", err) - } - if has { - t.Fatal("HasUpstream must reject inherited origin/main for user/slug") - } runGitCommand(t, repo, "push", "-u", "origin", "user/slug") if ref := UpstreamRef(context.Background(), repo, "user/slug", nil); ref != "origin/user/slug" { t.Fatalf("UpstreamRef after push -u = %q, want origin/user/slug", ref) } - has, err = HasUpstream(context.Background(), repo, "user/slug", nil) - if err != nil { - t.Fatalf("HasUpstream after push: %v", err) - } - if !has { - t.Fatal("HasUpstream must accept exact origin/user/slug after push -u") - } -} - -func TestHasUpstreamMultiSegmentRemote(t *testing.T) { - root := t.TempDir() - runner := &fakeRunner{results: []CommandResult{ - {Stdout: "team/upstream/user/slug\n"}, - }} - has, err := HasUpstream(context.Background(), root, "user/slug", runner.Run) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !has { - t.Fatal("expected HasUpstream to return true for multi-segment remote tracking branch") - } } func TestResetBranchRefRefusesConcurrentDefaultAdvance(t *testing.T) {