forked from toolshed/abra
		
	
		
			
				
	
	
		
			142 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package tea
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/charmbracelet/x/term"
 | 
						|
	"github.com/muesli/cancelreader"
 | 
						|
)
 | 
						|
 | 
						|
func (p *Program) suspend() {
 | 
						|
	if err := p.ReleaseTerminal(); err != nil {
 | 
						|
		// If we can't release input, abort.
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	suspendProcess()
 | 
						|
 | 
						|
	_ = p.RestoreTerminal()
 | 
						|
	go p.Send(ResumeMsg{})
 | 
						|
}
 | 
						|
 | 
						|
func (p *Program) initTerminal() error {
 | 
						|
	if _, ok := p.renderer.(*nilRenderer); ok {
 | 
						|
		// No need to initialize the terminal if we're not rendering
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err := p.initInput(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	p.renderer.hideCursor()
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// restoreTerminalState restores the terminal to the state prior to running the
 | 
						|
// Bubble Tea program.
 | 
						|
func (p *Program) restoreTerminalState() error {
 | 
						|
	if p.renderer != nil {
 | 
						|
		p.renderer.disableBracketedPaste()
 | 
						|
		p.renderer.showCursor()
 | 
						|
		p.disableMouse()
 | 
						|
 | 
						|
		if p.renderer.reportFocus() {
 | 
						|
			p.renderer.disableReportFocus()
 | 
						|
		}
 | 
						|
 | 
						|
		if p.renderer.altScreen() {
 | 
						|
			p.renderer.exitAltScreen()
 | 
						|
 | 
						|
			// give the terminal a moment to catch up
 | 
						|
			time.Sleep(time.Millisecond * 10) //nolint:mnd
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return p.restoreInput()
 | 
						|
}
 | 
						|
 | 
						|
// restoreInput restores the tty input to its original state.
 | 
						|
func (p *Program) restoreInput() error {
 | 
						|
	if p.ttyInput != nil && p.previousTtyInputState != nil {
 | 
						|
		if err := term.Restore(p.ttyInput.Fd(), p.previousTtyInputState); err != nil {
 | 
						|
			return fmt.Errorf("error restoring console: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if p.ttyOutput != nil && p.previousOutputState != nil {
 | 
						|
		if err := term.Restore(p.ttyOutput.Fd(), p.previousOutputState); err != nil {
 | 
						|
			return fmt.Errorf("error restoring console: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// initCancelReader (re)commences reading inputs.
 | 
						|
func (p *Program) initCancelReader(cancel bool) error {
 | 
						|
	if cancel && p.cancelReader != nil {
 | 
						|
		p.cancelReader.Cancel()
 | 
						|
		p.waitForReadLoop()
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
	p.cancelReader, err = newInputReader(p.input, p.mouseMode)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("error creating cancelreader: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	p.readLoopDone = make(chan struct{})
 | 
						|
	go p.readLoop()
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (p *Program) readLoop() {
 | 
						|
	defer close(p.readLoopDone)
 | 
						|
 | 
						|
	err := readInputs(p.ctx, p.msgs, p.cancelReader)
 | 
						|
	if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) {
 | 
						|
		select {
 | 
						|
		case <-p.ctx.Done():
 | 
						|
		case p.errs <- err:
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// waitForReadLoop waits for the cancelReader to finish its read loop.
 | 
						|
func (p *Program) waitForReadLoop() {
 | 
						|
	select {
 | 
						|
	case <-p.readLoopDone:
 | 
						|
	case <-time.After(500 * time.Millisecond): //nolint:mnd
 | 
						|
		// The read loop hangs, which means the input
 | 
						|
		// cancelReader's cancel function has returned true even
 | 
						|
		// though it was not able to cancel the read.
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// checkResize detects the current size of the output and informs the program
 | 
						|
// via a WindowSizeMsg.
 | 
						|
func (p *Program) checkResize() {
 | 
						|
	if p.ttyOutput == nil {
 | 
						|
		// can't query window size
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	w, h, err := term.GetSize(p.ttyOutput.Fd())
 | 
						|
	if err != nil {
 | 
						|
		select {
 | 
						|
		case <-p.ctx.Done():
 | 
						|
		case p.errs <- err:
 | 
						|
		}
 | 
						|
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	p.Send(WindowSizeMsg{
 | 
						|
		Width:  w,
 | 
						|
		Height: h,
 | 
						|
	})
 | 
						|
}
 |