about summary refs log tree commit diff stats
path: root/widgets/msgviewer.go
diff options
context:
space:
mode:
authorDaniel Bridges <bridges2@gmail.com>2019-07-15 11:56:44 -0700
committerDrew DeVault <sir@cmpwn.com>2019-07-17 17:26:43 -0400
commitdfc048fe285939d9de3b5753f723c935d042cc2b (patch)
treeea037462fb702d0f503a34bfc8edd310d4dbfaeb /widgets/msgviewer.go
parentd7975132b62942530da6c4907bed3eb3ab99e4a3 (diff)
downloadaerc-dfc048fe285939d9de3b5753f723c935d042cc2b.tar.gz
Display user specified headers in viewer if present
Diffstat (limited to 'widgets/msgviewer.go')
-rw-r--r--widgets/msgviewer.go101
1 files changed, 61 insertions, 40 deletions
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)
 	}
 }
 
light .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#include <glib.h>
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
#include <string.h>

#include <stabber.h>
#include <expect.h>

#include "proftest.h"

void
presence_online(void **state)
{
    prof_connect();

    prof_input("/online");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to online (priority 0)"));
}

void
presence_online_with_message(void **state)
{
    prof_connect();

    prof_input("/online \"Hi there\"");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<status>Hi there</status>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to online (priority 0), \"Hi there\"."));
}

void
presence_away(void **state)
{
    prof_connect();

    prof_input("/away");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>away</show>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to away (priority 0)"));
}

void
presence_away_with_message(void **state)
{
    prof_connect();

    prof_input("/away \"I'm not here for a bit\"");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>away</show>"
            "<status>I'm not here for a bit</status>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to away (priority 0), \"I'm not here for a bit\"."));
}

void
presence_xa(void **state)
{
    prof_connect();

    prof_input("/xa");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>xa</show>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to xa (priority 0)"));
}

void
presence_xa_with_message(void **state)
{
    prof_connect();

    prof_input("/xa \"Gone to the shops\"");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>xa</show>"
            "<status>Gone to the shops</status>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to xa (priority 0), \"Gone to the shops\"."));
}

void
presence_dnd(void **state)
{
    prof_connect();

    prof_input("/dnd");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>dnd</show>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to dnd (priority 0)"));
}

void
presence_dnd_with_message(void **state)
{
    prof_connect();

    prof_input("/dnd \"Working\"");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>dnd</show>"
            "<status>Working</status>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to dnd (priority 0), \"Working\"."));
}

void
presence_chat(void **state)
{
    prof_connect();

    prof_input("/chat");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>chat</show>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to chat (priority 0)"));
}

void
presence_chat_with_message(void **state)
{
    prof_connect();

    prof_input("/chat \"Free to talk\"");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<show>chat</show>"
            "<status>Free to talk</status>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Status set to chat (priority 0), \"Free to talk\"."));
}

void
presence_set_priority(void **state)
{
    prof_connect();

    prof_input("/priority 25");

    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<priority>25</priority>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));

    assert_true(prof_output_exact("Priority set to 25."));
}

void
presence_includes_priority(void **state)
{
    prof_connect();

    prof_input("/priority 25");
    assert_true(stbbr_received(
        "<presence id='prof_presence_2'>"
            "<priority>25</priority>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));
    assert_true(prof_output_exact("Priority set to 25."));

    prof_input("/chat \"Free to talk\"");
    assert_true(stbbr_received(
        "<presence id='prof_presence_3'>"
            "<priority>25</priority>"
            "<show>chat</show>"
            "<status>Free to talk</status>"
            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://www.profanity.im'/>"
        "</presence>"
    ));
    assert_true(prof_output_exact("Status set to chat (priority 25), \"Free to talk\"."));
}

void
presence_received(void **state)
{
    prof_connect();

    stbbr_send(
        "<presence to='stabber@localhost' from='buddy1@localhost/mobile'>"
            "<priority>10</priority>"
            "<status>I'm here</status>"
        "</presence>"
    );

    assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\""));
}

// Typical use case for gateways that don't support resources
void
presence_missing_resource_defaults(void **state)
{
    prof_connect();

    stbbr_send(
        "<presence to='stabber@localhost' from='buddy1@localhost'>"
            "<priority>15</priority>"
            "<status>My status</status>"
        "</presence>"
    );

    assert_true(prof_output_exact("Buddy1 is online, \"My status\""));

    prof_input("/info Buddy1");

    assert_true(prof_output_exact("__prof_default (15), online"));
}