Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ You can enable or disable this feature in your configuration file:
}
```

### Theme Configuration

OpenCode supports various themes with an optional transparent background mode. The transparent background feature removes all background colors from the interface, making it perfect for terminal transparency or minimal setups.

To enable transparent background mode, add the following to your configuration file:

```json
{
"tui": {
"theme": "opencode",
"transparentBackground": true
}
}
```
### Environment Variables

You can configure OpenCode using environment variables:
Expand Down Expand Up @@ -173,6 +187,10 @@ This is useful if you want to use a different shell than your default system she
"maxTokens": 80
}
},
"tui": {
"theme": "opencode",
"transparentBackground": false
},
"shell": {
"path": "/bin/bash",
"args": ["-l"]
Expand Down
19 changes: 18 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ type LSPConfig struct {

// TUIConfig defines the configuration for the Terminal User Interface.
type TUIConfig struct {
Theme string `json:"theme,omitempty"`
Theme string `json:"theme,omitempty"`
TransparentBackground bool `json:"transparentBackground,omitempty"`
}

// ShellConfig defines the configuration for the shell used by the bash tool.
Expand Down Expand Up @@ -231,6 +232,7 @@ func setDefaults(debug bool) {
viper.SetDefault("data.directory", defaultDataDirectory)
viper.SetDefault("contextPaths", defaultContextPaths)
viper.SetDefault("tui.theme", "opencode")
viper.SetDefault("tui.transparentBackground", false)
viper.SetDefault("autoCompact", true)

// Set default shell from environment or fallback to /bin/bash
Expand Down Expand Up @@ -929,6 +931,21 @@ func UpdateTheme(themeName string) error {
})
}

// UpdateTransparentBackground updates the transparent background setting in the configuration.
func UpdateTransparentBackground(enabled bool) error {
if cfg == nil {
return fmt.Errorf("config not loaded")
}

// Update the in-memory config
cfg.TUI.TransparentBackground = enabled

// Update the file config
return updateCfgFile(func(config *Config) {
config.TUI.TransparentBackground = enabled
})
}

// Tries to load Github token from all possible locations
func LoadGitHubToken() (string, error) {
// First check environment variable
Expand Down
160 changes: 109 additions & 51 deletions internal/tui/components/core/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,18 @@ func getHelpWidget() string {
t := theme.CurrentTheme()
helpText := "ctrl+? help"

return styles.Padded().
Background(t.TextMuted()).
Foreground(t.BackgroundDarker()).
Bold(true).
Render(helpText)
helpStyle := styles.Padded().Bold(true)

// In transparency mode, remove background
if theme.IsTransparentBackground() {
helpStyle = helpStyle.Foreground(t.TextMuted())
} else {
helpStyle = helpStyle.
Background(t.TextMuted()).
Foreground(t.BackgroundDarker())
}

return helpStyle.Render(helpText)
}

func formatTokensAndCost(tokens, contextWindow int64, cost float64) string {
Expand Down Expand Up @@ -128,35 +135,63 @@ func (m statusCmp) View() string {
if m.session.ID != "" {
totalTokens := m.session.PromptTokens + m.session.CompletionTokens
tokens := formatTokensAndCost(totalTokens, model.ContextWindow, m.session.Cost)
tokensStyle := styles.Padded().
Background(t.Text()).
Foreground(t.BackgroundSecondary())
tokensStyle := styles.Padded()

// In transparency mode, remove background
if theme.IsTransparentBackground() {
tokensStyle = tokensStyle.Foreground(t.Text())
} else {
tokensStyle = tokensStyle.
Background(t.Text()).
Foreground(t.BackgroundSecondary())
}

percentage := (float64(totalTokens) / float64(model.ContextWindow)) * 100
if percentage > 80 {
// Even in transparency mode, show warning background for high token usage
tokensStyle = tokensStyle.Background(t.Warning())
}
tokenInfoWidth = lipgloss.Width(tokens) + 2
status += tokensStyle.Render(tokens)
}

diagnostics := styles.Padded().
Background(t.BackgroundDarker()).
Render(m.projectDiagnostics())
diagnosticsStyle := styles.Padded()

// In transparency mode, remove background
if theme.IsTransparentBackground() {
diagnosticsStyle = diagnosticsStyle.Foreground(t.Text())
} else {
diagnosticsStyle = diagnosticsStyle.Background(t.BackgroundDarker())
}

diagnostics := diagnosticsStyle.Render(m.projectDiagnostics())

availableWidht := max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(m.model())-lipgloss.Width(diagnostics)-tokenInfoWidth)

if m.info.Msg != "" {
infoStyle := styles.Padded().
Foreground(t.Background()).
Width(availableWidht)

switch m.info.Type {
case util.InfoTypeInfo:
infoStyle = infoStyle.Background(t.Info())
case util.InfoTypeWarn:
infoStyle = infoStyle.Background(t.Warning())
case util.InfoTypeError:
infoStyle = infoStyle.Background(t.Error())
infoStyle := styles.Padded().Width(availableWidht)

if theme.IsTransparentBackground() {
// In transparent mode, use colored foreground on transparent background
switch m.info.Type {
case util.InfoTypeInfo:
infoStyle = infoStyle.Foreground(t.Info())
case util.InfoTypeWarn:
infoStyle = infoStyle.Foreground(t.Warning())
case util.InfoTypeError:
infoStyle = infoStyle.Foreground(t.Error())
}
} else {
// In non-transparent mode, use colored background with contrasting text
infoStyle = infoStyle.Foreground(t.Background())
switch m.info.Type {
case util.InfoTypeInfo:
infoStyle = infoStyle.Background(t.Info())
case util.InfoTypeWarn:
infoStyle = infoStyle.Background(t.Warning())
case util.InfoTypeError:
infoStyle = infoStyle.Background(t.Error())
}
}

infoWidth := availableWidht - 10
Expand All @@ -167,11 +202,18 @@ func (m statusCmp) View() string {
}
status += infoStyle.Render(msg)
} else {
status += styles.Padded().
Foreground(t.Text()).
Background(t.BackgroundSecondary()).
Width(availableWidht).
Render("")
emptyStyle := styles.Padded().Width(availableWidht)

// In transparency mode, remove background
if theme.IsTransparentBackground() {
emptyStyle = emptyStyle.Foreground(t.Text())
} else {
emptyStyle = emptyStyle.
Foreground(t.Text()).
Background(t.BackgroundSecondary())
}

status += emptyStyle.Render("")
}

status += diagnostics
Expand All @@ -193,10 +235,14 @@ func (m *statusCmp) projectDiagnostics() string {

// If any server is initializing, show that status
if initializing {
return lipgloss.NewStyle().
Background(t.BackgroundDarker()).
Foreground(t.Warning()).
Render(fmt.Sprintf("%s Initializing LSP...", styles.SpinnerIcon))
initStyle := lipgloss.NewStyle().Foreground(t.Warning())

// In transparency mode, remove background
if !theme.IsTransparentBackground() {
initStyle = initStyle.Background(t.BackgroundDarker())
}

return initStyle.Render(fmt.Sprintf("%s Initializing LSP...", styles.SpinnerIcon))
}

errorDiagnostics := []protocol.Diagnostic{}
Expand Down Expand Up @@ -227,31 +273,35 @@ func (m *statusCmp) projectDiagnostics() string {
diagnostics := []string{}

if len(errorDiagnostics) > 0 {
errStr := lipgloss.NewStyle().
Background(t.BackgroundDarker()).
Foreground(t.Error()).
Render(fmt.Sprintf("%s %d", styles.ErrorIcon, len(errorDiagnostics)))
errStyle := lipgloss.NewStyle().Foreground(t.Error())
if !theme.IsTransparentBackground() {
errStyle = errStyle.Background(t.BackgroundDarker())
}
errStr := errStyle.Render(fmt.Sprintf("%s %d", styles.ErrorIcon, len(errorDiagnostics)))
diagnostics = append(diagnostics, errStr)
}
if len(warnDiagnostics) > 0 {
warnStr := lipgloss.NewStyle().
Background(t.BackgroundDarker()).
Foreground(t.Warning()).
Render(fmt.Sprintf("%s %d", styles.WarningIcon, len(warnDiagnostics)))
warnStyle := lipgloss.NewStyle().Foreground(t.Warning())
if !theme.IsTransparentBackground() {
warnStyle = warnStyle.Background(t.BackgroundDarker())
}
warnStr := warnStyle.Render(fmt.Sprintf("%s %d", styles.WarningIcon, len(warnDiagnostics)))
diagnostics = append(diagnostics, warnStr)
}
if len(hintDiagnostics) > 0 {
hintStr := lipgloss.NewStyle().
Background(t.BackgroundDarker()).
Foreground(t.Text()).
Render(fmt.Sprintf("%s %d", styles.HintIcon, len(hintDiagnostics)))
hintStyle := lipgloss.NewStyle().Foreground(t.Text())
if !theme.IsTransparentBackground() {
hintStyle = hintStyle.Background(t.BackgroundDarker())
}
hintStr := hintStyle.Render(fmt.Sprintf("%s %d", styles.HintIcon, len(hintDiagnostics)))
diagnostics = append(diagnostics, hintStr)
}
if len(infoDiagnostics) > 0 {
infoStr := lipgloss.NewStyle().
Background(t.BackgroundDarker()).
Foreground(t.Info()).
Render(fmt.Sprintf("%s %d", styles.InfoIcon, len(infoDiagnostics)))
infoStyle := lipgloss.NewStyle().Foreground(t.Info())
if !theme.IsTransparentBackground() {
infoStyle = infoStyle.Background(t.BackgroundDarker())
}
infoStr := infoStyle.Render(fmt.Sprintf("%s %d", styles.InfoIcon, len(infoDiagnostics)))
diagnostics = append(diagnostics, infoStr)
}

Expand All @@ -277,10 +327,18 @@ func (m statusCmp) model() string {
}
model := models.SupportedModels[coder.Model]

return styles.Padded().
Background(t.Secondary()).
Foreground(t.Background()).
Render(model.Name)
modelStyle := styles.Padded()

// In transparency mode, remove background
if theme.IsTransparentBackground() {
modelStyle = modelStyle.Foreground(t.Secondary())
} else {
modelStyle = modelStyle.
Background(t.Secondary()).
Foreground(t.Background())
}

return modelStyle.Render(model.Name)
}

func NewStatusCmp(lspClients map[string]*lsp.Client) StatusCmp {
Expand Down
24 changes: 17 additions & 7 deletions internal/tui/components/dialog/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,23 @@ func (ci Command) Render(selected bool, width int) string {
Background(t.Background())

if selected {
itemStyle = itemStyle.
Background(t.Primary()).
Foreground(t.Background()).
Bold(true)
descStyle = descStyle.
Background(t.Primary()).
Foreground(t.Background())
if theme.IsTransparentBackground() {
itemStyle = itemStyle.
Background(t.Background()).
Foreground(t.Primary()).
Bold(true)
descStyle = descStyle.
Background(t.Background()).
Foreground(t.Primary())
} else {
itemStyle = itemStyle.
Background(t.Primary()).
Foreground(t.Background()).
Bold(true)
descStyle = descStyle.
Background(t.Primary()).
Foreground(t.Background())
}
}

title := itemStyle.Padding(0, 1).Render(ci.Title)
Expand Down
15 changes: 11 additions & 4 deletions internal/tui/components/dialog/complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,17 @@ func (ci *CompletionItem) Render(selected bool, width int) string {
Padding(0, 1)

if selected {
itemStyle = itemStyle.
Background(t.Background()).
Foreground(t.Primary()).
Bold(true)
if theme.IsTransparentBackground() {
itemStyle = itemStyle.
Background(t.Background()).
Foreground(t.Primary()).
Bold(true)
} else {
itemStyle = itemStyle.
Background(t.Primary()).
Foreground(t.Background()).
Bold(true)
}
}

title := itemStyle.Render(
Expand Down
15 changes: 11 additions & 4 deletions internal/tui/components/dialog/filepicker.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,17 @@ func (f *filepickerCmp) View() string {
itemStyle := styles.BaseStyle().Width(adjustedWidth)

if i == f.cursor {
itemStyle = itemStyle.
Background(t.Primary()).
Foreground(t.Background()).
Bold(true)
if theme.IsTransparentBackground() {
itemStyle = itemStyle.
Background(t.Background()).
Foreground(t.Primary()).
Bold(true)
} else {
itemStyle = itemStyle.
Background(t.Primary()).
Foreground(t.Background()).
Bold(true)
}
}
filename := file.Name()

Expand Down
13 changes: 11 additions & 2 deletions internal/tui/components/dialog/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,17 @@ func (m *modelDialogCmp) View() string {
for i := m.scrollOffset; i < endIdx; i++ {
itemStyle := baseStyle.Width(maxDialogWidth)
if i == m.selectedIdx {
itemStyle = itemStyle.Background(t.Primary()).
Foreground(t.Background()).Bold(true)
if theme.IsTransparentBackground() {
itemStyle = itemStyle.
Background(t.Background()).
Foreground(t.Primary()).
Bold(true)
} else {
itemStyle = itemStyle.
Background(t.Primary()).
Foreground(t.Background()).
Bold(true)
}
}
modelItems = append(modelItems, itemStyle.Render(m.models[i].Name))
}
Expand Down
Loading