aboutsummaryrefslogblamecommitdiffstats
path: root/lib/state/templates.go
blob: e93d0bb04f9546eff00709860aa4522cfa83a025 (plain) (tree)
1
2
3
4
5
6
7
8
9
             

        
             
                 

              
                                       
                                          
                                       
                                                           



                                             


                                                            
                                   




                                              
                                 

                                
 

                                         
                                   
                                                        

                                                


                                      

 












                                                                                




                                                               

                                                               





                                                          


         
                                                            







                                                                                  







                                                                
                                         



                                     


                                        










                                            

 
                                              
                                                                      

 
                                             






                                                     



                                             







                                                     


                                              







                                                     


                                               




































                                                              


                                         









                                                     

 



                                                              
                    




                                
                                 

                             





                                                                    















                                                          
                       




                                  



                                              






                                                     
         
                                

                            
                      

 
                                             







                                                     


                   

                                     






























                                                                            
                                                                            



















                                                    
                                   


                                                    
                               

 


                                              
         

                            
 


                                                 
         













                                                             
         
                                

 










                                                             
                                                               

                                      



                                             








                                                     

                                                      


                

                                                      


                

                                                      
                
 
 












                                                       
























                                                
                                               























                                                                             
                                               














                                                              
 
                                                           



                                                










                                                                                 
package state

import (
	"fmt"
	"strings"
	"time"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/parse"
	"git.sr.ht/~rjarry/aerc/models"
	sortthread "github.com/emersion/go-imap-sortthread"
	"github.com/emersion/go-message/mail"
)

type TemplateData struct {
	// only available when composing/replying/forwarding
	headers *mail.Header
	// only available when replying with a quote
	parent *models.OriginalMail
	// only available for the message list
	info   *models.MessageInfo
	marked bool
	msgNum int

	// message list threading
	threadSameSubject bool
	threadPrefix      string

	// selected account
	account     *config.AccountConfig
	myAddresses map[string]bool
	folder      *models.Directory // selected folder
	folders     []string
	getRUEcount func(string) (int, int, int)

	state       *AccountState
	pendingKeys []config.KeyStroke
}

// only used for compose/reply/forward
func (d *TemplateData) SetHeaders(h *mail.Header, o *models.OriginalMail) {
	d.headers = h
	d.parent = o
}

// only used for message list templates
func (d *TemplateData) SetInfo(info *models.MessageInfo, num int, marked bool) {
	d.info = info
	d.msgNum = num
	d.marked = marked
}

func (d *TemplateData) SetThreading(prefix string, same bool) {
	d.threadPrefix = prefix
	d.threadSameSubject = same
}

func (d *TemplateData) SetAccount(acct *config.AccountConfig) {
	d.account = acct
	d.myAddresses = make(map[string]bool)
	if acct != nil {
		d.myAddresses[acct.From.Address] = true
		for _, addr := range acct.Aliases {
			d.myAddresses[addr.Address] = true
		}
	}
}

func (d *TemplateData) SetFolder(folder *models.Directory) {
	d.folder = folder
}

func (d *TemplateData) SetRUE(folders []string, cb func(string) (int, int, int)) {
	d.folders = folders
	d.getRUEcount = cb
}

func (d *TemplateData) SetState(state *AccountState) {
	d.state = state
}

func (d *TemplateData) SetPendingKeys(keys []config.KeyStroke) {
	d.pendingKeys = keys
}

func (d *TemplateData) Account() string {
	if d.account != nil {
		return d.account.Name
	}
	return ""
}

func (d *TemplateData) Folder() string {
	if d.folder != nil {
		return d.folder.Name
	}
	return ""
}

func (d *TemplateData) Role() string {
	if d.folder != nil {
		return string(d.folder.Role)
	}
	return ""
}

func (d *TemplateData) ui() *config.UIConfig {
	return config.Ui.ForAccount(d.Account()).ForFolder(d.Folder())
}

func (d *TemplateData) To() []*mail.Address {
	var to []*mail.Address
	switch {
	case d.info != nil && d.info.Envelope != nil:
		to = d.info.Envelope.To
	case d.headers != nil:
		to, _ = d.headers.AddressList("to")
	}
	return to
}

func (d *TemplateData) Cc() []*mail.Address {
	var cc []*mail.Address
	switch {
	case d.info != nil && d.info.Envelope != nil:
		cc = d.info.Envelope.Cc
	case d.headers != nil:
		cc, _ = d.headers.AddressList("cc")
	}
	return cc
}

func (d *TemplateData) Bcc() []*mail.Address {
	var bcc []*mail.Address
	switch {
	case d.info != nil && d.info.Envelope != nil:
		bcc = d.info.Envelope.Bcc
	case d.headers != nil:
		bcc, _ = d.headers.AddressList("bcc")
	}
	return bcc
}

func (d *TemplateData) From() []*mail.Address {
	var from []*mail.Address
	switch {
	case d.info != nil && d.info.Envelope != nil:
		from = d.info.Envelope.From
	case d.headers != nil:
		from, _ = d.headers.AddressList("from")
	}
	return from
}

func (d *TemplateData) Peer() []*mail.Address {
	var from, to []*mail.Address
	switch {
	case d.info != nil && d.info.Envelope != nil:
		from = d.info.Envelope.From
		to = d.info.Envelope.To
	case d.headers != nil:
		from, _ = d.headers.AddressList("from")
		to, _ = d.headers.AddressList("to")
	}
	for _, addr := range from {
		if d.myAddresses[addr.Address] {
			return to
		}
	}
	return from
}

func (d *TemplateData) ReplyTo() []*mail.Address {
	var replyTo []*mail.Address
	switch {
	case d.info != nil && d.info.Envelope != nil:
		replyTo = d.info.Envelope.ReplyTo
	case d.headers != nil:
		replyTo, _ = d.headers.AddressList("reply-to")
	}
	return replyTo
}

func (d *TemplateData) Date() time.Time {
	var date time.Time
	switch {
	case d.info != nil && d.info.Envelope != nil:
		date = d.info.Envelope.Date
	case d.info != nil:
		date = d.info.InternalDate
	default:
		date = time.Now()
	}
	return date
}

func (d *TemplateData) DateAutoFormat(date time.Time) string {
	if date.IsZero() {
		return ""
	}
	ui := d.ui()
	year := date.Year()
	day := date.YearDay()
	now := time.Now()
	thisYear := now.Year()
	thisDay := now.YearDay()
	fmt := ui.TimestampFormat
	if year == thisYear {
		switch {
		case day == thisDay && ui.ThisDayTimeFormat != "":
			fmt = ui.ThisDayTimeFormat
		case day > thisDay-7 && ui.ThisWeekTimeFormat != "":
			fmt = ui.ThisWeekTimeFormat
		case ui.ThisYearTimeFormat != "":
			fmt = ui.ThisYearTimeFormat
		}
	}
	return date.Format(fmt)
}

func (d *TemplateData) Header(name string) string {
	var h *mail.Header
	switch {
	case d.headers != nil:
		h = d.headers
	case d.info != nil && d.info.RFC822Headers != nil:
		h = d.info.RFC822Headers
	default:
		return ""
	}
	text, err := h.Text(name)
	if err != nil {
		text = h.Get(name)
	}
	return text
}

func (d *TemplateData) ThreadPrefix() string {
	return d.threadPrefix
}

func (d *TemplateData) Subject() string {
	var subject string
	switch {
	case d.info != nil && d.info.Envelope != nil:
		subject = d.info.Envelope.Subject
	case d.headers != nil:
		subject = d.Header("subject")
	}
	if d.threadSameSubject {
		subject = ""
	}
	return subject
}

func (d *TemplateData) SubjectBase() string {
	var subject string
	switch {
	case d.info != nil && d.info.Envelope != nil:
		subject = d.info.Envelope.Subject
	case d.headers != nil:
		subject = d.Header("subject")
	}
	base, _ := sortthread.GetBaseSubject(subject)
	return base
}

func (d *TemplateData) Number() int {
	return d.msgNum
}

func (d *TemplateData) Labels() []string {
	if d.info == nil {
		return nil
	}
	return d.info.Labels
}

func (d *TemplateData) Flags() []string {
	var flags []string
	if d.info == nil {
		return flags
	}

	switch {
	case d.info.Flags.Has(models.SeenFlag | models.AnsweredFlag):
		flags = append(flags, "r") // message has been replied to
	case d.info.Flags.Has(models.SeenFlag):
		break
	case d.info.Flags.Has(models.RecentFlag):
		flags = append(flags, "N") // message is new
	default:
		flags = append(flags, "O") // message is old
	}
	if d.info.Flags.Has(models.DeletedFlag) {
		flags = append(flags, "D")
	}
	if d.info.BodyStructure != nil {
		for _, bS := range d.info.BodyStructure.Parts {
			if strings.ToLower(bS.Disposition) == "attachment" {
				flags = append(flags, d.ui().IconAttachment)
				break
			}
		}
	}
	if d.info.Flags.Has(models.FlaggedFlag) {
		flags = append(flags, "!")
	}
	if d.marked {
		flags = append(flags, "*")
	}
	return flags
}

func (d *TemplateData) MessageId() string {
	if d.info == nil || d.info.Envelope == nil {
		return ""
	}
	return d.info.Envelope.MessageId
}

func (d *TemplateData) Size() int {
	if d.info == nil || d.info.Envelope == nil {
		return 0
	}
	return int(d.info.Size)
}

func (d *TemplateData) OriginalText() string {
	if d.parent == nil {
		return ""
	}
	return d.parent.Text
}

func (d *TemplateData) OriginalDate() time.Time {
	if d.parent == nil {
		return time.Time{}
	}
	return d.parent.Date
}

func (d *TemplateData) OriginalFrom() []*mail.Address {
	if d.parent == nil || d.parent.RFC822Headers == nil {
		return nil
	}
	from, _ := d.parent.RFC822Headers.AddressList("from")
	return from
}

func (d *TemplateData) OriginalMIMEType() string {
	if d.parent == nil {
		return ""
	}
	return d.parent.MIMEType
}

func (d *TemplateData) OriginalHeader(name string) string {
	if d.parent == nil || d.parent.RFC822Headers == nil {
		return ""
	}
	text, err := d.parent.RFC822Headers.Text(name)
	if err != nil {
		text = d.parent.RFC822Headers.Get(name)
	}
	return text
}

func (d *TemplateData) rue(folders ...string) (int, int, int) {
	var recent, unread, exists int
	if d.getRUEcount != nil {
		if len(folders) == 0 {
			folders = d.folders
		}
		for _, dir := range folders {
			r, u, e := d.getRUEcount(dir)
			recent += r
			unread += u
			exists += e
		}
	}
	return recent, unread, exists
}

func (d *TemplateData) Recent(folders ...string) int {
	r, _, _ := d.rue(folders...)
	return r
}

func (d *TemplateData) Unread(folders ...string) int {
	_, u, _ := d.rue(folders...)
	return u
}

func (d *TemplateData) Exists(folders ...string) int {
	_, _, e := d.rue(folders...)
	return e
}

func (d *TemplateData) RUE(folders ...string) string {
	r, u, e := d.rue(folders...)
	switch {
	case r > 0:
		return fmt.Sprintf("%d/%d/%d", r, u, e)
	case u > 0:
		return fmt.Sprintf("%d/%d", u, e)
	case e > 0:
		return fmt.Sprintf("%d", e)
	}
	return ""
}

func (d *TemplateData) Connected() bool {
	if d.state != nil {
		return d.state.Connected
	}
	return false
}

func (d *TemplateData) ConnectionInfo() string {
	switch {
	case d.state == nil:
		return ""
	case d.state.connActivity != "":
		return d.state.connActivity
	case d.state.Connected:
		return texter().Connected()
	default:
		return texter().Disconnected()
	}
}

func (d *TemplateData) ContentInfo() string {
	if d.state == nil {
		return ""
	}
	var content []string
	fldr := d.state.folderState(d.Folder())
	if fldr.FilterActivity != "" {
		content = append(content, fldr.FilterActivity)
	} else if fldr.Filter != "" {
		content = append(content, texter().FormatFilter(fldr.Filter))
	}
	if fldr.Search != "" {
		content = append(content, texter().FormatSearch(fldr.Search))
	}
	return strings.Join(content, config.Statusline.Separator)
}

func (d *TemplateData) StatusInfo() string {
	stat := d.ConnectionInfo()
	if content := d.ContentInfo(); content != "" {
		stat += config.Statusline.Separator + content
	}
	return stat
}

func (d *TemplateData) TrayInfo() string {
	if d.state == nil {
		return ""
	}
	var tray []string
	fldr := d.state.folderState(d.Folder())
	if fldr.Sorting {
		tray = append(tray, texter().Sorting())
	}
	if fldr.Threading {
		tray = append(tray, texter().Threading())
	}
	if d.state.passthrough {
		tray = append(tray, texter().Passthrough())
	}
	return strings.Join(tray, config.Statusline.Separator)
}

func (d *TemplateData) PendingKeys() string {
	return config.FormatKeyStrokes(d.pendingKeys)
}

func (d *TemplateData) Style(content, name string) string {
	cfg := config.Ui.ForAccount(d.Account())
	style := cfg.GetUserStyle(name)
	return parse.ApplyStyle(style, content)
}

func (d *TemplateData) StyleSwitch(content string, cases ...models.Case) string {
	for _, c := range cases {
		if c.Matches(content) {
			cfg := config.Ui.ForAccount(d.Account())
			style := cfg.GetUserStyle(c.Value())
			return parse.ApplyStyle(style, content)
		}
	}
	return content
}