Skip to content
Merged
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.24
toolchain go1.24.5

require (
github.com/BurntSushi/toml v1.5.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/fsnotify/fsnotify v1.9.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
Expand Down
56 changes: 56 additions & 0 deletions internal/tui/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package tui

import (
"fmt"
"os"
"path/filepath"
)

var (
ConfigDirName = ".config/gitx"
ConfigFileName = "config.toml"
ConfigDirPath string
ConfigFilePath string
ConfigThemesDirPath string
)

func initializeConfig() error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("error getting user home directory: %w", err)
}

ConfigDirPath = filepath.Join(homeDir, ConfigDirName)
ConfigFilePath = filepath.Join(ConfigDirPath, ConfigFileName)
ConfigThemesDirPath = filepath.Join(ConfigDirPath, "themes")

err = os.MkdirAll(ConfigDirPath, 0755)
if err != nil {
return fmt.Errorf("error creating config directory: %w", err)
}

err = os.MkdirAll(ConfigThemesDirPath, 0755)
if err != nil {
return fmt.Errorf("error creating themes directory: %w", err)
}

if _, err := os.Stat(ConfigFilePath); err != nil {
if os.IsNotExist(err) {
defaultConfig := fmt.Sprintf("Theme = %q\n", DefaultThemeName)
if writeErr := os.WriteFile(ConfigFilePath, []byte(defaultConfig), 0644); writeErr != nil {
return fmt.Errorf("failed to create default config file: %w", writeErr)
}
} else {
return fmt.Errorf("failed to check config file: %w", err)
}
}

return nil
}

func init() {
if err := initializeConfig(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize config: %v\n", err)
os.Exit(1)
}
}
32 changes: 29 additions & 3 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,24 @@ type Model struct {

// initialModel creates the initial state of the application.
func initialModel() Model {
themeNames := ThemeNames()
themeNames := ThemeNames() //built-in themes load
cfg, _ := load_config()

var selectedThemeName string
if t, ok := Themes[cfg.Theme]; ok{
selectedThemeName = cfg.Theme
_ = t // to avoid unused variable warning
} else{
if _, err := load_custom_theme(cfg.Theme); err == nil{
selectedThemeName = cfg.Theme
} else{
//fallback
selectedThemeName = themeNames[0]
}
}

themeNames = ThemeNames() // reload

gc := git.NewGitCommands()
repoName, branchName, _ := gc.GetRepoInfo()
initialContent := initialContentLoading
Expand All @@ -77,9 +94,9 @@ func initialModel() Model {
ta.SetHeight(5)

return Model{
theme: Themes[themeNames[0]],
theme: Themes[selectedThemeName],
themeNames: themeNames,
themeIndex: 0,
themeIndex: indexOf(themeNames, selectedThemeName),
focusedPanel: StatusPanel,
activeSourcePanel: StatusPanel,
help: help.New(),
Expand All @@ -95,6 +112,15 @@ func initialModel() Model {
}
}

func indexOf(arr []string, val string) int{
for i, s := range arr{
if s == val{
return i
}
}
return 0
}

// Init is the first command that is run when the program starts.
func (m Model) Init() tea.Cmd {
// fetch initial content for all panels.
Expand Down
68 changes: 68 additions & 0 deletions internal/tui/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ import (
"sort"

"github.com/charmbracelet/lipgloss"

"os"

"path/filepath"

"github.com/BurntSushi/toml"

"fmt"
)

// DefaultThemeName is the name of the default theme.
const DefaultThemeName = "GitHub Dark"

// Palette defines a set of colors for a theme.
type Palette struct {
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White,
Expand Down Expand Up @@ -139,6 +150,20 @@ type TreeStyle struct {
Connector, ConnectorLast, Prefix, PrefixLast string
}

//config.toml
type themeConfig struct{
Theme string `toml:"theme"`
}

// custom_theme.toml
type ThemeFile struct{
Fg string `toml:"fg"`
Bg string `toml:"bg"`
Normal map[string]string `toml:"normal"`
Bright map[string]string `toml:"bright"`
Dark map[string]string `toml:"dark"`
}

// Themes holds all the available themes, generated from palettes.
var Themes = map[string]Theme{}

Expand Down Expand Up @@ -216,3 +241,46 @@ func ThemeNames() []string {
sort.Strings(names)
return names
}

func load_config() (*themeConfig, error){
cfgPath := ConfigFilePath

var cfg themeConfig
if _, err := toml.DecodeFile(cfgPath, &cfg); err != nil {
return nil, err
}

return &cfg, nil
}

func load_custom_theme(name string) (*Palette, error){
themePath := filepath.Join(ConfigThemesDirPath, name + ".toml")
if _,err := os.Stat(themePath); os.IsNotExist(err) {
return nil, fmt.Errorf("theme not found: %s", name)
}

var tf ThemeFile
if _, err := toml.DecodeFile(themePath, &tf); err != nil {
return nil, err
}

// Create a Palette from the ThemeFile
p := Palette{
Fg: tf.Fg,
Bg: tf.Bg,
Black: tf.Normal["Black"], Red: tf.Normal["Red"], Green: tf.Normal["Green"], Yellow: tf.Normal["Yellow"],
Blue: tf.Normal["Blue"], Magenta: tf.Normal["Magenta"], Cyan: tf.Normal["Cyan"], White: tf.Normal["White"],

BrightBlack: tf.Bright["Black"], BrightRed: tf.Bright["Red"], BrightGreen: tf.Bright["Green"], BrightYellow: tf.Bright["Yellow"],
BrightBlue: tf.Bright["Blue"], BrightMagenta: tf.Bright["Magenta"], BrightCyan: tf.Bright["Cyan"], BrightWhite: tf.Bright["White"],

DarkBlack: tf.Dark["Black"], DarkRed: tf.Dark["Red"], DarkGreen: tf.Dark["Green"], DarkYellow: tf.Dark["Yellow"],
DarkBlue: tf.Dark["Blue"], DarkMagenta: tf.Dark["Magenta"], DarkCyan: tf.Dark["Cyan"], DarkWhite: tf.Dark["White"],

}

Palettes[name] = p // Add to Palettes map for future use
Themes[name] = NewThemeFromPalette(p) // Add to Themes map

return &p, nil
}