on main help sign in
more theme cleanup
dfd2378Kujtim Hoxha
cspell.json+1−1
@@ -1 +1 @@
1−{"words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps"],"version":"0.2","language":"en","flagWords":[]}
1+{"words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph"],"version":"0.2","language":"en","flagWords":[]}
internal/tui/components/chat/chat.go−4
@@ -14,7 +14,6 @@ import (
14 "github.com/opencode-ai/opencode/internal/session"
14 "github.com/opencode-ai/opencode/internal/session"
15 "github.com/opencode-ai/opencode/internal/tui/components/chat/messages"
15 "github.com/opencode-ai/opencode/internal/tui/components/chat/messages"
16 "github.com/opencode-ai/opencode/internal/tui/components/core/list"
16 "github.com/opencode-ai/opencode/internal/tui/components/core/list"
17− "github.com/opencode-ai/opencode/internal/tui/components/dialog"
18 "github.com/opencode-ai/opencode/internal/tui/layout"
17 "github.com/opencode-ai/opencode/internal/tui/layout"
19 "github.com/opencode-ai/opencode/internal/tui/util"
18 "github.com/opencode-ai/opencode/internal/tui/util"
20)
19)
@@ -87,9 +86,6 @@ func (m *messageListCmp) Init() tea.Cmd {
87// Update handles incoming messages and updates the component state.
86// Update handles incoming messages and updates the component state.
88func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
87func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
89 switch msg := msg.(type) {
88 switch msg := msg.(type) {
90− case dialog.ThemeChangedMsg:
91− m.listCmp.ResetView()
92− return m, nil
93 case SessionSelectedMsg:
89 case SessionSelectedMsg:
94 if msg.ID != m.session.ID {
90 if msg.ID != m.session.ID {
95 cmd := m.SetSession(msg)
91 cmd := m.SetSession(msg)
internal/tui/components/chat/editor/editor.go+4−8
@@ -22,7 +22,6 @@ import (
22 "github.com/opencode-ai/opencode/internal/tui/components/dialog"
22 "github.com/opencode-ai/opencode/internal/tui/components/dialog"
23 "github.com/opencode-ai/opencode/internal/tui/layout"
23 "github.com/opencode-ai/opencode/internal/tui/layout"
24 "github.com/opencode-ai/opencode/internal/tui/styles"
24 "github.com/opencode-ai/opencode/internal/tui/styles"
25− "github.com/opencode-ai/opencode/internal/tui/theme"
26 "github.com/opencode-ai/opencode/internal/tui/util"
25 "github.com/opencode-ai/opencode/internal/tui/util"
27)
26)
28
27
@@ -138,9 +137,6 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
138 var cmd tea.Cmd
137 var cmd tea.Cmd
139 var cmds []tea.Cmd
138 var cmds []tea.Cmd
140 switch msg := msg.(type) {
139 switch msg := msg.(type) {
141− case dialog.ThemeChangedMsg:
142− m.textarea = CreateTextArea(&m.textarea)
143− return m, cmd
144 case chat.SessionSelectedMsg:
140 case chat.SessionSelectedMsg:
145 if msg.ID != m.session.ID {
141 if msg.ID != m.session.ID {
146 m.session = msg
142 m.session = msg
@@ -300,11 +296,11 @@ func (m *editorCmp) GetSize() (int, int) {
300
296
301func (m *editorCmp) attachmentsContent() string {
297func (m *editorCmp) attachmentsContent() string {
302 var styledAttachments []string
298 var styledAttachments []string
303− t := theme.CurrentTheme()
299+ t := styles.CurrentTheme()
304− attachmentStyles := styles.BaseStyle().
300+ attachmentStyles := t.S().Base.
305 MarginLeft(1).
301 MarginLeft(1).
306− Background(t.TextMuted()).
302+ Background(t.FgMuted).
307− Foreground(t.Text())
303+ Foreground(t.FgBase)
308 for i, attachment := range m.attachments {
304 for i, attachment := range m.attachments {
309 var filename string
305 var filename string
310 if len(attachment.FileName) > 10 {
306 if len(attachment.FileName) > 10 {
internal/tui/components/chat/messages/messages.go+8−11
@@ -16,7 +16,6 @@ import (
16 "github.com/opencode-ai/opencode/internal/tui/components/anim"
16 "github.com/opencode-ai/opencode/internal/tui/components/anim"
17 "github.com/opencode-ai/opencode/internal/tui/layout"
17 "github.com/opencode-ai/opencode/internal/tui/layout"
18 "github.com/opencode-ai/opencode/internal/tui/styles"
18 "github.com/opencode-ai/opencode/internal/tui/styles"
19− "github.com/opencode-ai/opencode/internal/tui/theme"
20 "github.com/opencode-ai/opencode/internal/tui/util"
19 "github.com/opencode-ai/opencode/internal/tui/util"
21)
20)
22
21
@@ -124,7 +123,7 @@ func (m *messageCmp) textWidth() int {
124// style returns the lipgloss style for the message component.
123// style returns the lipgloss style for the message component.
125// Applies different border colors and styles based on message role and focus state.
124// Applies different border colors and styles based on message role and focus state.
126func (msg *messageCmp) style() lipgloss.Style {
125func (msg *messageCmp) style() lipgloss.Style {
127− t := theme.CurrentTheme()
126+ t := styles.CurrentTheme()
128 var borderColor color.Color
127 var borderColor color.Color
129 borderStyle := lipgloss.NormalBorder()
128 borderStyle := lipgloss.NormalBorder()
130 if msg.focused {
129 if msg.focused {
@@ -133,17 +132,16 @@ func (msg *messageCmp) style() lipgloss.Style {
133
132
134 switch msg.message.Role {
133 switch msg.message.Role {
135 case message.User:
134 case message.User:
136− borderColor = t.Secondary()
135+ borderColor = t.Secondary
137 case message.Assistant:
136 case message.Assistant:
138− borderColor = t.Primary()
137+ borderColor = t.Primary
139 default:
138 default:
140 // Tool call
139 // Tool call
141− borderColor = t.TextMuted()
140+ borderColor = t.BgSubtle
142 }
141 }
143
142
144− return styles.BaseStyle().
143+ return t.S().Muted.
145 BorderLeft(true).
144 BorderLeft(true).
146− Foreground(t.TextMuted()).
147 BorderForeground(borderColor).
145 BorderForeground(borderColor).
148 BorderStyle(borderStyle)
146 BorderStyle(borderStyle)
149}
147}
@@ -182,14 +180,13 @@ func (m *messageCmp) renderAssistantMessage() string {
182// renderUserMessage renders user messages with file attachments.
180// renderUserMessage renders user messages with file attachments.
183// Displays message content and any attached files with appropriate icons.
181// Displays message content and any attached files with appropriate icons.
184func (m *messageCmp) renderUserMessage() string {
182func (m *messageCmp) renderUserMessage() string {
185− t := theme.CurrentTheme()
183+ t := styles.CurrentTheme()
186 parts := []string{
184 parts := []string{
187 m.markdownContent(),
185 m.markdownContent(),
188 }
186 }
189− attachmentStyles := styles.BaseStyle().
187+ attachmentStyles := t.S().Text.
190 MarginLeft(1).
188 MarginLeft(1).
191− Background(t.BackgroundSecondary()).
189+ Background(t.BgSubtle)
192− Foreground(t.Text())
193 attachments := []string{}
190 attachments := []string{}
194 for _, attachment := range m.message.BinaryContent() {
191 for _, attachment := range m.message.BinaryContent() {
195 file := filepath.Base(attachment.Path)
192 file := filepath.Base(attachment.Path)
internal/tui/components/chat/messages/renderer.go+18−22
@@ -15,7 +15,6 @@ import (
15 "github.com/opencode-ai/opencode/internal/llm/agent"
15 "github.com/opencode-ai/opencode/internal/llm/agent"
16 "github.com/opencode-ai/opencode/internal/llm/tools"
16 "github.com/opencode-ai/opencode/internal/llm/tools"
17 "github.com/opencode-ai/opencode/internal/tui/styles"
17 "github.com/opencode-ai/opencode/internal/tui/styles"
18− "github.com/opencode-ai/opencode/internal/tui/theme"
19)
18)
20
19
21// responseContextHeight limits the number of lines displayed in tool output
20// responseContextHeight limits the number of lines displayed in tool output
@@ -107,7 +106,7 @@ func (br baseRenderer) renderWithParams(v *toolCallCmp, toolName string, args []
107 return joinHeaderBody(header, body)
106 return joinHeaderBody(header, body)
108}
107}
109
108
110−// unmarshalParams safely unmarshals JSON parameters
109+// unmarshalParams safely unmarshal JSON parameters
111func (br baseRenderer) unmarshalParams(input string, target any) error {
110func (br baseRenderer) unmarshalParams(input string, target any) error {
112 return json.Unmarshal([]byte(input), target)
111 return json.Unmarshal([]byte(input), target)
113}
112}
@@ -593,7 +592,7 @@ func joinHeaderBody(header, body string) string {
593}
592}
594
593
595func renderPlainContent(v *toolCallCmp, content string) string {
594func renderPlainContent(v *toolCallCmp, content string) string {
596− t := theme.CurrentTheme()
595+ t := styles.CurrentTheme()
597 content = strings.TrimSpace(content)
596 content = strings.TrimSpace(content)
598 lines := strings.Split(content, "\n")
597 lines := strings.Split(content, "\n")
599
598
@@ -606,58 +605,55 @@ func renderPlainContent(v *toolCallCmp, content string) string {
606 if len(ln) > v.textWidth() {
605 if len(ln) > v.textWidth() {
607 ln = v.fit(ln, v.textWidth())
606 ln = v.fit(ln, v.textWidth())
608 }
607 }
609− out = append(out, lipgloss.NewStyle().
608+ out = append(out, t.S().Muted.
610 Width(v.textWidth()).
609 Width(v.textWidth()).
611− Background(t.BackgroundSecondary()).
610+ Background(t.BgSubtle).
612− Foreground(t.TextMuted()).
613 Render(ln))
611 Render(ln))
614 }
612 }
615
613
616 if len(lines) > responseContextHeight {
614 if len(lines) > responseContextHeight {
617− out = append(out, lipgloss.NewStyle().
615+ out = append(out, t.S().Muted.
618− Background(t.BackgroundSecondary()).
616+ Background(t.BgSubtle).
619− Foreground(t.TextMuted()).
620 Render(fmt.Sprintf("... (%d lines)", len(lines)-responseContextHeight)))
617 Render(fmt.Sprintf("... (%d lines)", len(lines)-responseContextHeight)))
621 }
618 }
622 return strings.Join(out, "\n")
619 return strings.Join(out, "\n")
623}
620}
624
621
625func renderCodeContent(v *toolCallCmp, path, content string, offset int) string {
622func renderCodeContent(v *toolCallCmp, path, content string, offset int) string {
626− t := theme.CurrentTheme()
623+ t := styles.CurrentTheme()
627 truncated := truncateHeight(content, responseContextHeight)
624 truncated := truncateHeight(content, responseContextHeight)
628
625
629− highlighted, _ := highlight.SyntaxHighlight(truncated, path, t.BackgroundSecondary())
626+ highlighted, _ := highlight.SyntaxHighlight(truncated, path, t.BgSubtle)
630 lines := strings.Split(highlighted, "\n")
627 lines := strings.Split(highlighted, "\n")
631
628
632 if len(strings.Split(content, "\n")) > responseContextHeight {
629 if len(strings.Split(content, "\n")) > responseContextHeight {
633− lines = append(lines, lipgloss.NewStyle().
630+ lines = append(lines, t.S().Muted.
634− Background(t.BackgroundSecondary()).
631+ Background(t.BgSubtle).
635− Foreground(t.TextMuted()).
636 Render(fmt.Sprintf("... (%d lines)", len(strings.Split(content, "\n"))-responseContextHeight)))
632 Render(fmt.Sprintf("... (%d lines)", len(strings.Split(content, "\n"))-responseContextHeight)))
637 }
633 }
638
634
639 for i, ln := range lines {
635 for i, ln := range lines {
640− num := lipgloss.NewStyle().
636+ num := t.S().Muted.
637+ Background(t.BgSubtle).
638+ PaddingLeft(4).
641− PaddingLeft(4).PaddingRight(2).
639+ PaddingRight(2).
642− Background(t.BackgroundSecondary()).
643− Foreground(t.TextMuted()).
644 Render(fmt.Sprintf("%d", i+1+offset))
640 Render(fmt.Sprintf("%d", i+1+offset))
645 w := v.textWidth() - lipgloss.Width(num)
641 w := v.textWidth() - lipgloss.Width(num)
646 lines[i] = lipgloss.JoinHorizontal(lipgloss.Left,
642 lines[i] = lipgloss.JoinHorizontal(lipgloss.Left,
647 num,
643 num,
648− lipgloss.NewStyle().
644+ t.S().Base.
649 Width(w).
645 Width(w).
650− Background(t.BackgroundSecondary()).
646+ Background(t.BgSubtle).
651 Render(v.fit(ln, w)))
647 Render(v.fit(ln, w)))
652 }
648 }
653 return lipgloss.JoinVertical(lipgloss.Left, lines...)
649 return lipgloss.JoinVertical(lipgloss.Left, lines...)
654}
650}
655
651
656func (v *toolCallCmp) renderToolError() string {
652func (v *toolCallCmp) renderToolError() string {
657− t := theme.CurrentTheme()
653+ t := styles.CurrentTheme()
658 err := strings.ReplaceAll(v.result.Content, "\n", " ")
654 err := strings.ReplaceAll(v.result.Content, "\n", " ")
659 err = fmt.Sprintf("Error: %s", err)
655 err = fmt.Sprintf("Error: %s", err)
660− return styles.BaseStyle().Foreground(t.Error()).Render(v.fit(err, v.textWidth()))
656+ return t.S().Base.Foreground(t.Error).Render(v.fit(err, v.textWidth()))
661}
657}
662
658
663func removeWorkingDirPrefix(path string) string {
659func removeWorkingDirPrefix(path string) string {
internal/tui/components/chat/messages/tool.go+6−9
@@ -11,7 +11,6 @@ import (
11 "github.com/opencode-ai/opencode/internal/tui/components/anim"
11 "github.com/opencode-ai/opencode/internal/tui/components/anim"
12 "github.com/opencode-ai/opencode/internal/tui/layout"
12 "github.com/opencode-ai/opencode/internal/tui/layout"
13 "github.com/opencode-ai/opencode/internal/tui/styles"
13 "github.com/opencode-ai/opencode/internal/tui/styles"
14− "github.com/opencode-ai/opencode/internal/tui/theme"
15 "github.com/opencode-ai/opencode/internal/tui/util"
14 "github.com/opencode-ai/opencode/internal/tui/util"
16)
15)
17
16
@@ -216,19 +215,17 @@ func (m *toolCallCmp) renderPending() string {
216// style returns the lipgloss style for the tool call component.
215// style returns the lipgloss style for the tool call component.
217// Applies muted colors and focus-dependent border styles.
216// Applies muted colors and focus-dependent border styles.
218func (m *toolCallCmp) style() lipgloss.Style {
217func (m *toolCallCmp) style() lipgloss.Style {
219− t := theme.CurrentTheme()
218+ t := styles.CurrentTheme()
220 if m.isNested {
219 if m.isNested {
221− return styles.BaseStyle().
220+ return t.S().Muted
222− Foreground(t.TextMuted())
223 }
221 }
224 borderStyle := lipgloss.NormalBorder()
222 borderStyle := lipgloss.NormalBorder()
225 if m.focused {
223 if m.focused {
226 borderStyle = lipgloss.DoubleBorder()
224 borderStyle = lipgloss.DoubleBorder()
227 }
225 }
228− return styles.BaseStyle().
226+ return t.S().Muted.
229 BorderLeft(true).
227 BorderLeft(true).
230− Foreground(t.TextMuted()).
231− BorderForeground(t.TextMuted()).
228+ BorderForeground(t.Border).
232 BorderStyle(borderStyle)
229 BorderStyle(borderStyle)
233}
230}
234
231
@@ -240,8 +237,8 @@ func (m *toolCallCmp) textWidth() int {
240
237
241// fit truncates content to fit within the specified width with ellipsis
238// fit truncates content to fit within the specified width with ellipsis
242func (m *toolCallCmp) fit(content string, width int) string {
239func (m *toolCallCmp) fit(content string, width int) string {
243− t := theme.CurrentTheme()
240+ t := styles.CurrentTheme()
244− lineStyle := lipgloss.NewStyle().Background(t.BackgroundSecondary()).Foreground(t.TextMuted())
241+ lineStyle := t.S().Muted.Background(t.BgSubtle)
245 dots := lineStyle.Render("...")
242 dots := lineStyle.Render("...")
246 return ansi.Truncate(content, width, dots)
243 return ansi.Truncate(content, width, dots)
247}
244}
internal/tui/components/completions/completions.go+3−4
@@ -6,7 +6,6 @@ import (
6 "github.com/charmbracelet/lipgloss/v2"
6 "github.com/charmbracelet/lipgloss/v2"
7 "github.com/opencode-ai/opencode/internal/tui/components/core/list"
7 "github.com/opencode-ai/opencode/internal/tui/components/core/list"
8 "github.com/opencode-ai/opencode/internal/tui/styles"
8 "github.com/opencode-ai/opencode/internal/tui/styles"
9− "github.com/opencode-ai/opencode/internal/tui/theme"
10 "github.com/opencode-ai/opencode/internal/tui/util"
9 "github.com/opencode-ai/opencode/internal/tui/util"
11)
10)
12
11
@@ -172,11 +171,11 @@ func (c *completionsCmp) View() tea.View {
172}
171}
173
172
174func (c *completionsCmp) style() lipgloss.Style {
173func (c *completionsCmp) style() lipgloss.Style {
175− t := theme.CurrentTheme()
174+ t := styles.CurrentTheme()
176− return styles.BaseStyle().
175+ return t.S().Base.
177 Width(c.width).
176 Width(c.width).
178 Height(c.height).
177 Height(c.height).
179− Background(t.BackgroundSecondary())
178+ Background(t.BgSubtle)
180}
179}
181
180
182func (c *completionsCmp) Open() bool {
181func (c *completionsCmp) Open() bool {
internal/tui/components/dialog/filepicker.go+14−18
@@ -19,7 +19,6 @@ import (
19 "github.com/opencode-ai/opencode/internal/message"
19 "github.com/opencode-ai/opencode/internal/message"
20 "github.com/opencode-ai/opencode/internal/tui/image"
20 "github.com/opencode-ai/opencode/internal/tui/image"
21 "github.com/opencode-ai/opencode/internal/tui/styles"
21 "github.com/opencode-ai/opencode/internal/tui/styles"
22− "github.com/opencode-ai/opencode/internal/tui/theme"
23 "github.com/opencode-ai/opencode/internal/tui/util"
22 "github.com/opencode-ai/opencode/internal/tui/util"
24)
23)
25
24
@@ -258,7 +257,8 @@ func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) {
258}
257}
259
258
260func (f *filepickerCmp) View() tea.View {
259func (f *filepickerCmp) View() tea.View {
261− t := theme.CurrentTheme()
260+ t := styles.CurrentTheme()
261+ baseStyle := t.S().Base
262 const maxVisibleDirs = 20
262 const maxVisibleDirs = 20
263 const maxWidth = 80
263 const maxWidth = 80
264
264
@@ -286,12 +286,11 @@ func (f *filepickerCmp) View() tea.View {
286
286
287 for i := startIdx; i < endIdx; i++ {
287 for i := startIdx; i < endIdx; i++ {
288 file := f.dirs[i]
288 file := f.dirs[i]
289− itemStyle := styles.BaseStyle().Width(adjustedWidth)
289+ itemStyle := t.S().Text.Width(adjustedWidth)
290
290
291 if i == f.cursor {
291 if i == f.cursor {
292 itemStyle = itemStyle.
292 itemStyle = itemStyle.
293− Background(t.Primary()).
293+ Background(t.Primary).
294− Foreground(t.Background()).
295 Bold(true)
294 Bold(true)
296 }
295 }
297 filename := file.Name()
296 filename := file.Name()
@@ -309,20 +308,18 @@ func (f *filepickerCmp) View() tea.View {
309
308
310 // Pad to always show exactly 21 lines
309 // Pad to always show exactly 21 lines
311 for len(files) < maxVisibleDirs {
310 for len(files) < maxVisibleDirs {
312− files = append(files, styles.BaseStyle().Width(adjustedWidth).Render(""))
311+ files = append(files, baseStyle.Width(adjustedWidth).Render(""))
313 }
312 }
314
313
315− currentPath := styles.BaseStyle().
314+ currentPath := baseStyle.
316 Height(1).
315 Height(1).
317 Width(adjustedWidth).
316 Width(adjustedWidth).
318 Render(f.cwd.View())
317 Render(f.cwd.View())
319
318
320− viewportstyle := lipgloss.NewStyle().
319+ viewportstyle := baseStyle.
321 Width(f.viewport.Width()).
320 Width(f.viewport.Width()).
322− Background(t.Background()).
323 Border(lipgloss.RoundedBorder()).
321 Border(lipgloss.RoundedBorder()).
324− BorderForeground(t.TextMuted()).
322+ BorderForeground(t.BorderFocus).
325− BorderBackground(t.Background()).
326 Padding(2).
323 Padding(2).
327 Render(f.viewport.View())
324 Render(f.viewport.View())
328 var insertExitText string
325 var insertExitText string
@@ -335,17 +332,16 @@ func (f *filepickerCmp) View() tea.View {
335 content := lipgloss.JoinVertical(
332 content := lipgloss.JoinVertical(
336 lipgloss.Left,
333 lipgloss.Left,
337 currentPath,
334 currentPath,
338− styles.BaseStyle().Width(adjustedWidth).Render(""),
335+ baseStyle.Width(adjustedWidth).Render(""),
339− styles.BaseStyle().Width(adjustedWidth).Render(lipgloss.JoinVertical(lipgloss.Left, files...)),
336+ baseStyle.Width(adjustedWidth).Render(lipgloss.JoinVertical(lipgloss.Left, files...)),
340− styles.BaseStyle().Width(adjustedWidth).Render(""),
337+ baseStyle.Width(adjustedWidth).Render(""),
341− styles.BaseStyle().Foreground(t.TextMuted()).Width(adjustedWidth).Render(insertExitText),
338+ t.S().Muted.Width(adjustedWidth).Render(insertExitText),
342 )
339 )
343
340
344 f.cwd.SetValue(f.cwd.Value())
341 f.cwd.SetValue(f.cwd.Value())
345− contentStyle := styles.BaseStyle().Padding(1, 2).
342+ contentStyle := baseStyle.Padding(1, 2).
346 Border(lipgloss.RoundedBorder()).
343 Border(lipgloss.RoundedBorder()).
347− BorderBackground(t.Background()).
348− BorderForeground(t.TextMuted()).
344+ BorderForeground(t.BorderFocus).
349 Width(lipgloss.Width(content) + 4)
345 Width(lipgloss.Width(content) + 4)
350
346
351 return tea.NewView(
347 return tea.NewView(
internal/tui/components/dialog/help.godeleted−203
@@ -1,203 +0,0 @@
1−package dialog
2−
3−import (
4− "strings"
5−
6− "github.com/charmbracelet/bubbles/v2/key"
7− tea "github.com/charmbracelet/bubbletea/v2"
8− "github.com/charmbracelet/lipgloss/v2"
9− "github.com/opencode-ai/opencode/internal/tui/styles"
10− "github.com/opencode-ai/opencode/internal/tui/theme"
11− "github.com/opencode-ai/opencode/internal/tui/util"
12−)
13−
14−type helpCmp struct {
15− width int
16− height int
17− keys []key.Binding
18−}
19−
20−func (h *helpCmp) Init() tea.Cmd {
21− return nil
22−}
23−
24−func (h *helpCmp) SetBindings(k []key.Binding) {
25− h.keys = k
26−}
27−
28−func (h *helpCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
29− switch msg := msg.(type) {
30− case tea.WindowSizeMsg:
31− h.width = 90
32− h.height = msg.Height
33− }
34− return h, nil
35−}
36−
37−func removeDuplicateBindings(bindings []key.Binding) []key.Binding {
38− seen := make(map[string]struct{})
39− result := make([]key.Binding, 0, len(bindings))
40−
41− // Process bindings in reverse order
42− for i := len(bindings) - 1; i >= 0; i-- {
43− b := bindings[i]
44− k := strings.Join(b.Keys(), " ")
45− if _, ok := seen[k]; ok {
46− // duplicate, skip
47− continue
48− }
49− seen[k] = struct{}{}
50− // Add to the beginning of result to maintain original order
51− result = append([]key.Binding{b}, result...)
52− }
53−
54− return result
55−}
56−
57−func (h *helpCmp) render() string {
58− t := theme.CurrentTheme()
59− baseStyle := styles.BaseStyle()
60−
61− helpKeyStyle := styles.Bold().
62− Background(t.Background()).
63− Foreground(t.Text()).
64− Padding(0, 1, 0, 0)
65−
66− helpDescStyle := styles.Regular().
67− Background(t.Background()).
68− Foreground(t.TextMuted())
69−
70− // Compile list of bindings to render
71− bindings := removeDuplicateBindings(h.keys)
72−
73− // Enumerate through each group of bindings, populating a series of
74− // pairs of columns, one for keys, one for descriptions
75− var (
76− pairs []string
77− width int
78− rows = 12 - 2
79− )
80−
81− for i := 0; i < len(bindings); i += rows {
82− var (
83− keys []string
84− descs []string
85− )
86− for j := i; j < min(i+rows, len(bindings)); j++ {
87− keys = append(keys, helpKeyStyle.Render(bindings[j].Help().Key))
88− descs = append(descs, helpDescStyle.Render(bindings[j].Help().Desc))
89− }
90−
91− // Render pair of columns; beyond the first pair, render a three space
92− // left margin, in order to visually separate the pairs.
93− var cols []string
94− if len(pairs) > 0 {
95− cols = []string{baseStyle.Render(" ")}
96− }
97−
98− maxDescWidth := 0
99− for _, desc := range descs {
100− if maxDescWidth < lipgloss.Width(desc) {
101− maxDescWidth = lipgloss.Width(desc)
102− }
103− }
104− for i := range descs {
105− remainingWidth := maxDescWidth - lipgloss.Width(descs[i])
106− if remainingWidth > 0 {
107− descs[i] = descs[i] + baseStyle.Render(strings.Repeat(" ", remainingWidth))
108− }
109− }
110− maxKeyWidth := 0
111− for _, key := range keys {
112− if maxKeyWidth < lipgloss.Width(key) {
113− maxKeyWidth = lipgloss.Width(key)
114− }
115− }
116− for i := range keys {
117− remainingWidth := maxKeyWidth - lipgloss.Width(keys[i])
118− if remainingWidth > 0 {
119− keys[i] = keys[i] + baseStyle.Render(strings.Repeat(" ", remainingWidth))
120− }
121− }
122−
123− cols = append(cols,
124− strings.Join(keys, "\n"),
125− strings.Join(descs, "\n"),
126− )
127−
128− pair := baseStyle.Render(lipgloss.JoinHorizontal(lipgloss.Top, cols...))
129− // check whether it exceeds the maximum width avail (the width of the
130− // terminal, subtracting 2 for the borders).
131− width += lipgloss.Width(pair)
132− if width > h.width-2 {
133− break
134− }
135− pairs = append(pairs, pair)
136− }
137−
138− // https://github.com/charmbracelet/lipgloss/v2/issues/209
139− if len(pairs) > 1 {
140− prefix := pairs[:len(pairs)-1]
141− lastPair := pairs[len(pairs)-1]
142− prefix = append(prefix, lipgloss.Place(
143− lipgloss.Width(lastPair), // width
144− lipgloss.Height(prefix[0]), // height
145− lipgloss.Left, // x
146− lipgloss.Top, // y
147− lastPair, // content
148− lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())),
149− ))
150− content := baseStyle.Width(h.width).Render(
151− lipgloss.JoinHorizontal(
152− lipgloss.Top,
153− prefix...,
154− ),
155− )
156− return content
157− }
158−
159− // Join pairs of columns and enclose in a border
160− content := baseStyle.Width(h.width).Render(
161− lipgloss.JoinHorizontal(
162− lipgloss.Top,
163− pairs...,
164− ),
165− )
166− return content
167−}
168−
169−func (h *helpCmp) View() tea.View {
170− t := theme.CurrentTheme()
171− baseStyle := styles.BaseStyle()
172−
173− content := h.render()
174− header := baseStyle.
175− Bold(true).
176− Width(lipgloss.Width(content)).
177− Foreground(t.Primary()).
178− Render("Keyboard Shortcuts")
179−
180− return tea.NewView(
181− baseStyle.Padding(1).
182− Border(lipgloss.RoundedBorder()).
183− BorderForeground(t.TextMuted()).
184− Width(h.width).
185− BorderBackground(t.Background()).
186− Render(
187− lipgloss.JoinVertical(lipgloss.Center,
188− header,
189− baseStyle.Render(strings.Repeat(" ", lipgloss.Width(header))),
190− content,
191− ),
192− ),
193− )
194−}
195−
196−type HelpCmp interface {
197− util.Model
198− SetBindings([]key.Binding)
199−}
200−
201−func NewHelpCmp() HelpCmp {
202− return &helpCmp{}
203−}
internal/tui/components/dialog/init.go+12−20
@@ -6,7 +6,6 @@ import (
6 "github.com/charmbracelet/lipgloss/v2"
6 "github.com/charmbracelet/lipgloss/v2"
7
7
8 "github.com/opencode-ai/opencode/internal/tui/styles"
8 "github.com/opencode-ai/opencode/internal/tui/styles"
9− "github.com/opencode-ai/opencode/internal/tui/theme"
10 "github.com/opencode-ai/opencode/internal/tui/util"
9 "github.com/opencode-ai/opencode/internal/tui/util"
11)
10)
12
11
@@ -93,51 +92,45 @@ func (m InitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
93
92
94// View implements tea.Model.
93// View implements tea.Model.
95func (m InitDialogCmp) View() string {
94func (m InitDialogCmp) View() string {
96− t := theme.CurrentTheme()
95+ t := styles.CurrentTheme()
97− baseStyle := styles.BaseStyle()
96+ baseStyle := t.S().Base
98
97
99 // Calculate width needed for content
98 // Calculate width needed for content
100 maxWidth := 60 // Width for explanation text
99 maxWidth := 60 // Width for explanation text
101
100
102 title := baseStyle.
101 title := baseStyle.
103− Foreground(t.Primary()).
102+ Foreground(t.Primary).
104 Bold(true).
103 Bold(true).
105 Width(maxWidth).
104 Width(maxWidth).
106 Padding(0, 1).
105 Padding(0, 1).
107 Render("Initialize Project")
106 Render("Initialize Project")
108
107
109− explanation := baseStyle.
108+ explanation := t.S().Text.
110− Foreground(t.Text()).
111 Width(maxWidth).
109 Width(maxWidth).
112 Padding(0, 1).
110 Padding(0, 1).
113 Render("Initialization generates a new OpenCode.md file that contains information about your codebase, this file serves as memory for each project, you can freely add to it to help the agents be better at their job.")
111 Render("Initialization generates a new OpenCode.md file that contains information about your codebase, this file serves as memory for each project, you can freely add to it to help the agents be better at their job.")
114
112
115− question := baseStyle.
113+ question := t.S().Text.
116− Foreground(t.Text()).
117 Width(maxWidth).
114 Width(maxWidth).
118 Padding(1, 1).
115 Padding(1, 1).
119 Render("Would you like to initialize this project?")
116 Render("Would you like to initialize this project?")
120
117
121 maxWidth = min(maxWidth, m.width-10)
118 maxWidth = min(maxWidth, m.width-10)
122− yesStyle := baseStyle
119+ yesStyle := t.S().Text
123− noStyle := baseStyle
120+ noStyle := yesStyle
124
121
125 if m.selected == 0 {
122 if m.selected == 0 {
126 yesStyle = yesStyle.
123 yesStyle = yesStyle.
127− Background(t.Primary()).
124+ Background(t.Primary).
128− Foreground(t.Background()).
129 Bold(true)
125 Bold(true)
130 noStyle = noStyle.
126 noStyle = noStyle.
131− Background(t.Background()).
127+ Background(t.BgSubtle)
132− Foreground(t.Primary())
133 } else {
128 } else {
134 noStyle = noStyle.
129 noStyle = noStyle.
135− Background(t.Primary()).
130+ Background(t.Primary).
136− Foreground(t.Background()).
137 Bold(true)
131 Bold(true)
138 yesStyle = yesStyle.
132 yesStyle = yesStyle.
139− Background(t.Background()).
133+ Background(t.BgSubtle)
140− Foreground(t.Primary())
141 }
134 }
142
135
143 yes := yesStyle.Padding(0, 3).Render("Yes")
136 yes := yesStyle.Padding(0, 3).Render("Yes")
@@ -161,8 +154,7 @@ func (m InitDialogCmp) View() string {
161
154
162 return baseStyle.Padding(1, 2).
155 return baseStyle.Padding(1, 2).
163 Border(lipgloss.RoundedBorder()).
156 Border(lipgloss.RoundedBorder()).
164− BorderBackground(t.Background()).
165− BorderForeground(t.TextMuted()).
157+ BorderForeground(t.BorderFocus).
166 Width(lipgloss.Width(content) + 4).
158 Width(lipgloss.Width(content) + 4).
167 Render(content)
159 Render(content)
168}
160}
internal/tui/components/dialog/permission.go+39−51
@@ -13,7 +13,6 @@ import (
13 "github.com/opencode-ai/opencode/internal/permission"
13 "github.com/opencode-ai/opencode/internal/permission"
14 "github.com/opencode-ai/opencode/internal/tui/layout"
14 "github.com/opencode-ai/opencode/internal/tui/layout"
15 "github.com/opencode-ai/opencode/internal/tui/styles"
15 "github.com/opencode-ai/opencode/internal/tui/styles"
16− "github.com/opencode-ai/opencode/internal/tui/theme"
17 "github.com/opencode-ai/opencode/internal/tui/util"
16 "github.com/opencode-ai/opencode/internal/tui/util"
18)
17)
19
18
@@ -149,28 +148,26 @@ func (p *permissionDialogCmp) selectCurrentOption() tea.Cmd {
149}
148}
150
149
151func (p *permissionDialogCmp) renderButtons() string {
150func (p *permissionDialogCmp) renderButtons() string {
152− t := theme.CurrentTheme()
151+ t := styles.CurrentTheme()
153− baseStyle := styles.BaseStyle()
154
152
155− allowStyle := baseStyle
153+ allowStyle := t.S().Text
156− allowSessionStyle := baseStyle
154+ allowSessionStyle := allowStyle
157− denyStyle := baseStyle
155+ denyStyle := allowStyle
158− spacerStyle := baseStyle.Background(t.Background())
159
156
160 // Style the selected button
157 // Style the selected button
161 switch p.selectedOption {
158 switch p.selectedOption {
162 case 0:
159 case 0:
163− allowStyle = allowStyle.Background(t.Primary()).Foreground(t.Background())
160+ allowStyle = allowStyle.Background(t.Primary)
164− allowSessionStyle = allowSessionStyle.Background(t.Background()).Foreground(t.Primary())
161+ allowSessionStyle = allowSessionStyle.Background(t.BgSubtle)
165− denyStyle = denyStyle.Background(t.Background()).Foreground(t.Primary())
162+ denyStyle = denyStyle.Background(t.BgSubtle)
166 case 1:
163 case 1:
167− allowStyle = allowStyle.Background(t.Background()).Foreground(t.Primary())
164+ allowStyle = allowStyle.Background(t.BgSubtle)
168− allowSessionStyle = allowSessionStyle.Background(t.Primary()).Foreground(t.Background())
165+ allowSessionStyle = allowSessionStyle.Background(t.Primary)
169− denyStyle = denyStyle.Background(t.Background()).Foreground(t.Primary())
166+ denyStyle = denyStyle.Background(t.BgSubtle)
170 case 2:
167 case 2:
171− allowStyle = allowStyle.Background(t.Background()).Foreground(t.Primary())
168+ allowStyle = allowStyle.Background(t.BgSubtle)
172− allowSessionStyle = allowSessionStyle.Background(t.Background()).Foreground(t.Primary())
169+ allowSessionStyle = allowSessionStyle.Background(t.BgSubtle)
173− denyStyle = denyStyle.Background(t.Primary()).Foreground(t.Background())
170+ denyStyle = denyStyle.Background(t.Primary)
174 }
171 }
175
172
176 allowButton := allowStyle.Padding(0, 1).Render("Allow (a)")
173 allowButton := allowStyle.Padding(0, 1).Render("Allow (a)")
@@ -180,33 +177,31 @@ func (p *permissionDialogCmp) renderButtons() string {
180 content := lipgloss.JoinHorizontal(
177 content := lipgloss.JoinHorizontal(
181 lipgloss.Left,
178 lipgloss.Left,
182 allowButton,
179 allowButton,
183− spacerStyle.Render(" "),
180+ " ",
184 allowSessionButton,
181 allowSessionButton,
185− spacerStyle.Render(" "),
182+ " ",
186 denyButton,
183 denyButton,
187− spacerStyle.Render(" "),
184+ " ",
188 )
185 )
189
186
190 remainingWidth := p.width - lipgloss.Width(content)
187 remainingWidth := p.width - lipgloss.Width(content)
191 if remainingWidth > 0 {
188 if remainingWidth > 0 {
192− content = spacerStyle.Render(strings.Repeat(" ", remainingWidth)) + content
189+ content = strings.Repeat(" ", remainingWidth) + content
193 }
190 }
194 return content
191 return content
195}
192}
196
193
197func (p *permissionDialogCmp) renderHeader() string {
194func (p *permissionDialogCmp) renderHeader() string {
198− t := theme.CurrentTheme()
195+ t := styles.CurrentTheme()
199− baseStyle := styles.BaseStyle()
196+ baseStyle := t.S().Base
200
197
201− toolKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("Tool")
198+ toolKey := t.S().Muted.Bold(true).Render("Tool")
202− toolValue := baseStyle.
199+ toolValue := t.S().Text.
203− Foreground(t.Text()).
204 Width(p.width - lipgloss.Width(toolKey)).
200 Width(p.width - lipgloss.Width(toolKey)).
205 Render(fmt.Sprintf(": %s", p.permission.ToolName))
201 Render(fmt.Sprintf(": %s", p.permission.ToolName))
206
202
207− pathKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("Path")
203+ pathKey := t.S().Muted.Bold(true).Render("Path")
208− pathValue := baseStyle.
204+ pathValue := t.S().Text.
209− Foreground(t.Text()).
210 Width(p.width - lipgloss.Width(pathKey)).
205 Width(p.width - lipgloss.Width(pathKey)).
211 Render(fmt.Sprintf(": %s", p.permission.Path))
206 Render(fmt.Sprintf(": %s", p.permission.Path))
212
207
@@ -228,12 +223,11 @@ func (p *permissionDialogCmp) renderHeader() string {
228 // Add tool-specific header information
223 // Add tool-specific header information
229 switch p.permission.ToolName {
224 switch p.permission.ToolName {
230 case tools.BashToolName:
225 case tools.BashToolName:
231− headerParts = append(headerParts, baseStyle.Foreground(t.TextMuted()).Width(p.width).Bold(true).Render("Command"))
226+ headerParts = append(headerParts, t.S().Muted.Width(p.width).Bold(true).Render("Command"))
232 case tools.EditToolName:
227 case tools.EditToolName:
233 params := p.permission.Params.(tools.EditPermissionsParams)
228 params := p.permission.Params.(tools.EditPermissionsParams)
234− fileKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("File")
229+ fileKey := t.S().Muted.Bold(true).Render("File")
235− filePath := baseStyle.
230+ filePath := t.S().Text.
236− Foreground(t.Text()).
237 Width(p.width - lipgloss.Width(fileKey)).
231 Width(p.width - lipgloss.Width(fileKey)).
238 Render(fmt.Sprintf(": %s", params.FilePath))
232 Render(fmt.Sprintf(": %s", params.FilePath))
239 headerParts = append(headerParts,
233 headerParts = append(headerParts,
@@ -247,9 +241,8 @@ func (p *permissionDialogCmp) renderHeader() string {
247
241
248 case tools.WriteToolName:
242 case tools.WriteToolName:
249 params := p.permission.Params.(tools.WritePermissionsParams)
243 params := p.permission.Params.(tools.WritePermissionsParams)
250− fileKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("File")
244+ fileKey := t.S().Muted.Bold(true).Render("File")
251− filePath := baseStyle.
245+ filePath := t.S().Text.
252− Foreground(t.Text()).
253 Width(p.width - lipgloss.Width(fileKey)).
246 Width(p.width - lipgloss.Width(fileKey)).
254 Render(fmt.Sprintf(": %s", params.FilePath))
247 Render(fmt.Sprintf(": %s", params.FilePath))
255 headerParts = append(headerParts,
248 headerParts = append(headerParts,
@@ -261,15 +254,14 @@ func (p *permissionDialogCmp) renderHeader() string {
261 baseStyle.Render(strings.Repeat(" ", p.width)),
254 baseStyle.Render(strings.Repeat(" ", p.width)),
262 )
255 )
263 case tools.FetchToolName:
256 case tools.FetchToolName:
264− headerParts = append(headerParts, baseStyle.Foreground(t.TextMuted()).Width(p.width).Bold(true).Render("URL"))
257+ headerParts = append(headerParts, t.S().Muted.Width(p.width).Bold(true).Render("URL"))
265 }
258 }
266
259
267− return lipgloss.NewStyle().Background(t.Background()).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
260+ return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
268}
261}
269
262
270func (p *permissionDialogCmp) renderBashContent() string {
263func (p *permissionDialogCmp) renderBashContent() string {
271− baseStyle := styles.BaseStyle()
264+ baseStyle := styles.CurrentTheme().S().Base
272−
273 if pr, ok := p.permission.Params.(tools.BashPermissionsParams); ok {
265 if pr, ok := p.permission.Params.(tools.BashPermissionsParams); ok {
274 content := fmt.Sprintf("```bash\n%s\n```", pr.Command)
266 content := fmt.Sprintf("```bash\n%s\n```", pr.Command)
275
267
@@ -327,8 +319,7 @@ func (p *permissionDialogCmp) renderWriteContent() string {
327}
319}
328
320
329func (p *permissionDialogCmp) renderFetchContent() string {
321func (p *permissionDialogCmp) renderFetchContent() string {
330− baseStyle := styles.BaseStyle()
322+ baseStyle := styles.CurrentTheme().S().Base
331−
332 if pr, ok := p.permission.Params.(tools.FetchPermissionsParams); ok {
323 if pr, ok := p.permission.Params.(tools.FetchPermissionsParams); ok {
333 content := fmt.Sprintf("```bash\n%s\n```", pr.URL)
324 content := fmt.Sprintf("```bash\n%s\n```", pr.URL)
334
325
@@ -349,7 +340,7 @@ func (p *permissionDialogCmp) renderFetchContent() string {
349}
340}
350
341
351func (p *permissionDialogCmp) renderDefaultContent() string {
342func (p *permissionDialogCmp) renderDefaultContent() string {
352− baseStyle := styles.BaseStyle()
343+ baseStyle := styles.CurrentTheme().S().Base
353
344
354 content := p.permission.Description
345 content := p.permission.Description
355
346
@@ -373,21 +364,19 @@ func (p *permissionDialogCmp) renderDefaultContent() string {
373}
364}
374
365
375func (p *permissionDialogCmp) styleViewport() string {
366func (p *permissionDialogCmp) styleViewport() string {
376− t := theme.CurrentTheme()
367+ t := styles.CurrentTheme()
377− contentStyle := lipgloss.NewStyle().
378− Background(t.Background())
379
368
380− return contentStyle.Render(p.contentViewPort.View())
369+ return t.S().Base.Render(p.contentViewPort.View())
381}
370}
382
371
383func (p *permissionDialogCmp) render() string {
372func (p *permissionDialogCmp) render() string {
384− t := theme.CurrentTheme()
373+ t := styles.CurrentTheme()
385− baseStyle := styles.BaseStyle()
374+ baseStyle := t.S().Base
386
375
387 title := baseStyle.
376 title := baseStyle.
388 Bold(true).
377 Bold(true).
389 Width(p.width - 4).
378 Width(p.width - 4).
390− Foreground(t.Primary()).
379+ Foreground(t.Primary).
391 Render("Permission Required")
380 Render("Permission Required")
392 // Render header
381 // Render header
393 headerContent := p.renderHeader()
382 headerContent := p.renderHeader()
@@ -428,8 +417,7 @@ func (p *permissionDialogCmp) render() string {
428 return baseStyle.
417 return baseStyle.
429 Padding(1, 0, 0, 1).
418 Padding(1, 0, 0, 1).
430 Border(lipgloss.RoundedBorder()).
419 Border(lipgloss.RoundedBorder()).
431− BorderBackground(t.Background()).
432− BorderForeground(t.TextMuted()).
420+ BorderForeground(t.BorderFocus).
433 Width(p.width).
421 Width(p.width).
434 Height(p.height).
422 Height(p.height).
435 Render(
423 Render(
internal/tui/components/dialog/theme.godeleted−201
@@ -1,201 +0,0 @@
1−package dialog
2−
3−import (
4− "github.com/charmbracelet/bubbles/v2/key"
5− tea "github.com/charmbracelet/bubbletea/v2"
6− "github.com/charmbracelet/lipgloss/v2"
7− "github.com/opencode-ai/opencode/internal/tui/layout"
8− "github.com/opencode-ai/opencode/internal/tui/styles"
9− "github.com/opencode-ai/opencode/internal/tui/theme"
10− "github.com/opencode-ai/opencode/internal/tui/util"
11−)
12−
13−// ThemeChangedMsg is sent when the theme is changed
14−type ThemeChangedMsg struct {
15− ThemeName string
16−}
17−
18−// CloseThemeDialogMsg is sent when the theme dialog is closed
19−type CloseThemeDialogMsg struct{}
20−
21−// ThemeDialog interface for the theme switching dialog
22−type ThemeDialog interface {
23− util.Model
24− layout.Bindings
25−}
26−
27−type themeDialogCmp struct {
28− themes []string
29− selectedIdx int
30− width int
31− height int
32− currentTheme string
33−}
34−
35−type themeKeyMap struct {
36− Up key.Binding
37− Down key.Binding
38− Enter key.Binding
39− Escape key.Binding
40− J key.Binding
41− K key.Binding
42−}
43−
44−var themeKeys = themeKeyMap{
45− Up: key.NewBinding(
46− key.WithKeys("up"),
47− key.WithHelp("↑", "previous theme"),
48− ),
49− Down: key.NewBinding(
50− key.WithKeys("down"),
51− key.WithHelp("↓", "next theme"),
52− ),
53− Enter: key.NewBinding(
54− key.WithKeys("enter"),
55− key.WithHelp("enter", "select theme"),
56− ),
57− Escape: key.NewBinding(
58− key.WithKeys("esc"),
59− key.WithHelp("esc", "close"),
60− ),
61− J: key.NewBinding(
62− key.WithKeys("j"),
63− key.WithHelp("j", "next theme"),
64− ),
65− K: key.NewBinding(
66− key.WithKeys("k"),
67− key.WithHelp("k", "previous theme"),
68− ),
69−}
70−
71−func (t *themeDialogCmp) Init() tea.Cmd {
72− // Load available themes and update selectedIdx based on current theme
73− t.themes = theme.AvailableThemes()
74− t.currentTheme = theme.CurrentThemeName()
75−
76− // Find the current theme in the list
77− for i, name := range t.themes {
78− if name == t.currentTheme {
79− t.selectedIdx = i
80− break
81− }
82− }
83−
84− return nil
85−}
86−
87−func (t *themeDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
88− switch msg := msg.(type) {
89− case tea.KeyPressMsg:
90− switch {
91− case key.Matches(msg, themeKeys.Up) || key.Matches(msg, themeKeys.K):
92− if t.selectedIdx > 0 {
93− t.selectedIdx--
94− }
95− return t, nil
96− case key.Matches(msg, themeKeys.Down) || key.Matches(msg, themeKeys.J):
97− if t.selectedIdx < len(t.themes)-1 {
98− t.selectedIdx++
99− }
100− return t, nil
101− case key.Matches(msg, themeKeys.Enter):
102− if len(t.themes) > 0 {
103− previousTheme := theme.CurrentThemeName()
104− selectedTheme := t.themes[t.selectedIdx]
105− if previousTheme == selectedTheme {
106− return t, util.CmdHandler(CloseThemeDialogMsg{})
107− }
108− if err := theme.SetTheme(selectedTheme); err != nil {
109− return t, util.ReportError(err)
110− }
111− return t, util.CmdHandler(ThemeChangedMsg{
112− ThemeName: selectedTheme,
113− })
114− }
115− case key.Matches(msg, themeKeys.Escape):
116− return t, util.CmdHandler(CloseThemeDialogMsg{})
117− }
118− case tea.WindowSizeMsg:
119− t.width = msg.Width
120− t.height = msg.Height
121− }
122− return t, nil
123−}
124−
125−func (t *themeDialogCmp) View() tea.View {
126− currentTheme := theme.CurrentTheme()
127− baseStyle := styles.BaseStyle()
128−
129− if len(t.themes) == 0 {
130− return tea.NewView(
131− baseStyle.Padding(1, 2).
132− Border(lipgloss.RoundedBorder()).
133− BorderBackground(currentTheme.Background()).
134− BorderForeground(currentTheme.TextMuted()).
135− Width(40).
136− Render("No themes available"),
137− )
138− }
139−
140− // Calculate max width needed for theme names
141− maxWidth := 40 // Minimum width
142− for _, themeName := range t.themes {
143− if len(themeName) > maxWidth-4 { // Account for padding
144− maxWidth = len(themeName) + 4
145− }
146− }
147−
148− maxWidth = max(30, min(maxWidth, t.width-15)) // Limit width to avoid overflow
149−
150− // Build the theme list
151− themeItems := make([]string, 0, len(t.themes))
152− for i, themeName := range t.themes {
153− itemStyle := baseStyle.Width(maxWidth)
154−
155− if i == t.selectedIdx {
156− itemStyle = itemStyle.
157− Background(currentTheme.Primary()).
158− Foreground(currentTheme.Background()).
159− Bold(true)
160− }
161−
162− themeItems = append(themeItems, itemStyle.Padding(0, 1).Render(themeName))
163− }
164−
165− title := baseStyle.
166− Foreground(currentTheme.Primary()).
167− Bold(true).
168− Width(maxWidth).
169− Padding(0, 1).
170− Render("Select Theme")
171−
172− content := lipgloss.JoinVertical(
173− lipgloss.Left,
174− title,
175− baseStyle.Width(maxWidth).Render(""),
176− baseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, themeItems...)),
177− baseStyle.Width(maxWidth).Render(""),
178− )
179−
180− return tea.NewView(
181− baseStyle.Padding(1, 2).
182− Border(lipgloss.RoundedBorder()).
183− BorderBackground(currentTheme.Background()).
184− BorderForeground(currentTheme.TextMuted()).
185− Width(lipgloss.Width(content) + 4).
186− Render(content),
187− )
188−}
189−
190−func (t *themeDialogCmp) BindingKeys() []key.Binding {
191− return layout.KeyMapToSlice(themeKeys)
192−}
193−
194−// NewThemeDialogCmp creates a new theme switching dialog
195−func NewThemeDialogCmp() ThemeDialog {
196− return &themeDialogCmp{
197− themes: []string{},
198− selectedIdx: 0,
199− currentTheme: "",
200− }
201−}
internal/tui/components/dialogs/commands/arguments.go+16−33
@@ -11,7 +11,6 @@ import (
11 "github.com/charmbracelet/lipgloss/v2"
11 "github.com/charmbracelet/lipgloss/v2"
12 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
12 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
13 "github.com/opencode-ai/opencode/internal/tui/styles"
13 "github.com/opencode-ai/opencode/internal/tui/styles"
14− "github.com/opencode-ai/opencode/internal/tui/theme"
15 "github.com/opencode-ai/opencode/internal/tui/util"
14 "github.com/opencode-ai/opencode/internal/tui/util"
16)
15)
17
16
@@ -54,7 +53,7 @@ type commandArgumentsDialogCmp struct {
54}
53}
55
54
56func NewCommandArgumentsDialog(commandID, content string, argNames []string) CommandArgumentsDialog {
55func NewCommandArgumentsDialog(commandID, content string, argNames []string) CommandArgumentsDialog {
57− t := theme.CurrentTheme()
56+ t := styles.CurrentTheme()
58 inputs := make([]textinput.Model, len(argNames))
57 inputs := make([]textinput.Model, len(argNames))
59
58
60 for i, name := range argNames {
59 for i, name := range argNames {
@@ -63,15 +62,8 @@ func NewCommandArgumentsDialog(commandID, content string, argNames []string) Com
63 ti.SetWidth(40)
62 ti.SetWidth(40)
64 ti.SetVirtualCursor(false)
63 ti.SetVirtualCursor(false)
65 ti.Prompt = ""
64 ti.Prompt = ""
66− ds := ti.Styles()
65+
67−
68− ds.Blurred.Placeholder = ds.Blurred.Placeholder.Background(t.Background()).Foreground(t.TextMuted())
69− ds.Blurred.Prompt = ds.Blurred.Prompt.Background(t.Background()).Foreground(t.TextMuted())
70− ds.Blurred.Text = ds.Blurred.Text.Background(t.Background()).Foreground(t.TextMuted())
71− ds.Focused.Placeholder = ds.Blurred.Placeholder.Background(t.Background()).Foreground(t.TextMuted())
72− ds.Focused.Prompt = ds.Blurred.Prompt.Background(t.Background()).Foreground(t.Text())
73− ds.Focused.Text = ds.Blurred.Text.Background(t.Background()).Foreground(t.Text())
74− ti.SetStyles(ds)
66+ ti.SetStyles(t.S().TextInput)
75 // Only focus the first input initially
67 // Only focus the first input initially
76 if i == 0 {
68 if i == 0 {
77 ti.Focus()
69 ti.Focus()
@@ -148,42 +140,36 @@ func (c *commandArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
148
140
149// View implements CommandArgumentsDialog.
141// View implements CommandArgumentsDialog.
150func (c *commandArgumentsDialogCmp) View() tea.View {
142func (c *commandArgumentsDialogCmp) View() tea.View {
151− t := theme.CurrentTheme()
143+ t := styles.CurrentTheme()
152− baseStyle := styles.BaseStyle()
144+ baseStyle := t.S().Base
153
145
154 title := lipgloss.NewStyle().
146 title := lipgloss.NewStyle().
155− Foreground(t.Primary()).
147+ Foreground(t.Primary).
156 Bold(true).
148 Bold(true).
157 Padding(0, 1).
149 Padding(0, 1).
158− Background(t.Background()).
159 Render("Command Arguments")
150 Render("Command Arguments")
160
151
161− explanation := lipgloss.NewStyle().
152+ explanation := t.S().Text.
162− Foreground(t.Text()).
163 Padding(0, 1).
153 Padding(0, 1).
164− Background(t.Background()).
165 Render("This command requires arguments.")
154 Render("This command requires arguments.")
166
155
167 // Create input fields for each argument
156 // Create input fields for each argument
168 inputFields := make([]string, len(c.inputs))
157 inputFields := make([]string, len(c.inputs))
169 for i, input := range c.inputs {
158 for i, input := range c.inputs {
170 // Highlight the label of the focused input
159 // Highlight the label of the focused input
171− labelStyle := lipgloss.NewStyle().
160+ labelStyle := baseStyle.
172− Padding(1, 1, 0, 1).
161+ Padding(1, 1, 0, 1)
173− Background(t.Background())
174
162
175 if i == c.focusIndex {
163 if i == c.focusIndex {
176− labelStyle = labelStyle.Foreground(t.Text()).Bold(true)
164+ labelStyle = labelStyle.Foreground(t.FgBase).Bold(true)
177 } else {
165 } else {
178− labelStyle = labelStyle.Foreground(t.TextMuted())
166+ labelStyle = labelStyle.Foreground(t.FgMuted)
179 }
167 }
180
168
181 label := labelStyle.Render(c.argNames[i] + ":")
169 label := labelStyle.Render(c.argNames[i] + ":")
182
170
183− field := lipgloss.NewStyle().
171+ field := t.S().Text.
184− Foreground(t.Text()).
185 Padding(0, 1).
172 Padding(0, 1).
186− Background(t.Background()).
187 Render(input.View())
173 Render(input.View())
188
174
189 inputFields[i] = lipgloss.JoinVertical(lipgloss.Left, label, field)
175 inputFields[i] = lipgloss.JoinVertical(lipgloss.Left, label, field)
@@ -205,9 +191,7 @@ func (c *commandArgumentsDialogCmp) View() tea.View {
205 view := tea.NewView(
191 view := tea.NewView(
206 baseStyle.Padding(1, 1, 0, 1).
192 baseStyle.Padding(1, 1, 0, 1).
207 Border(lipgloss.RoundedBorder()).
193 Border(lipgloss.RoundedBorder()).
208− BorderBackground(t.Background()).
209− BorderForeground(t.TextMuted()).
194+ BorderForeground(t.BorderFocus).
210− Background(t.Background()).
211 Width(c.width).
195 Width(c.width).
212 Render(content),
196 Render(content),
213 )
197 )
@@ -228,13 +212,12 @@ func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
228}
212}
229
213
230func (c *commandArgumentsDialogCmp) style() lipgloss.Style {
214func (c *commandArgumentsDialogCmp) style() lipgloss.Style {
231− t := theme.CurrentTheme()
215+ t := styles.CurrentTheme()
232− return styles.BaseStyle().
216+ return t.S().Base.
233 Width(c.width).
217 Width(c.width).
234 Padding(1).
218 Padding(1).
235 Border(lipgloss.RoundedBorder()).
219 Border(lipgloss.RoundedBorder()).
236− BorderBackground(t.Background()).
237− BorderForeground(t.TextMuted())
220+ BorderForeground(t.BorderFocus)
238}
221}
239
222
240func (c *commandArgumentsDialogCmp) Position() (int, int) {
223func (c *commandArgumentsDialogCmp) Position() (int, int) {
internal/tui/components/dialogs/quit/quit.go+10−14
@@ -7,7 +7,6 @@ import (
7 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
7 "github.com/opencode-ai/opencode/internal/tui/components/dialogs"
8 "github.com/opencode-ai/opencode/internal/tui/layout"
8 "github.com/opencode-ai/opencode/internal/tui/layout"
9 "github.com/opencode-ai/opencode/internal/tui/styles"
9 "github.com/opencode-ai/opencode/internal/tui/styles"
10− "github.com/opencode-ai/opencode/internal/tui/theme"
11 "github.com/opencode-ai/opencode/internal/tui/util"
10 "github.com/opencode-ai/opencode/internal/tui/util"
12)
11)
13
12
@@ -69,26 +68,24 @@ func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
69
68
70// View renders the quit dialog with Yes/No buttons.
69// View renders the quit dialog with Yes/No buttons.
71func (q *quitDialogCmp) View() tea.View {
70func (q *quitDialogCmp) View() tea.View {
72− t := theme.CurrentTheme()
71+ t := styles.CurrentTheme()
73− baseStyle := styles.BaseStyle()
72+ baseStyle := t.S().Base
74−
75− yesStyle := baseStyle
73+ yesStyle := t.S().Text
76− noStyle := baseStyle
74+ noStyle := yesStyle
77− spacerStyle := baseStyle.Background(t.Background())
78
75
79 if q.selectedNo {
76 if q.selectedNo {
80− noStyle = noStyle.Background(t.Primary()).Foreground(t.Background())
77+ noStyle = noStyle.Background(t.Primary)
81− yesStyle = yesStyle.Background(t.Background()).Foreground(t.Primary())
78+ yesStyle = yesStyle.Background(t.BgSubtle)
82 } else {
79 } else {
83− yesStyle = yesStyle.Background(t.Primary()).Foreground(t.Background())
80+ yesStyle = yesStyle.Background(t.Primary)
84− noStyle = noStyle.Background(t.Background()).Foreground(t.Primary())
81+ noStyle = noStyle.Background(t.BgSubtle)
85 }
82 }
86
83
87 yesButton := yesStyle.Padding(0, 1).Render("Yes")
84 yesButton := yesStyle.Padding(0, 1).Render("Yes")
88 noButton := noStyle.Padding(0, 1).Render("No")
85 noButton := noStyle.Padding(0, 1).Render("No")
89
86
90 buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
87 buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
91− lipgloss.JoinHorizontal(lipgloss.Center, yesButton, spacerStyle.Render(" "), noButton),
88+ lipgloss.JoinHorizontal(lipgloss.Center, yesButton, " ", noButton),
92 )
89 )
93
90
94 content := baseStyle.Render(
91 content := baseStyle.Render(
@@ -103,8 +100,7 @@ func (q *quitDialogCmp) View() tea.View {
103 quitDialogStyle := baseStyle.
100 quitDialogStyle := baseStyle.
104 Padding(1, 2).
101 Padding(1, 2).
105 Border(lipgloss.RoundedBorder()).
102 Border(lipgloss.RoundedBorder()).
106− BorderBackground(t.Background()).
107− BorderForeground(t.TextMuted())
103+ BorderForeground(t.BorderFocus)
108
104
109 return tea.NewView(
105 return tea.NewView(
110 quitDialogStyle.Render(content),
106 quitDialogStyle.Render(content),
internal/tui/layout/overlay.godeleted−169
@@ -1,169 +0,0 @@
1−package layout
2−
3−import (
4− "strings"
5−
6− "github.com/charmbracelet/lipgloss/v2"
7− chAnsi "github.com/charmbracelet/x/ansi"
8− "github.com/muesli/ansi"
9− "github.com/muesli/reflow/truncate"
10− "github.com/muesli/termenv"
11− "github.com/opencode-ai/opencode/internal/tui/styles"
12− "github.com/opencode-ai/opencode/internal/tui/theme"
13− "github.com/opencode-ai/opencode/internal/tui/util"
14−)
15−
16−// Most of this code is borrowed from
17−// https://github.com/charmbracelet/lipgloss/v2/pull/102
18−// as well as the lipgloss library, with some modification for what I needed.
19−
20−// Split a string into lines, additionally returning the size of the widest
21−// line.
22−func getLines(s string) (lines []string, widest int) {
23− lines = strings.Split(s, "\n")
24−
25− for _, l := range lines {
26− w := ansi.PrintableRuneWidth(l)
27− if widest < w {
28− widest = w
29− }
30− }
31−
32− return lines, widest
33−}
34−
35−// PlaceOverlay places fg on top of bg.
36−func PlaceOverlay(
37− x, y int,
38− fg, bg string,
39− shadow bool, opts ...WhitespaceOption,
40−) string {
41− fgLines, fgWidth := getLines(fg)
42− bgLines, bgWidth := getLines(bg)
43− bgHeight := len(bgLines)
44− fgHeight := len(fgLines)
45−
46− if shadow {
47− t := theme.CurrentTheme()
48− baseStyle := styles.BaseStyle()
49−
50− var shadowbg string = ""
51− shadowchar := lipgloss.NewStyle().
52− Background(t.BackgroundDarker()).
53− Foreground(t.Background()).
54− Render("░")
55− bgchar := baseStyle.Render(" ")
56− for i := 0; i <= fgHeight; i++ {
57− if i == 0 {
58− shadowbg += bgchar + strings.Repeat(bgchar, fgWidth) + "\n"
59− } else {
60− shadowbg += bgchar + strings.Repeat(shadowchar, fgWidth) + "\n"
61− }
62− }
63−
64− fg = PlaceOverlay(0, 0, fg, shadowbg, false, opts...)
65− fgLines, fgWidth = getLines(fg)
66− fgHeight = len(fgLines)
67− }
68−
69− if fgWidth >= bgWidth && fgHeight >= bgHeight {
70− // FIXME: return fg or bg?
71− return fg
72− }
73− // TODO: allow placement outside of the bg box?
74− x = util.Clamp(x, 0, bgWidth-fgWidth)
75− y = util.Clamp(y, 0, bgHeight-fgHeight)
76−
77− ws := &whitespace{}
78− for _, opt := range opts {
79− opt(ws)
80− }
81−
82− var b strings.Builder
83− for i, bgLine := range bgLines {
84− if i > 0 {
85− b.WriteByte('\n')
86− }
87− if i < y || i >= y+fgHeight {
88− b.WriteString(bgLine)
89− continue
90− }
91−
92− pos := 0
93− if x > 0 {
94− left := truncate.String(bgLine, uint(x))
95− pos = ansi.PrintableRuneWidth(left)
96− b.WriteString(left)
97− if pos < x {
98− b.WriteString(ws.render(x - pos))
99− pos = x
100− }
101− }
102−
103− fgLine := fgLines[i-y]
104− b.WriteString(fgLine)
105− pos += ansi.PrintableRuneWidth(fgLine)
106−
107− right := cutLeft(bgLine, pos)
108− bgWidth := ansi.PrintableRuneWidth(bgLine)
109− rightWidth := ansi.PrintableRuneWidth(right)
110− if rightWidth <= bgWidth-pos {
111− b.WriteString(ws.render(bgWidth - rightWidth - pos))
112− }
113−
114− b.WriteString(right)
115− }
116−
117− return b.String()
118−}
119−
120−// cutLeft cuts printable characters from the left.
121−// This function is heavily based on muesli's ansi and truncate packages.
122−func cutLeft(s string, cutWidth int) string {
123− return chAnsi.Cut(s, cutWidth, lipgloss.Width(s))
124−}
125−
126−func max(a, b int) int {
127− if a > b {
128− return a
129− }
130− return b
131−}
132−
133−type whitespace struct {
134− style termenv.Style
135− chars string
136−}
137−
138−// Render whitespaces.
139−func (w whitespace) render(width int) string {
140− if w.chars == "" {
141− w.chars = " "
142− }
143−
144− r := []rune(w.chars)
145− j := 0
146− b := strings.Builder{}
147−
148− // Cycle through runes and print them into the whitespace.
149− for i := 0; i < width; {
150− b.WriteRune(r[j])
151− j++
152− if j >= len(r) {
153− j = 0
154− }
155− i += ansi.PrintableRuneWidth(string(r[j]))
156− }
157−
158− // Fill any extra gaps white spaces. This might be necessary if any runes
159− // are more than one cell wide, which could leave a one-rune gap.
160− short := width - ansi.PrintableRuneWidth(b.String())
161− if short > 0 {
162− b.WriteString(strings.Repeat(" ", short))
163− }
164−
165− return w.style.Styled(b.String())
166−}
167−
168−// WhitespaceOption sets a styling rule for rendering whitespace.
169−type WhitespaceOption func(*whitespace)
internal/tui/page/logs.go+1−1
@@ -43,7 +43,7 @@ func (p *logsPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
43}
43}
44
44
45func (p *logsPage) View() tea.View {
45func (p *logsPage) View() tea.View {
46− style := styles.BaseStyle().Width(p.width).Height(p.height)
46+ style := styles.CurrentTheme().S().Base.Width(p.width).Height(p.height)
47 return tea.NewView(
47 return tea.NewView(
48 style.Render(
48 style.Render(
49 lipgloss.JoinVertical(lipgloss.Top,
49 lipgloss.JoinVertical(lipgloss.Top,
internal/tui/styles/markdown.go+2−260
@@ -1,12 +1,7 @@
1package styles
1package styles
2
2
3import (
3import (
4− "fmt"
5− "image/color"
6−
7 "github.com/charmbracelet/glamour/v2"
4 "github.com/charmbracelet/glamour/v2"
8− "github.com/charmbracelet/glamour/v2/ansi"
9− "github.com/opencode-ai/opencode/internal/tui/theme"
10)
5)
11
6
12// Helper functions for style pointers
7// Helper functions for style pointers
@@ -16,263 +11,10 @@ func uintPtr(u uint) *uint { return &u }
16
11
17// returns a glamour TermRenderer configured with the current theme
12// returns a glamour TermRenderer configured with the current theme
18func GetMarkdownRenderer(width int) *glamour.TermRenderer {
13func GetMarkdownRenderer(width int) *glamour.TermRenderer {
14+ t := CurrentTheme()
19 r, _ := glamour.NewTermRenderer(
15 r, _ := glamour.NewTermRenderer(
20− glamour.WithStyles(generateMarkdownStyleConfig()),
16+ glamour.WithStyles(t.S().Markdown),
21 glamour.WithWordWrap(width),
17 glamour.WithWordWrap(width),
22 )
18 )
23 return r
19 return r
24}
20}
25−
26−// creates an ansi.StyleConfig for markdown rendering
27−// using adaptive colors from the provided theme.
28−func generateMarkdownStyleConfig() ansi.StyleConfig {
29− t := theme.CurrentTheme()
30−
31− return ansi.StyleConfig{
32− Document: ansi.StyleBlock{
33− StylePrimitive: ansi.StylePrimitive{
34− Color: stringPtr(colorToString(t.MarkdownText())),
35− },
36− Margin: uintPtr(defaultMargin),
37− },
38− BlockQuote: ansi.StyleBlock{
39− StylePrimitive: ansi.StylePrimitive{
40− Color: stringPtr(colorToString(t.MarkdownBlockQuote())),
41− Italic: boolPtr(true),
42− Prefix: "┃ ",
43− },
44− Indent: uintPtr(1),
45− IndentToken: stringPtr(BaseStyle().Render(" ")),
46− },
47− List: ansi.StyleList{
48− LevelIndent: defaultMargin,
49− StyleBlock: ansi.StyleBlock{
50− IndentToken: stringPtr(BaseStyle().Render(" ")),
51− StylePrimitive: ansi.StylePrimitive{
52− Color: stringPtr(colorToString(t.MarkdownText())),
53− },
54− },
55− },
56− Heading: ansi.StyleBlock{
57− StylePrimitive: ansi.StylePrimitive{
58− BlockSuffix: "\n",
59− Color: stringPtr(colorToString(t.MarkdownHeading())),
60− Bold: boolPtr(true),
61− },
62− },
63− H1: ansi.StyleBlock{
64− StylePrimitive: ansi.StylePrimitive{
65− Prefix: "# ",
66− Color: stringPtr(colorToString(t.MarkdownHeading())),
67− Bold: boolPtr(true),
68− },
69− },
70− H2: ansi.StyleBlock{
71− StylePrimitive: ansi.StylePrimitive{
72− Prefix: "## ",
73− Color: stringPtr(colorToString(t.MarkdownHeading())),
74− Bold: boolPtr(true),
75− },
76− },
77− H3: ansi.StyleBlock{
78− StylePrimitive: ansi.StylePrimitive{
79− Prefix: "### ",
80− Color: stringPtr(colorToString(t.MarkdownHeading())),
81− Bold: boolPtr(true),
82− },
83− },
84− H4: ansi.StyleBlock{
85− StylePrimitive: ansi.StylePrimitive{
86− Prefix: "#### ",
87− Color: stringPtr(colorToString(t.MarkdownHeading())),
88− Bold: boolPtr(true),
89− },
90− },
91− H5: ansi.StyleBlock{
92− StylePrimitive: ansi.StylePrimitive{
93− Prefix: "##### ",
94− Color: stringPtr(colorToString(t.MarkdownHeading())),
95− Bold: boolPtr(true),
96− },
97− },
98− H6: ansi.StyleBlock{
99− StylePrimitive: ansi.StylePrimitive{
100− Prefix: "###### ",
101− Color: stringPtr(colorToString(t.MarkdownHeading())),
102− Bold: boolPtr(true),
103− },
104− },
105− Strikethrough: ansi.StylePrimitive{
106− CrossedOut: boolPtr(true),
107− Color: stringPtr(colorToString(t.TextMuted())),
108− },
109− Emph: ansi.StylePrimitive{
110− Color: stringPtr(colorToString(t.MarkdownEmph())),
111− Italic: boolPtr(true),
112− },
113− Strong: ansi.StylePrimitive{
114− Bold: boolPtr(true),
115− Color: stringPtr(colorToString(t.MarkdownStrong())),
116− },
117− HorizontalRule: ansi.StylePrimitive{
118− Color: stringPtr(colorToString(t.MarkdownHorizontalRule())),
119− Format: "\n─────────────────────────────────────────\n",
120− },
121− Item: ansi.StylePrimitive{
122− BlockPrefix: "• ",
123− Color: stringPtr(colorToString(t.MarkdownListItem())),
124− },
125− Enumeration: ansi.StylePrimitive{
126− BlockPrefix: ". ",
127− Color: stringPtr(colorToString(t.MarkdownListEnumeration())),
128− },
129− Task: ansi.StyleTask{
130− StylePrimitive: ansi.StylePrimitive{},
131− Ticked: "[✓] ",
132− Unticked: "[ ] ",
133− },
134− Link: ansi.StylePrimitive{
135− Color: stringPtr(colorToString(t.MarkdownLink())),
136− Underline: boolPtr(true),
137− },
138− LinkText: ansi.StylePrimitive{
139− Color: stringPtr(colorToString(t.MarkdownLinkText())),
140− Bold: boolPtr(true),
141− },
142− Image: ansi.StylePrimitive{
143− Color: stringPtr(colorToString(t.MarkdownImage())),
144− Underline: boolPtr(true),
145− Format: "🖼 {{.text}}",
146− },
147− ImageText: ansi.StylePrimitive{
148− Color: stringPtr(colorToString(t.MarkdownImageText())),
149− Format: "{{.text}}",
150− },
151− Code: ansi.StyleBlock{
152− StylePrimitive: ansi.StylePrimitive{
153− Color: stringPtr(colorToString(t.MarkdownCode())),
154− Prefix: "",
155− Suffix: "",
156− },
157− },
158− CodeBlock: ansi.StyleCodeBlock{
159− StyleBlock: ansi.StyleBlock{
160− StylePrimitive: ansi.StylePrimitive{
161− Prefix: " ",
162− Color: stringPtr(colorToString(t.MarkdownCodeBlock())),
163− },
164− Margin: uintPtr(defaultMargin),
165− },
166− Chroma: &ansi.Chroma{
167− Text: ansi.StylePrimitive{
168− Color: stringPtr(colorToString(t.MarkdownText())),
169− },
170− Error: ansi.StylePrimitive{
171− Color: stringPtr(colorToString(t.Error())),
172− },
173− Comment: ansi.StylePrimitive{
174− Color: stringPtr(colorToString(t.SyntaxComment())),
175− },
176− CommentPreproc: ansi.StylePrimitive{
177− Color: stringPtr(colorToString(t.SyntaxKeyword())),
178− },
179− Keyword: ansi.StylePrimitive{
180− Color: stringPtr(colorToString(t.SyntaxKeyword())),
181− },
182− KeywordReserved: ansi.StylePrimitive{
183− Color: stringPtr(colorToString(t.SyntaxKeyword())),
184− },
185− KeywordNamespace: ansi.StylePrimitive{
186− Color: stringPtr(colorToString(t.SyntaxKeyword())),
187− },
188− KeywordType: ansi.StylePrimitive{
189− Color: stringPtr(colorToString(t.SyntaxType())),
190− },
191− Operator: ansi.StylePrimitive{
192− Color: stringPtr(colorToString(t.SyntaxOperator())),
193− },
194− Punctuation: ansi.StylePrimitive{
195− Color: stringPtr(colorToString(t.SyntaxPunctuation())),
196− },
197− Name: ansi.StylePrimitive{
198− Color: stringPtr(colorToString(t.SyntaxVariable())),
199− },
200− NameBuiltin: ansi.StylePrimitive{
201− Color: stringPtr(colorToString(t.SyntaxVariable())),
202− },
203− NameTag: ansi.StylePrimitive{
204− Color: stringPtr(colorToString(t.SyntaxKeyword())),
205− },
206− NameAttribute: ansi.StylePrimitive{
207− Color: stringPtr(colorToString(t.SyntaxFunction())),
208− },
209− NameClass: ansi.StylePrimitive{
210− Color: stringPtr(colorToString(t.SyntaxType())),
211− },
212− NameConstant: ansi.StylePrimitive{
213− Color: stringPtr(colorToString(t.SyntaxVariable())),
214− },
215− NameDecorator: ansi.StylePrimitive{
216− Color: stringPtr(colorToString(t.SyntaxFunction())),
217− },
218− NameFunction: ansi.StylePrimitive{
219− Color: stringPtr(colorToString(t.SyntaxFunction())),
220− },
221− LiteralNumber: ansi.StylePrimitive{
222− Color: stringPtr(colorToString(t.SyntaxNumber())),
223− },
224− LiteralString: ansi.StylePrimitive{
225− Color: stringPtr(colorToString(t.SyntaxString())),
226− },
227− LiteralStringEscape: ansi.StylePrimitive{
228− Color: stringPtr(colorToString(t.SyntaxKeyword())),
229− },
230− GenericDeleted: ansi.StylePrimitive{
231− Color: stringPtr(colorToString(t.DiffRemoved())),
232− },
233− GenericEmph: ansi.StylePrimitive{
234− Color: stringPtr(colorToString(t.MarkdownEmph())),
235− Italic: boolPtr(true),
236− },
237− GenericInserted: ansi.StylePrimitive{
238− Color: stringPtr(colorToString(t.DiffAdded())),
239− },
240− GenericStrong: ansi.StylePrimitive{
241− Color: stringPtr(colorToString(t.MarkdownStrong())),
242− Bold: boolPtr(true),
243− },
244− GenericSubheading: ansi.StylePrimitive{
245− Color: stringPtr(colorToString(t.MarkdownHeading())),
246− },
247− },
248− },
249− Table: ansi.StyleTable{
250− StyleBlock: ansi.StyleBlock{
251− StylePrimitive: ansi.StylePrimitive{
252− BlockPrefix: "\n",
253− BlockSuffix: "\n",
254− },
255− },
256− CenterSeparator: stringPtr("┼"),
257− ColumnSeparator: stringPtr("│"),
258− RowSeparator: stringPtr("─"),
259− },
260− DefinitionDescription: ansi.StylePrimitive{
261− BlockPrefix: "\n ❯ ",
262− Color: stringPtr(colorToString(t.MarkdownLinkText())),
263− },
264− Text: ansi.StylePrimitive{
265− Color: stringPtr(colorToString(t.MarkdownText())),
266− },
267− Paragraph: ansi.StyleBlock{
268− StylePrimitive: ansi.StylePrimitive{
269− Color: stringPtr(colorToString(t.MarkdownText())),
270− },
271− },
272− }
273−}
274−
275−func colorToString(c color.Color) string {
276− rgba := color.RGBAModel.Convert(c).(color.RGBA)
277− return fmt.Sprintf("#%02x%02x%02x", rgba.R, rgba.G, rgba.B)
278−}
internal/tui/styles/styles.godeleted−155
@@ -1,155 +0,0 @@
1−package styles
2−
3−import (
4− "image/color"
5−
6− "github.com/charmbracelet/lipgloss/v2"
7− "github.com/opencode-ai/opencode/internal/tui/theme"
8−)
9−
10−var ImageBakcground = "#212121"
11−
12−// Style generation functions that use the current theme
13−
14−// BaseStyle returns the base style with background and foreground colors
15−func BaseStyle() lipgloss.Style {
16− t := theme.CurrentTheme()
17− return lipgloss.NewStyle().
18− Background(t.Background()).
19− Foreground(t.Text())
20−}
21−
22−// Regular returns a basic unstyled lipgloss.Style
23−func Regular() lipgloss.Style {
24− return lipgloss.NewStyle()
25−}
26−
27−// Bold returns a bold style
28−func Bold() lipgloss.Style {
29− return Regular().Bold(true)
30−}
31−
32−// Padded returns a style with horizontal padding
33−func Padded() lipgloss.Style {
34− return Regular().Padding(0, 1)
35−}
36−
37−// Border returns a style with a normal border
38−func Border() lipgloss.Style {
39− t := theme.CurrentTheme()
40− return Regular().
41− Border(lipgloss.NormalBorder()).
42− BorderForeground(t.BorderNormal())
43−}
44−
45−// ThickBorder returns a style with a thick border
46−func ThickBorder() lipgloss.Style {
47− t := theme.CurrentTheme()
48− return Regular().
49− Border(lipgloss.ThickBorder()).
50− BorderForeground(t.BorderNormal())
51−}
52−
53−// DoubleBorder returns a style with a double border
54−func DoubleBorder() lipgloss.Style {
55− t := theme.CurrentTheme()
56− return Regular().
57− Border(lipgloss.DoubleBorder()).
58− BorderForeground(t.BorderNormal())
59−}
60−
61−// FocusedBorder returns a style with a border using the focused border color
62−func FocusedBorder() lipgloss.Style {
63− t := theme.CurrentTheme()
64− return Regular().
65− Border(lipgloss.NormalBorder()).
66− BorderForeground(t.BorderFocused())
67−}
68−
69−// DimBorder returns a style with a border using the dim border color
70−func DimBorder() lipgloss.Style {
71− t := theme.CurrentTheme()
72− return Regular().
73− Border(lipgloss.NormalBorder()).
74− BorderForeground(t.BorderDim())
75−}
76−
77−// PrimaryColor returns the primary color from the current theme
78−func PrimaryColor() color.Color {
79− return theme.CurrentTheme().Primary()
80−}
81−
82−// SecondaryColor returns the secondary color from the current theme
83−func SecondaryColor() color.Color {
84− return theme.CurrentTheme().Secondary()
85−}
86−
87−// AccentColor returns the accent color from the current theme
88−func AccentColor() color.Color {
89− return theme.CurrentTheme().Accent()
90−}
91−
92−// ErrorColor returns the error color from the current theme
93−func ErrorColor() color.Color {
94− return theme.CurrentTheme().Error()
95−}
96−
97−// WarningColor returns the warning color from the current theme
98−func WarningColor() color.Color {
99− return theme.CurrentTheme().Warning()
100−}
101−
102−// SuccessColor returns the success color from the current theme
103−func SuccessColor() color.Color {
104− return theme.CurrentTheme().Success()
105−}
106−
107−// InfoColor returns the info color from the current theme
108−func InfoColor() color.Color {
109− return theme.CurrentTheme().Info()
110−}
111−
112−// TextColor returns the text color from the current theme
113−func TextColor() color.Color {
114− return theme.CurrentTheme().Text()
115−}
116−
117−// TextMutedColor returns the muted text color from the current theme
118−func TextMutedColor() color.Color {
119− return theme.CurrentTheme().TextMuted()
120−}
121−
122−// TextEmphasizedColor returns the emphasized text color from the current theme
123−func TextEmphasizedColor() color.Color {
124− return theme.CurrentTheme().TextEmphasized()
125−}
126−
127−// BackgroundColor returns the background color from the current theme
128−func BackgroundColor() color.Color {
129− return theme.CurrentTheme().Background()
130−}
131−
132−// BackgroundSecondaryColor returns the secondary background color from the current theme
133−func BackgroundSecondaryColor() color.Color {
134− return theme.CurrentTheme().BackgroundSecondary()
135−}
136−
137−// BackgroundDarkerColor returns the darker background color from the current theme
138−func BackgroundDarkerColor() color.Color {
139− return theme.CurrentTheme().BackgroundDarker()
140−}
141−
142−// BorderNormalColor returns the normal border color from the current theme
143−func BorderNormalColor() color.Color {
144− return theme.CurrentTheme().BorderNormal()
145−}
146−
147−// BorderFocusedColor returns the focused border color from the current theme
148−func BorderFocusedColor() color.Color {
149− return theme.CurrentTheme().BorderFocused()
150−}
151−
152−// BorderDimColor returns the dim border color from the current theme
153−func BorderDimColor() color.Color {
154− return theme.CurrentTheme().BorderDim()
155−}