feat(frame): replace raw ANSI runLive with tea.Program
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
331bcd564d
commit
1c6e910251
2 changed files with 34 additions and 51 deletions
|
|
@ -116,6 +116,10 @@ func (f *Frame) Back() bool {
|
|||
|
||||
// Stop signals the Frame to exit its Run loop.
|
||||
func (f *Frame) Stop() {
|
||||
if f.program != nil {
|
||||
f.program.Quit()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-f.done:
|
||||
default:
|
||||
|
|
@ -123,6 +127,14 @@ func (f *Frame) Stop() {
|
|||
}
|
||||
}
|
||||
|
||||
// Send injects a message into the Frame's tea.Program.
|
||||
// Safe to call before Run() (message is discarded).
|
||||
func (f *Frame) Send(msg tea.Msg) {
|
||||
if f.program != nil {
|
||||
f.program.Send(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeyMap sets custom key bindings for Frame navigation.
|
||||
func (f *Frame) WithKeyMap(km KeyMap) *Frame {
|
||||
f.keyMap = km
|
||||
|
|
@ -482,60 +494,18 @@ func (f *Frame) regionSize(r Region, totalW, totalH int) (int, int) {
|
|||
}
|
||||
|
||||
func (f *Frame) runLive() {
|
||||
// Enter alt-screen.
|
||||
fmt.Fprint(f.out, "\033[?1049h")
|
||||
// Hide cursor.
|
||||
fmt.Fprint(f.out, "\033[?25l")
|
||||
|
||||
defer func() {
|
||||
// Show cursor.
|
||||
fmt.Fprint(f.out, "\033[?25h")
|
||||
// Leave alt-screen.
|
||||
fmt.Fprint(f.out, "\033[?1049l")
|
||||
}()
|
||||
|
||||
ticker := time.NewTicker(80 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
f.renderFrame()
|
||||
|
||||
select {
|
||||
case <-f.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
opts := []tea.ProgramOption{
|
||||
tea.WithAltScreen(),
|
||||
}
|
||||
if f.out != os.Stdout {
|
||||
opts = append(opts, tea.WithOutput(f.out))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) renderFrame() {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
p := tea.NewProgram(f, opts...)
|
||||
f.program = p
|
||||
|
||||
w, h := f.termSize()
|
||||
|
||||
// Move to top-left.
|
||||
fmt.Fprint(f.out, "\033[H")
|
||||
// Clear screen.
|
||||
fmt.Fprint(f.out, "\033[2J")
|
||||
|
||||
order := []Region{RegionHeader, RegionLeft, RegionContent, RegionRight, RegionFooter}
|
||||
for _, r := range order {
|
||||
if _, exists := f.layout.regions[r]; !exists {
|
||||
continue
|
||||
}
|
||||
m, ok := f.models[r]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rw, rh := f.regionSize(r, w, h)
|
||||
view := m.View(rw, rh)
|
||||
if view != "" {
|
||||
fmt.Fprint(f.out, view)
|
||||
if !strings.HasSuffix(view, "\n") {
|
||||
fmt.Fprintln(f.out)
|
||||
}
|
||||
}
|
||||
if _, err := p.Run(); err != nil {
|
||||
Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -426,6 +426,19 @@ func TestFrameTeaModel_Good(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestFrameSend_Good(t *testing.T) {
|
||||
t.Run("Send is safe before Run", func(t *testing.T) {
|
||||
f := NewFrame("C")
|
||||
f.out = &bytes.Buffer{}
|
||||
f.Content(StaticModel("x"))
|
||||
|
||||
// Should not panic when program is nil
|
||||
assert.NotPanics(t, func() {
|
||||
f.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'x'}})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// indexOf returns the position of substr in s, or -1 if not found.
|
||||
func indexOf(s, substr string) int {
|
||||
for i := range len(s) - len(substr) + 1 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue