mirror of
https://github.com/sst/opencode.git
synced 2025-09-03 11:47:45 +00:00
feat: default system theme (#419)
Co-authored-by: adamdottv <2363879+adamdottv@users.noreply.github.com>
This commit is contained in:
parent
31b56e5a05
commit
2e26b58d16
33 changed files with 1214 additions and 429 deletions
|
@ -66,6 +66,7 @@ func main() {
|
||||||
|
|
||||||
program := tea.NewProgram(
|
program := tea.NewProgram(
|
||||||
tui.NewModel(app_),
|
tui.NewModel(app_),
|
||||||
|
// tea.WithColorProfile(colorprofile.ANSI),
|
||||||
tea.WithAltScreen(),
|
tea.WithAltScreen(),
|
||||||
tea.WithKeyboardEnhancements(),
|
tea.WithKeyboardEnhancements(),
|
||||||
tea.WithMouseCellMotion(),
|
tea.WithMouseCellMotion(),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/sst/opencode/internal/commands"
|
"github.com/sst/opencode/internal/commands"
|
||||||
"github.com/sst/opencode/internal/components/toast"
|
"github.com/sst/opencode/internal/components/toast"
|
||||||
"github.com/sst/opencode/internal/config"
|
"github.com/sst/opencode/internal/config"
|
||||||
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
"github.com/sst/opencode/internal/util"
|
"github.com/sst/opencode/internal/util"
|
||||||
"github.com/sst/opencode/pkg/client"
|
"github.com/sst/opencode/pkg/client"
|
||||||
|
@ -103,6 +104,12 @@ func New(
|
||||||
}
|
}
|
||||||
|
|
||||||
if appState.Theme != "" {
|
if appState.Theme != "" {
|
||||||
|
if appState.Theme == "system" && styles.Terminal != nil {
|
||||||
|
theme.UpdateSystemTheme(
|
||||||
|
styles.Terminal.Background,
|
||||||
|
styles.Terminal.BackgroundIsDark,
|
||||||
|
)
|
||||||
|
}
|
||||||
theme.SetTheme(appState.Theme)
|
theme.SetTheme(appState.Theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/sst/opencode/internal/app"
|
"github.com/sst/opencode/internal/app"
|
||||||
"github.com/sst/opencode/internal/commands"
|
"github.com/sst/opencode/internal/commands"
|
||||||
"github.com/sst/opencode/internal/components/dialog"
|
"github.com/sst/opencode/internal/components/dialog"
|
||||||
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ func (c *CommandCompletionProvider) GetEmptyMessage() string {
|
||||||
|
|
||||||
func getCommandCompletionItem(cmd commands.Command, space int, t theme.Theme) dialog.CompletionItemI {
|
func getCommandCompletionItem(cmd commands.Command, space int, t theme.Theme) dialog.CompletionItemI {
|
||||||
spacer := strings.Repeat(" ", space)
|
spacer := strings.Repeat(" ", space)
|
||||||
title := " /" + cmd.Trigger + lipgloss.NewStyle().Foreground(t.TextMuted()).Render(spacer+cmd.Description)
|
title := " /" + cmd.Trigger + styles.NewStyle().Foreground(t.TextMuted()).Render(spacer+cmd.Description)
|
||||||
value := string(cmd.Name)
|
value := string(cmd.Name)
|
||||||
return dialog.NewCompletionItem(dialog.CompletionItem{
|
return dialog.NewCompletionItem(dialog.CompletionItem{
|
||||||
Title: title,
|
Title: title,
|
||||||
|
|
|
@ -26,6 +26,9 @@ type EditorComponent interface {
|
||||||
Content() string
|
Content() string
|
||||||
Lines() int
|
Lines() int
|
||||||
Value() string
|
Value() string
|
||||||
|
Focused() bool
|
||||||
|
Focus() (tea.Model, tea.Cmd)
|
||||||
|
Blur()
|
||||||
Submit() (tea.Model, tea.Cmd)
|
Submit() (tea.Model, tea.Cmd)
|
||||||
Clear() (tea.Model, tea.Cmd)
|
Clear() (tea.Model, tea.Cmd)
|
||||||
Paste() (tea.Model, tea.Cmd)
|
Paste() (tea.Model, tea.Cmd)
|
||||||
|
@ -48,7 +51,7 @@ type editorComponent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorComponent) Init() tea.Cmd {
|
func (m *editorComponent) Init() tea.Cmd {
|
||||||
return tea.Batch(textarea.Blink, m.spinner.Tick, tea.EnableReportFocus)
|
return tea.Batch(m.textarea.Focus(), m.spinner.Tick, tea.EnableReportFocus)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
@ -69,7 +72,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
case dialog.ThemeSelectedMsg:
|
case dialog.ThemeSelectedMsg:
|
||||||
m.textarea = createTextArea(&m.textarea)
|
m.textarea = createTextArea(&m.textarea)
|
||||||
m.spinner = createSpinner()
|
m.spinner = createSpinner()
|
||||||
return m, tea.Batch(m.spinner.Tick, textarea.Blink)
|
return m, tea.Batch(m.spinner.Tick, m.textarea.Focus())
|
||||||
case dialog.CompletionSelectedMsg:
|
case dialog.CompletionSelectedMsg:
|
||||||
if msg.IsCommand {
|
if msg.IsCommand {
|
||||||
commandName := strings.TrimPrefix(msg.CompletionValue, "/")
|
commandName := strings.TrimPrefix(msg.CompletionValue, "/")
|
||||||
|
@ -104,12 +107,11 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
func (m *editorComponent) Content() string {
|
func (m *editorComponent) Content() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
base := styles.BaseStyle().Background(t.Background()).Render
|
base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render
|
||||||
muted := styles.Muted().Background(t.Background()).Render
|
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
||||||
promptStyle := lipgloss.NewStyle().
|
promptStyle := styles.NewStyle().Foreground(t.Primary()).
|
||||||
Padding(0, 0, 0, 1).
|
Padding(0, 0, 0, 1).
|
||||||
Bold(true).
|
Bold(true)
|
||||||
Foreground(t.Primary())
|
|
||||||
prompt := promptStyle.Render(">")
|
prompt := promptStyle.Render(">")
|
||||||
|
|
||||||
textarea := lipgloss.JoinHorizontal(
|
textarea := lipgloss.JoinHorizontal(
|
||||||
|
@ -117,11 +119,11 @@ func (m *editorComponent) Content() string {
|
||||||
prompt,
|
prompt,
|
||||||
m.textarea.View(),
|
m.textarea.View(),
|
||||||
)
|
)
|
||||||
textarea = styles.BaseStyle().
|
textarea = styles.NewStyle().
|
||||||
|
Background(t.BackgroundElement()).
|
||||||
Width(m.width).
|
Width(m.width).
|
||||||
PaddingTop(1).
|
PaddingTop(1).
|
||||||
PaddingBottom(1).
|
PaddingBottom(1).
|
||||||
Background(t.BackgroundElement()).
|
|
||||||
Render(textarea)
|
Render(textarea)
|
||||||
|
|
||||||
hint := base(m.getSubmitKeyText()) + muted(" send ")
|
hint := base(m.getSubmitKeyText()) + muted(" send ")
|
||||||
|
@ -140,10 +142,10 @@ func (m *editorComponent) Content() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
space := m.width - 2 - lipgloss.Width(model) - lipgloss.Width(hint)
|
space := m.width - 2 - lipgloss.Width(model) - lipgloss.Width(hint)
|
||||||
spacer := lipgloss.NewStyle().Background(t.Background()).Width(space).Render("")
|
spacer := styles.NewStyle().Background(t.Background()).Width(space).Render("")
|
||||||
|
|
||||||
info := hint + spacer + model
|
info := hint + spacer + model
|
||||||
info = styles.Padded().Background(t.Background()).Render(info)
|
info = styles.NewStyle().Background(t.Background()).Padding(0, 1).Render(info)
|
||||||
|
|
||||||
content := strings.Join([]string{"", textarea, info}, "\n")
|
content := strings.Join([]string{"", textarea, info}, "\n")
|
||||||
return content
|
return content
|
||||||
|
@ -156,6 +158,18 @@ func (m *editorComponent) View() string {
|
||||||
return m.Content()
|
return m.Content()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *editorComponent) Focused() bool {
|
||||||
|
return m.textarea.Focused()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *editorComponent) Focus() (tea.Model, tea.Cmd) {
|
||||||
|
return m, m.textarea.Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *editorComponent) Blur() {
|
||||||
|
m.textarea.Blur()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *editorComponent) GetSize() (width, height int) {
|
func (m *editorComponent) GetSize() (width, height int) {
|
||||||
return m.width, m.height
|
return m.width, m.height
|
||||||
}
|
}
|
||||||
|
@ -297,14 +311,14 @@ func createTextArea(existing *textarea.Model) textarea.Model {
|
||||||
|
|
||||||
ta := textarea.New()
|
ta := textarea.New()
|
||||||
|
|
||||||
ta.Styles.Blurred.Base = lipgloss.NewStyle().Background(bgColor).Foreground(textColor)
|
ta.Styles.Blurred.Base = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Blurred.CursorLine = lipgloss.NewStyle().Background(bgColor)
|
ta.Styles.Blurred.CursorLine = styles.NewStyle().Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Blurred.Placeholder = lipgloss.NewStyle().Background(bgColor).Foreground(textMutedColor)
|
ta.Styles.Blurred.Placeholder = styles.NewStyle().Foreground(textMutedColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Blurred.Text = lipgloss.NewStyle().Background(bgColor).Foreground(textColor)
|
ta.Styles.Blurred.Text = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.Base = lipgloss.NewStyle().Background(bgColor).Foreground(textColor)
|
ta.Styles.Focused.Base = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.CursorLine = lipgloss.NewStyle().Background(bgColor)
|
ta.Styles.Focused.CursorLine = styles.NewStyle().Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.Placeholder = lipgloss.NewStyle().Background(bgColor).Foreground(textMutedColor)
|
ta.Styles.Focused.Placeholder = styles.NewStyle().Foreground(textMutedColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.Text = lipgloss.NewStyle().Background(bgColor).Foreground(textColor)
|
ta.Styles.Focused.Text = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Cursor.Color = t.Primary()
|
ta.Styles.Cursor.Color = t.Primary()
|
||||||
|
|
||||||
ta.Prompt = " "
|
ta.Prompt = " "
|
||||||
|
@ -317,18 +331,21 @@ func createTextArea(existing *textarea.Model) textarea.Model {
|
||||||
ta.SetHeight(existing.Height())
|
ta.SetHeight(existing.Height())
|
||||||
}
|
}
|
||||||
|
|
||||||
ta.Focus()
|
// ta.Focus()
|
||||||
return ta
|
return ta
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSpinner() spinner.Model {
|
func createSpinner() spinner.Model {
|
||||||
|
t := theme.CurrentTheme()
|
||||||
return spinner.New(
|
return spinner.New(
|
||||||
spinner.WithSpinner(spinner.Ellipsis),
|
spinner.WithSpinner(spinner.Ellipsis),
|
||||||
spinner.WithStyle(
|
spinner.WithStyle(
|
||||||
styles.
|
styles.NewStyle().
|
||||||
Muted().
|
Foreground(t.Background()).
|
||||||
Background(theme.CurrentTheme().Background()).
|
Foreground(t.TextMuted()).
|
||||||
Width(3)),
|
Width(3).
|
||||||
|
Lipgloss(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,15 +129,13 @@ func renderContentBlock(content string, options ...renderingOption) string {
|
||||||
option(renderer)
|
option(renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
style := styles.BaseStyle().
|
style := styles.NewStyle().Foreground(t.TextMuted()).Background(t.BackgroundPanel()).
|
||||||
// MarginTop(renderer.marginTop).
|
// MarginTop(renderer.marginTop).
|
||||||
// MarginBottom(renderer.marginBottom).
|
// MarginBottom(renderer.marginBottom).
|
||||||
PaddingTop(renderer.paddingTop).
|
PaddingTop(renderer.paddingTop).
|
||||||
PaddingBottom(renderer.paddingBottom).
|
PaddingBottom(renderer.paddingBottom).
|
||||||
PaddingLeft(renderer.paddingLeft).
|
PaddingLeft(renderer.paddingLeft).
|
||||||
PaddingRight(renderer.paddingRight).
|
PaddingRight(renderer.paddingRight).
|
||||||
Background(t.BackgroundPanel()).
|
|
||||||
Foreground(t.TextMuted()).
|
|
||||||
BorderStyle(lipgloss.ThickBorder())
|
BorderStyle(lipgloss.ThickBorder())
|
||||||
|
|
||||||
align := lipgloss.Left
|
align := lipgloss.Left
|
||||||
|
@ -179,13 +177,13 @@ func renderContentBlock(content string, options ...renderingOption) string {
|
||||||
layout.Current.Container.Width,
|
layout.Current.Container.Width,
|
||||||
align,
|
align,
|
||||||
content,
|
content,
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
content = lipgloss.PlaceHorizontal(
|
content = lipgloss.PlaceHorizontal(
|
||||||
layout.Current.Viewport.Width,
|
layout.Current.Viewport.Width,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
content,
|
content,
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
if renderer.marginTop > 0 {
|
if renderer.marginTop > 0 {
|
||||||
for range renderer.marginTop {
|
for range renderer.marginTop {
|
||||||
|
@ -226,7 +224,7 @@ func renderText(message client.MessageInfo, text string, author string) string {
|
||||||
textWidth := max(lipgloss.Width(text), lipgloss.Width(info))
|
textWidth := max(lipgloss.Width(text), lipgloss.Width(info))
|
||||||
markdownWidth := min(textWidth, width-padding-4) // -4 for the border and padding
|
markdownWidth := min(textWidth, width-padding-4) // -4 for the border and padding
|
||||||
if message.Role == client.Assistant {
|
if message.Role == client.Assistant {
|
||||||
markdownWidth = width - padding - 4 - 2
|
markdownWidth = width - padding - 4 - 3
|
||||||
}
|
}
|
||||||
if message.Role == client.User {
|
if message.Role == client.User {
|
||||||
text = strings.ReplaceAll(text, "<", "\\<")
|
text = strings.ReplaceAll(text, "<", "\\<")
|
||||||
|
@ -275,9 +273,10 @@ func renderToolInvocation(
|
||||||
}
|
}
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
style := styles.Muted().
|
style := styles.NewStyle().
|
||||||
Width(outerWidth).
|
Foreground(t.TextMuted()).
|
||||||
Background(t.BackgroundPanel()).
|
Background(t.BackgroundPanel()).
|
||||||
|
Width(outerWidth).
|
||||||
PaddingTop(paddingTop).
|
PaddingTop(paddingTop).
|
||||||
PaddingBottom(paddingBottom).
|
PaddingBottom(paddingBottom).
|
||||||
PaddingLeft(2).
|
PaddingLeft(2).
|
||||||
|
@ -293,7 +292,9 @@ func renderToolInvocation(
|
||||||
if !showDetails {
|
if !showDetails {
|
||||||
title = "∟ " + title
|
title = "∟ " + title
|
||||||
padding := calculatePadding()
|
padding := calculatePadding()
|
||||||
style := lipgloss.NewStyle().Width(outerWidth - padding - 4).Background(t.BackgroundPanel())
|
style := styles.NewStyle().
|
||||||
|
Background(t.BackgroundPanel()).
|
||||||
|
Width(outerWidth - padding - 4 - 3)
|
||||||
return renderContentBlock(style.Render(title),
|
return renderContentBlock(style.Render(title),
|
||||||
WithAlign(lipgloss.Left),
|
WithAlign(lipgloss.Left),
|
||||||
WithBorderColor(t.Accent()),
|
WithBorderColor(t.Accent()),
|
||||||
|
@ -334,9 +335,9 @@ func renderToolInvocation(
|
||||||
if e, ok := metadata.Get("error"); ok && e.(bool) == true {
|
if e, ok := metadata.Get("error"); ok && e.(bool) == true {
|
||||||
if m, ok := metadata.Get("message"); ok {
|
if m, ok := metadata.Get("message"); ok {
|
||||||
style = style.BorderLeftForeground(t.Error())
|
style = style.BorderLeftForeground(t.Error())
|
||||||
error = styles.BaseStyle().
|
error = styles.NewStyle().
|
||||||
Background(t.BackgroundPanel()).
|
|
||||||
Foreground(t.Error()).
|
Foreground(t.Error()).
|
||||||
|
Background(t.BackgroundPanel()).
|
||||||
Render(m.(string))
|
Render(m.(string))
|
||||||
error = renderContentBlock(
|
error = renderContentBlock(
|
||||||
error,
|
error,
|
||||||
|
@ -374,7 +375,7 @@ func renderToolInvocation(
|
||||||
formattedDiff, _ = diff.FormatDiff(filename, patch, diff.WithTotalWidth(diffWidth))
|
formattedDiff, _ = diff.FormatDiff(filename, patch, diff.WithTotalWidth(diffWidth))
|
||||||
}
|
}
|
||||||
formattedDiff = strings.TrimSpace(formattedDiff)
|
formattedDiff = strings.TrimSpace(formattedDiff)
|
||||||
formattedDiff = lipgloss.NewStyle().
|
formattedDiff = styles.NewStyle().
|
||||||
BorderStyle(lipgloss.ThickBorder()).
|
BorderStyle(lipgloss.ThickBorder()).
|
||||||
BorderBackground(t.Background()).
|
BorderBackground(t.Background()).
|
||||||
BorderForeground(t.BackgroundPanel()).
|
BorderForeground(t.BackgroundPanel()).
|
||||||
|
@ -394,7 +395,7 @@ func renderToolInvocation(
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
lipgloss.Top,
|
lipgloss.Top,
|
||||||
body,
|
body,
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,7 +507,7 @@ func renderToolInvocation(
|
||||||
if !showDetails {
|
if !showDetails {
|
||||||
title = "∟ " + title
|
title = "∟ " + title
|
||||||
padding := calculatePadding()
|
padding := calculatePadding()
|
||||||
style := lipgloss.NewStyle().Width(outerWidth - padding - 4).Background(t.BackgroundPanel())
|
style := styles.NewStyle().Background(t.BackgroundPanel()).Width(outerWidth - padding - 4 - 3)
|
||||||
paddingBottom := 0
|
paddingBottom := 0
|
||||||
if isLast {
|
if isLast {
|
||||||
paddingBottom = 1
|
paddingBottom = 1
|
||||||
|
@ -530,7 +531,7 @@ func renderToolInvocation(
|
||||||
layout.Current.Viewport.Width,
|
layout.Current.Viewport.Width,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
content,
|
content,
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
if showDetails && body != "" && error == "" {
|
if showDetails && body != "" && error == "" {
|
||||||
content += "\n" + body
|
content += "\n" + body
|
||||||
|
|
|
@ -245,7 +245,7 @@ func (m *messagesComponent) renderView() {
|
||||||
m.width,
|
m.width,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
block,
|
block,
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,8 +260,8 @@ func (m *messagesComponent) header() string {
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
width := layout.Current.Container.Width
|
width := layout.Current.Container.Width
|
||||||
base := styles.BaseStyle().Background(t.Background()).Render
|
base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render
|
||||||
muted := styles.Muted().Background(t.Background()).Render
|
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
||||||
headerLines := []string{}
|
headerLines := []string{}
|
||||||
headerLines = append(headerLines, toMarkdown("# "+m.app.Session.Title, width-6, t.Background()))
|
headerLines = append(headerLines, toMarkdown("# "+m.app.Session.Title, width-6, t.Background()))
|
||||||
if m.app.Session.Share != nil && m.app.Session.Share.Url != "" {
|
if m.app.Session.Share != nil && m.app.Session.Share.Url != "" {
|
||||||
|
@ -271,11 +271,11 @@ func (m *messagesComponent) header() string {
|
||||||
}
|
}
|
||||||
header := strings.Join(headerLines, "\n")
|
header := strings.Join(headerLines, "\n")
|
||||||
|
|
||||||
header = styles.BaseStyle().
|
header = styles.NewStyle().
|
||||||
|
Background(t.Background()).
|
||||||
Width(width).
|
Width(width).
|
||||||
PaddingLeft(2).
|
PaddingLeft(2).
|
||||||
PaddingRight(2).
|
PaddingRight(2).
|
||||||
Background(t.Background()).
|
|
||||||
BorderLeft(true).
|
BorderLeft(true).
|
||||||
BorderRight(true).
|
BorderRight(true).
|
||||||
BorderBackground(t.Background()).
|
BorderBackground(t.Background()).
|
||||||
|
@ -306,7 +306,7 @@ func (m *messagesComponent) View() string {
|
||||||
m.width,
|
m.width,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
m.header(),
|
m.header(),
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
),
|
),
|
||||||
m.viewport.View(),
|
m.viewport.View(),
|
||||||
)
|
)
|
||||||
|
@ -314,9 +314,9 @@ func (m *messagesComponent) View() string {
|
||||||
|
|
||||||
func (m *messagesComponent) home() string {
|
func (m *messagesComponent) home() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle().Background(t.Background())
|
baseStyle := styles.NewStyle().Background(t.Background())
|
||||||
base := baseStyle.Render
|
base := baseStyle.Render
|
||||||
muted := styles.Muted().Background(t.Background()).Render
|
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
|
||||||
|
|
||||||
open := `
|
open := `
|
||||||
█▀▀█ █▀▀█ █▀▀ █▀▀▄
|
█▀▀█ █▀▀█ █▀▀ █▀▀▄
|
||||||
|
@ -335,9 +335,9 @@ func (m *messagesComponent) home() string {
|
||||||
// cwd := app.Info.Path.Cwd
|
// cwd := app.Info.Path.Cwd
|
||||||
// config := app.Info.Path.Config
|
// config := app.Info.Path.Config
|
||||||
|
|
||||||
versionStyle := lipgloss.NewStyle().
|
versionStyle := styles.NewStyle().
|
||||||
Background(t.Background()).
|
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
|
Background(t.Background()).
|
||||||
Width(lipgloss.Width(logo)).
|
Width(lipgloss.Width(logo)).
|
||||||
Align(lipgloss.Right)
|
Align(lipgloss.Right)
|
||||||
version := versionStyle.Render(m.app.Version)
|
version := versionStyle.Render(m.app.Version)
|
||||||
|
@ -347,14 +347,14 @@ func (m *messagesComponent) home() string {
|
||||||
m.width,
|
m.width,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
logoAndVersion,
|
logoAndVersion,
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
m.commands.SetBackgroundColor(t.Background())
|
m.commands.SetBackgroundColor(t.Background())
|
||||||
commands := lipgloss.PlaceHorizontal(
|
commands := lipgloss.PlaceHorizontal(
|
||||||
m.width,
|
m.width,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
m.commands.View(),
|
m.commands.View(),
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
|
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
|
@ -372,7 +372,7 @@ func (m *messagesComponent) home() string {
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
baseStyle.Render(strings.Join(lines, "\n")),
|
baseStyle.Render(strings.Join(lines, "\n")),
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
styles.WhitespaceStyle(t.Background()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,15 +60,9 @@ func (c *commandsComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
func (c *commandsComponent) View() string {
|
func (c *commandsComponent) View() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
triggerStyle := lipgloss.NewStyle().
|
triggerStyle := styles.NewStyle().Foreground(t.Primary()).Bold(true)
|
||||||
Foreground(t.Primary()).
|
descriptionStyle := styles.NewStyle().Foreground(t.Text())
|
||||||
Bold(true)
|
keybindStyle := styles.NewStyle().Foreground(t.TextMuted())
|
||||||
|
|
||||||
descriptionStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(t.Text())
|
|
||||||
|
|
||||||
keybindStyle := lipgloss.NewStyle().
|
|
||||||
Foreground(t.TextMuted())
|
|
||||||
|
|
||||||
if c.background != nil {
|
if c.background != nil {
|
||||||
triggerStyle = triggerStyle.Background(*c.background)
|
triggerStyle = triggerStyle.Background(*c.background)
|
||||||
|
@ -99,10 +93,11 @@ func (c *commandsComponent) View() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(commandsToShow) == 0 {
|
if len(commandsToShow) == 0 {
|
||||||
|
muted := styles.NewStyle().Foreground(theme.CurrentTheme().TextMuted())
|
||||||
if c.showAll {
|
if c.showAll {
|
||||||
return styles.Muted().Render("No commands available")
|
return muted.Render("No commands available")
|
||||||
}
|
}
|
||||||
return styles.Muted().Render("No commands with triggers available")
|
return muted.Render("No commands with triggers available")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate column widths
|
// Calculate column widths
|
||||||
|
@ -188,7 +183,7 @@ func (c *commandsComponent) View() string {
|
||||||
// Remove trailing newline
|
// Remove trailing newline
|
||||||
result := strings.TrimSuffix(output.String(), "\n")
|
result := strings.TrimSuffix(output.String(), "\n")
|
||||||
if c.background != nil {
|
if c.background != nil {
|
||||||
result = lipgloss.NewStyle().Background(c.background).Width(maxWidth).Render(result)
|
result = styles.NewStyle().Background(*c.background).Width(maxWidth).Render(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -26,7 +26,7 @@ type CompletionItemI interface {
|
||||||
|
|
||||||
func (ci *CompletionItem) Render(selected bool, width int) string {
|
func (ci *CompletionItem) Render(selected bool, width int) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.NewStyle().Foreground(t.Text())
|
||||||
|
|
||||||
itemStyle := baseStyle.
|
itemStyle := baseStyle.
|
||||||
Background(t.BackgroundElement()).
|
Background(t.BackgroundElement()).
|
||||||
|
@ -185,7 +185,7 @@ func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
func (c *completionDialogComponent) View() string {
|
func (c *completionDialogComponent) View() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.NewStyle().Foreground(t.Text())
|
||||||
|
|
||||||
maxWidth := 40
|
maxWidth := 40
|
||||||
completions := c.list.GetItems()
|
completions := c.list.GetItems()
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (m InitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
// View implements tea.Model.
|
// View implements tea.Model.
|
||||||
func (m InitDialogCmp) View() string {
|
func (m InitDialogCmp) View() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.NewStyle().Foreground(t.Text())
|
||||||
|
|
||||||
// Calculate width needed for content
|
// Calculate width needed for content
|
||||||
maxWidth := 60 // Width for explanation text
|
maxWidth := 60 // Width for explanation text
|
||||||
|
|
|
@ -158,7 +158,7 @@ func (m *modelDialog) getScrollIndicators(maxWidth int) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
return styles.BaseStyle().
|
return styles.NewStyle().
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
Width(maxWidth).
|
Width(maxWidth).
|
||||||
Align(lipgloss.Right).
|
Align(lipgloss.Right).
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (p *permissionDialogComponent) selectCurrentOption() tea.Cmd {
|
||||||
|
|
||||||
func (p *permissionDialogComponent) renderButtons() string {
|
func (p *permissionDialogComponent) renderButtons() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.NewStyle().Foreground(t.Text())
|
||||||
|
|
||||||
allowStyle := baseStyle
|
allowStyle := baseStyle
|
||||||
allowSessionStyle := baseStyle
|
allowSessionStyle := baseStyle
|
||||||
|
@ -355,8 +355,7 @@ func (p *permissionDialogComponent) renderDefaultContent() string {
|
||||||
|
|
||||||
func (p *permissionDialogComponent) styleViewport() string {
|
func (p *permissionDialogComponent) styleViewport() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
contentStyle := lipgloss.NewStyle().
|
contentStyle := styles.NewStyle().Background(t.Background())
|
||||||
Background(t.Background())
|
|
||||||
|
|
||||||
return contentStyle.Render(p.contentViewPort.View())
|
return contentStyle.Render(p.contentViewPort.View())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
|
||||||
"github.com/muesli/reflow/truncate"
|
"github.com/muesli/reflow/truncate"
|
||||||
"github.com/sst/opencode/internal/app"
|
"github.com/sst/opencode/internal/app"
|
||||||
"github.com/sst/opencode/internal/components/list"
|
"github.com/sst/opencode/internal/components/list"
|
||||||
|
@ -33,7 +32,7 @@ type sessionItem struct {
|
||||||
|
|
||||||
func (s sessionItem) Render(selected bool, width int) string {
|
func (s sessionItem) Render(selected bool, width int) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.NewStyle()
|
||||||
|
|
||||||
var text string
|
var text string
|
||||||
if s.isDeleteConfirming {
|
if s.isDeleteConfirming {
|
||||||
|
@ -44,7 +43,7 @@ func (s sessionItem) Render(selected bool, width int) string {
|
||||||
|
|
||||||
truncatedStr := truncate.StringWithTail(text, uint(width-1), "...")
|
truncatedStr := truncate.StringWithTail(text, uint(width-1), "...")
|
||||||
|
|
||||||
var itemStyle lipgloss.Style
|
var itemStyle styles.Style
|
||||||
if selected {
|
if selected {
|
||||||
if s.isDeleteConfirming {
|
if s.isDeleteConfirming {
|
||||||
// Red background for delete confirmation
|
// Red background for delete confirmation
|
||||||
|
@ -151,9 +150,9 @@ func (s *sessionDialog) Render(background string) string {
|
||||||
listView := s.list.View()
|
listView := s.list.View()
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
helpStyle := styles.BaseStyle().PaddingLeft(1).PaddingTop(1)
|
helpStyle := styles.NewStyle().PaddingLeft(1).PaddingTop(1)
|
||||||
helpText := styles.BaseStyle().Foreground(t.Text()).Render("x/del")
|
helpText := styles.NewStyle().Foreground(t.Text()).Render("x/del")
|
||||||
helpText = helpText + styles.BaseStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Render(" delete session")
|
helpText = helpText + styles.NewStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Render(" delete session")
|
||||||
helpText = helpStyle.Render(helpText)
|
helpText = helpStyle.Render(helpText)
|
||||||
|
|
||||||
content := strings.Join([]string{listView, helpText}, "\n")
|
content := strings.Join([]string{listView, helpText}, "\n")
|
||||||
|
|
|
@ -103,7 +103,7 @@ func NewThemeDialog() ThemeDialog {
|
||||||
|
|
||||||
// Set the initial selection to the current theme
|
// Set the initial selection to the current theme
|
||||||
list.SetSelectedIndex(selectedIdx)
|
list.SetSelectedIndex(selectedIdx)
|
||||||
|
|
||||||
// Set the max width for the list to match the modal width
|
// Set the max width for the list to match the modal width
|
||||||
list.SetMaxWidth(36) // 40 (modal max width) - 4 (modal padding)
|
list.SetMaxWidth(36) // 40 (modal max width) - 4 (modal padding)
|
||||||
|
|
||||||
|
|
|
@ -441,84 +441,84 @@ func SyntaxHighlight(w io.Writer, source, fileName, formatter string, bg color.C
|
||||||
<entry type="TextWhitespace" style="%s"/>
|
<entry type="TextWhitespace" style="%s"/>
|
||||||
</style>
|
</style>
|
||||||
`,
|
`,
|
||||||
getColor(t.BackgroundPanel()), // Background
|
getChromaColor(t.BackgroundPanel()), // Background
|
||||||
getColor(t.Text()), // Text
|
getChromaColor(t.Text()), // Text
|
||||||
getColor(t.Text()), // Other
|
getChromaColor(t.Text()), // Other
|
||||||
getColor(t.Error()), // Error
|
getChromaColor(t.Error()), // Error
|
||||||
|
|
||||||
getColor(t.SyntaxKeyword()), // Keyword
|
getChromaColor(t.SyntaxKeyword()), // Keyword
|
||||||
getColor(t.SyntaxKeyword()), // KeywordConstant
|
getChromaColor(t.SyntaxKeyword()), // KeywordConstant
|
||||||
getColor(t.SyntaxKeyword()), // KeywordDeclaration
|
getChromaColor(t.SyntaxKeyword()), // KeywordDeclaration
|
||||||
getColor(t.SyntaxKeyword()), // KeywordNamespace
|
getChromaColor(t.SyntaxKeyword()), // KeywordNamespace
|
||||||
getColor(t.SyntaxKeyword()), // KeywordPseudo
|
getChromaColor(t.SyntaxKeyword()), // KeywordPseudo
|
||||||
getColor(t.SyntaxKeyword()), // KeywordReserved
|
getChromaColor(t.SyntaxKeyword()), // KeywordReserved
|
||||||
getColor(t.SyntaxType()), // KeywordType
|
getChromaColor(t.SyntaxType()), // KeywordType
|
||||||
|
|
||||||
getColor(t.Text()), // Name
|
getChromaColor(t.Text()), // Name
|
||||||
getColor(t.SyntaxVariable()), // NameAttribute
|
getChromaColor(t.SyntaxVariable()), // NameAttribute
|
||||||
getColor(t.SyntaxType()), // NameBuiltin
|
getChromaColor(t.SyntaxType()), // NameBuiltin
|
||||||
getColor(t.SyntaxVariable()), // NameBuiltinPseudo
|
getChromaColor(t.SyntaxVariable()), // NameBuiltinPseudo
|
||||||
getColor(t.SyntaxType()), // NameClass
|
getChromaColor(t.SyntaxType()), // NameClass
|
||||||
getColor(t.SyntaxVariable()), // NameConstant
|
getChromaColor(t.SyntaxVariable()), // NameConstant
|
||||||
getColor(t.SyntaxFunction()), // NameDecorator
|
getChromaColor(t.SyntaxFunction()), // NameDecorator
|
||||||
getColor(t.SyntaxVariable()), // NameEntity
|
getChromaColor(t.SyntaxVariable()), // NameEntity
|
||||||
getColor(t.SyntaxType()), // NameException
|
getChromaColor(t.SyntaxType()), // NameException
|
||||||
getColor(t.SyntaxFunction()), // NameFunction
|
getChromaColor(t.SyntaxFunction()), // NameFunction
|
||||||
getColor(t.Text()), // NameLabel
|
getChromaColor(t.Text()), // NameLabel
|
||||||
getColor(t.SyntaxType()), // NameNamespace
|
getChromaColor(t.SyntaxType()), // NameNamespace
|
||||||
getColor(t.SyntaxVariable()), // NameOther
|
getChromaColor(t.SyntaxVariable()), // NameOther
|
||||||
getColor(t.SyntaxKeyword()), // NameTag
|
getChromaColor(t.SyntaxKeyword()), // NameTag
|
||||||
getColor(t.SyntaxVariable()), // NameVariable
|
getChromaColor(t.SyntaxVariable()), // NameVariable
|
||||||
getColor(t.SyntaxVariable()), // NameVariableClass
|
getChromaColor(t.SyntaxVariable()), // NameVariableClass
|
||||||
getColor(t.SyntaxVariable()), // NameVariableGlobal
|
getChromaColor(t.SyntaxVariable()), // NameVariableGlobal
|
||||||
getColor(t.SyntaxVariable()), // NameVariableInstance
|
getChromaColor(t.SyntaxVariable()), // NameVariableInstance
|
||||||
|
|
||||||
getColor(t.SyntaxString()), // Literal
|
getChromaColor(t.SyntaxString()), // Literal
|
||||||
getColor(t.SyntaxString()), // LiteralDate
|
getChromaColor(t.SyntaxString()), // LiteralDate
|
||||||
getColor(t.SyntaxString()), // LiteralString
|
getChromaColor(t.SyntaxString()), // LiteralString
|
||||||
getColor(t.SyntaxString()), // LiteralStringBacktick
|
getChromaColor(t.SyntaxString()), // LiteralStringBacktick
|
||||||
getColor(t.SyntaxString()), // LiteralStringChar
|
getChromaColor(t.SyntaxString()), // LiteralStringChar
|
||||||
getColor(t.SyntaxString()), // LiteralStringDoc
|
getChromaColor(t.SyntaxString()), // LiteralStringDoc
|
||||||
getColor(t.SyntaxString()), // LiteralStringDouble
|
getChromaColor(t.SyntaxString()), // LiteralStringDouble
|
||||||
getColor(t.SyntaxString()), // LiteralStringEscape
|
getChromaColor(t.SyntaxString()), // LiteralStringEscape
|
||||||
getColor(t.SyntaxString()), // LiteralStringHeredoc
|
getChromaColor(t.SyntaxString()), // LiteralStringHeredoc
|
||||||
getColor(t.SyntaxString()), // LiteralStringInterpol
|
getChromaColor(t.SyntaxString()), // LiteralStringInterpol
|
||||||
getColor(t.SyntaxString()), // LiteralStringOther
|
getChromaColor(t.SyntaxString()), // LiteralStringOther
|
||||||
getColor(t.SyntaxString()), // LiteralStringRegex
|
getChromaColor(t.SyntaxString()), // LiteralStringRegex
|
||||||
getColor(t.SyntaxString()), // LiteralStringSingle
|
getChromaColor(t.SyntaxString()), // LiteralStringSingle
|
||||||
getColor(t.SyntaxString()), // LiteralStringSymbol
|
getChromaColor(t.SyntaxString()), // LiteralStringSymbol
|
||||||
|
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumber
|
getChromaColor(t.SyntaxNumber()), // LiteralNumber
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumberBin
|
getChromaColor(t.SyntaxNumber()), // LiteralNumberBin
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumberFloat
|
getChromaColor(t.SyntaxNumber()), // LiteralNumberFloat
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumberHex
|
getChromaColor(t.SyntaxNumber()), // LiteralNumberHex
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumberInteger
|
getChromaColor(t.SyntaxNumber()), // LiteralNumberInteger
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumberIntegerLong
|
getChromaColor(t.SyntaxNumber()), // LiteralNumberIntegerLong
|
||||||
getColor(t.SyntaxNumber()), // LiteralNumberOct
|
getChromaColor(t.SyntaxNumber()), // LiteralNumberOct
|
||||||
|
|
||||||
getColor(t.SyntaxOperator()), // Operator
|
getChromaColor(t.SyntaxOperator()), // Operator
|
||||||
getColor(t.SyntaxKeyword()), // OperatorWord
|
getChromaColor(t.SyntaxKeyword()), // OperatorWord
|
||||||
getColor(t.SyntaxPunctuation()), // Punctuation
|
getChromaColor(t.SyntaxPunctuation()), // Punctuation
|
||||||
|
|
||||||
getColor(t.SyntaxComment()), // Comment
|
getChromaColor(t.SyntaxComment()), // Comment
|
||||||
getColor(t.SyntaxComment()), // CommentHashbang
|
getChromaColor(t.SyntaxComment()), // CommentHashbang
|
||||||
getColor(t.SyntaxComment()), // CommentMultiline
|
getChromaColor(t.SyntaxComment()), // CommentMultiline
|
||||||
getColor(t.SyntaxComment()), // CommentSingle
|
getChromaColor(t.SyntaxComment()), // CommentSingle
|
||||||
getColor(t.SyntaxComment()), // CommentSpecial
|
getChromaColor(t.SyntaxComment()), // CommentSpecial
|
||||||
getColor(t.SyntaxKeyword()), // CommentPreproc
|
getChromaColor(t.SyntaxKeyword()), // CommentPreproc
|
||||||
|
|
||||||
getColor(t.Text()), // Generic
|
getChromaColor(t.Text()), // Generic
|
||||||
getColor(t.Error()), // GenericDeleted
|
getChromaColor(t.Error()), // GenericDeleted
|
||||||
getColor(t.Text()), // GenericEmph
|
getChromaColor(t.Text()), // GenericEmph
|
||||||
getColor(t.Error()), // GenericError
|
getChromaColor(t.Error()), // GenericError
|
||||||
getColor(t.Text()), // GenericHeading
|
getChromaColor(t.Text()), // GenericHeading
|
||||||
getColor(t.Success()), // GenericInserted
|
getChromaColor(t.Success()), // GenericInserted
|
||||||
getColor(t.TextMuted()), // GenericOutput
|
getChromaColor(t.TextMuted()), // GenericOutput
|
||||||
getColor(t.Text()), // GenericPrompt
|
getChromaColor(t.Text()), // GenericPrompt
|
||||||
getColor(t.Text()), // GenericStrong
|
getChromaColor(t.Text()), // GenericStrong
|
||||||
getColor(t.Text()), // GenericSubheading
|
getChromaColor(t.Text()), // GenericSubheading
|
||||||
getColor(t.Error()), // GenericTraceback
|
getChromaColor(t.Error()), // GenericTraceback
|
||||||
getColor(t.Text()), // TextWhitespace
|
getChromaColor(t.Text()), // TextWhitespace
|
||||||
)
|
)
|
||||||
|
|
||||||
r := strings.NewReader(syntaxThemeXml)
|
r := strings.NewReader(syntaxThemeXml)
|
||||||
|
@ -527,6 +527,9 @@ func SyntaxHighlight(w io.Writer, source, fileName, formatter string, bg color.C
|
||||||
// Modify the style to use the provided background
|
// Modify the style to use the provided background
|
||||||
s, err := style.Builder().Transform(
|
s, err := style.Builder().Transform(
|
||||||
func(t chroma.StyleEntry) chroma.StyleEntry {
|
func(t chroma.StyleEntry) chroma.StyleEntry {
|
||||||
|
if _, ok := bg.(lipgloss.NoColor); ok {
|
||||||
|
return t
|
||||||
|
}
|
||||||
r, g, b, _ := bg.RGBA()
|
r, g, b, _ := bg.RGBA()
|
||||||
t.Background = chroma.NewColour(uint8(r>>8), uint8(g>>8), uint8(b>>8))
|
t.Background = chroma.NewColour(uint8(r>>8), uint8(g>>8), uint8(b>>8))
|
||||||
return t
|
return t
|
||||||
|
@ -546,10 +549,18 @@ func SyntaxHighlight(w io.Writer, source, fileName, formatter string, bg color.C
|
||||||
}
|
}
|
||||||
|
|
||||||
// getColor returns the appropriate hex color string based on terminal background
|
// getColor returns the appropriate hex color string based on terminal background
|
||||||
func getColor(adaptiveColor compat.AdaptiveColor) string {
|
func getColor(adaptiveColor compat.AdaptiveColor) *string {
|
||||||
return stylesi.AdaptiveColorToString(adaptiveColor)
|
return stylesi.AdaptiveColorToString(adaptiveColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getChromaColor(adaptiveColor compat.AdaptiveColor) string {
|
||||||
|
color := stylesi.AdaptiveColorToString(adaptiveColor)
|
||||||
|
if color == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *color
|
||||||
|
}
|
||||||
|
|
||||||
// highlightLine applies syntax highlighting to a single line
|
// highlightLine applies syntax highlighting to a single line
|
||||||
func highlightLine(fileName string, line string, bg color.Color) string {
|
func highlightLine(fileName string, line string, bg color.Color) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
@ -561,11 +572,11 @@ func highlightLine(fileName string, line string, bg color.Color) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// createStyles generates the lipgloss styles needed for rendering diffs
|
// createStyles generates the lipgloss styles needed for rendering diffs
|
||||||
func createStyles(t theme.Theme) (removedLineStyle, addedLineStyle, contextLineStyle, lineNumberStyle lipgloss.Style) {
|
func createStyles(t theme.Theme) (removedLineStyle, addedLineStyle, contextLineStyle, lineNumberStyle stylesi.Style) {
|
||||||
removedLineStyle = lipgloss.NewStyle().Background(t.DiffRemovedBg())
|
removedLineStyle = stylesi.NewStyle().Background(t.DiffRemovedBg())
|
||||||
addedLineStyle = lipgloss.NewStyle().Background(t.DiffAddedBg())
|
addedLineStyle = stylesi.NewStyle().Background(t.DiffAddedBg())
|
||||||
contextLineStyle = lipgloss.NewStyle().Background(t.DiffContextBg())
|
contextLineStyle = stylesi.NewStyle().Background(t.DiffContextBg())
|
||||||
lineNumberStyle = lipgloss.NewStyle().Background(t.DiffLineNumber()).Foreground(t.TextMuted())
|
lineNumberStyle = stylesi.NewStyle().Foreground(t.TextMuted()).Background(t.DiffLineNumber())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,9 +624,17 @@ func applyHighlighting(content string, segments []Segment, segmentType LineType,
|
||||||
currentPos := 0
|
currentPos := 0
|
||||||
|
|
||||||
// Get the appropriate color based on terminal background
|
// Get the appropriate color based on terminal background
|
||||||
bgColor := lipgloss.Color(getColor(highlightBg))
|
bg := getColor(highlightBg)
|
||||||
fgColor := lipgloss.Color(getColor(theme.CurrentTheme().BackgroundPanel()))
|
fg := getColor(theme.CurrentTheme().BackgroundPanel())
|
||||||
|
var bgColor color.Color
|
||||||
|
var fgColor color.Color
|
||||||
|
|
||||||
|
if bg != nil {
|
||||||
|
bgColor = lipgloss.Color(*bg)
|
||||||
|
}
|
||||||
|
if fg != nil {
|
||||||
|
fgColor = lipgloss.Color(*fg)
|
||||||
|
}
|
||||||
for i := 0; i < len(content); {
|
for i := 0; i < len(content); {
|
||||||
// Check if we're at an ANSI sequence
|
// Check if we're at an ANSI sequence
|
||||||
isAnsi := false
|
isAnsi := false
|
||||||
|
@ -651,12 +670,20 @@ func applyHighlighting(content string, segments []Segment, segmentType LineType,
|
||||||
currentStyle := ansiSequences[currentPos]
|
currentStyle := ansiSequences[currentPos]
|
||||||
|
|
||||||
// Apply foreground and background highlight
|
// Apply foreground and background highlight
|
||||||
sb.WriteString("\x1b[38;2;")
|
if fgColor != nil {
|
||||||
r, g, b, _ := fgColor.RGBA()
|
sb.WriteString("\x1b[38;2;")
|
||||||
sb.WriteString(fmt.Sprintf("%d;%d;%dm", r>>8, g>>8, b>>8))
|
r, g, b, _ := fgColor.RGBA()
|
||||||
sb.WriteString("\x1b[48;2;")
|
sb.WriteString(fmt.Sprintf("%d;%d;%dm", r>>8, g>>8, b>>8))
|
||||||
r, g, b, _ = bgColor.RGBA()
|
} else {
|
||||||
sb.WriteString(fmt.Sprintf("%d;%d;%dm", r>>8, g>>8, b>>8))
|
sb.WriteString("\x1b[49m")
|
||||||
|
}
|
||||||
|
if bgColor != nil {
|
||||||
|
sb.WriteString("\x1b[48;2;")
|
||||||
|
r, g, b, _ := bgColor.RGBA()
|
||||||
|
sb.WriteString(fmt.Sprintf("%d;%d;%dm", r>>8, g>>8, b>>8))
|
||||||
|
} else {
|
||||||
|
sb.WriteString("\x1b[39m")
|
||||||
|
}
|
||||||
sb.WriteString(char)
|
sb.WriteString(char)
|
||||||
|
|
||||||
// Full reset of all attributes to ensure clean state
|
// Full reset of all attributes to ensure clean state
|
||||||
|
@ -677,16 +704,16 @@ func applyHighlighting(content string, segments []Segment, segmentType LineType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderLinePrefix renders the line number and marker prefix for a diff line
|
// renderLinePrefix renders the line number and marker prefix for a diff line
|
||||||
func renderLinePrefix(dl DiffLine, lineNum string, marker string, lineNumberStyle lipgloss.Style, t theme.Theme) string {
|
func renderLinePrefix(dl DiffLine, lineNum string, marker string, lineNumberStyle stylesi.Style, t theme.Theme) string {
|
||||||
// Style the marker based on line type
|
// Style the marker based on line type
|
||||||
var styledMarker string
|
var styledMarker string
|
||||||
switch dl.Kind {
|
switch dl.Kind {
|
||||||
case LineRemoved:
|
case LineRemoved:
|
||||||
styledMarker = lipgloss.NewStyle().Background(t.DiffRemovedBg()).Foreground(t.DiffRemoved()).Render(marker)
|
styledMarker = stylesi.NewStyle().Foreground(t.DiffRemoved()).Background(t.DiffRemovedBg()).Render(marker)
|
||||||
case LineAdded:
|
case LineAdded:
|
||||||
styledMarker = lipgloss.NewStyle().Background(t.DiffAddedBg()).Foreground(t.DiffAdded()).Render(marker)
|
styledMarker = stylesi.NewStyle().Foreground(t.DiffAdded()).Background(t.DiffAddedBg()).Render(marker)
|
||||||
case LineContext:
|
case LineContext:
|
||||||
styledMarker = lipgloss.NewStyle().Background(t.DiffContextBg()).Foreground(t.TextMuted()).Render(marker)
|
styledMarker = stylesi.NewStyle().Foreground(t.TextMuted()).Background(t.DiffContextBg()).Render(marker)
|
||||||
default:
|
default:
|
||||||
styledMarker = marker
|
styledMarker = marker
|
||||||
}
|
}
|
||||||
|
@ -695,7 +722,7 @@ func renderLinePrefix(dl DiffLine, lineNum string, marker string, lineNumberStyl
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderLineContent renders the content of a diff line with syntax and intra-line highlighting
|
// renderLineContent renders the content of a diff line with syntax and intra-line highlighting
|
||||||
func renderLineContent(fileName string, dl DiffLine, bgStyle lipgloss.Style, highlightColor compat.AdaptiveColor, width int, t theme.Theme) string {
|
func renderLineContent(fileName string, dl DiffLine, bgStyle stylesi.Style, highlightColor compat.AdaptiveColor, width int) string {
|
||||||
// Apply syntax highlighting
|
// Apply syntax highlighting
|
||||||
content := highlightLine(fileName, dl.Content, bgStyle.GetBackground())
|
content := highlightLine(fileName, dl.Content, bgStyle.GetBackground())
|
||||||
|
|
||||||
|
@ -714,7 +741,9 @@ func renderLineContent(fileName string, dl DiffLine, bgStyle lipgloss.Style, hig
|
||||||
ansi.Truncate(
|
ansi.Truncate(
|
||||||
content,
|
content,
|
||||||
width,
|
width,
|
||||||
lipgloss.NewStyle().Background(bgStyle.GetBackground()).Foreground(t.TextMuted()).Render("..."),
|
"...",
|
||||||
|
// stylesi.NewStyleWithColors(t.TextMuted(), bgStyle.GetBackground()).Render("..."),
|
||||||
|
// stylesi.WithForeground(stylesi.NewStyle().Background(bgStyle.GetBackground()), t.TextMuted()).Render("..."),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -725,7 +754,7 @@ func renderUnifiedLine(fileName string, dl DiffLine, width int, t theme.Theme) s
|
||||||
|
|
||||||
// Determine line style and marker based on line type
|
// Determine line style and marker based on line type
|
||||||
var marker string
|
var marker string
|
||||||
var bgStyle lipgloss.Style
|
var bgStyle stylesi.Style
|
||||||
var lineNum string
|
var lineNum string
|
||||||
var highlightColor compat.AdaptiveColor
|
var highlightColor compat.AdaptiveColor
|
||||||
|
|
||||||
|
@ -733,8 +762,8 @@ func renderUnifiedLine(fileName string, dl DiffLine, width int, t theme.Theme) s
|
||||||
case LineRemoved:
|
case LineRemoved:
|
||||||
marker = "-"
|
marker = "-"
|
||||||
bgStyle = removedLineStyle
|
bgStyle = removedLineStyle
|
||||||
lineNumberStyle = lineNumberStyle.Foreground(t.DiffRemoved()).Background(t.DiffRemovedLineNumberBg())
|
lineNumberStyle = lineNumberStyle.Background(t.DiffRemovedLineNumberBg()).Foreground(t.DiffRemoved())
|
||||||
highlightColor = t.DiffHighlightRemoved()
|
highlightColor = t.DiffHighlightRemoved() // TODO: handle "none"
|
||||||
if dl.OldLineNo > 0 {
|
if dl.OldLineNo > 0 {
|
||||||
lineNum = fmt.Sprintf("%6d ", dl.OldLineNo)
|
lineNum = fmt.Sprintf("%6d ", dl.OldLineNo)
|
||||||
} else {
|
} else {
|
||||||
|
@ -743,8 +772,8 @@ func renderUnifiedLine(fileName string, dl DiffLine, width int, t theme.Theme) s
|
||||||
case LineAdded:
|
case LineAdded:
|
||||||
marker = "+"
|
marker = "+"
|
||||||
bgStyle = addedLineStyle
|
bgStyle = addedLineStyle
|
||||||
lineNumberStyle = lineNumberStyle.Foreground(t.DiffAdded()).Background(t.DiffAddedLineNumberBg())
|
lineNumberStyle = lineNumberStyle.Background(t.DiffAddedLineNumberBg()).Foreground(t.DiffAdded())
|
||||||
highlightColor = t.DiffHighlightAdded()
|
highlightColor = t.DiffHighlightAdded() // TODO: handle "none"
|
||||||
if dl.NewLineNo > 0 {
|
if dl.NewLineNo > 0 {
|
||||||
lineNum = fmt.Sprintf(" %7d", dl.NewLineNo)
|
lineNum = fmt.Sprintf(" %7d", dl.NewLineNo)
|
||||||
} else {
|
} else {
|
||||||
|
@ -766,7 +795,7 @@ func renderUnifiedLine(fileName string, dl DiffLine, width int, t theme.Theme) s
|
||||||
// Render the content
|
// Render the content
|
||||||
prefixWidth := ansi.StringWidth(prefix)
|
prefixWidth := ansi.StringWidth(prefix)
|
||||||
contentWidth := width - prefixWidth
|
contentWidth := width - prefixWidth
|
||||||
content := renderLineContent(fileName, dl, bgStyle, highlightColor, contentWidth, t)
|
content := renderLineContent(fileName, dl, bgStyle, highlightColor, contentWidth)
|
||||||
|
|
||||||
return prefix + content
|
return prefix + content
|
||||||
}
|
}
|
||||||
|
@ -780,7 +809,7 @@ func renderDiffColumnLine(
|
||||||
t theme.Theme,
|
t theme.Theme,
|
||||||
) string {
|
) string {
|
||||||
if dl == nil {
|
if dl == nil {
|
||||||
contextLineStyle := lipgloss.NewStyle().Background(t.DiffContextBg())
|
contextLineStyle := stylesi.NewStyle().Background(t.DiffContextBg())
|
||||||
return contextLineStyle.Width(colWidth).Render("")
|
return contextLineStyle.Width(colWidth).Render("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,7 +817,7 @@ func renderDiffColumnLine(
|
||||||
|
|
||||||
// Determine line style based on line type and column
|
// Determine line style based on line type and column
|
||||||
var marker string
|
var marker string
|
||||||
var bgStyle lipgloss.Style
|
var bgStyle stylesi.Style
|
||||||
var lineNum string
|
var lineNum string
|
||||||
var highlightColor compat.AdaptiveColor
|
var highlightColor compat.AdaptiveColor
|
||||||
|
|
||||||
|
@ -798,8 +827,8 @@ func renderDiffColumnLine(
|
||||||
case LineRemoved:
|
case LineRemoved:
|
||||||
marker = "-"
|
marker = "-"
|
||||||
bgStyle = removedLineStyle
|
bgStyle = removedLineStyle
|
||||||
lineNumberStyle = lineNumberStyle.Foreground(t.DiffRemoved()).Background(t.DiffRemovedLineNumberBg())
|
lineNumberStyle = lineNumberStyle.Background(t.DiffRemovedLineNumberBg()).Foreground(t.DiffRemoved())
|
||||||
highlightColor = t.DiffHighlightRemoved()
|
highlightColor = t.DiffHighlightRemoved() // TODO: handle "none"
|
||||||
case LineAdded:
|
case LineAdded:
|
||||||
marker = "?"
|
marker = "?"
|
||||||
bgStyle = contextLineStyle
|
bgStyle = contextLineStyle
|
||||||
|
@ -818,7 +847,7 @@ func renderDiffColumnLine(
|
||||||
case LineAdded:
|
case LineAdded:
|
||||||
marker = "+"
|
marker = "+"
|
||||||
bgStyle = addedLineStyle
|
bgStyle = addedLineStyle
|
||||||
lineNumberStyle = lineNumberStyle.Foreground(t.DiffAdded()).Background(t.DiffAddedLineNumberBg())
|
lineNumberStyle = lineNumberStyle.Background(t.DiffAddedLineNumberBg()).Foreground(t.DiffAdded())
|
||||||
highlightColor = t.DiffHighlightAdded()
|
highlightColor = t.DiffHighlightAdded()
|
||||||
case LineRemoved:
|
case LineRemoved:
|
||||||
marker = "?"
|
marker = "?"
|
||||||
|
@ -849,7 +878,7 @@ func renderDiffColumnLine(
|
||||||
// Render the content
|
// Render the content
|
||||||
prefixWidth := ansi.StringWidth(prefix)
|
prefixWidth := ansi.StringWidth(prefix)
|
||||||
contentWidth := colWidth - prefixWidth
|
contentWidth := colWidth - prefixWidth
|
||||||
content := renderLineContent(fileName, *dl, bgStyle, highlightColor, contentWidth, t)
|
content := renderLineContent(fileName, *dl, bgStyle, highlightColor, contentWidth)
|
||||||
|
|
||||||
return prefix + content
|
return prefix + content
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/v2/key"
|
"github.com/charmbracelet/bubbles/v2/key"
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
|
||||||
"github.com/muesli/reflow/truncate"
|
"github.com/muesli/reflow/truncate"
|
||||||
"github.com/sst/opencode/internal/styles"
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
|
@ -174,11 +173,11 @@ type StringItem string
|
||||||
|
|
||||||
func (s StringItem) Render(selected bool, width int) string {
|
func (s StringItem) Render(selected bool, width int) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.NewStyle()
|
||||||
|
|
||||||
truncatedStr := truncate.StringWithTail(string(s), uint(width-1), "...")
|
truncatedStr := truncate.StringWithTail(string(s), uint(width-1), "...")
|
||||||
|
|
||||||
var itemStyle lipgloss.Style
|
var itemStyle styles.Style
|
||||||
if selected {
|
if selected {
|
||||||
itemStyle = baseStyle.
|
itemStyle = baseStyle.
|
||||||
Background(t.Primary()).
|
Background(t.Primary()).
|
||||||
|
@ -187,6 +186,7 @@ func (s StringItem) Render(selected bool, width int) string {
|
||||||
PaddingLeft(1)
|
PaddingLeft(1)
|
||||||
} else {
|
} else {
|
||||||
itemStyle = baseStyle.
|
itemStyle = baseStyle.
|
||||||
|
Foreground(t.TextMuted()).
|
||||||
PaddingLeft(1)
|
PaddingLeft(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,12 +90,8 @@ func (m *Modal) Render(contentView string, background string) string {
|
||||||
|
|
||||||
innerWidth := outerWidth - 4
|
innerWidth := outerWidth - 4
|
||||||
|
|
||||||
// Base style for the modal
|
baseStyle := styles.NewStyle().Foreground(t.TextMuted()).Background(t.BackgroundElement())
|
||||||
baseStyle := styles.BaseStyle().
|
|
||||||
Background(t.BackgroundElement()).
|
|
||||||
Foreground(t.TextMuted())
|
|
||||||
|
|
||||||
// Add title if provided
|
|
||||||
var finalContent string
|
var finalContent string
|
||||||
if m.title != "" {
|
if m.title != "" {
|
||||||
titleStyle := baseStyle.
|
titleStyle := baseStyle.
|
||||||
|
|
|
@ -3,7 +3,7 @@ package qr
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
"rsc.io/qr"
|
"rsc.io/qr"
|
||||||
)
|
)
|
||||||
|
@ -23,9 +23,7 @@ func Generate(text string) (string, int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create lipgloss style for QR code with theme colors
|
// Create lipgloss style for QR code with theme colors
|
||||||
qrStyle := lipgloss.NewStyle().
|
qrStyle := styles.NewStyleWithColors(t.Text(), t.Background())
|
||||||
Foreground(t.Text()).
|
|
||||||
Background(t.Background())
|
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
|
|
||||||
|
|
|
@ -36,14 +36,15 @@ func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
func (m statusComponent) logo() string {
|
func (m statusComponent) logo() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
base := lipgloss.NewStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Render
|
base := styles.NewStyle().Foreground(t.TextMuted()).Background(t.BackgroundElement()).Render
|
||||||
emphasis := lipgloss.NewStyle().Bold(true).Background(t.BackgroundElement()).Foreground(t.Text()).Render
|
emphasis := styles.NewStyle().Foreground(t.Text()).Background(t.BackgroundElement()).Bold(true).Render
|
||||||
|
|
||||||
open := base("open")
|
open := base("open")
|
||||||
code := emphasis("code ")
|
code := emphasis("code ")
|
||||||
version := base(m.app.Version)
|
version := base(m.app.Version)
|
||||||
return styles.Padded().
|
return styles.NewStyle().
|
||||||
Background(t.BackgroundElement()).
|
Background(t.BackgroundElement()).
|
||||||
|
Padding(0, 1).
|
||||||
Render(open + code + version)
|
Render(open + code + version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ func formatTokensAndCost(tokens float32, contextWindow float32, cost float32) st
|
||||||
func (m statusComponent) View() string {
|
func (m statusComponent) View() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
if m.app.Session.Id == "" {
|
if m.app.Session.Id == "" {
|
||||||
return styles.BaseStyle().
|
return styles.NewStyle().
|
||||||
Background(t.Background()).
|
Background(t.Background()).
|
||||||
Width(m.width).
|
Width(m.width).
|
||||||
Height(2).
|
Height(2).
|
||||||
|
@ -86,9 +87,10 @@ func (m statusComponent) View() string {
|
||||||
|
|
||||||
logo := m.logo()
|
logo := m.logo()
|
||||||
|
|
||||||
cwd := styles.Padded().
|
cwd := styles.NewStyle().
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
Background(t.BackgroundPanel()).
|
Background(t.BackgroundPanel()).
|
||||||
|
Padding(0, 1).
|
||||||
Render(m.app.Info.Path.Cwd)
|
Render(m.app.Info.Path.Cwd)
|
||||||
|
|
||||||
sessionInfo := ""
|
sessionInfo := ""
|
||||||
|
@ -111,9 +113,10 @@ func (m statusComponent) View() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionInfo = styles.Padded().
|
sessionInfo = styles.NewStyle().
|
||||||
Background(t.BackgroundElement()).
|
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
|
Background(t.BackgroundElement()).
|
||||||
|
Padding(0, 1).
|
||||||
Render(formatTokensAndCost(tokens, contextWindow, cost))
|
Render(formatTokensAndCost(tokens, contextWindow, cost))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,11 +126,11 @@ func (m statusComponent) View() string {
|
||||||
0,
|
0,
|
||||||
m.width-lipgloss.Width(logo)-lipgloss.Width(cwd)-lipgloss.Width(sessionInfo),
|
m.width-lipgloss.Width(logo)-lipgloss.Width(cwd)-lipgloss.Width(sessionInfo),
|
||||||
)
|
)
|
||||||
spacer := lipgloss.NewStyle().Background(t.BackgroundPanel()).Width(space).Render("")
|
spacer := styles.NewStyle().Background(t.BackgroundPanel()).Width(space).Render("")
|
||||||
|
|
||||||
status := logo + cwd + spacer + sessionInfo
|
status := logo + cwd + spacer + sessionInfo
|
||||||
|
|
||||||
blank := styles.BaseStyle().Background(t.Background()).Width(m.width).Render("")
|
blank := styles.NewStyle().Background(t.Background()).Width(m.width).Render("")
|
||||||
return blank + "\n" + status
|
return blank + "\n" + status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,9 +90,9 @@ func (tm *ToastManager) Update(msg tea.Msg) (*ToastManager, tea.Cmd) {
|
||||||
func (tm *ToastManager) renderSingleToast(toast Toast) string {
|
func (tm *ToastManager) renderSingleToast(toast Toast) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
baseStyle := styles.BaseStyle().
|
baseStyle := styles.NewStyle().
|
||||||
Background(t.BackgroundElement()).
|
|
||||||
Foreground(t.Text()).
|
Foreground(t.Text()).
|
||||||
|
Background(t.BackgroundElement()).
|
||||||
Padding(1, 2)
|
Padding(1, 2)
|
||||||
|
|
||||||
maxWidth := max(40, layout.Current.Viewport.Width/3)
|
maxWidth := max(40, layout.Current.Viewport.Width/3)
|
||||||
|
@ -101,15 +101,14 @@ func (tm *ToastManager) renderSingleToast(toast Toast) string {
|
||||||
// Build content with wrapping
|
// Build content with wrapping
|
||||||
var content strings.Builder
|
var content strings.Builder
|
||||||
if toast.Title != nil {
|
if toast.Title != nil {
|
||||||
titleStyle := lipgloss.NewStyle().
|
titleStyle := styles.NewStyle().Foreground(toast.Color).
|
||||||
Foreground(toast.Color).
|
|
||||||
Bold(true)
|
Bold(true)
|
||||||
content.WriteString(titleStyle.Render(*toast.Title))
|
content.WriteString(titleStyle.Render(*toast.Title))
|
||||||
content.WriteString("\n")
|
content.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap message text
|
// Wrap message text
|
||||||
messageStyle := lipgloss.NewStyle()
|
messageStyle := styles.NewStyle()
|
||||||
contentWidth := lipgloss.Width(toast.Message)
|
contentWidth := lipgloss.Width(toast.Message)
|
||||||
if contentWidth > contentMaxWidth {
|
if contentWidth > contentMaxWidth {
|
||||||
messageStyle = messageStyle.Width(contentMaxWidth)
|
messageStyle = messageStyle.Width(contentMaxWidth)
|
||||||
|
|
|
@ -18,7 +18,7 @@ type State struct {
|
||||||
|
|
||||||
func NewState() *State {
|
func NewState() *State {
|
||||||
return &State{
|
return &State{
|
||||||
Theme: "opencode",
|
Theme: "system",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package layout
|
||||||
import (
|
import (
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
func (c *container) View() string {
|
func (c *container) View() string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
style := lipgloss.NewStyle()
|
style := styles.NewStyle().Background(t.Background())
|
||||||
width := c.width
|
width := c.width
|
||||||
height := c.height
|
height := c.height
|
||||||
|
|
||||||
|
@ -66,8 +67,6 @@ func (c *container) View() string {
|
||||||
width = c.maxWidth
|
width = c.maxWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
style = style.Background(t.Background())
|
|
||||||
|
|
||||||
// Apply border if any side is enabled
|
// Apply border if any side is enabled
|
||||||
if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft {
|
if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft {
|
||||||
// Adjust width and height for borders
|
// Adjust width and height for borders
|
||||||
|
|
|
@ -3,6 +3,7 @@ package layout
|
||||||
import (
|
import (
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ func (f *flexLayout) View() string {
|
||||||
alignment,
|
alignment,
|
||||||
child.View(),
|
child.View(),
|
||||||
// TODO: make configurable WithBackgroundStyle
|
// TODO: make configurable WithBackgroundStyle
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
lipgloss.WithWhitespaceStyle(styles.NewStyle().Background(t.Background()).Lipgloss()),
|
||||||
)
|
)
|
||||||
views = append(views, view)
|
views = append(views, view)
|
||||||
} else {
|
} else {
|
||||||
|
@ -78,7 +79,7 @@ func (f *flexLayout) View() string {
|
||||||
alignment,
|
alignment,
|
||||||
child.View(),
|
child.View(),
|
||||||
// TODO: make configurable WithBackgroundStyle
|
// TODO: make configurable WithBackgroundStyle
|
||||||
lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
|
lipgloss.WithWhitespaceStyle(styles.NewStyle().Background(t.Background()).Lipgloss()),
|
||||||
)
|
)
|
||||||
views = append(views, view)
|
views = append(views, view)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package styles
|
package styles
|
||||||
|
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
type TerminalInfo struct {
|
type TerminalInfo struct {
|
||||||
|
Background color.Color
|
||||||
BackgroundIsDark bool
|
BackgroundIsDark bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +11,7 @@ var Terminal *TerminalInfo
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Terminal = &TerminalInfo{
|
Terminal = &TerminalInfo{
|
||||||
|
Background: color.Black,
|
||||||
BackgroundIsDark: true,
|
BackgroundIsDark: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package styles
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/glamour"
|
"github.com/charmbracelet/glamour"
|
||||||
"github.com/charmbracelet/glamour/ansi"
|
"github.com/charmbracelet/glamour/ansi"
|
||||||
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2/compat"
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
|
@ -29,7 +30,7 @@ func GetMarkdownRenderer(width int, backgroundColor compat.AdaptiveColor) *glamo
|
||||||
// using adaptive colors from the provided theme.
|
// using adaptive colors from the provided theme.
|
||||||
func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.StyleConfig {
|
func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.StyleConfig {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
background := stringPtr(AdaptiveColorToString(backgroundColor))
|
background := AdaptiveColorToString(backgroundColor)
|
||||||
|
|
||||||
return ansi.StyleConfig{
|
return ansi.StyleConfig{
|
||||||
Document: ansi.StyleBlock{
|
Document: ansi.StyleBlock{
|
||||||
|
@ -37,12 +38,12 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||||
BlockPrefix: "",
|
BlockPrefix: "",
|
||||||
BlockSuffix: "",
|
BlockSuffix: "",
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownText())),
|
Color: AdaptiveColorToString(t.MarkdownText()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BlockQuote: ansi.StyleBlock{
|
BlockQuote: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownBlockQuote())),
|
Color: AdaptiveColorToString(t.MarkdownBlockQuote()),
|
||||||
Italic: boolPtr(true),
|
Italic: boolPtr(true),
|
||||||
Prefix: "┃ ",
|
Prefix: "┃ ",
|
||||||
},
|
},
|
||||||
|
@ -54,108 +55,108 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||||
StyleBlock: ansi.StyleBlock{
|
StyleBlock: ansi.StyleBlock{
|
||||||
IndentToken: stringPtr(" "),
|
IndentToken: stringPtr(" "),
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownText())),
|
Color: AdaptiveColorToString(t.MarkdownText()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Heading: ansi.StyleBlock{
|
Heading: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
BlockSuffix: "\n",
|
BlockSuffix: "\n",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
H1: ansi.StyleBlock{
|
H1: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Prefix: "# ",
|
Prefix: "# ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
H2: ansi.StyleBlock{
|
H2: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Prefix: "## ",
|
Prefix: "## ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
H3: ansi.StyleBlock{
|
H3: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Prefix: "### ",
|
Prefix: "### ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
H4: ansi.StyleBlock{
|
H4: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Prefix: "#### ",
|
Prefix: "#### ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
H5: ansi.StyleBlock{
|
H5: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Prefix: "##### ",
|
Prefix: "##### ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
H6: ansi.StyleBlock{
|
H6: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Prefix: "###### ",
|
Prefix: "###### ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Strikethrough: ansi.StylePrimitive{
|
Strikethrough: ansi.StylePrimitive{
|
||||||
CrossedOut: boolPtr(true),
|
CrossedOut: boolPtr(true),
|
||||||
Color: stringPtr(AdaptiveColorToString(t.TextMuted())),
|
Color: AdaptiveColorToString(t.TextMuted()),
|
||||||
},
|
},
|
||||||
Emph: ansi.StylePrimitive{
|
Emph: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownEmph())),
|
Color: AdaptiveColorToString(t.MarkdownEmph()),
|
||||||
Italic: boolPtr(true),
|
Italic: boolPtr(true),
|
||||||
},
|
},
|
||||||
Strong: ansi.StylePrimitive{
|
Strong: ansi.StylePrimitive{
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownStrong())),
|
Color: AdaptiveColorToString(t.MarkdownStrong()),
|
||||||
},
|
},
|
||||||
HorizontalRule: ansi.StylePrimitive{
|
HorizontalRule: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHorizontalRule())),
|
Color: AdaptiveColorToString(t.MarkdownHorizontalRule()),
|
||||||
Format: "\n─────────────────────────────────────────\n",
|
Format: "\n─────────────────────────────────────────\n",
|
||||||
},
|
},
|
||||||
Item: ansi.StylePrimitive{
|
Item: ansi.StylePrimitive{
|
||||||
BlockPrefix: "• ",
|
BlockPrefix: "• ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownListItem())),
|
Color: AdaptiveColorToString(t.MarkdownListItem()),
|
||||||
},
|
},
|
||||||
Enumeration: ansi.StylePrimitive{
|
Enumeration: ansi.StylePrimitive{
|
||||||
BlockPrefix: ". ",
|
BlockPrefix: ". ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownListEnumeration())),
|
Color: AdaptiveColorToString(t.MarkdownListEnumeration()),
|
||||||
},
|
},
|
||||||
Task: ansi.StyleTask{
|
Task: ansi.StyleTask{
|
||||||
Ticked: "[✓] ",
|
Ticked: "[✓] ",
|
||||||
Unticked: "[ ] ",
|
Unticked: "[ ] ",
|
||||||
},
|
},
|
||||||
Link: ansi.StylePrimitive{
|
Link: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownLink())),
|
Color: AdaptiveColorToString(t.MarkdownLink()),
|
||||||
Underline: boolPtr(true),
|
Underline: boolPtr(true),
|
||||||
},
|
},
|
||||||
LinkText: ansi.StylePrimitive{
|
LinkText: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownLinkText())),
|
Color: AdaptiveColorToString(t.MarkdownLinkText()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
Image: ansi.StylePrimitive{
|
Image: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownImage())),
|
Color: AdaptiveColorToString(t.MarkdownImage()),
|
||||||
Underline: boolPtr(true),
|
Underline: boolPtr(true),
|
||||||
Format: "🖼 {{.text}}",
|
Format: "🖼 {{.text}}",
|
||||||
},
|
},
|
||||||
ImageText: ansi.StylePrimitive{
|
ImageText: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownImageText())),
|
Color: AdaptiveColorToString(t.MarkdownImageText()),
|
||||||
Format: "{{.text}}",
|
Format: "{{.text}}",
|
||||||
},
|
},
|
||||||
Code: ansi.StyleBlock{
|
Code: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownCode())),
|
Color: AdaptiveColorToString(t.MarkdownCode()),
|
||||||
Prefix: "",
|
Prefix: "",
|
||||||
Suffix: "",
|
Suffix: "",
|
||||||
},
|
},
|
||||||
|
@ -165,7 +166,7 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Prefix: " ",
|
Prefix: " ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownCodeBlock())),
|
Color: AdaptiveColorToString(t.MarkdownCodeBlock()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Chroma: &ansi.Chroma{
|
Chroma: &ansi.Chroma{
|
||||||
|
@ -174,109 +175,109 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||||
},
|
},
|
||||||
Text: ansi.StylePrimitive{
|
Text: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownText())),
|
Color: AdaptiveColorToString(t.MarkdownText()),
|
||||||
},
|
},
|
||||||
Error: ansi.StylePrimitive{
|
Error: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.Error())),
|
Color: AdaptiveColorToString(t.Error()),
|
||||||
},
|
},
|
||||||
Comment: ansi.StylePrimitive{
|
Comment: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxComment())),
|
Color: AdaptiveColorToString(t.SyntaxComment()),
|
||||||
},
|
},
|
||||||
CommentPreproc: ansi.StylePrimitive{
|
CommentPreproc: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxKeyword())),
|
Color: AdaptiveColorToString(t.SyntaxKeyword()),
|
||||||
},
|
},
|
||||||
Keyword: ansi.StylePrimitive{
|
Keyword: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxKeyword())),
|
Color: AdaptiveColorToString(t.SyntaxKeyword()),
|
||||||
},
|
},
|
||||||
KeywordReserved: ansi.StylePrimitive{
|
KeywordReserved: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxKeyword())),
|
Color: AdaptiveColorToString(t.SyntaxKeyword()),
|
||||||
},
|
},
|
||||||
KeywordNamespace: ansi.StylePrimitive{
|
KeywordNamespace: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxKeyword())),
|
Color: AdaptiveColorToString(t.SyntaxKeyword()),
|
||||||
},
|
},
|
||||||
KeywordType: ansi.StylePrimitive{
|
KeywordType: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxType())),
|
Color: AdaptiveColorToString(t.SyntaxType()),
|
||||||
},
|
},
|
||||||
Operator: ansi.StylePrimitive{
|
Operator: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxOperator())),
|
Color: AdaptiveColorToString(t.SyntaxOperator()),
|
||||||
},
|
},
|
||||||
Punctuation: ansi.StylePrimitive{
|
Punctuation: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxPunctuation())),
|
Color: AdaptiveColorToString(t.SyntaxPunctuation()),
|
||||||
},
|
},
|
||||||
Name: ansi.StylePrimitive{
|
Name: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxVariable())),
|
Color: AdaptiveColorToString(t.SyntaxVariable()),
|
||||||
},
|
},
|
||||||
NameBuiltin: ansi.StylePrimitive{
|
NameBuiltin: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxVariable())),
|
Color: AdaptiveColorToString(t.SyntaxVariable()),
|
||||||
},
|
},
|
||||||
NameTag: ansi.StylePrimitive{
|
NameTag: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxKeyword())),
|
Color: AdaptiveColorToString(t.SyntaxKeyword()),
|
||||||
},
|
},
|
||||||
NameAttribute: ansi.StylePrimitive{
|
NameAttribute: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxFunction())),
|
Color: AdaptiveColorToString(t.SyntaxFunction()),
|
||||||
},
|
},
|
||||||
NameClass: ansi.StylePrimitive{
|
NameClass: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxType())),
|
Color: AdaptiveColorToString(t.SyntaxType()),
|
||||||
},
|
},
|
||||||
NameConstant: ansi.StylePrimitive{
|
NameConstant: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxVariable())),
|
Color: AdaptiveColorToString(t.SyntaxVariable()),
|
||||||
},
|
},
|
||||||
NameDecorator: ansi.StylePrimitive{
|
NameDecorator: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxFunction())),
|
Color: AdaptiveColorToString(t.SyntaxFunction()),
|
||||||
},
|
},
|
||||||
NameFunction: ansi.StylePrimitive{
|
NameFunction: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxFunction())),
|
Color: AdaptiveColorToString(t.SyntaxFunction()),
|
||||||
},
|
},
|
||||||
LiteralNumber: ansi.StylePrimitive{
|
LiteralNumber: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxNumber())),
|
Color: AdaptiveColorToString(t.SyntaxNumber()),
|
||||||
},
|
},
|
||||||
LiteralString: ansi.StylePrimitive{
|
LiteralString: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxString())),
|
Color: AdaptiveColorToString(t.SyntaxString()),
|
||||||
},
|
},
|
||||||
LiteralStringEscape: ansi.StylePrimitive{
|
LiteralStringEscape: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.SyntaxKeyword())),
|
Color: AdaptiveColorToString(t.SyntaxKeyword()),
|
||||||
},
|
},
|
||||||
GenericDeleted: ansi.StylePrimitive{
|
GenericDeleted: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.DiffRemoved())),
|
Color: AdaptiveColorToString(t.DiffRemoved()),
|
||||||
},
|
},
|
||||||
GenericEmph: ansi.StylePrimitive{
|
GenericEmph: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownEmph())),
|
Color: AdaptiveColorToString(t.MarkdownEmph()),
|
||||||
Italic: boolPtr(true),
|
Italic: boolPtr(true),
|
||||||
},
|
},
|
||||||
GenericInserted: ansi.StylePrimitive{
|
GenericInserted: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.DiffAdded())),
|
Color: AdaptiveColorToString(t.DiffAdded()),
|
||||||
},
|
},
|
||||||
GenericStrong: ansi.StylePrimitive{
|
GenericStrong: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownStrong())),
|
Color: AdaptiveColorToString(t.MarkdownStrong()),
|
||||||
Bold: boolPtr(true),
|
Bold: boolPtr(true),
|
||||||
},
|
},
|
||||||
GenericSubheading: ansi.StylePrimitive{
|
GenericSubheading: ansi.StylePrimitive{
|
||||||
BackgroundColor: background,
|
BackgroundColor: background,
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownHeading())),
|
Color: AdaptiveColorToString(t.MarkdownHeading()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -293,14 +294,14 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||||
},
|
},
|
||||||
DefinitionDescription: ansi.StylePrimitive{
|
DefinitionDescription: ansi.StylePrimitive{
|
||||||
BlockPrefix: "\n ❯ ",
|
BlockPrefix: "\n ❯ ",
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownLinkText())),
|
Color: AdaptiveColorToString(t.MarkdownLinkText()),
|
||||||
},
|
},
|
||||||
Text: ansi.StylePrimitive{
|
Text: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownText())),
|
Color: AdaptiveColorToString(t.MarkdownText()),
|
||||||
},
|
},
|
||||||
Paragraph: ansi.StyleBlock{
|
Paragraph: ansi.StyleBlock{
|
||||||
StylePrimitive: ansi.StylePrimitive{
|
StylePrimitive: ansi.StylePrimitive{
|
||||||
Color: stringPtr(AdaptiveColorToString(t.MarkdownText())),
|
Color: AdaptiveColorToString(t.MarkdownText()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -308,11 +309,17 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
|
||||||
|
|
||||||
// AdaptiveColorToString converts a compat.AdaptiveColor to the appropriate
|
// AdaptiveColorToString converts a compat.AdaptiveColor to the appropriate
|
||||||
// hex color string based on the current terminal background
|
// hex color string based on the current terminal background
|
||||||
func AdaptiveColorToString(color compat.AdaptiveColor) string {
|
func AdaptiveColorToString(color compat.AdaptiveColor) *string {
|
||||||
if Terminal.BackgroundIsDark {
|
if Terminal.BackgroundIsDark {
|
||||||
|
if _, ok := color.Dark.(lipgloss.NoColor); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
c1, _ := colorful.MakeColor(color.Dark)
|
c1, _ := colorful.MakeColor(color.Dark)
|
||||||
return c1.Hex()
|
return stringPtr(c1.Hex())
|
||||||
|
}
|
||||||
|
if _, ok := color.Light.(lipgloss.NoColor); ok {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
c1, _ := colorful.MakeColor(color.Light)
|
c1, _ := colorful.MakeColor(color.Light)
|
||||||
return c1.Hex()
|
return stringPtr(c1.Hex())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,155 +3,8 @@ package styles
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2/compat"
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||||
"github.com/sst/opencode/internal/theme"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BaseStyle returns the base style with background and foreground colors
|
func WhitespaceStyle(bg compat.AdaptiveColor) lipgloss.WhitespaceOption {
|
||||||
func BaseStyle() lipgloss.Style {
|
return lipgloss.WithWhitespaceStyle(NewStyle().Background(bg).Lipgloss())
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return lipgloss.NewStyle().Foreground(t.Text())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Panel() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return lipgloss.NewStyle().
|
|
||||||
Background(t.BackgroundPanel()).
|
|
||||||
Border(lipgloss.NormalBorder(), true, false, true, false).
|
|
||||||
BorderForeground(t.BorderSubtle()).
|
|
||||||
Foreground(t.Text())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular returns a basic unstyled lipgloss.Style
|
|
||||||
func Regular() lipgloss.Style {
|
|
||||||
return lipgloss.NewStyle()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Muted() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return lipgloss.NewStyle().Foreground(t.TextMuted())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bold returns a bold style
|
|
||||||
func Bold() lipgloss.Style {
|
|
||||||
return BaseStyle().Bold(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Padded returns a style with horizontal padding
|
|
||||||
func Padded() lipgloss.Style {
|
|
||||||
return BaseStyle().Padding(0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border returns a style with a normal border
|
|
||||||
func Border() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return Regular().
|
|
||||||
Border(lipgloss.NormalBorder()).
|
|
||||||
BorderForeground(t.Border())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThickBorder returns a style with a thick border
|
|
||||||
func ThickBorder() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return Regular().
|
|
||||||
Border(lipgloss.ThickBorder()).
|
|
||||||
BorderForeground(t.Border())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoubleBorder returns a style with a double border
|
|
||||||
func DoubleBorder() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return Regular().
|
|
||||||
Border(lipgloss.DoubleBorder()).
|
|
||||||
BorderForeground(t.Border())
|
|
||||||
}
|
|
||||||
|
|
||||||
// FocusedBorder returns a style with a border using the focused border color
|
|
||||||
func FocusedBorder() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return Regular().
|
|
||||||
Border(lipgloss.NormalBorder()).
|
|
||||||
BorderForeground(t.BorderActive())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DimBorder returns a style with a border using the dim border color
|
|
||||||
func DimBorder() lipgloss.Style {
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
return Regular().
|
|
||||||
Border(lipgloss.NormalBorder()).
|
|
||||||
BorderForeground(t.BorderSubtle())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrimaryColor returns the primary color from the current theme
|
|
||||||
func PrimaryColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Primary()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondaryColor returns the secondary color from the current theme
|
|
||||||
func SecondaryColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Secondary()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccentColor returns the accent color from the current theme
|
|
||||||
func AccentColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Accent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorColor returns the error color from the current theme
|
|
||||||
func ErrorColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarningColor returns the warning color from the current theme
|
|
||||||
func WarningColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Warning()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessColor returns the success color from the current theme
|
|
||||||
func SuccessColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Success()
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoColor returns the info color from the current theme
|
|
||||||
func InfoColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Info()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextColor returns the text color from the current theme
|
|
||||||
func TextColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Text()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextMutedColor returns the muted text color from the current theme
|
|
||||||
func TextMutedColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().TextMuted()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackgroundColor returns the background color from the current theme
|
|
||||||
func BackgroundColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackgroundPanelColor returns the subtle background color from the current theme
|
|
||||||
func BackgroundPanelColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().BackgroundPanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackgroundElementColor returns the darker background color from the current theme
|
|
||||||
func BackgroundElementColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().BackgroundElement()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BorderColor returns the border color from the current theme
|
|
||||||
func BorderColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().Border()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BorderActiveColor returns the active border color from the current theme
|
|
||||||
func BorderActiveColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().BorderActive()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BorderSubtleColor returns the subtle border color from the current theme
|
|
||||||
func BorderSubtleColor() compat.AdaptiveColor {
|
|
||||||
return theme.CurrentTheme().BorderSubtle()
|
|
||||||
}
|
}
|
||||||
|
|
295
packages/tui/internal/styles/utilities.go
Normal file
295
packages/tui/internal/styles/utilities.go
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
package styles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsNoColor checks if a color is the special NoColor type
|
||||||
|
func IsNoColor(c color.Color) bool {
|
||||||
|
_, ok := c.(lipgloss.NoColor)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style wraps lipgloss.Style to provide a fluent API for handling "none" colors
|
||||||
|
type Style struct {
|
||||||
|
lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStyle creates a new Style with proper handling of "none" colors
|
||||||
|
func NewStyle() Style {
|
||||||
|
return Style{lipgloss.NewStyle()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Lipgloss() lipgloss.Style {
|
||||||
|
return s.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreground sets the foreground color, handling "none" appropriately
|
||||||
|
func (s Style) Foreground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetForeground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.Foreground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background sets the background color, handling "none" appropriately
|
||||||
|
func (s Style) Background(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBackground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.Background(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderForeground sets the border foreground color, handling "none" appropriately
|
||||||
|
func (s Style) BorderForeground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderForeground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderForeground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderBackground sets the border background color, handling "none" appropriately
|
||||||
|
func (s Style) BorderBackground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderBackground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderBackground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderTopForeground sets the border top foreground color, handling "none" appropriately
|
||||||
|
func (s Style) BorderTopForeground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderTopForeground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderTopForeground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderTopBackground sets the border top background color, handling "none" appropriately
|
||||||
|
func (s Style) BorderTopBackground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderTopBackground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderTopBackground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderBottomForeground sets the border bottom foreground color, handling "none" appropriately
|
||||||
|
func (s Style) BorderBottomForeground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderBottomForeground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderBottomForeground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderBottomBackground sets the border bottom background color, handling "none" appropriately
|
||||||
|
func (s Style) BorderBottomBackground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderBottomBackground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderBottomBackground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderLeftForeground sets the border left foreground color, handling "none" appropriately
|
||||||
|
func (s Style) BorderLeftForeground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderLeftForeground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderLeftForeground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderLeftBackground sets the border left background color, handling "none" appropriately
|
||||||
|
func (s Style) BorderLeftBackground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderLeftBackground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderLeftBackground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderRightForeground sets the border right foreground color, handling "none" appropriately
|
||||||
|
func (s Style) BorderRightForeground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderRightForeground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderRightForeground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BorderRightBackground sets the border right background color, handling "none" appropriately
|
||||||
|
func (s Style) BorderRightBackground(c compat.AdaptiveColor) Style {
|
||||||
|
if IsNoColor(c.Dark) && IsNoColor(c.Light) {
|
||||||
|
return Style{s.Style.UnsetBorderRightBackground()}
|
||||||
|
}
|
||||||
|
return Style{s.Style.BorderRightBackground(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render applies the style to a string
|
||||||
|
func (s Style) Render(str string) string {
|
||||||
|
return s.Style.Render(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common lipgloss.Style method delegations for seamless usage
|
||||||
|
|
||||||
|
func (s Style) Bold(v bool) Style {
|
||||||
|
return Style{s.Style.Bold(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Italic(v bool) Style {
|
||||||
|
return Style{s.Style.Italic(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Underline(v bool) Style {
|
||||||
|
return Style{s.Style.Underline(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Strikethrough(v bool) Style {
|
||||||
|
return Style{s.Style.Strikethrough(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Blink(v bool) Style {
|
||||||
|
return Style{s.Style.Blink(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Faint(v bool) Style {
|
||||||
|
return Style{s.Style.Faint(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Reverse(v bool) Style {
|
||||||
|
return Style{s.Style.Reverse(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Width(i int) Style {
|
||||||
|
return Style{s.Style.Width(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Height(i int) Style {
|
||||||
|
return Style{s.Style.Height(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Padding(i ...int) Style {
|
||||||
|
return Style{s.Style.Padding(i...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) PaddingTop(i int) Style {
|
||||||
|
return Style{s.Style.PaddingTop(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) PaddingBottom(i int) Style {
|
||||||
|
return Style{s.Style.PaddingBottom(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) PaddingLeft(i int) Style {
|
||||||
|
return Style{s.Style.PaddingLeft(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) PaddingRight(i int) Style {
|
||||||
|
return Style{s.Style.PaddingRight(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Margin(i ...int) Style {
|
||||||
|
return Style{s.Style.Margin(i...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) MarginTop(i int) Style {
|
||||||
|
return Style{s.Style.MarginTop(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) MarginBottom(i int) Style {
|
||||||
|
return Style{s.Style.MarginBottom(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) MarginLeft(i int) Style {
|
||||||
|
return Style{s.Style.MarginLeft(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) MarginRight(i int) Style {
|
||||||
|
return Style{s.Style.MarginRight(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Border(b lipgloss.Border, sides ...bool) Style {
|
||||||
|
return Style{s.Style.Border(b, sides...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) BorderStyle(b lipgloss.Border) Style {
|
||||||
|
return Style{s.Style.BorderStyle(b)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) BorderTop(v bool) Style {
|
||||||
|
return Style{s.Style.BorderTop(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) BorderBottom(v bool) Style {
|
||||||
|
return Style{s.Style.BorderBottom(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) BorderLeft(v bool) Style {
|
||||||
|
return Style{s.Style.BorderLeft(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) BorderRight(v bool) Style {
|
||||||
|
return Style{s.Style.BorderRight(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Align(p ...lipgloss.Position) Style {
|
||||||
|
return Style{s.Style.Align(p...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) AlignHorizontal(p lipgloss.Position) Style {
|
||||||
|
return Style{s.Style.AlignHorizontal(p)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) AlignVertical(p lipgloss.Position) Style {
|
||||||
|
return Style{s.Style.AlignVertical(p)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Inline(v bool) Style {
|
||||||
|
return Style{s.Style.Inline(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) MaxWidth(n int) Style {
|
||||||
|
return Style{s.Style.MaxWidth(n)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) MaxHeight(n int) Style {
|
||||||
|
return Style{s.Style.MaxHeight(n)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) TabWidth(n int) Style {
|
||||||
|
return Style{s.Style.TabWidth(n)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetBold() Style {
|
||||||
|
return Style{s.Style.UnsetBold()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetItalic() Style {
|
||||||
|
return Style{s.Style.UnsetItalic()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetUnderline() Style {
|
||||||
|
return Style{s.Style.UnsetUnderline()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetStrikethrough() Style {
|
||||||
|
return Style{s.Style.UnsetStrikethrough()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetBlink() Style {
|
||||||
|
return Style{s.Style.UnsetBlink()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetFaint() Style {
|
||||||
|
return Style{s.Style.UnsetFaint()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) UnsetReverse() Style {
|
||||||
|
return Style{s.Style.UnsetReverse()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Copy() Style {
|
||||||
|
return Style{s.Style}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Style) Inherit(i Style) Style {
|
||||||
|
return Style{s.Style.Inherit(i.Style)}
|
||||||
|
}
|
|
@ -171,7 +171,7 @@ func (r *colorResolver) resolveColor(key string, value any) (any, error) {
|
||||||
|
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.HasPrefix(v, "#") {
|
if strings.HasPrefix(v, "#") || v == "none" {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
return r.resolveReference(v)
|
return r.resolveReference(v)
|
||||||
|
@ -205,7 +205,7 @@ func (r *colorResolver) resolveColor(key string, value any) (any, error) {
|
||||||
func (r *colorResolver) resolveColorValue(value any) (any, error) {
|
func (r *colorResolver) resolveColorValue(value any) (any, error) {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.HasPrefix(v, "#") {
|
if strings.HasPrefix(v, "#") || v == "none" {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
return r.resolveReference(v)
|
return r.resolveReference(v)
|
||||||
|
@ -240,6 +240,12 @@ func (r *colorResolver) resolveReference(ref string) (any, error) {
|
||||||
func parseResolvedColor(value any) (compat.AdaptiveColor, error) {
|
func parseResolvedColor(value any) (compat.AdaptiveColor, error) {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
if v == "none" {
|
||||||
|
return compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
return compat.AdaptiveColor{
|
return compat.AdaptiveColor{
|
||||||
Dark: lipgloss.Color(v),
|
Dark: lipgloss.Color(v),
|
||||||
Light: lipgloss.Color(v),
|
Light: lipgloss.Color(v),
|
||||||
|
@ -277,6 +283,9 @@ func parseResolvedColor(value any) (compat.AdaptiveColor, error) {
|
||||||
func parseColorValue(value any) (color.Color, error) {
|
func parseColorValue(value any) (color.Color, error) {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
if v == "none" {
|
||||||
|
return lipgloss.NoColor{}, nil
|
||||||
|
}
|
||||||
return lipgloss.Color(v), nil
|
return lipgloss.Color(v), nil
|
||||||
case float64:
|
case float64:
|
||||||
return lipgloss.Color(fmt.Sprintf("%d", int(v))), nil
|
return lipgloss.Color(fmt.Sprintf("%d", int(v))), nil
|
||||||
|
|
|
@ -2,19 +2,25 @@ package theme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image/color"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/styles"
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||||
|
"github.com/charmbracelet/x/ansi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager handles theme registration, selection, and retrieval.
|
// Manager handles theme registration, selection, and retrieval.
|
||||||
// It maintains a registry of available themes and tracks the currently active theme.
|
// It maintains a registry of available themes and tracks the currently active theme.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
themes map[string]Theme
|
themes map[string]Theme
|
||||||
currentName string
|
currentName string
|
||||||
mu sync.RWMutex
|
currentUsesAnsiCache bool // Cache whether current theme uses ANSI colors
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global instance of the theme manager
|
// Global instance of the theme manager
|
||||||
|
@ -34,6 +40,7 @@ func RegisterTheme(name string, theme Theme) {
|
||||||
// If this is the first theme, make it the default
|
// If this is the first theme, make it the default
|
||||||
if globalManager.currentName == "" {
|
if globalManager.currentName == "" {
|
||||||
globalManager.currentName = name
|
globalManager.currentName = name
|
||||||
|
globalManager.currentUsesAnsiCache = themeUsesAnsiColors(theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +51,13 @@ func SetTheme(name string) error {
|
||||||
defer globalManager.mu.Unlock()
|
defer globalManager.mu.Unlock()
|
||||||
delete(styles.Registry, "charm")
|
delete(styles.Registry, "charm")
|
||||||
|
|
||||||
if _, exists := globalManager.themes[name]; !exists {
|
theme, exists := globalManager.themes[name]
|
||||||
|
if !exists {
|
||||||
return fmt.Errorf("theme '%s' not found", name)
|
return fmt.Errorf("theme '%s' not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
globalManager.currentName = name
|
globalManager.currentName = name
|
||||||
|
globalManager.currentUsesAnsiCache = themeUsesAnsiColors(theme)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -84,7 +93,11 @@ func AvailableThemes() []string {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
slices.SortFunc(names, func(a, b string) int {
|
slices.SortFunc(names, func(a, b string) int {
|
||||||
// list system theme first
|
if a == "system" {
|
||||||
|
return -1
|
||||||
|
} else if b == "system" {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
if a == "opencode" {
|
if a == "opencode" {
|
||||||
return -1
|
return -1
|
||||||
} else if b == "opencode" {
|
} else if b == "opencode" {
|
||||||
|
@ -103,3 +116,114 @@ func GetTheme(name string) Theme {
|
||||||
|
|
||||||
return globalManager.themes[name]
|
return globalManager.themes[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSystemTheme updates the system theme with terminal background info
|
||||||
|
func UpdateSystemTheme(terminalBg color.Color, isDark bool) {
|
||||||
|
globalManager.mu.Lock()
|
||||||
|
defer globalManager.mu.Unlock()
|
||||||
|
|
||||||
|
dynamicTheme := NewSystemTheme(terminalBg, isDark)
|
||||||
|
globalManager.themes["system"] = dynamicTheme
|
||||||
|
if globalManager.currentName == "system" {
|
||||||
|
globalManager.currentUsesAnsiCache = themeUsesAnsiColors(dynamicTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentThemeUsesAnsiColors returns true if the current theme uses ANSI 0-16 colors
|
||||||
|
func CurrentThemeUsesAnsiColors() bool {
|
||||||
|
// globalManager.mu.RLock()
|
||||||
|
// defer globalManager.mu.RUnlock()
|
||||||
|
|
||||||
|
return globalManager.currentUsesAnsiCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAnsiColor checks if a color represents an ANSI 0-16 color
|
||||||
|
func isAnsiColor(c color.Color) bool {
|
||||||
|
if _, ok := c.(lipgloss.NoColor); ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, ok := c.(ansi.BasicColor); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other color types, check if they represent ANSI colors
|
||||||
|
// by examining their string representation
|
||||||
|
if stringer, ok := c.(fmt.Stringer); ok {
|
||||||
|
str := stringer.String()
|
||||||
|
// Check if it's a numeric ANSI color (0-15)
|
||||||
|
if num, err := strconv.Atoi(str); err == nil && num >= 0 && num <= 15 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// adaptiveColorUsesAnsi checks if an AdaptiveColor uses ANSI colors
|
||||||
|
func adaptiveColorUsesAnsi(ac compat.AdaptiveColor) bool {
|
||||||
|
if isAnsiColor(ac.Dark) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isAnsiColor(ac.Light) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// themeUsesAnsiColors checks if a theme uses any ANSI 0-16 colors
|
||||||
|
func themeUsesAnsiColors(theme Theme) bool {
|
||||||
|
if theme == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return adaptiveColorUsesAnsi(theme.Primary()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Secondary()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Accent()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Error()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Warning()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Success()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Info()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Text()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.TextMuted()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Background()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.BackgroundPanel()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.BackgroundElement()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.Border()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.BorderActive()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.BorderSubtle()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffAdded()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffRemoved()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffContext()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffHunkHeader()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffHighlightAdded()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffHighlightRemoved()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffAddedBg()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffRemovedBg()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffContextBg()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffLineNumber()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffAddedLineNumberBg()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.DiffRemovedLineNumberBg()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownText()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownHeading()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownLink()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownLinkText()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownCode()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownBlockQuote()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownEmph()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownStrong()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownHorizontalRule()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownListItem()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownListEnumeration()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownImage()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownImageText()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.MarkdownCodeBlock()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxComment()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxKeyword()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxFunction()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxVariable()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxString()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxNumber()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxType()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxOperator()) ||
|
||||||
|
adaptiveColorUsesAnsi(theme.SyntaxPunctuation())
|
||||||
|
}
|
||||||
|
|
299
packages/tui/internal/theme/system.go
Normal file
299
packages/tui/internal/theme/system.go
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
package theme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/v2/compat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SystemTheme is a dynamic theme that derives its gray scale colors
|
||||||
|
// from the terminal's background color at runtime
|
||||||
|
type SystemTheme struct {
|
||||||
|
BaseTheme
|
||||||
|
terminalBg color.Color
|
||||||
|
terminalBgIsDark bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSystemTheme creates a new instance of the dynamic system theme
|
||||||
|
func NewSystemTheme(terminalBg color.Color, isDark bool) *SystemTheme {
|
||||||
|
theme := &SystemTheme{
|
||||||
|
terminalBg: terminalBg,
|
||||||
|
terminalBgIsDark: isDark,
|
||||||
|
}
|
||||||
|
theme.initializeColors()
|
||||||
|
return theme
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeColors sets up all theme colors
|
||||||
|
func (t *SystemTheme) initializeColors() {
|
||||||
|
// Generate gray scale based on terminal background
|
||||||
|
grays := t.generateGrayScale()
|
||||||
|
|
||||||
|
// Set ANSI colors for primary colors
|
||||||
|
t.PrimaryColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Cyan,
|
||||||
|
Light: lipgloss.Cyan,
|
||||||
|
}
|
||||||
|
t.SecondaryColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Magenta,
|
||||||
|
Light: lipgloss.Magenta,
|
||||||
|
}
|
||||||
|
t.AccentColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Cyan,
|
||||||
|
Light: lipgloss.Cyan,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status colors using ANSI
|
||||||
|
t.ErrorColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Red,
|
||||||
|
Light: lipgloss.Red,
|
||||||
|
}
|
||||||
|
t.WarningColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Yellow,
|
||||||
|
Light: lipgloss.Yellow,
|
||||||
|
}
|
||||||
|
t.SuccessColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Green,
|
||||||
|
Light: lipgloss.Green,
|
||||||
|
}
|
||||||
|
t.InfoColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Cyan,
|
||||||
|
Light: lipgloss.Cyan,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text colors
|
||||||
|
t.TextColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
// Derive muted text color from terminal foreground
|
||||||
|
t.TextMutedColor = t.generateMutedTextColor()
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
t.BackgroundColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
t.BackgroundPanelColor = grays[2]
|
||||||
|
t.BackgroundElementColor = grays[3]
|
||||||
|
|
||||||
|
// Border colors
|
||||||
|
t.BorderSubtleColor = grays[6]
|
||||||
|
t.BorderColor = grays[7]
|
||||||
|
t.BorderActiveColor = grays[8]
|
||||||
|
|
||||||
|
// Diff colors using ANSI colors
|
||||||
|
t.DiffAddedColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("2"), // green
|
||||||
|
Light: lipgloss.Color("2"),
|
||||||
|
}
|
||||||
|
t.DiffRemovedColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("1"), // red
|
||||||
|
Light: lipgloss.Color("1"),
|
||||||
|
}
|
||||||
|
t.DiffContextColor = grays[7] // Use gray for context
|
||||||
|
t.DiffHunkHeaderColor = grays[7]
|
||||||
|
t.DiffHighlightAddedColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("2"), // green
|
||||||
|
Light: lipgloss.Color("2"),
|
||||||
|
}
|
||||||
|
t.DiffHighlightRemovedColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("1"), // red
|
||||||
|
Light: lipgloss.Color("1"),
|
||||||
|
}
|
||||||
|
// Use subtle gray backgrounds for diff
|
||||||
|
t.DiffAddedBgColor = grays[2]
|
||||||
|
t.DiffRemovedBgColor = grays[2]
|
||||||
|
t.DiffContextBgColor = grays[1]
|
||||||
|
t.DiffLineNumberColor = grays[6]
|
||||||
|
t.DiffAddedLineNumberBgColor = grays[3]
|
||||||
|
t.DiffRemovedLineNumberBgColor = grays[3]
|
||||||
|
|
||||||
|
// Markdown colors using ANSI
|
||||||
|
t.MarkdownTextColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
t.MarkdownHeadingColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
t.MarkdownLinkColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("4"), // blue
|
||||||
|
Light: lipgloss.Color("4"),
|
||||||
|
}
|
||||||
|
t.MarkdownLinkTextColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("6"), // cyan
|
||||||
|
Light: lipgloss.Color("6"),
|
||||||
|
}
|
||||||
|
t.MarkdownCodeColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("2"), // green
|
||||||
|
Light: lipgloss.Color("2"),
|
||||||
|
}
|
||||||
|
t.MarkdownBlockQuoteColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("3"), // yellow
|
||||||
|
Light: lipgloss.Color("3"),
|
||||||
|
}
|
||||||
|
t.MarkdownEmphColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("3"), // yellow
|
||||||
|
Light: lipgloss.Color("3"),
|
||||||
|
}
|
||||||
|
t.MarkdownStrongColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
t.MarkdownHorizontalRuleColor = t.BorderColor
|
||||||
|
t.MarkdownListItemColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("4"), // blue
|
||||||
|
Light: lipgloss.Color("4"),
|
||||||
|
}
|
||||||
|
t.MarkdownListEnumerationColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("6"), // cyan
|
||||||
|
Light: lipgloss.Color("6"),
|
||||||
|
}
|
||||||
|
t.MarkdownImageColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("4"), // blue
|
||||||
|
Light: lipgloss.Color("4"),
|
||||||
|
}
|
||||||
|
t.MarkdownImageTextColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("6"), // cyan
|
||||||
|
Light: lipgloss.Color("6"),
|
||||||
|
}
|
||||||
|
t.MarkdownCodeBlockColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax colors
|
||||||
|
t.SyntaxCommentColor = t.TextMutedColor // Use same as muted text
|
||||||
|
t.SyntaxKeywordColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("5"), // magenta
|
||||||
|
Light: lipgloss.Color("5"),
|
||||||
|
}
|
||||||
|
t.SyntaxFunctionColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("4"), // blue
|
||||||
|
Light: lipgloss.Color("4"),
|
||||||
|
}
|
||||||
|
t.SyntaxVariableColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
t.SyntaxStringColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("2"), // green
|
||||||
|
Light: lipgloss.Color("2"),
|
||||||
|
}
|
||||||
|
t.SyntaxNumberColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("3"), // yellow
|
||||||
|
Light: lipgloss.Color("3"),
|
||||||
|
}
|
||||||
|
t.SyntaxTypeColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("6"), // cyan
|
||||||
|
Light: lipgloss.Color("6"),
|
||||||
|
}
|
||||||
|
t.SyntaxOperatorColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color("6"), // cyan
|
||||||
|
Light: lipgloss.Color("6"),
|
||||||
|
}
|
||||||
|
t.SyntaxPunctuationColor = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.NoColor{},
|
||||||
|
Light: lipgloss.NoColor{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateGrayScale creates a gray scale based on the terminal background
|
||||||
|
func (t *SystemTheme) generateGrayScale() map[int]compat.AdaptiveColor {
|
||||||
|
grays := make(map[int]compat.AdaptiveColor)
|
||||||
|
|
||||||
|
r, g, b, _ := t.terminalBg.RGBA()
|
||||||
|
bgR := float64(r >> 8)
|
||||||
|
bgG := float64(g >> 8)
|
||||||
|
bgB := float64(b >> 8)
|
||||||
|
|
||||||
|
luminance := 0.299*bgR + 0.587*bgG + 0.114*bgB
|
||||||
|
|
||||||
|
for i := 1; i <= 12; i++ {
|
||||||
|
var stepColor string
|
||||||
|
factor := float64(i) / 12.0
|
||||||
|
|
||||||
|
if t.terminalBgIsDark {
|
||||||
|
if luminance < 10 {
|
||||||
|
grayValue := int(factor * 0.4 * 255)
|
||||||
|
stepColor = fmt.Sprintf("#%02x%02x%02x", grayValue, grayValue, grayValue)
|
||||||
|
} else {
|
||||||
|
newLum := luminance + (255-luminance)*factor*0.4
|
||||||
|
|
||||||
|
ratio := newLum / luminance
|
||||||
|
newR := math.Min(bgR*ratio, 255)
|
||||||
|
newG := math.Min(bgG*ratio, 255)
|
||||||
|
newB := math.Min(bgB*ratio, 255)
|
||||||
|
|
||||||
|
stepColor = fmt.Sprintf("#%02x%02x%02x", int(newR), int(newG), int(newB))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if luminance > 245 {
|
||||||
|
grayValue := int(255 - factor*0.4*255)
|
||||||
|
stepColor = fmt.Sprintf("#%02x%02x%02x", grayValue, grayValue, grayValue)
|
||||||
|
} else {
|
||||||
|
newLum := luminance * (1 - factor*0.4)
|
||||||
|
|
||||||
|
ratio := newLum / luminance
|
||||||
|
newR := math.Max(bgR*ratio, 0)
|
||||||
|
newG := math.Max(bgG*ratio, 0)
|
||||||
|
newB := math.Max(bgB*ratio, 0)
|
||||||
|
|
||||||
|
stepColor = fmt.Sprintf("#%02x%02x%02x", int(newR), int(newG), int(newB))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grays[i] = compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color(stepColor),
|
||||||
|
Light: lipgloss.Color(stepColor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grays
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateMutedTextColor creates a muted gray color based on the terminal background
|
||||||
|
func (t *SystemTheme) generateMutedTextColor() compat.AdaptiveColor {
|
||||||
|
bgR, bgG, bgB, _ := t.terminalBg.RGBA()
|
||||||
|
|
||||||
|
bgRf := float64(bgR >> 8)
|
||||||
|
bgGf := float64(bgG >> 8)
|
||||||
|
bgBf := float64(bgB >> 8)
|
||||||
|
|
||||||
|
bgLum := 0.299*bgRf + 0.587*bgGf + 0.114*bgBf
|
||||||
|
|
||||||
|
var grayValue int
|
||||||
|
if t.terminalBgIsDark {
|
||||||
|
if bgLum < 10 {
|
||||||
|
// Very dark/black background
|
||||||
|
// grays[3] would be around #2e (46), so we need much lighter
|
||||||
|
grayValue = 180 // #b4b4b4
|
||||||
|
} else {
|
||||||
|
// Scale up for lighter dark backgrounds
|
||||||
|
// Ensure we're always significantly brighter than BackgroundElement
|
||||||
|
grayValue = min(int(160+(bgLum*0.3)), 200)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if bgLum > 245 {
|
||||||
|
// Very light/white background
|
||||||
|
// grays[3] would be around #f5 (245), so we need much darker
|
||||||
|
grayValue = 75 // #4b4b4b
|
||||||
|
} else {
|
||||||
|
// Scale down for darker light backgrounds
|
||||||
|
// Ensure we're always significantly darker than BackgroundElement
|
||||||
|
grayValue = max(int(100-((255-bgLum)*0.2)), 60)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutedColor := fmt.Sprintf("#%02x%02x%02x", grayValue, grayValue, grayValue)
|
||||||
|
|
||||||
|
return compat.AdaptiveColor{
|
||||||
|
Dark: lipgloss.Color(mutedColor),
|
||||||
|
Light: lipgloss.Color(mutedColor),
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/sst/opencode/internal/components/toast"
|
"github.com/sst/opencode/internal/components/toast"
|
||||||
"github.com/sst/opencode/internal/layout"
|
"github.com/sst/opencode/internal/layout"
|
||||||
"github.com/sst/opencode/internal/styles"
|
"github.com/sst/opencode/internal/styles"
|
||||||
|
"github.com/sst/opencode/internal/theme"
|
||||||
"github.com/sst/opencode/internal/util"
|
"github.com/sst/opencode/internal/util"
|
||||||
"github.com/sst/opencode/pkg/client"
|
"github.com/sst/opencode/pkg/client"
|
||||||
)
|
)
|
||||||
|
@ -230,9 +231,19 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return a, tea.Batch(cmds...)
|
return a, tea.Batch(cmds...)
|
||||||
case tea.BackgroundColorMsg:
|
case tea.BackgroundColorMsg:
|
||||||
styles.Terminal = &styles.TerminalInfo{
|
styles.Terminal = &styles.TerminalInfo{
|
||||||
|
Background: msg.Color,
|
||||||
BackgroundIsDark: msg.IsDark(),
|
BackgroundIsDark: msg.IsDark(),
|
||||||
}
|
}
|
||||||
slog.Debug("Background color", "isDark", msg.IsDark())
|
slog.Debug("Background color", "color", msg.String(), "isDark", msg.IsDark())
|
||||||
|
return a, func() tea.Msg {
|
||||||
|
theme.UpdateSystemTheme(
|
||||||
|
styles.Terminal.Background,
|
||||||
|
styles.Terminal.BackgroundIsDark,
|
||||||
|
)
|
||||||
|
return dialog.ThemeSelectedMsg{
|
||||||
|
ThemeName: theme.CurrentThemeName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
case modal.CloseModalMsg:
|
case modal.CloseModalMsg:
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
if a.modal != nil {
|
if a.modal != nil {
|
||||||
|
@ -424,6 +435,9 @@ func (a appModel) View() string {
|
||||||
|
|
||||||
appView = a.toastManager.RenderOverlay(appView)
|
appView = a.toastManager.RenderOverlay(appView)
|
||||||
|
|
||||||
|
if theme.CurrentThemeUsesAnsiColors() {
|
||||||
|
appView = util.ConvertRGBToAnsi16Colors(appView)
|
||||||
|
}
|
||||||
return appView
|
return appView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
93
packages/tui/internal/util/color.go
Normal file
93
packages/tui/internal/util/color.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var csiRE *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
csiRE = regexp.MustCompile(`\x1b\[([0-9;]+)m`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetFGMap = map[string]string{
|
||||||
|
"0;0;0": "\x1b[30m", // Black
|
||||||
|
"128;0;0": "\x1b[31m", // Red
|
||||||
|
"0;128;0": "\x1b[32m", // Green
|
||||||
|
"128;128;0": "\x1b[33m", // Yellow
|
||||||
|
"0;0;128": "\x1b[34m", // Blue
|
||||||
|
"128;0;128": "\x1b[35m", // Magenta
|
||||||
|
"0;128;128": "\x1b[36m", // Cyan
|
||||||
|
"192;192;192": "\x1b[37m", // White (light grey)
|
||||||
|
"128;128;128": "\x1b[90m", // Bright Black (dark grey)
|
||||||
|
"255;0;0": "\x1b[91m", // Bright Red
|
||||||
|
"0;255;0": "\x1b[92m", // Bright Green
|
||||||
|
"255;255;0": "\x1b[93m", // Bright Yellow
|
||||||
|
"0;0;255": "\x1b[94m", // Bright Blue
|
||||||
|
"255;0;255": "\x1b[95m", // Bright Magenta
|
||||||
|
"0;255;255": "\x1b[96m", // Bright Cyan
|
||||||
|
"255;255;255": "\x1b[97m", // Bright White
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetBGMap = map[string]string{
|
||||||
|
"0;0;0": "\x1b[40m",
|
||||||
|
"128;0;0": "\x1b[41m",
|
||||||
|
"0;128;0": "\x1b[42m",
|
||||||
|
"128;128;0": "\x1b[43m",
|
||||||
|
"0;0;128": "\x1b[44m",
|
||||||
|
"128;0;128": "\x1b[45m",
|
||||||
|
"0;128;128": "\x1b[46m",
|
||||||
|
"192;192;192": "\x1b[47m",
|
||||||
|
"128;128;128": "\x1b[100m",
|
||||||
|
"255;0;0": "\x1b[101m",
|
||||||
|
"0;255;0": "\x1b[102m",
|
||||||
|
"255;255;0": "\x1b[103m",
|
||||||
|
"0;0;255": "\x1b[104m",
|
||||||
|
"255;0;255": "\x1b[105m",
|
||||||
|
"0;255;255": "\x1b[106m",
|
||||||
|
"255;255;255": "\x1b[107m",
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertRGBToAnsi16Colors(s string) string {
|
||||||
|
return csiRE.ReplaceAllStringFunc(s, func(seq string) string {
|
||||||
|
params := strings.Split(csiRE.FindStringSubmatch(seq)[1], ";")
|
||||||
|
out := make([]string, 0, len(params))
|
||||||
|
|
||||||
|
for i := 0; i < len(params); {
|
||||||
|
// Detect “38 | 48 ; 2 ; r ; g ; b ( ; alpha? )”
|
||||||
|
if (params[i] == "38" || params[i] == "48") &&
|
||||||
|
i+4 < len(params) &&
|
||||||
|
params[i+1] == "2" {
|
||||||
|
|
||||||
|
key := strings.Join(params[i+2:i+5], ";")
|
||||||
|
var repl string
|
||||||
|
if params[i] == "38" {
|
||||||
|
repl = targetFGMap[key]
|
||||||
|
} else {
|
||||||
|
repl = targetBGMap[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
if repl != "" { // exact RGB hit
|
||||||
|
out = append(out, repl[2:len(repl)-1])
|
||||||
|
i += 5 // skip 38/48;2;r;g;b
|
||||||
|
|
||||||
|
// if i == len(params)-1 && looksLikeByte(params[i]) {
|
||||||
|
// i++ // swallow the alpha byte
|
||||||
|
// }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Normal token — keep verbatim.
|
||||||
|
out = append(out, params[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return "\x1b[" + strings.Join(out, ";") + "m"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// func looksLikeByte(tok string) bool {
|
||||||
|
// v, err := strconv.Atoi(tok)
|
||||||
|
// return err == nil && v >= 0 && v <= 255
|
||||||
|
// }
|
|
@ -22,6 +22,11 @@
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"maximum": 255,
|
"maximum": 255,
|
||||||
"description": "ANSI color code (0-255)"
|
"description": "ANSI color code (0-255)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["none"],
|
||||||
|
"description": "No color (uses terminal default)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -110,6 +115,11 @@
|
||||||
"maximum": 255,
|
"maximum": 255,
|
||||||
"description": "ANSI color code (0-255, same for dark and light)"
|
"description": "ANSI color code (0-255, same for dark and light)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["none"],
|
||||||
|
"description": "No color (uses terminal default)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
||||||
|
@ -131,6 +141,11 @@
|
||||||
"maximum": 255,
|
"maximum": 255,
|
||||||
"description": "ANSI color code for dark mode"
|
"description": "ANSI color code for dark mode"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["none"],
|
||||||
|
"description": "No color (uses terminal default)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
||||||
|
@ -151,6 +166,11 @@
|
||||||
"maximum": 255,
|
"maximum": 255,
|
||||||
"description": "ANSI color code for light mode"
|
"description": "ANSI color code for light mode"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["none"],
|
||||||
|
"description": "No color (uses terminal default)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
"pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: Themes
|
title: Themes
|
||||||
---
|
---
|
||||||
|
|
||||||
opencode supports a flexible JSON-based theme system that allows users to create and customize themes easily.
|
opencode supports a flexible JSON-based theme system that allows users to create and customize themes easily.
|
||||||
|
|
||||||
## Theme Loading Hierarchy
|
## Theme Loading Hierarchy
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ Themes use a flexible JSON format with support for:
|
||||||
- **ANSI colors**: `3` (0-255)
|
- **ANSI colors**: `3` (0-255)
|
||||||
- **Color references**: `"primary"` or custom definitions
|
- **Color references**: `"primary"` or custom definitions
|
||||||
- **Dark/light variants**: `{"dark": "#000", "light": "#fff"}`
|
- **Dark/light variants**: `{"dark": "#000", "light": "#fff"}`
|
||||||
|
- **No color**: `"none"` - Uses the terminal's default color (transparent)
|
||||||
|
|
||||||
### Example Theme
|
### Example Theme
|
||||||
|
|
||||||
|
@ -270,10 +271,30 @@ Themes use a flexible JSON format with support for:
|
||||||
|
|
||||||
The `defs` section (optional) allows you to define reusable colors that can be referenced in the theme.
|
The `defs` section (optional) allows you to define reusable colors that can be referenced in the theme.
|
||||||
|
|
||||||
|
### Using "none" for Terminal Defaults
|
||||||
|
|
||||||
|
The special value `\"none\"` can be used for any color to inherit the terminal's default color. This is particularly useful for creating themes that blend seamlessly with your terminal's color scheme:
|
||||||
|
|
||||||
|
- `\"text\": \"none\"` - Uses terminal's default foreground color
|
||||||
|
- `\"background\": \"none\"` - Uses terminal's default background color
|
||||||
|
|
||||||
|
## The System Theme
|
||||||
|
|
||||||
|
The `system` theme is opencode's default theme, designed to automatically adapt to your terminal's color scheme. Unlike traditional themes that use fixed colors, the system theme:
|
||||||
|
|
||||||
|
- **Generates gray scale**: Creates a custom gray scale based on your terminal's background color, ensuring optimal contrast
|
||||||
|
- **Uses ANSI colors**: Leverages standard ANSI colors (0-15) for syntax highlighting and UI elements, which respect your terminal's color palette
|
||||||
|
- **Preserves terminal defaults**: Uses `none` for text and background colors to maintain your terminal's native appearance
|
||||||
|
|
||||||
|
The system theme is ideal for users who:
|
||||||
|
- Want opencode to match their terminal's appearance
|
||||||
|
- Use custom terminal color schemes
|
||||||
|
- Prefer a consistent look across all terminal applications
|
||||||
|
|
||||||
## Built-in Themes
|
## Built-in Themes
|
||||||
|
|
||||||
opencode comes with several built-in themes:
|
opencode comes with several built-in themes:
|
||||||
- `opencode` - Default opencode theme
|
- `system` - Default theme that dynamically adapts to your terminal's background color
|
||||||
- `tokyonight` - Tokyonight theme
|
- `tokyonight` - Tokyonight theme
|
||||||
- `everforest` - Everforest theme
|
- `everforest` - Everforest theme
|
||||||
- `ayu` - Ayu dark theme
|
- `ayu` - Ayu dark theme
|
||||||
|
@ -281,7 +302,8 @@ opencode comes with several built-in themes:
|
||||||
- `gruvbox` - Gruvbox theme
|
- `gruvbox` - Gruvbox theme
|
||||||
- `kanagawa` - Kanagawa theme
|
- `kanagawa` - Kanagawa theme
|
||||||
- `nord` - Nord theme
|
- `nord` - Nord theme
|
||||||
- and more (see ./packages/tui/internal/theme/themes)
|
- `matrix` - Hacker-style green on black theme
|
||||||
|
- `one-dark` - Atom One Dark inspired theme
|
||||||
|
|
||||||
## Using a Theme
|
## Using a Theme
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue