commit
dde4074b8b
|
|
@ -9,21 +9,28 @@ import (
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Colors struct {
|
Colors struct {
|
||||||
Border string `yaml:"border"`
|
Border string `yaml:"border"`
|
||||||
Focus string `yaml:"focus"`
|
Focus string `yaml:"focus"`
|
||||||
LineNumber string `yaml:"line_number"`
|
LineNumber string `yaml:"line_number"`
|
||||||
|
DiffSelectionBg string `yaml:"diff_selection_bg"` // New config
|
||||||
} `yaml:"colors"`
|
} `yaml:"colors"`
|
||||||
UI struct {
|
UI struct {
|
||||||
LineNumbers string `yaml:"line_numbers"` // "absolute", "relative", "hybrid", "hidden"
|
LineNumbers string `yaml:"line_numbers"`
|
||||||
ShowGuide bool `yaml:"show_guide"` // The vertical separation line
|
ShowGuide bool `yaml:"show_guide"`
|
||||||
} `yaml:"ui"`
|
} `yaml:"ui"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
var c Config
|
var c Config
|
||||||
c.Colors.Border = "#D9DCCF"
|
c.Colors.Border = "#D9DCCF"
|
||||||
c.Colors.Focus = "#000000" // Default neutral focus
|
c.Colors.Focus = "#6e7781"
|
||||||
c.Colors.LineNumber = "#808080"
|
c.Colors.LineNumber = "#808080"
|
||||||
|
|
||||||
|
// Default: "Neutral Light Transparent Blue"
|
||||||
|
// Dark Mode: Deep subtle blue-grey | Light Mode: Very faint blue
|
||||||
|
// We only set one default here, but AdaptiveColor handles the split in styles.go
|
||||||
|
c.Colors.DiffSelectionBg = "" // Empty means use internal defaults
|
||||||
|
|
||||||
c.UI.LineNumbers = "hybrid"
|
c.UI.LineNumbers = "hybrid"
|
||||||
c.UI.ShowGuide = true
|
c.UI.ShowGuide = true
|
||||||
return c
|
return c
|
||||||
|
|
@ -31,7 +38,6 @@ func DefaultConfig() Config {
|
||||||
|
|
||||||
func Load() Config {
|
func Load() Config {
|
||||||
cfg := DefaultConfig()
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg
|
return cfg
|
||||||
|
|
@ -43,7 +49,6 @@ func Load() Config {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse YAML
|
|
||||||
_ = yaml.Unmarshal(data, &cfg)
|
_ = yaml.Unmarshal(data, &cfg)
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ func (i TreeItem) Title() string {
|
||||||
return fmt.Sprintf("%s%s %s", indent, icon, i.Path)
|
return fmt.Sprintf("%s%s %s", indent, icon, i.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build converts a list of file paths into a compacted, sorted tree list.
|
// Build converts a list of file paths into a sorted tree list.
|
||||||
|
// Compaction is disabled to ensure tree stability.
|
||||||
func Build(paths []string) []list.Item {
|
func Build(paths []string) []list.Item {
|
||||||
// FIX 1: Initialize root as a directory so logic works,
|
// Initialize root
|
||||||
// but we won't compact the root itself.
|
|
||||||
root := &node{
|
root := &node{
|
||||||
children: make(map[string]*node),
|
children: make(map[string]*node),
|
||||||
isDir: true,
|
isDir: true,
|
||||||
|
|
@ -53,13 +53,7 @@ func Build(paths []string) []list.Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIX 2: Do NOT compact the root node itself (which would hide top-level folders).
|
// 2. Flatten to list items (Sorting happens here)
|
||||||
// Instead, compact each top-level child individually.
|
|
||||||
for _, child := range root.children {
|
|
||||||
compact(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Flatten to list items
|
|
||||||
var items []list.Item
|
var items []list.Item
|
||||||
flatten(root, 0, &items)
|
flatten(root, 0, &items)
|
||||||
return items
|
return items
|
||||||
|
|
@ -74,41 +68,14 @@ type node struct {
|
||||||
isDir bool
|
isDir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// compact recursively merges directories that contain only a single directory child.
|
// flatten recursively converts the tree into a linear list, sorting children by type and name.
|
||||||
func compact(n *node) {
|
|
||||||
if !n.isDir {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact children first (bottom-up traversal)
|
|
||||||
for _, child := range n.children {
|
|
||||||
compact(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logic: If I am a directory, and I have exactly 1 child, and that child is also a directory...
|
|
||||||
if len(n.children) == 1 {
|
|
||||||
var child *node
|
|
||||||
for _, c := range n.children {
|
|
||||||
child = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if child.isDir {
|
|
||||||
// Merge child into parent
|
|
||||||
// e.g. "internal" + "ui" becomes "internal/ui"
|
|
||||||
n.name = filepath.Join(n.name, child.name)
|
|
||||||
n.fullPath = child.fullPath
|
|
||||||
n.children = child.children // Inherit grandchildren
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func flatten(n *node, depth int, items *[]list.Item) {
|
func flatten(n *node, depth int, items *[]list.Item) {
|
||||||
keys := make([]string, 0, len(n.children))
|
keys := make([]string, 0, len(n.children))
|
||||||
for k := range n.children {
|
for k := range n.children {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort: Directories first, then alphabetical
|
||||||
sort.Slice(keys, func(i, j int) bool {
|
sort.Slice(keys, func(i, j int) bool {
|
||||||
a, b := n.children[keys[i]], n.children[keys[j]]
|
a, b := n.children[keys[i]], n.children[keys[j]]
|
||||||
// Folders first
|
// Folders first
|
||||||
|
|
@ -123,6 +90,7 @@ func flatten(n *node, depth int, items *[]list.Item) {
|
||||||
|
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
child := n.children[k]
|
child := n.children[k]
|
||||||
|
// Add current node
|
||||||
*items = append(*items, TreeItem{
|
*items = append(*items, TreeItem{
|
||||||
Path: child.name,
|
Path: child.name,
|
||||||
FullPath: child.fullPath,
|
FullPath: child.fullPath,
|
||||||
|
|
@ -130,6 +98,7 @@ func flatten(n *node, depth int, items *[]list.Item) {
|
||||||
Depth: depth,
|
Depth: depth,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Recurse if directory
|
||||||
if child.isDir {
|
if child.isDir {
|
||||||
flatten(child, depth+1, items)
|
flatten(child, depth+1, items)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
113
internal/ui/delegate.go
Normal file
113
internal/ui/delegate.go
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/list"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/oug-t/difi/internal/tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TreeDelegate struct {
|
||||||
|
Focused bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d TreeDelegate) Height() int { return 1 }
|
||||||
|
func (d TreeDelegate) Spacing() int { return 0 }
|
||||||
|
func (d TreeDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||||
|
|
||||||
|
func (d TreeDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
||||||
|
i, ok := item.(tree.TreeItem)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Setup Indentation
|
||||||
|
indentSize := i.Depth * 2
|
||||||
|
indent := strings.Repeat(" ", indentSize)
|
||||||
|
|
||||||
|
// 2. Get Icon and Raw Name
|
||||||
|
iconStr, iconStyle := getIconInfo(i.Path, i.IsDir)
|
||||||
|
|
||||||
|
// 3. Truncation (Safety)
|
||||||
|
availableWidth := m.Width() - indentSize - 4
|
||||||
|
displayName := i.Path
|
||||||
|
if availableWidth > 0 && len(displayName) > availableWidth {
|
||||||
|
displayName = displayName[:max(0, availableWidth-1)] + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Render Logic ("Oil" Block Cursor)
|
||||||
|
var row string
|
||||||
|
isSelected := index == m.Index()
|
||||||
|
|
||||||
|
if isSelected && d.Focused {
|
||||||
|
// -- SELECTED STATE (Oil Style) --
|
||||||
|
// We do NOT use iconStyle here. We want the icon to inherit the
|
||||||
|
// selection text color so the background block is unbroken.
|
||||||
|
// Content: Icon + Space + Name
|
||||||
|
content := fmt.Sprintf("%s %s", iconStr, displayName)
|
||||||
|
|
||||||
|
// Apply the solid block style to the whole content
|
||||||
|
renderedContent := SelectedBlockStyle.Render(content)
|
||||||
|
|
||||||
|
// Combine: Indent (unhighlighted) + Block (highlighted)
|
||||||
|
row = fmt.Sprintf("%s%s", indent, renderedContent)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// -- NORMAL / INACTIVE STATE --
|
||||||
|
// Render icon with its specific color
|
||||||
|
renderedIcon := iconStyle.Render(iconStr)
|
||||||
|
|
||||||
|
// Combine
|
||||||
|
row = fmt.Sprintf("%s%s %s", indent, renderedIcon, displayName)
|
||||||
|
|
||||||
|
// Apply generic padding/style
|
||||||
|
row = ItemStyle.Render(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Returns raw icon string and its preferred style
|
||||||
|
func getIconInfo(name string, isDir bool) (string, lipgloss.Style) {
|
||||||
|
if isDir {
|
||||||
|
return "", FolderIconStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := filepath.Ext(name)
|
||||||
|
icon := ""
|
||||||
|
|
||||||
|
switch strings.ToLower(ext) {
|
||||||
|
case ".go":
|
||||||
|
icon = ""
|
||||||
|
case ".js", ".ts", ".tsx", ".jsx":
|
||||||
|
icon = ""
|
||||||
|
case ".md":
|
||||||
|
icon = ""
|
||||||
|
case ".json", ".yml", ".yaml", ".toml":
|
||||||
|
icon = ""
|
||||||
|
case ".css", ".scss":
|
||||||
|
icon = ""
|
||||||
|
case ".html":
|
||||||
|
icon = ""
|
||||||
|
case ".git":
|
||||||
|
icon = ""
|
||||||
|
case ".dockerfile":
|
||||||
|
icon = ""
|
||||||
|
case ".svelte":
|
||||||
|
icon = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon, FileIconStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -28,6 +28,7 @@ const (
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
fileTree list.Model
|
fileTree list.Model
|
||||||
|
treeDelegate TreeDelegate
|
||||||
diffViewport viewport.Model
|
diffViewport viewport.Model
|
||||||
|
|
||||||
selectedPath string
|
selectedPath string
|
||||||
|
|
@ -47,21 +48,24 @@ type Model struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModel(cfg config.Config) Model {
|
func NewModel(cfg config.Config) Model {
|
||||||
// Initialize styles with the loaded config
|
|
||||||
InitStyles(cfg)
|
InitStyles(cfg)
|
||||||
|
|
||||||
files, _ := git.ListChangedFiles(TargetBranch)
|
files, _ := git.ListChangedFiles(TargetBranch)
|
||||||
items := tree.Build(files)
|
items := tree.Build(files)
|
||||||
|
|
||||||
l := list.New(items, listDelegate{}, 0, 0)
|
delegate := TreeDelegate{Focused: true}
|
||||||
|
l := list.New(items, delegate, 0, 0)
|
||||||
|
|
||||||
l.SetShowTitle(false)
|
l.SetShowTitle(false)
|
||||||
l.SetShowHelp(false)
|
l.SetShowHelp(false)
|
||||||
l.SetShowStatusBar(false)
|
l.SetShowStatusBar(false)
|
||||||
l.SetFilteringEnabled(false)
|
l.SetFilteringEnabled(false)
|
||||||
|
l.SetShowPagination(false)
|
||||||
l.DisableQuitKeybindings()
|
l.DisableQuitKeybindings()
|
||||||
|
|
||||||
m := Model{
|
m := Model{
|
||||||
fileTree: l,
|
fileTree: l,
|
||||||
|
treeDelegate: delegate,
|
||||||
diffViewport: viewport.New(0, 0),
|
diffViewport: viewport.New(0, 0),
|
||||||
focus: FocusTree,
|
focus: FocusTree,
|
||||||
currentBranch: git.GetCurrentBranch(),
|
currentBranch: git.GetCurrentBranch(),
|
||||||
|
|
@ -132,14 +136,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
} else {
|
} else {
|
||||||
m.focus = FocusTree
|
m.focus = FocusTree
|
||||||
}
|
}
|
||||||
|
m.updateTreeFocus()
|
||||||
m.inputBuffer = ""
|
m.inputBuffer = ""
|
||||||
|
|
||||||
case "l", "]", "ctrl+l", "right":
|
case "l", "]", "ctrl+l", "right":
|
||||||
m.focus = FocusDiff
|
m.focus = FocusDiff
|
||||||
|
m.updateTreeFocus()
|
||||||
m.inputBuffer = ""
|
m.inputBuffer = ""
|
||||||
|
|
||||||
case "h", "[", "ctrl+h", "left":
|
case "h", "[", "ctrl+h", "left":
|
||||||
m.focus = FocusTree
|
m.focus = FocusTree
|
||||||
|
m.updateTreeFocus()
|
||||||
m.inputBuffer = ""
|
m.inputBuffer = ""
|
||||||
|
|
||||||
case "e", "enter":
|
case "e", "enter":
|
||||||
|
|
@ -243,6 +250,11 @@ func (m *Model) updateSizes() {
|
||||||
m.diffViewport.Height = contentHeight
|
m.diffViewport.Height = contentHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) updateTreeFocus() {
|
||||||
|
m.treeDelegate.Focused = (m.focus == FocusTree)
|
||||||
|
m.fileTree.SetDelegate(m.treeDelegate)
|
||||||
|
}
|
||||||
|
|
||||||
func (m Model) View() string {
|
func (m Model) View() string {
|
||||||
if m.width == 0 {
|
if m.width == 0 {
|
||||||
return "Loading..."
|
return "Loading..."
|
||||||
|
|
@ -272,29 +284,22 @@ func (m Model) View() string {
|
||||||
for i := start; i < end; i++ {
|
for i := start; i < end; i++ {
|
||||||
line := m.diffLines[i]
|
line := m.diffLines[i]
|
||||||
|
|
||||||
// --- LINE NUMBER LOGIC ---
|
// --- LINE NUMBERS ---
|
||||||
var numStr string
|
var numStr string
|
||||||
mode := CurrentConfig.UI.LineNumbers
|
mode := CurrentConfig.UI.LineNumbers
|
||||||
|
|
||||||
if mode == "hidden" {
|
if mode == "hidden" {
|
||||||
numStr = ""
|
numStr = ""
|
||||||
} else {
|
} else {
|
||||||
// Is this the cursor line?
|
|
||||||
isCursor := (i == m.diffCursor)
|
isCursor := (i == m.diffCursor)
|
||||||
|
|
||||||
if isCursor && mode == "hybrid" {
|
if isCursor && mode == "hybrid" {
|
||||||
// HYBRID: Show Real File Line Number
|
|
||||||
realLine := git.CalculateFileLine(m.diffContent, m.diffCursor)
|
realLine := git.CalculateFileLine(m.diffContent, m.diffCursor)
|
||||||
numStr = fmt.Sprintf("%d", realLine)
|
numStr = fmt.Sprintf("%d", realLine)
|
||||||
} else if isCursor && mode == "relative" {
|
} else if isCursor && mode == "relative" {
|
||||||
numStr = "0"
|
numStr = "0"
|
||||||
} else if mode == "absolute" {
|
} 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)
|
numStr = fmt.Sprintf("%d", i+1)
|
||||||
} else {
|
} else {
|
||||||
// Default / Hybrid-non-cursor: Show Relative Distance
|
|
||||||
dist := int(math.Abs(float64(i - m.diffCursor)))
|
dist := int(math.Abs(float64(i - m.diffCursor)))
|
||||||
numStr = fmt.Sprintf("%d", dist)
|
numStr = fmt.Sprintf("%d", dist)
|
||||||
}
|
}
|
||||||
|
|
@ -304,10 +309,11 @@ func (m Model) View() string {
|
||||||
if numStr != "" {
|
if numStr != "" {
|
||||||
lineNumRendered = LineNumberStyle.Render(numStr)
|
lineNumRendered = LineNumberStyle.Render(numStr)
|
||||||
}
|
}
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
|
// --- DIFF VIEW HIGHLIGHT ---
|
||||||
if m.focus == FocusDiff && i == m.diffCursor {
|
if m.focus == FocusDiff && i == m.diffCursor {
|
||||||
line = SelectedItemStyle.Render(line)
|
cleanLine := stripAnsi(line)
|
||||||
|
line = DiffSelectionStyle.Render(" " + cleanLine)
|
||||||
} else {
|
} else {
|
||||||
line = " " + line
|
line = " " + line
|
||||||
}
|
}
|
||||||
|
|
@ -381,21 +387,7 @@ func (m Model) View() string {
|
||||||
return finalView
|
return finalView
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Delegates (unchanged) --
|
func stripAnsi(str string) string {
|
||||||
type listDelegate struct{}
|
re := regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
|
||||||
|
return re.ReplaceAllString(str, "")
|
||||||
func (d listDelegate) Height() int { return 1 }
|
|
||||||
func (d listDelegate) Spacing() int { return 0 }
|
|
||||||
func (d listDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
|
||||||
func (d listDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
|
||||||
i, ok := item.(tree.TreeItem)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
str := i.Title()
|
|
||||||
if index == m.Index() {
|
|
||||||
fmt.Fprint(w, SelectedItemStyle.Render(str))
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(w, ItemStyle.Render(str))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,38 @@ import (
|
||||||
"github.com/oug-t/difi/internal/config"
|
"github.com/oug-t/difi/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Global UI colors and styles.
|
|
||||||
// Initialized once at startup via InitStyles.
|
|
||||||
var (
|
var (
|
||||||
ColorBorder lipgloss.AdaptiveColor
|
// -- Colors --
|
||||||
ColorFocus lipgloss.AdaptiveColor
|
ColorText = lipgloss.AdaptiveColor{Light: "#24292f", Dark: "#c9d1d9"}
|
||||||
ColorText = lipgloss.AdaptiveColor{Light: "#1F1F1F", Dark: "#F8F8F2"}
|
ColorSubtle = lipgloss.AdaptiveColor{Light: "#6e7781", Dark: "#8b949e"}
|
||||||
ColorSubtle = lipgloss.AdaptiveColor{Light: "#A8A8A8", Dark: "#626262"}
|
|
||||||
ColorCursorBg = lipgloss.AdaptiveColor{Light: "#E5E5E5", Dark: "#3E3E3E"}
|
// UNIFIED SELECTION COLOR (The "Neutral Light Transparent Blue")
|
||||||
|
// This is used for BOTH the file tree and the diff panel background.
|
||||||
|
// Dark: Deep subtle slate blue | Light: Pale selection blue
|
||||||
|
ColorVisualBg = lipgloss.AdaptiveColor{Light: "#daeaff", Dark: "#3a4b5c"}
|
||||||
|
|
||||||
|
// Tree Text Color (High Contrast for the block cursor)
|
||||||
|
ColorVisualFg = lipgloss.AdaptiveColor{Light: "#000000", Dark: "#ffffff"}
|
||||||
|
|
||||||
|
ColorFolder = lipgloss.AdaptiveColor{Light: "#0969da", Dark: "#83a598"}
|
||||||
|
ColorFile = lipgloss.AdaptiveColor{Light: "#24292f", Dark: "#ebdbb2"}
|
||||||
|
|
||||||
ColorBarBg = lipgloss.AdaptiveColor{Light: "#F2F2F2", Dark: "#1F1F1F"}
|
ColorBarBg = lipgloss.AdaptiveColor{Light: "#F2F2F2", Dark: "#1F1F1F"}
|
||||||
ColorBarFg = lipgloss.AdaptiveColor{Light: "#6E6E6E", Dark: "#9E9E9E"}
|
ColorBarFg = lipgloss.AdaptiveColor{Light: "#6E6E6E", Dark: "#9E9E9E"}
|
||||||
|
|
||||||
PaneStyle lipgloss.Style
|
// -- Styles --
|
||||||
FocusedPaneStyle lipgloss.Style
|
PaneStyle lipgloss.Style
|
||||||
DiffStyle lipgloss.Style
|
FocusedPaneStyle lipgloss.Style
|
||||||
|
DiffStyle lipgloss.Style
|
||||||
|
|
||||||
ItemStyle lipgloss.Style
|
ItemStyle lipgloss.Style
|
||||||
SelectedItemStyle lipgloss.Style
|
SelectedBlockStyle lipgloss.Style // Tree (Opaque)
|
||||||
LineNumberStyle lipgloss.Style
|
DiffSelectionStyle lipgloss.Style // Diff (Transparent/BG only)
|
||||||
|
|
||||||
|
FolderIconStyle lipgloss.Style
|
||||||
|
FileIconStyle lipgloss.Style
|
||||||
|
LineNumberStyle lipgloss.Style
|
||||||
|
|
||||||
StatusBarStyle lipgloss.Style
|
StatusBarStyle lipgloss.Style
|
||||||
StatusKeyStyle lipgloss.Style
|
StatusKeyStyle lipgloss.Style
|
||||||
StatusDividerStyle lipgloss.Style
|
StatusDividerStyle lipgloss.Style
|
||||||
|
|
@ -32,16 +46,20 @@ var (
|
||||||
CurrentConfig config.Config
|
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) {
|
func InitStyles(cfg config.Config) {
|
||||||
CurrentConfig = cfg
|
CurrentConfig = cfg
|
||||||
|
|
||||||
// Colors derived from user config
|
ColorBorder := lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: cfg.Colors.Border}
|
||||||
ColorBorder = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: cfg.Colors.Border}
|
ColorFocus := lipgloss.AdaptiveColor{Light: "#6e7781", Dark: cfg.Colors.Focus}
|
||||||
ColorFocus = lipgloss.AdaptiveColor{Light: "#000000", Dark: cfg.Colors.Focus}
|
|
||||||
|
// Allow user override for the selection background
|
||||||
|
var selectionBg lipgloss.TerminalColor
|
||||||
|
if cfg.Colors.DiffSelectionBg != "" {
|
||||||
|
selectionBg = lipgloss.Color(cfg.Colors.DiffSelectionBg)
|
||||||
|
} else {
|
||||||
|
selectionBg = ColorVisualBg
|
||||||
|
}
|
||||||
|
|
||||||
// Pane styles
|
|
||||||
PaneStyle = lipgloss.NewStyle().
|
PaneStyle = lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder(), false, cfg.UI.ShowGuide, false, false).
|
Border(lipgloss.NormalBorder(), false, cfg.UI.ShowGuide, false, false).
|
||||||
BorderForeground(ColorBorder)
|
BorderForeground(ColorBorder)
|
||||||
|
|
@ -49,44 +67,42 @@ func InitStyles(cfg config.Config) {
|
||||||
FocusedPaneStyle = PaneStyle.Copy().
|
FocusedPaneStyle = PaneStyle.Copy().
|
||||||
BorderForeground(ColorFocus)
|
BorderForeground(ColorFocus)
|
||||||
|
|
||||||
// Diff and list item styles
|
|
||||||
DiffStyle = lipgloss.NewStyle().Padding(0, 0)
|
DiffStyle = lipgloss.NewStyle().Padding(0, 0)
|
||||||
ItemStyle = lipgloss.NewStyle().PaddingLeft(2)
|
|
||||||
|
|
||||||
SelectedItemStyle = lipgloss.NewStyle().
|
// Base Row
|
||||||
|
ItemStyle = lipgloss.NewStyle().
|
||||||
PaddingLeft(1).
|
PaddingLeft(1).
|
||||||
Background(ColorCursorBg).
|
PaddingRight(1).
|
||||||
Foreground(ColorText).
|
Foreground(ColorText)
|
||||||
Bold(true).
|
|
||||||
Width(1000)
|
// 1. LEFT PANE STYLE (Tree)
|
||||||
|
// Uses the shared background + forces a foreground color for readability
|
||||||
|
SelectedBlockStyle = lipgloss.NewStyle().
|
||||||
|
Background(selectionBg).
|
||||||
|
Foreground(ColorVisualFg).
|
||||||
|
PaddingLeft(1).
|
||||||
|
PaddingRight(1).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
// 2. RIGHT PANE STYLE (Diff)
|
||||||
|
// Uses the SAME shared background, but NO foreground.
|
||||||
|
// This makes it "transparent" so Green(+)/Red(-) text colors show through.
|
||||||
|
DiffSelectionStyle = lipgloss.NewStyle().
|
||||||
|
Background(selectionBg)
|
||||||
|
|
||||||
|
FolderIconStyle = lipgloss.NewStyle().Foreground(ColorFolder)
|
||||||
|
FileIconStyle = lipgloss.NewStyle().Foreground(ColorFile)
|
||||||
|
|
||||||
// Line numbers
|
|
||||||
LineNumberStyle = lipgloss.NewStyle().
|
LineNumberStyle = lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color(cfg.Colors.LineNumber)).
|
Foreground(lipgloss.Color(cfg.Colors.LineNumber)).
|
||||||
PaddingRight(1).
|
PaddingRight(1).
|
||||||
Width(4)
|
Width(4)
|
||||||
|
|
||||||
// Status bar
|
StatusBarStyle = lipgloss.NewStyle().Foreground(ColorBarFg).Background(ColorBarBg).Padding(0, 1)
|
||||||
StatusBarStyle = lipgloss.NewStyle().
|
StatusKeyStyle = lipgloss.NewStyle().Foreground(ColorText).Background(ColorBarBg).Bold(true).Padding(0, 1)
|
||||||
Foreground(ColorBarFg).
|
StatusDividerStyle = lipgloss.NewStyle().Foreground(ColorSubtle).Background(ColorBarBg).Padding(0, 0)
|
||||||
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)
|
|
||||||
|
|
||||||
|
HelpTextStyle = lipgloss.NewStyle().Foreground(ColorSubtle).Padding(0, 1)
|
||||||
HelpDrawerStyle = lipgloss.NewStyle().
|
HelpDrawerStyle = lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder(), true, false, false, false).
|
Border(lipgloss.NormalBorder(), true, false, false, false).
|
||||||
BorderForeground(ColorBorder).
|
BorderForeground(ColorBorder).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user