Skip to content

Commit 4c4356a

Browse files
committed
fix: Fixed tui resizing problems
1 parent 17e63af commit 4c4356a

File tree

1 file changed

+87
-12
lines changed

1 file changed

+87
-12
lines changed

internal/tui/tui.go

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path/filepath"
1010
"sort"
1111
"strings"
12+
"sync"
1213
"time"
1314
"unicode/utf8"
1415

@@ -132,6 +133,9 @@ type Model struct {
132133
lastUpdateHeight int
133134
renderer *glamour.TermRenderer
134135
renderWrapWidth int
136+
rendererCache map[int]*glamour.TermRenderer // Cache renderers by width
137+
rendererInitInFlight bool // Track if async init is running
138+
rendererInitMutex sync.Mutex // Protect cache and flag
135139
contextFile string
136140
suggestions []string
137141
selectedSuggIndex int
@@ -200,6 +204,13 @@ type ProcessingStatusMsg struct {
200204
Status string
201205
}
202206

207+
// RendererReadyMsg is sent when async renderer creation completes
208+
type RendererReadyMsg struct {
209+
Renderer *glamour.TermRenderer
210+
Width int
211+
Err error
212+
}
213+
203214
func New(currentModel, contextFile string, disableAnimations bool) *Model {
204215
ta := textarea.New()
205216
ta.Placeholder = "Type your prompt here... (@ for files, Shift+Enter (or Alt+Enter) for newline, Ctrl+X for commands, Ctrl+B to background shell, Ctrl+D or Ctrl+C×2 to quit)"
@@ -229,13 +240,20 @@ func New(currentModel, contextFile string, disableAnimations bool) *Model {
229240
spinner.WithStyle(statusStyle.MarginLeft(0)),
230241
)
231242

243+
// Initialize renderer cache
244+
rendererCache := make(map[int]*glamour.TermRenderer)
245+
if renderer != nil {
246+
rendererCache[80] = renderer
247+
}
248+
232249
m := &Model{
233250
textarea: ta,
234251
viewport: vp,
235252
messages: []message{},
236253
currentModel: currentModel,
237254
contextFile: contextFile,
238255
renderer: renderer,
256+
rendererCache: rendererCache,
239257
spinner: sp,
240258
animationsDisabled: disableAnimations,
241259
contextFreePercent: 100,
@@ -310,6 +328,21 @@ func (m *Model) scheduleViewportRefresh() tea.Cmd {
310328
})
311329
}
312330

331+
func (m *Model) createRendererAsync(wrapWidth int) tea.Cmd {
332+
return func() tea.Msg {
333+
renderer, err := glamour.NewTermRenderer(
334+
glamour.WithAutoStyle(),
335+
glamour.WithWordWrap(wrapWidth),
336+
glamour.WithPreservedNewLines(),
337+
)
338+
return RendererReadyMsg{
339+
Renderer: renderer,
340+
Width: wrapWidth,
341+
Err: err,
342+
}
343+
}
344+
}
345+
313346
func (m *Model) Init() tea.Cmd {
314347
initialWindowSize := func() tea.Msg {
315348
fd := int(os.Stdout.Fd())
@@ -331,9 +364,9 @@ func (m *Model) Init() tea.Cmd {
331364
)
332365
}
333366

334-
func (m *Model) applyWindowSize(width, height int) (bool, bool) {
367+
func (m *Model) applyWindowSize(width, height int) (bool, bool, tea.Cmd) {
335368
if width <= 0 || height <= 0 {
336-
return false, false
369+
return false, false, nil
337370
}
338371

339372
widthChanged := !m.ready || width != m.width
@@ -384,22 +417,34 @@ func (m *Model) applyWindowSize(width, height int) (bool, bool) {
384417
wrapWidth = 10
385418
}
386419

387-
if wrapWidth != m.renderWrapWidth || m.renderer == nil {
388-
if renderer, err := glamour.NewTermRenderer(
389-
glamour.WithAutoStyle(),
390-
glamour.WithWordWrap(wrapWidth),
391-
glamour.WithPreservedNewLines(),
392-
); err == nil {
393-
m.renderer = renderer
420+
// Check if we need a different renderer
421+
var rendererCmd tea.Cmd
422+
needsNewRenderer := wrapWidth != m.renderWrapWidth || m.renderer == nil
423+
424+
if needsNewRenderer {
425+
m.rendererInitMutex.Lock()
426+
427+
// Check cache first
428+
if cachedRenderer, exists := m.rendererCache[wrapWidth]; exists {
429+
m.renderer = cachedRenderer
394430
m.renderWrapWidth = wrapWidth
431+
m.rendererInitMutex.Unlock()
432+
} else if !m.rendererInitInFlight {
433+
// Mark that initialization is in flight and create renderer async
434+
m.rendererInitInFlight = true
435+
m.rendererInitMutex.Unlock()
436+
rendererCmd = m.createRendererAsync(wrapWidth)
437+
} else {
438+
// Renderer init already in flight, don't start another
439+
m.rendererInitMutex.Unlock()
395440
}
396441
}
397442

398443
if !m.ready {
399444
m.ready = true
400445
}
401446

402-
return widthChanged, heightChanged
447+
return widthChanged, heightChanged, rendererCmd
403448
}
404449

405450
type ansiSeqMode int
@@ -1124,18 +1169,26 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
11241169
return m, baseCmd
11251170

11261171
case tea.WindowSizeMsg:
1127-
widthChanged, heightChanged := m.applyWindowSize(msg.Width, msg.Height)
1172+
widthChanged, heightChanged, rendererCmd := m.applyWindowSize(msg.Width, msg.Height)
11281173
if !widthChanged && heightChanged && m.viewport.AtBottom() {
11291174
m.viewport.GotoBottom()
11301175
}
11311176

1177+
var extraCmds []tea.Cmd
1178+
if rendererCmd != nil {
1179+
extraCmds = append(extraCmds, rendererCmd)
1180+
}
1181+
11321182
if widthChanged || wasReady != m.ready {
11331183
m.viewportDirty = true
11341184
if cmd := m.scheduleViewportRefresh(); cmd != nil {
1135-
return m, tea.Batch(baseCmd, cmd)
1185+
extraCmds = append(extraCmds, cmd)
11361186
}
11371187
}
11381188

1189+
if len(extraCmds) > 0 {
1190+
return m, tea.Batch(append([]tea.Cmd{baseCmd}, extraCmds...)...)
1191+
}
11391192
return m, baseCmd
11401193

11411194
case viewportRefreshMsg:
@@ -1147,6 +1200,28 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
11471200
}
11481201
return m, baseCmd
11491202

1203+
case RendererReadyMsg:
1204+
m.rendererInitMutex.Lock()
1205+
if msg.Err == nil && msg.Renderer != nil {
1206+
// Cache the new renderer
1207+
m.rendererCache[msg.Width] = msg.Renderer
1208+
1209+
// If this is the current width, update active renderer
1210+
if msg.Width == m.renderWrapWidth || m.renderer == nil {
1211+
m.renderer = msg.Renderer
1212+
m.renderWrapWidth = msg.Width
1213+
m.viewportDirty = true
1214+
}
1215+
}
1216+
m.rendererInitInFlight = false
1217+
m.rendererInitMutex.Unlock()
1218+
1219+
// Refresh viewport if needed
1220+
if m.viewportDirty {
1221+
return m, tea.Batch(baseCmd, m.scheduleViewportRefresh())
1222+
}
1223+
return m, baseCmd
1224+
11501225
case GeneratingMsg:
11511226
if msg.Content != "" {
11521227
// If this is the first chunk of assistant content, summarize any previous tool results

0 commit comments

Comments
 (0)