commit
d4ddcc6e98
|
|
@ -5,13 +5,19 @@ import (
|
|||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/oug-t/difi/internal/config"
|
||||
"github.com/oug-t/difi/internal/ui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := tea.NewProgram(ui.NewModel(), tea.WithAltScreen())
|
||||
// Load config (defaults used if file missing)
|
||||
cfg := config.Load()
|
||||
|
||||
// Pass config to model
|
||||
p := tea.NewProgram(ui.NewModel(cfg), tea.WithAltScreen())
|
||||
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
fmt.Printf("Error running difi: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/charmbracelet/bubbles v0.21.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -53,3 +53,7 @@ golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
|||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
49
internal/config/config.go
Normal file
49
internal/config/config.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Colors struct {
|
||||
Border string `yaml:"border"`
|
||||
Focus string `yaml:"focus"`
|
||||
LineNumber string `yaml:"line_number"`
|
||||
} `yaml:"colors"`
|
||||
UI struct {
|
||||
LineNumbers string `yaml:"line_numbers"` // "absolute", "relative", "hybrid", "hidden"
|
||||
ShowGuide bool `yaml:"show_guide"` // The vertical separation line
|
||||
} `yaml:"ui"`
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
var c Config
|
||||
c.Colors.Border = "#D9DCCF"
|
||||
c.Colors.Focus = "#000000" // Default neutral focus
|
||||
c.Colors.LineNumber = "#808080"
|
||||
c.UI.LineNumbers = "hybrid"
|
||||
c.UI.ShowGuide = true
|
||||
return c
|
||||
}
|
||||
|
||||
func Load() Config {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return cfg
|
||||
}
|
||||
|
||||
configPath := filepath.Join(home, ".config", "difi", "config.yml")
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Parse YAML
|
||||
_ = yaml.Unmarshal(data, &cfg)
|
||||
return cfg
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"github.com/oug-t/difi/internal/config"
|
||||
"github.com/oug-t/difi/internal/git"
|
||||
"github.com/oug-t/difi/internal/tree"
|
||||
)
|
||||
|
|
@ -29,27 +30,26 @@ type Model struct {
|
|||
fileTree list.Model
|
||||
diffViewport viewport.Model
|
||||
|
||||
// Data
|
||||
selectedPath string
|
||||
currentBranch string
|
||||
repoName string
|
||||
|
||||
// Diff State
|
||||
diffContent string
|
||||
diffLines []string
|
||||
diffCursor int
|
||||
|
||||
// Input State for Vim Motions
|
||||
inputBuffer string
|
||||
|
||||
// UI State
|
||||
focus Focus
|
||||
showHelp bool
|
||||
|
||||
width, height int
|
||||
}
|
||||
|
||||
func NewModel() Model {
|
||||
func NewModel(cfg config.Config) Model {
|
||||
// Initialize styles with the loaded config
|
||||
InitStyles(cfg)
|
||||
|
||||
files, _ := git.ListChangedFiles(TargetBranch)
|
||||
items := tree.Build(files)
|
||||
|
||||
|
|
@ -101,7 +101,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
var cmd tea.Cmd
|
||||
var cmds []tea.Cmd
|
||||
|
||||
// Flag to track if we manually handled navigation
|
||||
keyHandled := false
|
||||
|
||||
switch msg := msg.(type) {
|
||||
|
|
@ -155,9 +154,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return m, git.OpenEditorCmd(m.selectedPath, line)
|
||||
}
|
||||
|
||||
// Vim Motions
|
||||
case "j", "down":
|
||||
keyHandled = true // Mark as handled so we don't pass to list.Update()
|
||||
keyHandled = true
|
||||
count := m.getRepeatCount()
|
||||
for i := 0; i < count; i++ {
|
||||
if m.focus == FocusDiff {
|
||||
|
|
@ -174,7 +172,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.inputBuffer = ""
|
||||
|
||||
case "k", "up":
|
||||
keyHandled = true // Mark as handled
|
||||
keyHandled = true
|
||||
count := m.getRepeatCount()
|
||||
for i := 0; i < count; i++ {
|
||||
if m.focus == FocusDiff {
|
||||
|
|
@ -195,7 +193,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update Components
|
||||
if m.focus == FocusTree {
|
||||
if !keyHandled {
|
||||
m.fileTree, cmd = m.fileTree.Update(msg)
|
||||
|
|
@ -251,6 +248,7 @@ func (m Model) View() string {
|
|||
return "Loading..."
|
||||
}
|
||||
|
||||
// 1. PANES
|
||||
treeStyle := PaneStyle
|
||||
if m.focus == FocusTree {
|
||||
treeStyle = FocusedPaneStyle
|
||||
|
|
@ -270,13 +268,43 @@ func (m Model) View() string {
|
|||
end = len(m.diffLines)
|
||||
}
|
||||
|
||||
// RENDER LOOP
|
||||
for i := start; i < end; i++ {
|
||||
line := m.diffLines[i]
|
||||
|
||||
// Relative Numbers
|
||||
distance := int(math.Abs(float64(i - m.diffCursor)))
|
||||
relNum := fmt.Sprintf("%d", distance)
|
||||
lineNumStr := LineNumberStyle.Render(relNum)
|
||||
// --- LINE NUMBER LOGIC ---
|
||||
var numStr string
|
||||
mode := CurrentConfig.UI.LineNumbers
|
||||
|
||||
if mode == "hidden" {
|
||||
numStr = ""
|
||||
} else {
|
||||
// Is this the cursor line?
|
||||
isCursor := (i == m.diffCursor)
|
||||
|
||||
if isCursor && mode == "hybrid" {
|
||||
// HYBRID: Show Real File Line Number
|
||||
realLine := git.CalculateFileLine(m.diffContent, m.diffCursor)
|
||||
numStr = fmt.Sprintf("%d", realLine)
|
||||
} else if isCursor && mode == "relative" {
|
||||
numStr = "0"
|
||||
} else if mode == "absolute" {
|
||||
// Note: Calculating absolute for every line is expensive,
|
||||
// usually absolute view shows Diff Line Index or File Line.
|
||||
// For simple 'absolute' view, we often show viewport index + 1
|
||||
numStr = fmt.Sprintf("%d", i+1)
|
||||
} else {
|
||||
// Default / Hybrid-non-cursor: Show Relative Distance
|
||||
dist := int(math.Abs(float64(i - m.diffCursor)))
|
||||
numStr = fmt.Sprintf("%d", dist)
|
||||
}
|
||||
}
|
||||
|
||||
lineNumRendered := ""
|
||||
if numStr != "" {
|
||||
lineNumRendered = LineNumberStyle.Render(numStr)
|
||||
}
|
||||
// -------------------------
|
||||
|
||||
if m.focus == FocusDiff && i == m.diffCursor {
|
||||
line = SelectedItemStyle.Render(line)
|
||||
|
|
@ -284,7 +312,7 @@ func (m Model) View() string {
|
|||
line = " " + line
|
||||
}
|
||||
|
||||
renderedDiff.WriteString(lineNumStr + line + "\n")
|
||||
renderedDiff.WriteString(lineNumRendered + line + "\n")
|
||||
}
|
||||
|
||||
diffView := DiffStyle.Copy().
|
||||
|
|
@ -294,6 +322,7 @@ func (m Model) View() string {
|
|||
|
||||
mainPanes := lipgloss.JoinHorizontal(lipgloss.Top, treeView, diffView)
|
||||
|
||||
// 2. BOTTOM AREA
|
||||
repoSection := StatusKeyStyle.Render(" " + m.repoName)
|
||||
divider := StatusDividerStyle.Render("│")
|
||||
|
||||
|
|
@ -352,6 +381,7 @@ func (m Model) View() string {
|
|||
return finalView
|
||||
}
|
||||
|
||||
// -- Delegates (unchanged) --
|
||||
type listDelegate struct{}
|
||||
|
||||
func (d listDelegate) Height() int { return 1 }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package ui
|
||||
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/oug-t/difi/internal/config"
|
||||
)
|
||||
|
||||
// Global UI colors and styles.
|
||||
// Initialized once at startup via InitStyles.
|
||||
var (
|
||||
ColorBorder = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
|
||||
ColorFocus = lipgloss.AdaptiveColor{Light: "#000000", Dark: "#E5E5E5"}
|
||||
ColorBorder lipgloss.AdaptiveColor
|
||||
ColorFocus lipgloss.AdaptiveColor
|
||||
ColorText = lipgloss.AdaptiveColor{Light: "#1F1F1F", Dark: "#F8F8F2"}
|
||||
ColorSubtle = lipgloss.AdaptiveColor{Light: "#A8A8A8", Dark: "#626262"}
|
||||
ColorCursorBg = lipgloss.AdaptiveColor{Light: "#E5E5E5", Dark: "#3E3E3E"}
|
||||
|
|
@ -12,13 +17,39 @@ var (
|
|||
ColorBarBg = lipgloss.AdaptiveColor{Light: "#F2F2F2", Dark: "#1F1F1F"}
|
||||
ColorBarFg = lipgloss.AdaptiveColor{Light: "#6E6E6E", Dark: "#9E9E9E"}
|
||||
|
||||
PaneStyle lipgloss.Style
|
||||
FocusedPaneStyle lipgloss.Style
|
||||
DiffStyle lipgloss.Style
|
||||
ItemStyle lipgloss.Style
|
||||
SelectedItemStyle lipgloss.Style
|
||||
LineNumberStyle lipgloss.Style
|
||||
StatusBarStyle lipgloss.Style
|
||||
StatusKeyStyle lipgloss.Style
|
||||
StatusDividerStyle lipgloss.Style
|
||||
HelpTextStyle lipgloss.Style
|
||||
HelpDrawerStyle lipgloss.Style
|
||||
|
||||
CurrentConfig config.Config
|
||||
)
|
||||
|
||||
// InitStyles initializes global styles based on the provided config.
|
||||
// This should be called once during application startup.
|
||||
func InitStyles(cfg config.Config) {
|
||||
CurrentConfig = cfg
|
||||
|
||||
// Colors derived from user config
|
||||
ColorBorder = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: cfg.Colors.Border}
|
||||
ColorFocus = lipgloss.AdaptiveColor{Light: "#000000", Dark: cfg.Colors.Focus}
|
||||
|
||||
// Pane styles
|
||||
PaneStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), false, true, false, false).
|
||||
Border(lipgloss.NormalBorder(), false, cfg.UI.ShowGuide, false, false).
|
||||
BorderForeground(ColorBorder)
|
||||
|
||||
FocusedPaneStyle = PaneStyle.Copy().
|
||||
BorderForeground(ColorFocus)
|
||||
|
||||
// Diff and list item styles
|
||||
DiffStyle = lipgloss.NewStyle().Padding(0, 0)
|
||||
ItemStyle = lipgloss.NewStyle().PaddingLeft(2)
|
||||
|
||||
|
|
@ -29,14 +60,35 @@ var (
|
|||
Bold(true).
|
||||
Width(1000)
|
||||
|
||||
// Line numbers
|
||||
LineNumberStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#707070")). // Solid gray, easy to read
|
||||
Foreground(lipgloss.Color(cfg.Colors.LineNumber)).
|
||||
PaddingRight(1).
|
||||
Width(4)
|
||||
|
||||
StatusBarStyle = lipgloss.NewStyle().Foreground(ColorBarFg).Background(ColorBarBg).Padding(0, 1)
|
||||
StatusKeyStyle = lipgloss.NewStyle().Foreground(ColorText).Background(ColorBarBg).Bold(true).Padding(0, 1)
|
||||
StatusDividerStyle = lipgloss.NewStyle().Foreground(ColorSubtle).Background(ColorBarBg).Padding(0, 0)
|
||||
HelpTextStyle = lipgloss.NewStyle().Foreground(ColorSubtle).Padding(0, 1)
|
||||
HelpDrawerStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false, false, false).BorderForeground(ColorBorder).Padding(1, 2)
|
||||
)
|
||||
// Status bar
|
||||
StatusBarStyle = lipgloss.NewStyle().
|
||||
Foreground(ColorBarFg).
|
||||
Background(ColorBarBg).
|
||||
Padding(0, 1)
|
||||
|
||||
StatusKeyStyle = lipgloss.NewStyle().
|
||||
Foreground(ColorText).
|
||||
Background(ColorBarBg).
|
||||
Bold(true).
|
||||
Padding(0, 1)
|
||||
|
||||
StatusDividerStyle = lipgloss.NewStyle().
|
||||
Foreground(ColorSubtle).
|
||||
Background(ColorBarBg)
|
||||
|
||||
// Help drawer
|
||||
HelpTextStyle = lipgloss.NewStyle().
|
||||
Foreground(ColorSubtle).
|
||||
Padding(0, 1)
|
||||
|
||||
HelpDrawerStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), true, false, false, false).
|
||||
BorderForeground(ColorBorder).
|
||||
Padding(1, 2)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user