~taiite/public-inbox

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH senpai] ui/buffers: Display bar above unread messages

Details
Message ID
<20220122212136.5147-1-me@adnano.co>
DKIM signature
pass
Download raw message
Patch: +177 -12
Implements: https://todo.sr.ht/~taiite/senpai/32
Implements: https://todo.sr.ht/~taiite/senpai/76
---
 app.go             |  20 ++++++++-
 cmd/senpai/main.go |  53 ++++++++++++++++++++++++
 ui/buffers.go      | 101 ++++++++++++++++++++++++++++++++++++++++-----
 ui/ui.go           |  15 +++++++
 4 files changed, 177 insertions(+), 12 deletions(-)

diff --git a/app.go b/app.go
index 1144560..f61f0c8 100644
--- a/app.go
+++ b/app.go
@@ -97,6 +97,7 @@ type App struct {

	lastMessageTime time.Time
	lastCloseTime   time.Time
	lastReadTime    map[boundKey]time.Time
}

func NewApp(cfg Config) (app *App, err error) {
@@ -184,6 +185,17 @@ func (app *App) SetLastClose(t time.Time) {
	app.lastCloseTime = t
}

func (app *App) ReadTimes() []ui.ReadTime {
	return app.win.ReadTimes()
}

func (app *App) SetRead(netID, buffer string, stamp time.Time) {
	if app.lastReadTime == nil {
		app.lastReadTime = make(map[boundKey]time.Time)
	}
	app.lastReadTime[boundKey{netID, buffer}] = stamp
}

// eventLoop retrieves events (in batches) from the event channel and handle
// them, then draws the interface after each batch is handled.
func (app *App) eventLoop() {
@@ -655,6 +667,12 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
		}
	case irc.SelfJoinEvent:
		i, added := app.win.AddBuffer(netID, "", ev.Channel)
		if added {
			if stamp, ok := app.lastReadTime[boundKey{netID, ev.Channel}]; ok {
				app.win.SetRead(netID, ev.Channel, stamp)
			}
		}

		bounds, ok := app.messageBounds[boundKey{netID, ev.Channel}]
		if added || !ok {
			s.NewHistoryRequest(ev.Channel).
@@ -766,7 +784,7 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
			var line ui.Line
			switch ev := m.(type) {
			case irc.MessageEvent:
				_, line, _ = app.formatMessage(s, ev)
				_, line, line.Notify = app.formatMessage(s, ev)
			default:
				line = app.formatEvent(ev)
			}
diff --git a/cmd/senpai/main.go b/cmd/senpai/main.go
index d177a8b..d9a4600 100644
--- a/cmd/senpai/main.go
+++ b/cmd/senpai/main.go
@@ -1,6 +1,8 @@
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
@@ -48,6 +50,7 @@ func main() {
	lastNetID, lastBuffer := getLastBuffer()
	app.SwitchToBuffer(lastNetID, lastBuffer)
	app.SetLastClose(getLastStamp())
	loadLastRead(app)

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
@@ -61,6 +64,7 @@ func main() {
	app.Close()
	writeLastBuffer(app)
	writeLastStamp(app)
	writeLastRead(app)
}

func cachePath() string {
@@ -131,3 +135,52 @@ func writeLastStamp(app *senpai.App) {
		fmt.Fprintf(os.Stderr, "failed to write last stamp at %q: %s\n", lastStampPath, err)
	}
}

func lastReadPath() string {
	return path.Join(cachePath(), "lastread.txt")
}

func loadLastRead(app *senpai.App) {
	buf, err := ioutil.ReadFile(lastReadPath())
	if err != nil {
		return
	}

	scanner := bufio.NewScanner(bytes.NewReader(buf))
	for scanner.Scan() {
		split := strings.SplitN(scanner.Text(), " ", 3)
		if len(split) != 3 {
			continue
		}
		netID, buffer := split[0], split[1]
		stamp, err := time.Parse(time.RFC3339Nano, split[2])
		if err != nil {
			continue
		}
		app.SetRead(netID, buffer, stamp)
	}
}

func writeLastRead(app *senpai.App) {
	lastReadPath := lastReadPath()
	lastRead := app.ReadTimes()

	f, err := os.Create(lastReadPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to write last read at %q: %s\n", lastReadPath, err)
	}
	defer f.Close()

	w := bufio.NewWriter(f)
	for _, read := range lastRead {
		w.WriteString(read.NetID)
		w.WriteByte(' ')
		w.WriteString(read.Buffer)
		w.WriteByte(' ')
		w.WriteString(read.Stamp.Format(time.RFC3339Nano))
		w.WriteByte('\n')
	}
	if err := w.Flush(); err != nil {
		fmt.Fprintf(os.Stderr, "failed to write last read at %q: %s\n", lastReadPath, err)
	}
}
diff --git a/ui/buffers.go b/ui/buffers.go
index 1ccb921..25de270 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -33,6 +33,7 @@ type Line struct {
	HeadColor tcell.Color
	Highlight bool
	Mergeable bool
	Notify    NotifyType
	Data      []interface{}

	splitPoints []point
@@ -181,6 +182,7 @@ type buffer struct {
	netName    string
	title      string
	highlights int
	read       time.Time
	unread     bool

	lines []Line
@@ -223,6 +225,7 @@ func (bs *BufferList) To(i int) bool {
		return false
	}
	if 0 <= i {
		bs.updateRead()
		bs.current = i
		if len(bs.list) <= bs.current {
			bs.current = len(bs.list) - 1
@@ -239,15 +242,11 @@ func (bs *BufferList) ShowBufferNumbers(enabled bool) {
}

func (bs *BufferList) Next() {
	bs.current = (bs.current + 1) % len(bs.list)
	bs.list[bs.current].highlights = 0
	bs.list[bs.current].unread = false
	bs.To((bs.current + 1) % len(bs.list))
}

func (bs *BufferList) Previous() {
	bs.current = (bs.current - 1 + len(bs.list)) % len(bs.list)
	bs.list[bs.current].highlights = 0
	bs.list[bs.current].unread = false
	bs.To((bs.current - 1 + len(bs.list)) % len(bs.list))
}

func (bs *BufferList) Add(netID, netName, title string) (i int, added bool) {
@@ -335,13 +334,20 @@ func (bs *BufferList) AddLine(netID, title string, notify NotifyType, line Line)
		if idx == bs.current && 0 < b.scrollAmt {
			b.scrollAmt += len(line.NewLines(bs.tlInnerWidth)) + 1
		}
		// TODO make sure first unread message is visible
	}

	if notify != NotifyNone && idx != bs.current {
		b.unread = true
	if idx != bs.current && line.At.After(b.read) {
		if !b.unread && notify != NotifyNone {
			b.unread = true
		}
		if notify == NotifyHighlight {
			b.highlights++
		}
	}
	if notify == NotifyHighlight && idx != bs.current {
		b.highlights++
	if idx == bs.current && len(b.lines) > 1 &&
		!b.lines[len(b.lines)-2].At.After(b.read) {
		bs.updateRead()
	}
}

@@ -368,6 +374,14 @@ func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
				}
				lines = append(lines, line)
			}
			if buf != &b.lines && idx != bs.current && line.At.After(b.read) {
				if !b.unread && line.Notify != NotifyNone {
					b.unread = true
				}
				if line.Notify == NotifyHighlight {
					b.highlights++
				}
			}
		}
	}
	b.lines = lines
@@ -387,6 +401,45 @@ func (bs *BufferList) Current() (netID, title string) {
	return b.netID, b.title
}

func (bs *BufferList) SetRead(netID, title string, stamp time.Time) {
	idx := bs.idx(netID, title)
	if idx < 0 {
		return
	}
	b := &bs.list[idx]
	b.read = stamp
}

func (bs *BufferList) ReadTimes() []ReadTime {
	read := []ReadTime{}
	for _, b := range bs.list {
		if len(b.title) == 0 {
			continue
		}
		read = append(read, ReadTime{
			NetID:  b.netID,
			Buffer: b.title,
			Stamp:  b.read,
		})
	}
	return read
}

func (bs *BufferList) updateRead() {
	b := &bs.list[bs.current]
	if len(b.lines) == 0 {
		return
	}
	if b.scrollAmt != 0 {
		return
	}
	lastStamp := b.lines[len(b.lines)-1].At
	if !lastStamp.After(b.read) {
		return
	}
	b.read = lastStamp
}

func (bs *BufferList) ScrollUp(n int) {
	b := &bs.list[bs.current]
	if b.isAtTop {
@@ -569,6 +622,10 @@ func (bs *BufferList) DrawTimeline(screen tcell.Screen, x0, y0, nickColWidth int
	}
	y0++

	firstLine := false
	readAll := false
	unreadBar := false

	yi := b.scrollAmt + y0 + bs.tlHeight
	for i := len(b.lines) - 1; 0 <= i; i-- {
		if yi < y0 {
@@ -576,8 +633,30 @@ func (bs *BufferList) DrawTimeline(screen tcell.Screen, x0, y0, nickColWidth int
		}

		x1 := x0 + 9 + nickColWidth

		line := &b.lines[i]

		if !firstLine {
			firstLine = true
			readAll = !line.At.After(b.read)
		}

		// Draw unread bar after the last read line
		if !readAll && !unreadBar && !line.At.After(b.read) {
			unreadBar = true
			yi--
			if yi < y0 {
				break
			}

			style := tcell.StyleDefault.Bold(true)
			for x := x1; x < x1+bs.tlInnerWidth; x++ {
				screen.SetContent(x, yi, '-', nil, style)
			}
			const msg = " NEW MESSAGES "
			x2 := x1 + bs.tlInnerWidth/2 - len(msg)/2
			printString(screen, &x2, yi, Styled(msg, style))
		}

		nls := line.NewLines(bs.tlInnerWidth)
		yi -= len(nls) + 1
		if y0+bs.tlHeight <= yi {
diff --git a/ui/ui.go b/ui/ui.go
index 7e02812..c47c44e 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -3,6 +3,7 @@ package ui
import (
	"strings"
	"sync/atomic"
	"time"

	"git.sr.ht/~taiite/senpai/irc"

@@ -244,6 +245,20 @@ func (ui *UI) SetPrompt(prompt StyledString) {
	ui.prompt = prompt
}

func (ui *UI) SetRead(netID, buffer string, stamp time.Time) {
	ui.bs.SetRead(netID, buffer, stamp)
}

type ReadTime struct {
	NetID  string
	Buffer string
	Stamp  time.Time
}

func (ui *UI) ReadTimes() []ReadTime {
	return ui.bs.ReadTimes()
}

// InputContent result must not be modified.
func (ui *UI) InputContent() []rune {
	return ui.e.Content()
-- 
2.34.1
Reply to thread Export thread (mbox)