summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--aerc.go2
-rw-r--r--commands/account/pipe.go3
-rw-r--r--commands/term.go5
-rw-r--r--commands/terminal/close.go6
-rwxr-xr-xcontrib/hldiff.py27
-rw-r--r--lib/ui/text.go10
-rw-r--r--widgets/aerc.go18
-rw-r--r--widgets/msgviewer.go207
-rw-r--r--widgets/termhost.go52
9 files changed, 257 insertions, 73 deletions
diff --git a/aerc.go b/aerc.go
index c2bc69c..ebf5068 100644
--- a/aerc.go
+++ b/aerc.go
@@ -24,7 +24,7 @@ func getCommands(selected libui.Drawable) []*commands.Commands {
 			account.AccountCommands,
 			commands.GlobalCommands,
 		}
-	case *widgets.TermHost:
+	case *widgets.Terminal:
 		return []*commands.Commands{
 			terminal.TerminalCommands,
 			commands.GlobalCommands,
diff --git a/commands/account/pipe.go b/commands/account/pipe.go
index ab2518b..60ac793 100644
--- a/commands/account/pipe.go
+++ b/commands/account/pipe.go
@@ -41,12 +41,11 @@ func Pipe(aerc *widgets.Aerc, args []string) error {
 				Color(tcell.ColorDefault, tcell.ColorRed)
 			return
 		}
-		host := widgets.NewTermHost(term, aerc.Config())
 		name := msg.Subject()
 		if len(name) > 12 {
 			name = name[:12]
 		}
-		aerc.NewTab(host, args[1] + " <" + name)
+		aerc.NewTab(term, args[1] + " <" + name)
 		term.OnClose = func(err error) {
 			if err != nil {
 				aerc.PushStatus(" "+err.Error(), 10*time.Second).
diff --git a/commands/term.go b/commands/term.go
index aea6382..91ffebd 100644
--- a/commands/term.go
+++ b/commands/term.go
@@ -26,8 +26,7 @@ func Term(aerc *widgets.Aerc, args []string) error {
 	if err != nil {
 		return err
 	}
-	host := widgets.NewTermHost(term, aerc.Config())
-	tab := aerc.NewTab(host, args[1])
+	tab := aerc.NewTab(term, args[1])
 	term.OnTitle = func(title string) {
 		if title == "" {
 			title = args[1]
@@ -36,7 +35,7 @@ func Term(aerc *widgets.Aerc, args []string) error {
 		tab.Content.Invalidate()
 	}
 	term.OnClose = func(err error) {
-		aerc.RemoveTab(host)
+		aerc.RemoveTab(term)
 		if err != nil {
 			aerc.PushStatus(" "+err.Error(), 10*time.Second).
 				Color(tcell.ColorDefault, tcell.ColorRed)
diff --git a/commands/terminal/close.go b/commands/terminal/close.go
index cb5702e..0a9d100 100644
--- a/commands/terminal/close.go
+++ b/commands/terminal/close.go
@@ -14,11 +14,11 @@ func CommandClose(aerc *widgets.Aerc, args []string) error {
 	if len(args) != 1 {
 		return errors.New("Usage: close")
 	}
-	thost, ok := aerc.SelectedTab().(*widgets.TermHost)
+	term, ok := aerc.SelectedTab().(*widgets.Terminal)
 	if !ok {
 		return errors.New("Error: not a terminal")
 	}
-	thost.Terminal().Close(nil)
-	aerc.RemoveTab(thost)
+	term.Close(nil)
+	aerc.RemoveTab(term)
 	return nil
 }
diff --git a/contrib/hldiff.py b/contrib/hldiff.py
new file mode 100755
index 0000000..e12f688
--- /dev/null
+++ b/contrib/hldiff.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+from colorama import Fore, Style
+import sys
+import re
+
+patch = sys.stdin.read().replace("\r\n", "\n")
+stat_re = re.compile(r'(\+*)(\-*)')
+
+hit_diff = False
+for line in patch.split("\n"):
+    if line.startswith("diff "):
+        hit_diff = True
+        print(line)
+        continue
+    if hit_diff:
+        if line.startswith("-"):
+            print(f"{Fore.RED}{line}{Style.RESET_ALL}")
+        elif line.startswith("+"):
+            print(f"{Fore.GREEN}{line}{Style.RESET_ALL}")
+        else:
+            print(line)
+    else:
+        if line.startswith(" ") and "|" in line and ("+" in line or "-" in line):
+            line = stat_re.sub(
+                    f'{Fore.GREEN}\\1{Fore.RED}\\2{Style.RESET_ALL}',
+                    line)
+        print(line)
diff --git a/lib/ui/text.go b/lib/ui/text.go
index aa97954..761673c 100644
--- a/lib/ui/text.go
+++ b/lib/ui/text.go
@@ -16,6 +16,7 @@ type Text struct {
 	strategy     uint
 	fg           tcell.Color
 	bg           tcell.Color
+	bold         bool
 	reverse      bool
 	onInvalidate func(d Drawable)
 }
@@ -40,6 +41,12 @@ func (t *Text) Strategy(strategy uint) *Text {
 	return t
 }
 
+func (t *Text) Bold(bold bool) *Text {
+	t.bold = bold
+	t.Invalidate()
+	return t
+}
+
 func (t *Text) Color(fg tcell.Color, bg tcell.Color) *Text {
 	t.fg = fg
 	t.bg = bg
@@ -63,6 +70,9 @@ func (t *Text) Draw(ctx *Context) {
 		x = ctx.Width() - size
 	}
 	style := tcell.StyleDefault.Background(t.bg).Foreground(t.fg)
+	if t.bold {
+		style = style.Bold(true)
+	}
 	if t.reverse {
 		style = style.Reverse(true)
 	}
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 3ba4e0d..a36db23 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -39,19 +39,11 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
 		{libui.SIZE_WEIGHT, 1},
 		{libui.SIZE_EXACT, 1},
 	}).Columns([]libui.GridSpec{
-		{libui.SIZE_EXACT, conf.Ui.SidebarWidth},
 		{libui.SIZE_WEIGHT, 1},
 	})
-	grid.AddChild(statusbar).At(2, 1)
-	// Minor hack
-	grid.AddChild(libui.NewBordered(
-		libui.NewFill(' '), libui.BORDER_RIGHT)).At(2, 0)
-
-	grid.AddChild(libui.NewText("aerc").
-		Strategy(libui.TEXT_CENTER).
-		Reverse(true))
-	grid.AddChild(tabs.TabStrip).At(0, 1)
-	grid.AddChild(tabs.TabContent).At(1, 0).Span(1, 2)
+	grid.AddChild(tabs.TabStrip)
+	grid.AddChild(tabs.TabContent).At(1, 0)
+	grid.AddChild(statusbar).At(2, 0)
 
 	aerc := &Aerc{
 		accounts:   make(map[string]*AccountView),
@@ -70,6 +62,8 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
 		tabs.Add(view, acct.Name)
 	}
 
+	tabs.Add(NewMessageViewer(), "[PATCH todo.sr.ht v2 …")
+
 	return aerc
 }
 
@@ -99,7 +93,7 @@ func (aerc *Aerc) getBindings() *config.KeyBindings {
 	switch aerc.SelectedTab().(type) {
 	case *AccountView:
 		return aerc.conf.Bindings.MessageList
-	case *TermHost:
+	case *Terminal:
 		return aerc.conf.Bindings.Terminal
 	default:
 		return aerc.conf.Bindings.Global
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
new file mode 100644
index 0000000..e94ece4
--- /dev/null
+++ b/widgets/msgviewer.go
@@ -0,0 +1,207 @@
+package widgets
+
+import (
+	"bytes"
+	"io"
+	"os/exec"
+
+	"github.com/gdamore/tcell"
+	"github.com/mattn/go-runewidth"
+
+	"git.sr.ht/~sircmpwn/aerc2/lib/ui"
+)
+
+type MessageViewer struct {
+	grid *ui.Grid
+	term *Terminal
+}
+
+var testMsg = `Makes the following changes to the Event type:
+
+* make 'user' and 'ticket' nullable since some events require it
+* add 'by_user' and 'from_ticket' to enable mentions
+* remove 'assinged_user' which is no longer used
+
+Ticket: https://todo.sr.ht/~sircmpwn/todo.sr.ht/156
+---
+ tests/test_comments.py                        |  23 ++-
+ .../versions/75ff2f7624fd_new_event_fields.py | 142 ++++++++++++++++++
+ todosrht/templates/events.html                |  18 ++-
+ todosrht/templates/ticket.html                |  31 +++-
+ todosrht/tickets.py                           |  14 +-
+ todosrht/types/event.py                       |  16 +-
+ 6 files changed, 207 insertions(+), 37 deletions(-)
+ create mode 100644 todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py
+
+diff --git a/tests/test_comments.py b/tests/test_comments.py
+index 4b3161d..b85d751 100644
+--- a/tests/test_comments.py
++++ b/tests/test_comments.py
+@@ -253,20 +253,25 @@ def test_notifications_and_events(mailbox):
+     # Check correct events are generated
+     comment_events = {e for e in ticket.events
+         if e.event_type == EventType.comment}
+-    user_events = {e for e in ticket.events
++    u1_events = {e for e in u1.events
++        if e.event_type == EventType.user_mentioned}
++    u2_events = {e for e in u2.events
+         if e.event_type == EventType.user_mentioned}
+
+     assert len(comment_events) == 1
+-    assert len(user_events) == 2
++    assert len(u1_events) == 1
++    assert len(u2_events) == 1
+
+-    u1_mention = next(e for e in user_events if e.user == u1)
+-    u2_mention = next(e for e in user_events if e.user == u2)
++    u1_mention = u1_events.pop()
++    u2_mention = u2_events.pop()
+
+     assert u1_mention.comment == comment
+-    assert u1_mention.ticket == ticket
++    assert u1_mention.from_ticket == ticket
++    assert u1_mention.by_user == commenter
+
+     assert u2_mention.comment == comment
+-    assert u2_mention.ticket == ticket
++    assert u2_mention.from_ticket == ticket
++    assert u2_mention.by_user == commenter
+
+     assert len(t1.events) == 1
+     assert len(t2.events) == 1
+@@ -276,10 +281,12 @@ def test_notifications_and_events(mailbox):
+     t2_mention = t2.events[0]
+
+     assert t1_mention.comment == comment
+-    assert t1_mention.user == commenter
++    assert t1_mention.from_ticket == ticket
++    assert t1_mention.by_user == commenter
+
+     assert t2_mention.comment == comment
+-    assert t2_mention.user == commenter
++    assert t2_mention.from_ticket == ticket
++    assert t2_mention.by_user == commenter
+
+ def test_ticket_mention_pattern():
+     def match(text):
+diff --git a/todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py
+b/todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py
+new file mode 100644
+index 0000000..1c55bfe
+--- /dev/null
++++ b/todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py
+@@ -0,0 +1,142 @@
++"""Add new event fields and migrate data.
++
++Also makes Event.ticket_id and Event.user_id nullable since some these fields
++can be empty for mention events.
++
++Revision ID: 75ff2f7624fd
++Revises: c7146cb70d6b
++Create Date: 2019-03-28 16:26:18.714300
++
++"""
++
++# revision identifiers, used by Alembic.
++revision = "75ff2f7624fd"
++down_revision = "c7146cb70d6b"
+`
+
+func NewMessageViewer() *MessageViewer {
+	grid := ui.NewGrid().Rows([]ui.GridSpec{
+		{ui.SIZE_EXACT, 3},
+		{ui.SIZE_WEIGHT, 1},
+	}).Columns([]ui.GridSpec{
+		{ui.SIZE_WEIGHT, 1},
+	})
+
+	headers := ui.NewGrid().Rows([]ui.GridSpec{
+		{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: "Ivan Habunek <ivan@habunek.com>",
+		}).At(0, 0)
+	headers.AddChild(
+		&HeaderView{
+			Name:  "To",
+			Value: "~sircmpwn/sr.ht-dev@lists.sr.ht",
+		}).At(0, 1)
+	headers.AddChild(
+		&HeaderView{
+			Name: "Subject",
+			Value: "[PATCH todo.sr.ht v2 1/3 Alter Event fields " +
+				"and migrate data]",
+		}).At(1, 0).Span(1, 2)
+	headers.AddChild(ui.NewFill(' ')).At(2, 0).Span(1, 2)
+
+	cmd := exec.Command("sh", "-c", "./contrib/hldiff.py | less -R")
+	pipe, _ := cmd.StdinPipe()
+	term, _ := NewTerminal(cmd)
+	term.OnStart = func() {
+		go func() {
+			reader := bytes.NewBufferString(testMsg)
+			io.Copy(pipe, reader)
+			pipe.Close()
+		}()
+	}
+	term.Focus(true)
+
+	grid.AddChild(headers).At(0, 0)
+	grid.AddChild(term).At(1, 0)
+	return &MessageViewer{grid, term}
+}
+
+func (mv *MessageViewer) Draw(ctx *ui.Context) {
+	mv.grid.Draw(ctx)
+}
+
+func (mv *MessageViewer) Invalidate() {
+	mv.grid.Invalidate()
+}
+
+func (mv *MessageViewer) OnInvalidate(fn func(d ui.Drawable)) {
+	mv.grid.OnInvalidate(func(_ ui.Drawable) {
+		fn(mv)
+	})
+}
+
+func (mv *MessageViewer) Event(event tcell.Event) bool {
+	return mv.term.Event(event)
+}
+
+func (mv *MessageViewer) Focus(focus bool) {
+	mv.term.Focus(focus)
+}
+
+type HeaderView struct {
+	onInvalidate func(d ui.Drawable)
+
+	Name  string
+	Value string
+}
+
+func (hv *HeaderView) Draw(ctx *ui.Context) {
+	size := runewidth.StringWidth(" " + hv.Name + " ")
+	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
+	style := tcell.StyleDefault.Reverse(true)
+	ctx.Printf(0, 0, style, " "+hv.Name+" ")
+	style = tcell.StyleDefault
+	ctx.Printf(size, 0, style, " "+hv.Value)
+}
+
+func (hv *HeaderView) Invalidate() {
+	if hv.onInvalidate != nil {
+		hv.onInvalidate(hv)
+	}
+}
+
+func (hv *HeaderView) OnInvalidate(fn func(d ui.Drawable)) {
+	hv.onInvalidate = fn
+}
diff --git a/widgets/termhost.go b/widgets/termhost.go
deleted file mode 100644
index 7898b44..0000000
--- a/widgets/termhost.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package widgets
-
-import (
-	"github.com/gdamore/tcell"
-
-	"git.sr.ht/~sircmpwn/aerc2/config"
-	"git.sr.ht/~sircmpwn/aerc2/lib/ui"
-)
-
-type TermHost struct {
-	grid *ui.Grid
-	term *Terminal
-}
-
-// Thin wrapper around terminal which puts it in a grid and passes through
-// input events. A bit of a hack tbh
-func NewTermHost(term *Terminal, conf *config.AercConfig) *TermHost {
-	grid := ui.NewGrid().Rows([]ui.GridSpec{
-		{ui.SIZE_WEIGHT, 1},
-	}).Columns([]ui.GridSpec{
-		{ui.SIZE_EXACT, conf.Ui.SidebarWidth},
-		{ui.SIZE_WEIGHT, 1},
-	})
-	grid.AddChild(term).At(0, 1)
-	return &TermHost{grid, term}
-}
-
-func (th *TermHost) Draw(ctx *ui.Context) {
-	th.grid.Draw(ctx)
-}
-
-func (th TermHost) Invalidate() {
-	th.grid.Invalidate()
-}
-
-func (th *TermHost) OnInvalidate(fn func(d ui.Drawable)) {
-	th.grid.OnInvalidate(func(_ ui.Drawable) {
-		fn(th)
-	})
-}
-
-func (th *TermHost) Event(event tcell.Event) bool {
-	return th.term.Event(event)
-}
-
-func (th *TermHost) Focus(focus bool) {
-	th.term.Focus(focus)
-}
-
-func (th *TermHost) Terminal() *Terminal {
-	return th.term
-}