diff options
| -rw-r--r-- | config/aerc.conf.in | 8 | ||||
| -rw-r--r-- | config/config.go | 26 | ||||
| -rw-r--r-- | doc/aerc-config.5.scd | 8 | ||||
| -rw-r--r-- | lib/ui/grid.go | 14 | ||||
| -rw-r--r-- | widgets/msgviewer.go | 101 | 
5 files changed, 116 insertions, 41 deletions
| diff --git a/config/aerc.conf.in b/config/aerc.conf.in index 41f4ce6..d490831 100644 --- a/config/aerc.conf.in +++ b/config/aerc.conf.in @@ -61,6 +61,14 @@ alternatives=text/plain,text/html  # Default: false  show-headers=false +# +# Layout of headers when viewing a message. To display multiple headers in the +# same row, separate them with a pipe, e.g. "From|To". Rows will be hidden if +# none of their specified headers are present in the message. +# +# Default: From|To,Cc|Bcc,Date,Subject +header-layout=From|To,Cc|Bcc,Date,Subject +  [compose]  #  # Specifies the command to run the editor with. It will be shown in an embedded diff --git a/config/config.go b/config/config.go index aab3905..9e081fd 100644 --- a/config/config.go +++ b/config/config.go @@ -79,7 +79,8 @@ type FilterConfig struct {  type ViewerConfig struct {  	Pager        string  	Alternatives []string -	ShowHeaders  bool `ini:"show-headers"` +	ShowHeaders  bool       `ini:"show-headers"` +	HeaderLayout [][]string `ini:"-"`  }  type AercConfig struct { @@ -261,6 +262,8 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {  			switch key {  			case "alternatives":  				config.Viewer.Alternatives = strings.Split(val, ",") +			case "header-layout": +				config.Viewer.HeaderLayout = parseLayout(val)  			}  		}  	} @@ -323,6 +326,18 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {  			EmptyDirlist:      "(no folders)",  			MouseEnabled:      false,  		}, + +		Viewer: ViewerConfig{ +			Pager:        "less -R", +			Alternatives: []string{"text/plain", "text/html"}, +			ShowHeaders:  false, +			HeaderLayout: [][]string{ +				{"From", "To"}, +				{"Cc", "Bcc"}, +				{"Date"}, +				{"Subject"}, +			}, +		},  	}  	// These bindings are not configurable  	config.Bindings.AccountWizard.ExKey = KeyStroke{ @@ -431,3 +446,12 @@ func checkConfigPerms(filename string) error {  	}  	return nil  } + +func parseLayout(layout string) [][]string { +	rows := strings.Split(layout, ",") +	l := make([][]string, len(rows)) +	for i, r := range rows { +		l[i] = strings.Split(r, "|") +	} +	return l +} diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index 95e9087..3d39ef6 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -119,6 +119,14 @@ These options are configured in the *[viewer]* section of aerc.conf.  	Default: text/plain,text/html +*header-layout* +	Defines the default headers to display when viewing a message. To display +	multiple headers in the same row, separate them with a pipe, e.g. "From|To". +	Rows will be hidden if none of their specified headers are present in the +	message. + +	Default: From|To,Cc|Bcc,Date,Subject +  *show-headers*  	Default setting to determine whether to show full headers or only parsed  	ones in message viewer. diff --git a/lib/ui/grid.go b/lib/ui/grid.go index 3f5dd60..96da1cb 100644 --- a/lib/ui/grid.go +++ b/lib/ui/grid.go @@ -54,6 +54,20 @@ func NewGrid() *Grid {  	return &Grid{invalid: true}  } +// MakeGrid creates a grid with the specified number of columns and rows. Each +// cell has a size of 1. +func MakeGrid(numRows, numCols, rowStrategy, colStrategy int) *Grid { +	rows := make([]GridSpec, numRows) +	for i := 0; i < numRows; i++ { +		rows[i] = GridSpec{rowStrategy, 1} +	} +	cols := make([]GridSpec, numCols) +	for i := 0; i < numCols; i++ { +		cols[i] = GridSpec{colStrategy, 1} +	} +	return NewGrid().Rows(rows).Columns(cols) +} +  func (cell *GridCell) At(row, col int) *GridCell {  	cell.Row = row  	cell.Column = col diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go index f15fbae..19de4b8 100644 --- a/widgets/msgviewer.go +++ b/widgets/msgviewer.go @@ -45,53 +45,26 @@ type PartSwitcher struct {  func NewMessageViewer(acct *AccountView, conf *config.AercConfig,  	store *lib.MessageStore, msg *models.MessageInfo) *MessageViewer { +	header, headerHeight := createHeader(msg, conf.Viewer.HeaderLayout)  	grid := ui.NewGrid().Rows([]ui.GridSpec{ -		{ui.SIZE_EXACT, 4}, // TODO: Based on number of header rows +		{ui.SIZE_EXACT, headerHeight},  		{ui.SIZE_WEIGHT, 1},  	}).Columns([]ui.GridSpec{  		{ui.SIZE_WEIGHT, 1},  	}) -	// TODO: let user specify additional headers to show by default -	headers := ui.NewGrid().Rows([]ui.GridSpec{ -		{ui.SIZE_EXACT, 1}, -		{ui.SIZE_EXACT, 1}, -		{ui.SIZE_EXACT, 1}, -		{ui.SIZE_EXACT, 1}, -	}).Columns([]ui.GridSpec{ -		{ui.SIZE_WEIGHT, 1}, -		{ui.SIZE_WEIGHT, 1}, -	}) -	headers.AddChild( -		&HeaderView{ -			Name:  "From", -			Value: models.FormatAddresses(msg.Envelope.From), -		}).At(0, 0) -	headers.AddChild( -		&HeaderView{ -			Name:  "To", -			Value: models.FormatAddresses(msg.Envelope.To), -		}).At(0, 1) -	headers.AddChild( -		&HeaderView{ -			Name:  "Date", -			Value: msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"), -		}).At(1, 0).Span(1, 2) -	headers.AddChild( -		&HeaderView{ -			Name:  "Subject", -			Value: msg.Envelope.Subject, -		}).At(2, 0).Span(1, 2) -	headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2) -  	switcher := &PartSwitcher{}  	err := createSwitcher(switcher, conf, store, msg, conf.Viewer.ShowHeaders)  	if err != nil { -		goto handle_error +		return &MessageViewer{ +			err:  err, +			grid: grid, +			msg:  msg, +		}  	} -	grid.AddChild(headers).At(0, 0) +	grid.AddChild(header).At(0, 0)  	grid.AddChild(switcher).At(1, 0)  	return &MessageViewer{ @@ -102,12 +75,60 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,  		store:    store,  		switcher: switcher,  	} +} -handle_error: -	return &MessageViewer{ -		err:  err, -		grid: grid, -		msg:  msg, +func createHeader(msg *models.MessageInfo, layout [][]string) (grid *ui.Grid, height int) { +	presentHeaders := presentHeaders(msg, layout) +	rowCount := len(presentHeaders) + 1 // extra row for spacer +	grid = ui.MakeGrid(rowCount, 1, ui.SIZE_EXACT, ui.SIZE_WEIGHT) +	for i, cols := range presentHeaders { +		r := ui.MakeGrid(1, len(cols), ui.SIZE_EXACT, ui.SIZE_WEIGHT) +		for j, col := range cols { +			r.AddChild( +				&HeaderView{ +					Name:  col, +					Value: fmtHeader(msg, col), +				}).At(0, j) +		} +		grid.AddChild(r).At(i, 0) +	} +	grid.AddChild(ui.NewFill(' ')).At(rowCount-1, 0) +	return grid, rowCount +} + +// presentHeaders returns a filtered header layout, removing rows whose headers +// do not appear in the provided message. +func presentHeaders(msg *models.MessageInfo, layout [][]string) [][]string { +	headers := msg.RFC822Headers +	result := make([][]string, 0, len(layout)) +	for _, row := range layout { +		// To preserve layout alignment, only hide rows if all columns are empty +		for _, col := range row { +			if headers.Get(col) != "" { +				result = append(result, row) +				break +			} +		} +	} +	return result +} + +func fmtHeader(msg *models.MessageInfo, header string) string { +	switch header { +	case "From": +		return models.FormatAddresses(msg.Envelope.From) +	case "To": +		return models.FormatAddresses(msg.Envelope.To) +	case "Cc": +		return models.FormatAddresses(msg.Envelope.Cc) +	case "Bcc": +		return models.FormatAddresses(msg.Envelope.Bcc) +	case "Date": +		return msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM") +	case "Subject": +		return msg.Envelope.Subject +	default: +		return msg.RFC822Headers.Get(header)  	}  } | 
