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

Commit 23e7a95

Browse files
committed
intiial layout
1 parent 6b1c64b commit 23e7a95

File tree

12 files changed

+638
-26
lines changed

12 files changed

+638
-26
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package chat
2+
3+
import (
4+
"github.com/charmbracelet/bubbles/key"
5+
"github.com/charmbracelet/bubbles/textarea"
6+
tea "github.com/charmbracelet/bubbletea"
7+
"github.com/charmbracelet/lipgloss"
8+
"github.com/kujtimiihoxha/termai/internal/tui/layout"
9+
"github.com/kujtimiihoxha/termai/internal/tui/styles"
10+
)
11+
12+
type editorCmp struct {
13+
textarea textarea.Model
14+
}
15+
16+
func (m *editorCmp) Init() tea.Cmd {
17+
return textarea.Blink
18+
}
19+
20+
func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
21+
var cmd tea.Cmd
22+
m.textarea, cmd = m.textarea.Update(msg)
23+
return m, cmd
24+
}
25+
26+
func (m *editorCmp) View() string {
27+
style := lipgloss.NewStyle().Padding(0, 0, 0, 1).Bold(true)
28+
29+
return lipgloss.JoinHorizontal(lipgloss.Top, style.Render(">"), m.textarea.View())
30+
}
31+
32+
func (m *editorCmp) SetSize(width, height int) {
33+
m.textarea.SetWidth(width - 3) // account for the prompt and padding right
34+
m.textarea.SetHeight(height)
35+
}
36+
37+
func (m *editorCmp) GetSize() (int, int) {
38+
return m.textarea.Width(), m.textarea.Height()
39+
}
40+
41+
func (m *editorCmp) BindingKeys() []key.Binding {
42+
return layout.KeyMapToSlice(m.textarea.KeyMap)
43+
}
44+
45+
func NewEditorCmp() tea.Model {
46+
ti := textarea.New()
47+
ti.Prompt = " "
48+
ti.ShowLineNumbers = false
49+
ti.BlurredStyle.Base = ti.BlurredStyle.Base.Background(styles.Background)
50+
ti.BlurredStyle.CursorLine = ti.BlurredStyle.CursorLine.Background(styles.Background)
51+
ti.BlurredStyle.Placeholder = ti.BlurredStyle.Placeholder.Background(styles.Background)
52+
ti.BlurredStyle.Text = ti.BlurredStyle.Text.Background(styles.Background)
53+
54+
ti.FocusedStyle.Base = ti.FocusedStyle.Base.Background(styles.Background)
55+
ti.FocusedStyle.CursorLine = ti.FocusedStyle.CursorLine.Background(styles.Background)
56+
ti.FocusedStyle.Placeholder = ti.FocusedStyle.Placeholder.Background(styles.Background)
57+
ti.FocusedStyle.Text = ti.BlurredStyle.Text.Background(styles.Background)
58+
ti.Focus()
59+
return &editorCmp{
60+
textarea: ti,
61+
}
62+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package chat
2+
3+
import tea "github.com/charmbracelet/bubbletea"
4+
5+
type messagesCmp struct{}
6+
7+
func (m *messagesCmp) Init() tea.Cmd {
8+
return nil
9+
}
10+
11+
func (m *messagesCmp) Update(tea.Msg) (tea.Model, tea.Cmd) {
12+
return m, nil
13+
}
14+
15+
func (m *messagesCmp) View() string {
16+
return "Messages"
17+
}
18+
19+
func NewMessagesCmp() tea.Model {
20+
return &messagesCmp{}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package chat
2+
3+
import tea "github.com/charmbracelet/bubbletea"
4+
5+
type sidebarCmp struct{}
6+
7+
func (m *sidebarCmp) Init() tea.Cmd {
8+
return nil
9+
}
10+
11+
func (m *sidebarCmp) Update(tea.Msg) (tea.Model, tea.Cmd) {
12+
return m, nil
13+
}
14+
15+
func (m *sidebarCmp) View() string {
16+
return "Sidebar"
17+
}
18+
19+
func NewSidebarCmp() tea.Model {
20+
return &sidebarCmp{}
21+
}

internal/tui/components/dialog/permission.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ func NewPermissionDialogCmd(permission permission.PermissionRequest) tea.Cmd {
441441
layout.WithSinglePaneBordered(true),
442442
layout.WithSinglePaneFocusable(true),
443443
layout.WithSinglePaneActiveColor(styles.Warning),
444-
layout.WithSignlePaneBorderText(map[layout.BorderPosition]string{
444+
layout.WithSinglePaneBorderText(map[layout.BorderPosition]string{
445445
layout.TopMiddleBorder: " Permission Required ",
446446
}),
447447
)

internal/tui/components/repl/editor.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -156,30 +156,36 @@ func (m *editorCmp) Cancel() tea.Cmd {
156156
}
157157

158158
func (m *editorCmp) Send() tea.Cmd {
159-
return func() tea.Msg {
160-
messages, err := m.app.Messages.List(m.sessionID)
161-
if err != nil {
162-
return util.ReportError(err)
163-
}
164-
if hasUnfinishedMessages(messages) {
165-
return util.ReportWarn("Assistant is still working on the previous message")
166-
}
167-
a, err := agent.NewCoderAgent(m.app)
168-
if err != nil {
169-
return util.ReportError(err)
170-
}
159+
if m.cancelMessage != nil {
160+
return util.ReportWarn("Assistant is still working on the previous message")
161+
}
171162

172-
content := strings.Join(m.editor.GetBuffer().Lines(), "\n")
173-
ctx, cancel := context.WithCancel(m.app.Context)
174-
m.cancelMessage = cancel
175-
go func() {
176-
defer cancel()
177-
a.Generate(ctx, m.sessionID, content)
178-
m.cancelMessage = nil
179-
}()
163+
messages, err := m.app.Messages.List(m.sessionID)
164+
if err != nil {
165+
return util.ReportError(err)
166+
}
167+
if hasUnfinishedMessages(messages) {
168+
return util.ReportWarn("Assistant is still working on the previous message")
169+
}
170+
171+
a, err := agent.NewCoderAgent(m.app)
172+
if err != nil {
173+
return util.ReportError(err)
174+
}
180175

181-
return m.editor.Reset()
176+
content := strings.Join(m.editor.GetBuffer().Lines(), "\n")
177+
if len(content) == 0 {
178+
return util.ReportWarn("Message is empty")
182179
}
180+
ctx, cancel := context.WithCancel(m.app.Context)
181+
m.cancelMessage = cancel
182+
go func() {
183+
defer cancel()
184+
a.Generate(ctx, m.sessionID, content)
185+
m.cancelMessage = nil
186+
}()
187+
188+
return m.editor.Reset()
183189
}
184190

185191
func (m *editorCmp) View() string {

internal/tui/layout/container.go

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package layout
2+
3+
import (
4+
"github.com/charmbracelet/bubbles/key"
5+
tea "github.com/charmbracelet/bubbletea"
6+
"github.com/charmbracelet/lipgloss"
7+
"github.com/kujtimiihoxha/termai/internal/tui/styles"
8+
)
9+
10+
type Container interface {
11+
tea.Model
12+
Sizeable
13+
}
14+
type container struct {
15+
width int
16+
height int
17+
18+
content tea.Model
19+
20+
// Style options
21+
paddingTop int
22+
paddingRight int
23+
paddingBottom int
24+
paddingLeft int
25+
26+
borderTop bool
27+
borderRight bool
28+
borderBottom bool
29+
borderLeft bool
30+
borderStyle lipgloss.Border
31+
borderColor lipgloss.TerminalColor
32+
33+
backgroundColor lipgloss.TerminalColor
34+
}
35+
36+
func (c *container) Init() tea.Cmd {
37+
return c.content.Init()
38+
}
39+
40+
func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
41+
u, cmd := c.content.Update(msg)
42+
c.content = u
43+
return c, cmd
44+
}
45+
46+
func (c *container) View() string {
47+
style := lipgloss.NewStyle()
48+
width := c.width
49+
height := c.height
50+
// Apply background color if specified
51+
if c.backgroundColor != nil {
52+
style = style.Background(c.backgroundColor)
53+
}
54+
55+
// Apply border if any side is enabled
56+
if c.borderTop || c.borderRight || c.borderBottom || c.borderLeft {
57+
// Adjust width and height for borders
58+
if c.borderTop {
59+
height--
60+
}
61+
if c.borderBottom {
62+
height--
63+
}
64+
if c.borderLeft {
65+
width--
66+
}
67+
if c.borderRight {
68+
width--
69+
}
70+
style = style.Border(c.borderStyle, c.borderTop, c.borderRight, c.borderBottom, c.borderLeft)
71+
72+
// Apply border color if specified
73+
if c.borderColor != nil {
74+
style = style.BorderBackground(c.backgroundColor).BorderForeground(c.borderColor)
75+
}
76+
}
77+
style = style.
78+
Width(width).
79+
Height(height).
80+
PaddingTop(c.paddingTop).
81+
PaddingRight(c.paddingRight).
82+
PaddingBottom(c.paddingBottom).
83+
PaddingLeft(c.paddingLeft)
84+
85+
return style.Render(c.content.View())
86+
}
87+
88+
func (c *container) SetSize(width, height int) {
89+
c.width = width
90+
c.height = height
91+
92+
// If the content implements Sizeable, adjust its size to account for padding and borders
93+
if sizeable, ok := c.content.(Sizeable); ok {
94+
// Calculate horizontal space taken by padding and borders
95+
horizontalSpace := c.paddingLeft + c.paddingRight
96+
if c.borderLeft {
97+
horizontalSpace++
98+
}
99+
if c.borderRight {
100+
horizontalSpace++
101+
}
102+
103+
// Calculate vertical space taken by padding and borders
104+
verticalSpace := c.paddingTop + c.paddingBottom
105+
if c.borderTop {
106+
verticalSpace++
107+
}
108+
if c.borderBottom {
109+
verticalSpace++
110+
}
111+
112+
// Set content size with adjusted dimensions
113+
contentWidth := max(0, width-horizontalSpace)
114+
contentHeight := max(0, height-verticalSpace)
115+
sizeable.SetSize(contentWidth, contentHeight)
116+
}
117+
}
118+
119+
func (c *container) GetSize() (int, int) {
120+
return c.width, c.height
121+
}
122+
123+
func (c *container) BindingKeys() []key.Binding {
124+
if b, ok := c.content.(Bindings); ok {
125+
return b.BindingKeys()
126+
}
127+
return []key.Binding{}
128+
}
129+
130+
type ContainerOption func(*container)
131+
132+
func NewContainer(content tea.Model, options ...ContainerOption) Container {
133+
c := &container{
134+
content: content,
135+
borderColor: styles.BorderColor,
136+
borderStyle: lipgloss.NormalBorder(),
137+
backgroundColor: styles.Background,
138+
}
139+
140+
for _, option := range options {
141+
option(c)
142+
}
143+
144+
return c
145+
}
146+
147+
// Padding options
148+
func WithPadding(top, right, bottom, left int) ContainerOption {
149+
return func(c *container) {
150+
c.paddingTop = top
151+
c.paddingRight = right
152+
c.paddingBottom = bottom
153+
c.paddingLeft = left
154+
}
155+
}
156+
157+
func WithPaddingAll(padding int) ContainerOption {
158+
return WithPadding(padding, padding, padding, padding)
159+
}
160+
161+
func WithPaddingHorizontal(padding int) ContainerOption {
162+
return func(c *container) {
163+
c.paddingLeft = padding
164+
c.paddingRight = padding
165+
}
166+
}
167+
168+
func WithPaddingVertical(padding int) ContainerOption {
169+
return func(c *container) {
170+
c.paddingTop = padding
171+
c.paddingBottom = padding
172+
}
173+
}
174+
175+
func WithBorder(top, right, bottom, left bool) ContainerOption {
176+
return func(c *container) {
177+
c.borderTop = top
178+
c.borderRight = right
179+
c.borderBottom = bottom
180+
c.borderLeft = left
181+
}
182+
}
183+
184+
func WithBorderAll() ContainerOption {
185+
return WithBorder(true, true, true, true)
186+
}
187+
188+
func WithBorderHorizontal() ContainerOption {
189+
return WithBorder(true, false, true, false)
190+
}
191+
192+
func WithBorderVertical() ContainerOption {
193+
return WithBorder(false, true, false, true)
194+
}
195+
196+
func WithBorderStyle(style lipgloss.Border) ContainerOption {
197+
return func(c *container) {
198+
c.borderStyle = style
199+
}
200+
}
201+
202+
func WithBorderColor(color lipgloss.TerminalColor) ContainerOption {
203+
return func(c *container) {
204+
c.borderColor = color
205+
}
206+
}
207+
208+
func WithRoundedBorder() ContainerOption {
209+
return WithBorderStyle(lipgloss.RoundedBorder())
210+
}
211+
212+
func WithThickBorder() ContainerOption {
213+
return WithBorderStyle(lipgloss.ThickBorder())
214+
}
215+
216+
func WithDoubleBorder() ContainerOption {
217+
return WithBorderStyle(lipgloss.DoubleBorder())
218+
}
219+
220+
func WithBackgroundColor(color lipgloss.TerminalColor) ContainerOption {
221+
return func(c *container) {
222+
c.backgroundColor = color
223+
}
224+
}

0 commit comments

Comments
 (0)