aboutsummaryrefslogblamecommitdiffstats
path: root/app/terminal.go
blob: 7f9857dcb059e43c35f8da29b47021d43a33e0f4 (plain) (tree)
1
2
3
4
5
6
7
8
9
           

        
                 
                     
 
                                       
                                        
                                       
                                     
                                                  

 



                            
                      




                           
                           
                    
 
                               
                                            
                      
                                  


                                                    
                          
                             
                                    
                              
         

                                                   


                        



                               






                                                                          
                                           
                                                             

                      
                              
                                        
                                   
                                  


                                             
         
                                
                                 
         
                       

 
                                 

                                                                    
                          

 
                                    
                       


                                             
                      
                                             
                                                   

                                                                                
                                                                     
                                          
                              
                 
                                   

                                        

                 
                                     

 



                                          



                                            
                                                                             
                                     





                                
                            
                      
         


                             

 
                                         
                            

                      
                          



                                  
         

 
                                                              

                                                
                      
         
                                

                              
                                       
                 


                                             
                 

                              
                               
         

 
                                                     




                                        
                            

                            

                                
 
package app

import (
	"os/exec"
	"sync/atomic"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/log"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
	"git.sr.ht/~rockorager/vaxis/widgets/term"
)

type HasTerminal interface {
	Terminal() *Terminal
}

type Terminal struct {
	closed  int32
	cmd     *exec.Cmd
	ctx     *ui.Context
	focus   bool
	visible bool
	vterm   *term.Model
	running bool

	OnClose func(err error)
	OnEvent func(event vaxis.Event) bool
	OnStart func()
	OnTitle func(title string)
}

func NewTerminal(cmd *exec.Cmd) (*Terminal, error) {
	term := &Terminal{
		cmd:     cmd,
		vterm:   term.New(),
		visible: true,
	}
	term.vterm.OSC8 = config.General.EnableOSC8
	term.vterm.TERM = config.General.Term
	return term, nil
}

func (term *Terminal) Close() {
	term.closeErr(nil)
}

// TODO: replace with atomic.Bool when min go version will have it (1.19+)
const closed int32 = 1

func (term *Terminal) isClosed() bool {
	return atomic.LoadInt32(&term.closed) == closed
}

func (term *Terminal) closeErr(err error) {
	if atomic.SwapInt32(&term.closed, closed) == closed {
		return
	}
	if term.vterm != nil {
		// Stop receiving events
		term.vterm.Detach()
		term.vterm.Close()
		if term.ctx != nil {
			term.ctx.HideCursor()
		}
	}
	if term.OnClose != nil {
		term.OnClose(err)
	}
	ui.Invalidate()
}

func (term *Terminal) Destroy() {
	// If we destroy, we don't want to call the OnClose callback
	term.OnClose = nil
	term.closeErr(nil)
}

func (term *Terminal) Invalidate() {
	ui.Invalidate()
}

func (term *Terminal) Draw(ctx *ui.Context) {
	term.ctx = ctx
	if !term.running && term.cmd != nil {
		term.vterm.Attach(term.HandleEvent)
		w, h := ctx.Window().Size()
		if err := term.vterm.StartWithSize(term.cmd, w, h); err != nil {
			log.Errorf("error running terminal: %v", err)
			term.closeErr(err)
			return
		}
		term.running = true
		if term.OnStart != nil {
			term.OnStart()
		}
	}
	term.vterm.Draw(ctx.Window())
}

func (term *Terminal) Show(visible bool) {
	term.visible = visible
}

func (term *Terminal) Terminal() *Terminal {
	return term
}

func (term *Terminal) MouseEvent(localX int, localY int, event vaxis.Event) {
	ev, ok := event.(vaxis.Mouse)
	if !ok {
		return
	}
	if term.OnEvent != nil {
		term.OnEvent(ev)
	}
	if term.isClosed() {
		return
	}
	ev.Row = localY
	ev.Col = localX
	term.vterm.Update(ev)
}

func (term *Terminal) Focus(focus bool) {
	if term.isClosed() {
		return
	}
	term.focus = focus
	if term.focus {
		term.vterm.Focus()
	} else {
		term.vterm.Blur()
	}
}

// HandleEvent is used to watch the underlying terminal events
func (t *Terminal) HandleEvent(ev vaxis.Event) {
	if t.isClosed() {
		return
	}
	switch ev := ev.(type) {
	case vaxis.Redraw:
		if t.visible {
			ui.Invalidate()
		}
	case term.EventTitle:
		if t.OnTitle != nil {
			t.OnTitle(string(ev))
		}
	case term.EventClosed:
		t.Close()
		ui.Invalidate()
	}
}

func (term *Terminal) Event(event vaxis.Event) bool {
	if term.OnEvent != nil {
		if term.OnEvent(event) {
			return true
		}
	}
	if term.isClosed() {
		return false
	}
	term.vterm.Update(event)
	return true
}