Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ run:
fmt:
go fmt ./...

theme-light:
go run ./cmd/diffium watch --theme light
76 changes: 58 additions & 18 deletions internal/cli/watch.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,66 @@
// package cli
//
// import (
// "fmt"
//
// "github.com/interpretive-systems/diffium/internal/gitx"
// "github.com/interpretive-systems/diffium/internal/tui"
// "github.com/spf13/cobra"
// )
//
// func newWatchCmd() *cobra.Command {
// cmd := &cobra.Command{
// Use: "watch",
// Short: "Open the TUI and watch for changes",
// RunE: func(cmd *cobra.Command, args []string) error {
// repoPath := mustGetStringFlag(cmd.Root(), "repo")
// root, err := gitx.RepoRoot(repoPath)
// if err != nil {
// return fmt.Errorf("not a git repo: %w", err)
// }
// return tui.Run(root)
// },
// }
// return cmd
// }
//


package cli

import (
"fmt"
"fmt"

"github.com/interpretive-systems/diffium/internal/gitx"
"github.com/interpretive-systems/diffium/internal/tui"
"github.com/spf13/cobra"
"github.com/interpretive-systems/diffium/internal/gitx"
"github.com/interpretive-systems/diffium/internal/tui"
"github.com/spf13/cobra"
)

func newWatchCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "watch",
Short: "Open the TUI and watch for changes",
RunE: func(cmd *cobra.Command, args []string) error {
repoPath := mustGetStringFlag(cmd.Root(), "repo")
root, err := gitx.RepoRoot(repoPath)
if err != nil {
return fmt.Errorf("not a git repo: %w", err)
}
return tui.Run(root)
},
}
return cmd
}
var theme string // 1. Define a variable to hold the flag value

cmd := &cobra.Command{
Use: "watch",
Short: "Open the TUI and watch for changes",
RunE: func(cmd *cobra.Command, args []string) error {
repoPath := mustGetStringFlag(cmd.Root(), "repo")
root, err := gitx.RepoRoot(repoPath)
if err != nil {
return fmt.Errorf("not a git repo: %w", err)
}

// 3. Pass the flag value to the updated tui.Run function
return tui.Run(root, theme)
},
}

// 2. Add the --theme flag to the command
cmd.Flags().StringVar(
&theme,
"theme",
"dark", // Set "dark" as the default theme
"The color theme to use: 'dark' or 'light'.",
)

return cmd
}
43 changes: 33 additions & 10 deletions internal/tui/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type model struct {
commitErr string
commitDone bool
lastCommit string

currentBranch string
// uncommit wizard state
showUncommit bool
Expand Down Expand Up @@ -106,7 +106,7 @@ type model struct {
plErr string
plDone bool
plOutput string

// search state
searchActive bool
searchInput textinput.Model
Expand All @@ -130,8 +130,31 @@ type diffMsg struct {
}

// Run instantiates and runs the Bubble Tea program.
func Run(repoRoot string) error {
m := model{repoRoot: repoRoot, sideBySide: true, diffMode: "head", theme: loadThemeFromRepo(repoRoot)}
// func Run(repoRoot, themeName string) error {
// baseTheme := "dark"
// if themeName != "" {
// baseTheme = themeName
//
// }
// m := model{repoRoot: repoRoot, sideBySide: true, diffMode: "head", theme: loadThemeFromRepo(repoRoot, baseTheme)}
// p := tea.NewProgram(m, tea.WithAltScreen())
// if _, err := p.Run(); err != nil {
// return err
// }
// return nil
// }

// In /internal/tui/program.go

// FIX: Change function signature to accept themeName
func Run(repoRoot string, themeName string) error {
m := model{
repoRoot: repoRoot,
sideBySide: true,
diffMode: "head",
// FIX: Pass themeName to loadThemeFromRepo
theme: loadThemeFromRepo(repoRoot, themeName),
}
p := tea.NewProgram(m, tea.WithAltScreen())
if _, err := p.Run(); err != nil {
return err
Expand Down Expand Up @@ -1982,7 +2005,7 @@ func (m model) rightBodyLinesAll(width int) []string {
}
}
return lines

}

func (m *model) openSearch() {
Expand Down Expand Up @@ -2017,20 +2040,20 @@ func (m model) handleSearchKeys(key tea.KeyMsg) (tea.Model, tea.Cmd) {
m.closeSearch()
return m, m.recalcViewport()
case "ctrl+c":
return m, tea.Quit
return m, tea.Quit
}


// Navigation that does NOT leave input mode
switch key.Type {
case tea.KeyEnter:
return m, (&m).advanceSearch(1)
case tea.KeyDown:
case tea.KeyDown:
return m, (&m).advanceSearch(1)
case tea.KeyUp:
return m, (&m).advanceSearch(-1)
}


// Fallback: always let input handle it
var cmd tea.Cmd
Expand Down
137 changes: 94 additions & 43 deletions internal/tui/theme.go
Original file line number Diff line number Diff line change
@@ -1,67 +1,118 @@
package tui

import (
"encoding/json"
"os"
"path/filepath"
"encoding/json"
"os"
"path/filepath"
"fmt" // <--- FIXED: Added missing fmt import

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss"
)

// Theme defines customizable colors for rendering.
type Theme struct {
AddColor string `json:"addColor"` // e.g. "34" or "#22c55e"
DelColor string `json:"delColor"` // e.g. "196" or "#ef4444"
MetaColor string `json:"metaColor"` // optional, currently unused
DividerColor string `json:"dividerColor"` // e.g. "240"
AddColor string `json:"addColor"`
DelColor string `json:"delColor"`
MetaColor string `json:"metaColor"`
DividerColor string `json:"dividerColor"`
// NEW: Background colors
AddBgColor string `json:"addBgColor"`
DelBgColor string `json:"delBgColor"`
}

func defaultTheme() Theme {
return Theme{
AddColor: "34",
DelColor: "196",
MetaColor: "63",
DividerColor: "240",
}
// --- Theme Definitions ---

func darkTheme() Theme {
return Theme{
AddColor: "34",
DelColor: "196",
MetaColor: "63",
DividerColor: "240",
AddBgColor: "235", // Subtle dark background
DelBgColor: "235", // Subtle dark background
}
}

func lightTheme() Theme {
return Theme{
AddColor: "22", // Dark Green
DelColor: "9", // Dark Red
MetaColor: "27", // Dark Blue
DividerColor: "244", // Medium Gray
AddBgColor: "255", // Very light background (e.g., White-Green tint)
DelBgColor: "255", // Very light background (e.g., White-Red tint)
}
}

// GetTheme returns the requested base theme.
func GetTheme(name string) Theme {
switch name {
case "light":
fmt.Printf("DEBUG: Loading Light Theme\n") // Temporary debug
return lightTheme()
default: // "dark" or any other value
fmt.Printf("DEBUG: Loading Dark Theme\n") // Temporary debug
return darkTheme()
}
}

// loadThemeFromRepo tries .diffium/theme.json at repoRoot.
func loadThemeFromRepo(repoRoot string) Theme {
t := defaultTheme()
path := filepath.Join(repoRoot, ".diffium", "theme.json")
b, err := os.ReadFile(path)
if err != nil {
return t
}
var u Theme
if err := json.Unmarshal(b, &u); err != nil {
return t
}
// Merge, keeping defaults for empty fields
if u.AddColor != "" {
t.AddColor = u.AddColor
}
if u.DelColor != "" {
t.DelColor = u.DelColor
}
if u.MetaColor != "" {
t.MetaColor = u.MetaColor
}
if u.DividerColor != "" {
t.DividerColor = u.DividerColor
}
return t
func loadThemeFromRepo(repoRoot, baseTheme string) Theme {
t := GetTheme(baseTheme)
path := filepath.Join(repoRoot, ".diffium", "theme.json")
b, err := os.ReadFile(path)
if err != nil {
return t
}
var u Theme
if err := json.Unmarshal(b, &u); err != nil {
return t
}
// Merge, keeping defaults for empty fields
if u.AddColor != "" {
t.AddColor = u.AddColor
}
if u.DelColor != "" {
t.DelColor = u.DelColor
}
if u.MetaColor != "" {
t.MetaColor = u.MetaColor
}
if u.DividerColor != "" {
t.DividerColor = u.DividerColor
}
if u.AddBgColor != "" { // <--- NEW: Merge background colors
t.AddBgColor = u.AddBgColor
}
if u.DelBgColor != "" { // <--- NEW: Merge background colors
t.DelBgColor = u.DelBgColor
}
return t
}

func (t Theme) AddText(s string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color(t.AddColor)).Render(s)
return lipgloss.NewStyle().Foreground(lipgloss.Color(t.AddColor)).Render(s)
}

func (t Theme) DelText(s string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color(t.DelColor)).Render(s)
return lipgloss.NewStyle().Foreground(lipgloss.Color(t.DelColor)).Render(s)
}

func (t Theme) DividerText(s string) string {
return lipgloss.NewStyle().Foreground(lipgloss.Color(t.DividerColor)).Render(s)
return lipgloss.NewStyle().Foreground(lipgloss.Color(t.DividerColor)).Render(s)
}

// NEW: Methods to apply both foreground (text) and background color for entire lines
func (t Theme) AddLine(s string) string {
return lipgloss.NewStyle().
Foreground(lipgloss.Color(t.AddColor)).
Background(lipgloss.Color(t.AddBgColor)).
Render(s)
}

func (t Theme) DelLine(s string) string {
return lipgloss.NewStyle().
Foreground(lipgloss.Color(t.DelColor)).
Background(lipgloss.Color(t.DelBgColor)).
Render(s)
}
1 change: 1 addition & 0 deletions test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testing
Loading