Skip to content

Commit 9970f6e

Browse files
authored
Merge pull request #66 from interpretive-systems/development
Development into main (fmt & fmt checks)
2 parents 8c0e64d + b3df027 commit 9970f6e

16 files changed

Lines changed: 3020 additions & 2844 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
releases/
2+
bin/

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ BIN ?= bin/diffium
22

33
.PHONY: build test run fmt
44

5-
build:
5+
build: fmt
66
go build -o $(BIN) ./cmd/diffium
77

88
test:

cmd/diffium/main.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package main
22

33
import (
4-
"log"
4+
"log"
55

6-
"github.com/interpretive-systems/diffium/internal/cli"
6+
"github.com/interpretive-systems/diffium/internal/cli"
77
)
88

99
func main() {
10-
if err := cli.Execute(); err != nil {
11-
log.Fatal(err)
12-
}
10+
if err := cli.Execute(); err != nil {
11+
log.Fatal(err)
12+
}
1313
}
14-

go.mod

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ module github.com/interpretive-systems/diffium
22

33
go 1.25.1
44

5+
require (
6+
github.com/charmbracelet/bubbles v0.21.0
7+
github.com/charmbracelet/bubbletea v1.3.8
8+
github.com/charmbracelet/lipgloss v1.1.0
9+
github.com/charmbracelet/x/ansi v0.10.1
10+
github.com/spf13/cobra v1.10.1
11+
)
12+
513
require (
614
github.com/atotto/clipboard v0.1.4 // indirect
715
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
8-
github.com/charmbracelet/bubbles v0.21.0 // indirect
9-
github.com/charmbracelet/bubbletea v1.3.8 // indirect
1016
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
11-
github.com/charmbracelet/lipgloss v1.1.0 // indirect
12-
github.com/charmbracelet/x/ansi v0.10.1 // indirect
1317
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
1418
github.com/charmbracelet/x/term v0.2.1 // indirect
1519
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
@@ -22,7 +26,6 @@ require (
2226
github.com/muesli/cancelreader v0.2.2 // indirect
2327
github.com/muesli/termenv v0.16.0 // indirect
2428
github.com/rivo/uniseg v0.4.7 // indirect
25-
github.com/spf13/cobra v1.10.1 // indirect
2629
github.com/spf13/pflag v1.0.9 // indirect
2730
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
2831
golang.org/x/sys v0.36.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
4545
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
4646
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
4747
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
48+
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
49+
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
4850
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
4951
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5052
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=

internal/cli/root.go

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
11
package cli
22

33
import (
4-
"fmt"
5-
"os"
4+
"fmt"
5+
"os"
66

7-
"github.com/spf13/cobra"
7+
"github.com/spf13/cobra"
88
)
99

1010
func Execute() error {
11-
root := &cobra.Command{
12-
Use: "diffium",
13-
Short: "Diff-first TUI for git changes",
14-
Long: "Diffium: Explore and review git diffs in a side-by-side TUI.",
15-
}
11+
root := &cobra.Command{
12+
Use: "diffium",
13+
Short: "Diff-first TUI for git changes",
14+
Long: "Diffium: Explore and review git diffs in a side-by-side TUI.",
15+
}
1616

17-
root.PersistentFlags().StringP("repo", "r", ".", "Path to repository root (default: current dir)")
17+
root.PersistentFlags().StringP("repo", "r", ".", "Path to repository root (default: current dir)")
1818

19-
// Add subcommands
20-
root.AddCommand(newWatchCmd())
19+
// Add subcommands
20+
root.AddCommand(newWatchCmd())
2121

22-
if err := root.Execute(); err != nil {
23-
return fmt.Errorf("execute: %w", err)
24-
}
25-
return nil
22+
if err := root.Execute(); err != nil {
23+
return fmt.Errorf("execute: %w", err)
24+
}
25+
return nil
2626
}
2727

2828
func mustGetStringFlag(cmd *cobra.Command, name string) string {
29-
v, err := cmd.Flags().GetString(name)
30-
if err != nil {
31-
fmt.Fprintln(os.Stderr, "flag error:", err)
32-
os.Exit(2)
33-
}
34-
return v
29+
v, err := cmd.Flags().GetString(name)
30+
if err != nil {
31+
fmt.Fprintln(os.Stderr, "flag error:", err)
32+
os.Exit(2)
33+
}
34+
return v
3535
}
36-

internal/cli/watch.go

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
package cli
22

33
import (
4-
"fmt"
4+
"fmt"
55

6-
"github.com/interpretive-systems/diffium/internal/gitx"
7-
"github.com/interpretive-systems/diffium/internal/tui"
8-
"github.com/spf13/cobra"
6+
"github.com/interpretive-systems/diffium/internal/gitx"
7+
"github.com/interpretive-systems/diffium/internal/tui"
8+
"github.com/spf13/cobra"
99
)
1010

1111
func newWatchCmd() *cobra.Command {
12-
cmd := &cobra.Command{
13-
Use: "watch",
14-
Short: "Open the TUI and watch for changes",
15-
RunE: func(cmd *cobra.Command, args []string) error {
16-
repoPath := mustGetStringFlag(cmd.Root(), "repo")
17-
root, err := gitx.RepoRoot(repoPath)
18-
if err != nil {
19-
return fmt.Errorf("not a git repo: %w", err)
20-
}
21-
return tui.Run(root)
22-
},
23-
}
24-
return cmd
12+
cmd := &cobra.Command{
13+
Use: "watch",
14+
Short: "Open the TUI and watch for changes",
15+
RunE: func(cmd *cobra.Command, args []string) error {
16+
repoPath := mustGetStringFlag(cmd.Root(), "repo")
17+
root, err := gitx.RepoRoot(repoPath)
18+
if err != nil {
19+
return fmt.Errorf("not a git repo: %w", err)
20+
}
21+
return tui.Run(root)
22+
},
23+
}
24+
return cmd
2525
}
26-

internal/diffview/side_by_side.go

Lines changed: 76 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,105 @@
11
package diffview
22

33
import (
4-
"bufio"
5-
"strings"
4+
"bufio"
5+
"strings"
66
)
77

88
// RowKind represents the semantic type of a side-by-side row.
99
type RowKind int
1010

1111
const (
12-
RowContext RowKind = iota
13-
RowAdd
14-
RowDel
15-
RowReplace
16-
RowHunk
17-
RowMeta
12+
RowContext RowKind = iota
13+
RowAdd
14+
RowDel
15+
RowReplace
16+
RowHunk
17+
RowMeta
1818
)
1919

2020
// Row represents a single visual row for side-by-side rendering.
2121
type Row struct {
22-
Left string
23-
Right string
24-
Kind RowKind
25-
Meta string // for hunk header text
22+
Left string
23+
Right string
24+
Kind RowKind
25+
Meta string // for hunk header text
2626
}
2727

2828
// BuildRowsFromUnified parses a unified diff string into side-by-side rows.
2929
// It uses a simple pairing strategy within each hunk: deletions are paired
3030
// with subsequent additions as replacements; any remaining lines are shown
3131
// as left-only (deletions) or right-only (additions).
3232
func BuildRowsFromUnified(unified string) []Row {
33-
s := bufio.NewScanner(strings.NewReader(unified))
34-
s.Buffer(make([]byte, 0, 64*1024), 10*1024*1024) // allow large lines
33+
s := bufio.NewScanner(strings.NewReader(unified))
34+
s.Buffer(make([]byte, 0, 64*1024), 10*1024*1024) // allow large lines
3535

36-
rows := make([]Row, 0, 256)
37-
pendingDel := make([]string, 0)
36+
rows := make([]Row, 0, 256)
37+
pendingDel := make([]string, 0)
3838

39-
flushPending := func() {
40-
for _, dl := range pendingDel {
41-
rows = append(rows, Row{Left: trimPrefix(dl), Right: "", Kind: RowDel})
42-
}
43-
pendingDel = pendingDel[:0]
44-
}
39+
flushPending := func() {
40+
for _, dl := range pendingDel {
41+
rows = append(rows, Row{Left: trimPrefix(dl), Right: "", Kind: RowDel})
42+
}
43+
pendingDel = pendingDel[:0]
44+
}
4545

46-
inHunk := false
47-
for s.Scan() {
48-
line := s.Text()
49-
if strings.HasPrefix(line, "diff --git ") || strings.HasPrefix(line, "index ") || strings.HasPrefix(line, "--- ") || strings.HasPrefix(line, "+++ ") {
50-
// Metadata; flush any pending deletions
51-
flushPending()
52-
rows = append(rows, Row{Kind: RowMeta, Meta: line})
53-
continue
54-
}
55-
if strings.HasPrefix(line, "@@ ") {
56-
flushPending()
57-
rows = append(rows, Row{Kind: RowHunk, Meta: line})
58-
inHunk = true
59-
continue
60-
}
61-
if !inHunk {
62-
// Outside hunks, we don't have meaningful line-level info; skip
63-
continue
64-
}
46+
inHunk := false
47+
for s.Scan() {
48+
line := s.Text()
49+
if strings.HasPrefix(line, "diff --git ") || strings.HasPrefix(line, "index ") || strings.HasPrefix(line, "--- ") || strings.HasPrefix(line, "+++ ") {
50+
// Metadata; flush any pending deletions
51+
flushPending()
52+
rows = append(rows, Row{Kind: RowMeta, Meta: line})
53+
continue
54+
}
55+
if strings.HasPrefix(line, "@@ ") {
56+
flushPending()
57+
rows = append(rows, Row{Kind: RowHunk, Meta: line})
58+
inHunk = true
59+
continue
60+
}
61+
if !inHunk {
62+
// Outside hunks, we don't have meaningful line-level info; skip
63+
continue
64+
}
6565

66-
if len(line) == 0 {
67-
// blank line inside hunk: treat as context
68-
flushPending()
69-
rows = append(rows, Row{Left: "", Right: "", Kind: RowContext})
70-
continue
71-
}
66+
if len(line) == 0 {
67+
// blank line inside hunk: treat as context
68+
flushPending()
69+
rows = append(rows, Row{Left: "", Right: "", Kind: RowContext})
70+
continue
71+
}
7272

73-
switch line[0] {
74-
case ' ':
75-
flushPending()
76-
t := trimPrefix(line)
77-
rows = append(rows, Row{Left: t, Right: t, Kind: RowContext})
78-
case '-':
79-
pendingDel = append(pendingDel, line)
80-
case '+':
81-
if len(pendingDel) > 0 {
82-
// Pair with the earliest pending deletion
83-
dl := pendingDel[0]
84-
pendingDel = pendingDel[1:]
85-
rows = append(rows, Row{Left: trimPrefix(dl), Right: trimPrefix(line), Kind: RowReplace})
86-
} else {
87-
rows = append(rows, Row{Left: "", Right: trimPrefix(line), Kind: RowAdd})
88-
}
89-
default:
90-
// Unknown line; ignore
91-
}
92-
}
93-
flushPending()
94-
return rows
73+
switch line[0] {
74+
case ' ':
75+
flushPending()
76+
t := trimPrefix(line)
77+
rows = append(rows, Row{Left: t, Right: t, Kind: RowContext})
78+
case '-':
79+
pendingDel = append(pendingDel, line)
80+
case '+':
81+
if len(pendingDel) > 0 {
82+
// Pair with the earliest pending deletion
83+
dl := pendingDel[0]
84+
pendingDel = pendingDel[1:]
85+
rows = append(rows, Row{Left: trimPrefix(dl), Right: trimPrefix(line), Kind: RowReplace})
86+
} else {
87+
rows = append(rows, Row{Left: "", Right: trimPrefix(line), Kind: RowAdd})
88+
}
89+
default:
90+
// Unknown line; ignore
91+
}
92+
}
93+
flushPending()
94+
return rows
9595
}
9696

9797
func trimPrefix(s string) string {
98-
if s == "" {
99-
return s
100-
}
101-
if s[0] == ' ' || s[0] == '+' || s[0] == '-' {
102-
return s[1:]
103-
}
104-
return s
98+
if s == "" {
99+
return s
100+
}
101+
if s[0] == ' ' || s[0] == '+' || s[0] == '-' {
102+
return s[1:]
103+
}
104+
return s
105105
}
106-

0 commit comments

Comments
 (0)