Add basic message viewer mockup
This commit is contained in:
parent
2958579ee7
commit
fa04a1e036
9 changed files with 257 additions and 73 deletions
2
aerc.go
2
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,
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
27
contrib/hldiff.py
Executable file
27
contrib/hldiff.py
Executable file
|
@ -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)
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
207
widgets/msgviewer.go
Normal file
207
widgets/msgviewer.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in a new issue