diff options
| -rw-r--r-- | aerc.go | 2 | ||||
| -rw-r--r-- | commands/account/pipe.go | 3 | ||||
| -rw-r--r-- | commands/term.go | 5 | ||||
| -rw-r--r-- | commands/terminal/close.go | 6 | ||||
| -rwxr-xr-x | contrib/hldiff.py | 27 | ||||
| -rw-r--r-- | lib/ui/text.go | 10 | ||||
| -rw-r--r-- | widgets/aerc.go | 18 | ||||
| -rw-r--r-- | widgets/msgviewer.go | 207 | ||||
| -rw-r--r-- | widgets/termhost.go | 52 | 
9 files changed, 257 insertions, 73 deletions
| @@ -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 -} | 
