Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

Commit aad4090

Browse files
committed
wip theme + new UI
1 parent b37ff5f commit aad4090

18 files changed

Lines changed: 938 additions & 68 deletions

File tree

.opencode.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"$schema": "./opencode-schema.json",
33
"lsp": {
4-
"gopls": {
4+
"Go": {
55
"command": "gopls"
66
}
77
},
88
"tui": {
9-
"theme": "opencode-dark"
9+
"theme": "charm"
1010
}
1111
}

cspell.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"flagWords":[],"language":"en","words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable"],"version":"0.2"}
1+
{"words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps"],"version":"0.2","language":"en","flagWords":[]}

diff.diff

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
diff --git a/.opencode.json b/.opencode.json
2+
index 75e357d..59be1e8 100644
3+
--- a/.opencode.json
4+
+++ b/.opencode.json
5+
@@ -6,6 +6,6 @@
6+
}
7+
},
8+
"tui": {
9+
- "theme": "opencode-dark"
10+
+ "theme": "charm"
11+
}
12+
}
13+
diff --git a/go.mod b/go.mod
14+
index 18ad042..940a8e8 100644
15+
--- a/go.mod
16+
+++ b/go.mod
17+
@@ -36,6 +36,8 @@ require (
18+
github.com/stretchr/testify v1.10.0
19+
)
20+
21+
+require github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9 // indirect
22+
+
23+
require (
24+
cloud.google.com/go v0.116.0 // indirect
25+
cloud.google.com/go/auth v0.13.0 // indirect
26+
diff --git a/go.sum b/go.sum
27+
index f6e08b7..8f347ed 100644
28+
--- a/go.sum
29+
+++ b/go.sum
30+
@@ -84,6 +84,8 @@ github.com/charmbracelet/x/ansi v0.9.3-0.20250516160309-24eee56f89fa h1:JU05TLAB
31+
github.com/charmbracelet/x/ansi v0.9.3-0.20250516160309-24eee56f89fa/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
32+
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME=
33+
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk=
34+
+github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9 h1:f6tG7ApqIvXTpgF6MZ+C4Ga7669eiW9BsMkXEjDFHfY=
35+
+github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9/go.mod h1:vr+xCFylsPYq2qSz+n5/jItjcK2/PgrKFMTI7VRR6CI=
36+
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw=
37+
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
38+
github.com/charmbracelet/x/exp/slice v0.0.0-20250528180458-2d5d6cb84620 h1:/PN4jqP3ki9NvtHRrYZ9ewCutKZB6DK8frTW+Dj/MWs=
39+
diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go
40+
index 2ee0b04..52c4dae 100644
41+
--- a/internal/tui/components/chat/chat.go
42+
+++ b/internal/tui/components/chat/chat.go
43+
@@ -95,7 +95,7 @@ func lspsConfigured() string {
44+
func logoBlock() string {
45+
t := theme.CurrentTheme()
46+
return logo.Render(version.Version, true, logo.Opts{
47+
- FieldColor: t.Accent(),
48+
+ FieldColor: t.Secondary(),
49+
TitleColorA: t.Primary(),
50+
TitleColorB: t.Secondary(),
51+
CharmColor: t.Primary(),
52+
diff --git a/internal/tui/tui.go b/internal/tui/tui.go
53+
index 9e8a62a..3f07956 100644
54+
--- a/internal/tui/tui.go
55+
+++ b/internal/tui/tui.go
56+
@@ -18,6 +18,7 @@ import (
57+
"github.com/opencode-ai/opencode/internal/tui/util"
58+
)
59+
60+
+// appModel represents the main application model that manages pages, dialogs, and UI state.
61+
type appModel struct {
62+
width, height int
63+
keyMap KeyMap
64+
@@ -35,6 +36,7 @@ type appModel struct {
65+
completions completions.Completions
66+
}
67+
68+
+// Init initializes the application model and returns initial commands.
69+
func (a appModel) Init() tea.Cmd {
70+
var cmds []tea.Cmd
71+
cmd := a.pages[a.currentPage].Init()
72+
@@ -46,6 +48,7 @@ func (a appModel) Init() tea.Cmd {
73+
return tea.Batch(cmds...)
74+
}
75+
76+
+// Update handles incoming messages and updates the application state.
77+
func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
78+
var cmds []tea.Cmd
79+
var cmd tea.Cmd
80+
@@ -111,6 +114,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
81+
return a, tea.Batch(cmds...)
82+
}
83+
84+
+// handleWindowResize processes window resize events and updates all components.
85+
func (a *appModel) handleWindowResize(msg tea.WindowSizeMsg) tea.Cmd {
86+
var cmds []tea.Cmd
87+
msg.Height -= 1 // Make space for the status bar
88+
@@ -134,6 +138,7 @@ func (a *appModel) handleWindowResize(msg tea.WindowSizeMsg) tea.Cmd {
89+
return tea.Batch(cmds...)
90+
}
91+
92+
+// handleKeyPressMsg processes keyboard input and routes to appropriate handlers.
93+
func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
94+
switch {
95+
// completions
96+
@@ -182,11 +187,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
97+
}
98+
}
99+
100+
-// RegisterCommand adds a command to the command dialog
101+
-// func (a *appModel) RegisterCommand(cmd dialog.Command) {
102+
-// a.commands = append(a.commands, cmd)
103+
-// }
104+
-
105+
+// moveToPage handles navigation between different pages in the application.
106+
func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
107+
if a.app.CoderAgent.IsBusy() {
108+
// For now we don't move to any page if the agent is busy
109+
@@ -209,6 +210,7 @@ func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
110+
return tea.Batch(cmds...)
111+
}
112+
113+
+// View renders the complete application interface including pages, dialogs, and overlays.
114+
func (a *appModel) View() tea.View {
115+
pageView := a.pages[a.currentPage].View()
116+
components := []string{
117+
@@ -252,6 +254,7 @@ func (a *appModel) View() tea.View {
118+
return view
119+
}
120+
121+
+// New creates and initializes a new TUI application model.
122+
func New(app *app.App) tea.Model {
123+
startPage := page.ChatPage
124+
model := &appModel{

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
1919
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1.0.20250523195325-2d1af06b557c
2020
github.com/charmbracelet/x/ansi v0.9.3-0.20250516160309-24eee56f89fa
21+
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9
2122
github.com/fsnotify/fsnotify v1.8.0
2223
github.com/go-logfmt/logfmt v0.6.0
2324
github.com/google/uuid v1.6.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ github.com/charmbracelet/x/ansi v0.9.3-0.20250516160309-24eee56f89fa h1:JU05TLAB
8484
github.com/charmbracelet/x/ansi v0.9.3-0.20250516160309-24eee56f89fa/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
8585
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME=
8686
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk=
87+
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9 h1:f6tG7ApqIvXTpgF6MZ+C4Ga7669eiW9BsMkXEjDFHfY=
88+
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250530202730-6ba1785cd7b9/go.mod h1:vr+xCFylsPYq2qSz+n5/jItjcK2/PgrKFMTI7VRR6CI=
8789
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw=
8890
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
8991
github.com/charmbracelet/x/exp/slice v0.0.0-20250528180458-2d5d6cb84620 h1:/PN4jqP3ki9NvtHRrYZ9ewCutKZB6DK8frTW+Dj/MWs=

internal/tui/components/chat/chat.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func lspsConfigured() string {
9595
func logoBlock() string {
9696
t := theme.CurrentTheme()
9797
return logo.Render(version.Version, true, logo.Opts{
98-
FieldColor: t.Accent(),
98+
FieldColor: t.Secondary(),
9999
TitleColorA: t.Primary(),
100100
TitleColorB: t.Secondary(),
101101
CharmColor: t.Primary(),

internal/tui/components/chat/editor/editor.go

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -261,38 +261,34 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
261261
}
262262

263263
func (m *editorCmp) View() tea.View {
264-
t := theme.CurrentTheme()
265-
266-
// Style the prompt with theme colors
267-
style := lipgloss.NewStyle().
268-
Padding(0, 0, 0, 1).
269-
Bold(true).
270-
Foreground(t.Primary())
271-
264+
t := styles.CurrentTheme()
272265
cursor := m.textarea.Cursor()
273-
cursor.X = cursor.X + m.x + 2
274-
cursor.Y = cursor.Y + m.y + 1
266+
cursor.X = cursor.X + m.x + 1
267+
cursor.Y = cursor.Y + m.y + 1 // adjust for padding
275268
if len(m.attachments) == 0 {
276-
view := tea.NewView(lipgloss.JoinHorizontal(lipgloss.Top, style.Render(">"), m.textarea.View()))
269+
content := t.S().Base.Padding(1).Render(
270+
m.textarea.View(),
271+
)
272+
view := tea.NewView(content)
277273
view.SetCursor(cursor)
278274
return view
279275
}
280-
m.textarea.SetHeight(m.height - 1)
281-
view := tea.NewView(lipgloss.JoinVertical(lipgloss.Top,
282-
m.attachmentsContent(),
283-
lipgloss.JoinHorizontal(lipgloss.Top, style.Render(">"),
276+
content := t.S().Base.Padding(0, 1, 1, 1).Render(
277+
lipgloss.JoinVertical(lipgloss.Top,
278+
m.attachmentsContent(),
284279
m.textarea.View(),
285280
),
286-
))
281+
)
282+
view := tea.NewView(content)
287283
view.SetCursor(cursor)
288284
return view
289285
}
290286

291287
func (m *editorCmp) SetSize(width, height int) tea.Cmd {
292288
m.width = width
293289
m.height = height
294-
m.textarea.SetWidth(width - 3) // account for the prompt and padding right
295-
m.textarea.SetHeight(height)
290+
m.textarea.SetWidth(width - 2) // adjust for padding
291+
m.textarea.SetHeight(height - 2) // adjust for padding
296292
return nil
297293
}
298294

@@ -359,32 +355,18 @@ func (m *editorCmp) startCompletions() tea.Msg {
359355
}
360356

361357
func CreateTextArea(existing *textarea.Model) textarea.Model {
362-
t := theme.CurrentTheme()
363-
bgColor := t.Background()
364-
textColor := t.Text()
365-
textMutedColor := t.TextMuted()
366-
358+
t := styles.CurrentTheme()
367359
ta := textarea.New()
368-
s := textarea.DefaultDarkStyles()
369-
b := s.Blurred
370-
b.Base = styles.BaseStyle().Background(bgColor).Foreground(textColor)
371-
b.CursorLine = styles.BaseStyle().Background(bgColor)
372-
b.Placeholder = styles.BaseStyle().Background(bgColor).Foreground(textMutedColor)
373-
b.Text = styles.BaseStyle().Background(bgColor).Foreground(textColor)
374-
375-
f := s.Focused
376-
f.Base = styles.BaseStyle().Background(bgColor).Foreground(textColor)
377-
f.CursorLine = styles.BaseStyle().Background(bgColor)
378-
f.Placeholder = styles.BaseStyle().Background(bgColor).Foreground(textMutedColor)
379-
f.Text = styles.BaseStyle().Background(bgColor).Foreground(textColor)
380-
381-
s.Focused = f
382-
s.Blurred = b
383-
ta.SetStyles(s)
384-
385-
ta.Prompt = " "
360+
ta.SetStyles(t.S().TextArea)
361+
ta.SetPromptFunc(2, func(lineIndex int) string {
362+
if lineIndex == 0 {
363+
return "> "
364+
}
365+
return t.S().Muted.Render(": ")
366+
})
386367
ta.ShowLineNumbers = false
387368
ta.CharLimit = -1
369+
ta.Placeholder = "Tell me more about this project..."
388370
ta.SetVirtualCursor(false)
389371

390372
if existing != nil {

0 commit comments

Comments
 (0)