Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
Comment thread
bakayu marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
)

require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
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
61 changes: 61 additions & 0 deletions internal/tui/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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)
}

_, err = os.Stat(ConfigFilePath)
if err == nil {
// File exists
// fmt.Println("Config file exists:", ConfigFilePath)
Comment thread
bakayu marked this conversation as resolved.
Outdated
} else if os.IsNotExist(err) {
// File does not exist
defaultConfigContent := fmt.Sprintf("Theme = \"%s\"\n", DefaultThemeName)
err = os.WriteFile(ConfigFilePath, []byte(defaultConfigContent), 0644)
if err != nil {
return fmt.Errorf("error creating default config file: %w", err)
}
} else {
// Some other error
return fmt.Errorf("error checking 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)
}
}
34 changes: 31 additions & 3 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,26 @@ 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

// fmt.Println("All themes:", themeNames)
Comment thread
bakayu marked this conversation as resolved.
Outdated

gc := git.NewGitCommands()
repoName, branchName, _ := gc.GetRepoInfo()
initialContent := initialContentLoading
Expand All @@ -77,9 +96,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 +114,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
}
Comment on lines +245 to +286

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the scope of this PR does not include adding proper functionality for the user provided config file, that is .config/gitx/config.toml. Would you be interested in refactoring this into a separate file? I'll help in the effort by pushing some changes in your branch if you are okay with that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, can you please clarify what you mean by "proper functionality" for the config file.
And yeah, I’m cool with refactoring and happy to have your help on my branch.

@bakayu bakayu Oct 5, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not clarifying it properly.
By "proper functionality" I mean basic settings any cli or tui app may have that can be configured via the config file.
For example the Theme setting you have implemented that can be found in the users's .config folder, in the config.toml file.
To have a proper chat about this you can join the discord server linked in the readme file and ping mere there in the appropriate channel, my discord handle is @bakayu.
This is a big effort feature request and I appreciate your help in this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah got it, thanks for clarifying, I'll ping you on discord so we can discuss and refactor together.