summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorcitrons <citrons@mondecitronne.com>2025-06-09 14:45:51 -0500
committercitrons <citrons@mondecitronne.com>2025-06-09 14:46:11 -0500
commitcd06a27e20717cdfbcc6840328f67405acc7c9e7 (patch)
tree9cc2e9c67c749f4a9e263d586a244b7e7746b5fd /client
parentdc957f6bb77c9d89b52f22b605f79f7be110f546 (diff)
jump to message
Diffstat (limited to 'client')
-rw-r--r--client/application.go17
-rw-r--r--client/buffer/buffer.go8
-rw-r--r--client/channel_list.go2
-rw-r--r--client/channel_window.go142
-rw-r--r--client/cmd_window.go6
-rw-r--r--client/empty_window.go2
-rw-r--r--client/navigation.go8
-rw-r--r--client/window/window.go9
8 files changed, 164 insertions, 30 deletions
diff --git a/client/application.go b/client/application.go
index 6b88281..f6310b9 100644
--- a/client/application.go
+++ b/client/application.go
@@ -30,9 +30,9 @@ type application struct {
func newApplication(serverAddress string) *application {
var app application
app.Client = client.New(serverAddress)
- app.goTo(app.cmdWindow.Location())
app.cache = object.NewCache(&app)
app.windowCache = window.NewCache()
+ app.goTo(app.cmdWindow.Location())
app.cmdWindow.info("welcome! type /help for help. try: /join talk")
app.cmdWindow.info("connecting to %s", app.Client.Address)
@@ -71,17 +71,24 @@ func (a *application) OnDisconnect(err error) {
func (a *application) OnEvent(cmd proto.Command) {
switch cmd.Kind {
case "p":
- cw := a.windowCache.Get(channelLocation {cmd.Target}).(*channelWindow)
- if cw != nil && len(cmd.Args) > 0 {
- cw.put(cmd.Args[0])
+ if len(cmd.Args) == 0 {
+ break
}
+ a.windowCache.ForAll(func(win window.Window) {
+ switch win.(type) {
+ case *channelWindow:
+ if win.(*channelWindow).location.id == cmd.Target {
+ win.(*channelWindow).put(cmd.Args[0])
+ }
+ }
+ })
case "update":
if len(cmd.Args) > 0 {
a.onUpdate(cmd.Target, cmd.Args[0])
}
case "delete":
a.cache.Gone(cmd.Target)
- cl := channelLocation {cmd.Target}
+ cl := channelLocation {id: cmd.Target}
if a.windowCache.Get(cl) != nil {
a.windowCache.Evict(cl)
a.removeFromHistory(cl)
diff --git a/client/buffer/buffer.go b/client/buffer/buffer.go
index b7e79e9..16be441 100644
--- a/client/buffer/buffer.go
+++ b/client/buffer/buffer.go
@@ -85,6 +85,10 @@ func (b *Buffer) ScrollPos() int {
return b.scroll.Get()
}
+func (b *Buffer) ScrollTo(id string) {
+ b.scroll.To(id)
+}
+
func (b *Buffer) AtBottom() bool {
return b.scroll.AtFirst()
}
@@ -93,6 +97,10 @@ func (b *Buffer) AtTop() bool {
return b.scroll.AtLast()
}
+func (b *Buffer) SetSnap(snap bool) {
+ b.scroll.NoSnap = !snap
+}
+
func (b *Buffer) Show(id string) (atTop bool) {
mouse := tui.Push(id, tui.Box {
Width: tui.Fill, Height: tui.Fill, Dir: tui.Up, Overflow: true,
diff --git a/client/channel_list.go b/client/channel_list.go
index 2daf619..25d093f 100644
--- a/client/channel_list.go
+++ b/client/channel_list.go
@@ -32,7 +32,7 @@ func (cl *channelList) setChannels(cs []proto.Object) {
*cl = nil
for _, c := range cs {
*cl = append(*cl, channelListEntry {
- c.Fields[""], channelLocation {c.Id}, false,
+ c.Fields[""], channelLocation {id: c.Id}, false,
})
}
sort.Sort(cl)
diff --git a/client/channel_window.go b/client/channel_window.go
index 3239b47..3dc208d 100644
--- a/client/channel_window.go
+++ b/client/channel_window.go
@@ -15,6 +15,7 @@ import (
type channelLocation struct {
id string
+ jumpTo string
}
type channelWindow struct {
@@ -25,6 +26,8 @@ type channelWindow struct {
replyingTo string
loadingHistory bool
endOfHistory bool
+ startOfHistory bool
+ jumpedTo buffer.Message
}
type channelMsg struct {
@@ -41,7 +44,11 @@ func (cl channelLocation) CreateWindow() window.Window {
}
cw.messageCache = object.NewCache(cw)
globalApp.cache.Watch(cl.id)
- cw.loadMoreHistory()
+ if cl.jumpTo == "" {
+ cw.startOfHistory = true
+ }
+ cw.Buf.SetSnap(cw.startOfHistory)
+ cw.loadMoreHistory(false)
return cw
}
@@ -72,14 +79,16 @@ func (cw *channelWindow) isGone(uid string) bool {
}
func (cw *channelWindow) put(msg proto.Object) {
+ if !cw.startOfHistory {
+ return
+ }
if msg.Kind == "membership" {
cw.endOfHistory = false
+ if cw.location.jumpTo != "" {
+ cw.startOfHistory = false
+ }
}
- cw.messageCache.Update(msg.Id, msg)
- if !cw.messageCache.IsWatched(msg.Id) {
- cw.messageCache.Watch(msg.Id)
- }
- cw.Buf.Add(channelMsg {msg, cw})
+ cw.addMessage(msg, true)
}
func (cw *channelWindow) Location() window.Location {
@@ -95,8 +104,12 @@ func (cw *channelWindow) Kill() {
func (cw *channelWindow) Buffer() *buffer.Buffer {
if cw.Buf.AtTop() {
- cw.loadMoreHistory()
+ cw.loadMoreHistory(false)
+ }
+ if cw.Buf.AtBottom() {
+ cw.loadMoreHistory(true)
}
+ cw.Buf.SetSnap(cw.startOfHistory)
return &cw.Buf
}
@@ -124,6 +137,11 @@ func (cw *channelWindow) Send(text string) {
msg := proto.Object {"m", "", fields}
globalApp.Request(proto.NewCmd("p", cw.location.id, msg), cb)
cw.In.SetText("")
+ if cw.location.jumpTo == "" {
+ cw.Buf.ScrollBottom()
+ } else {
+ globalApp.goTo(channelLocation {id: cw.location.id})
+ }
}
func (cw *channelWindow) replyTo(id string) {
@@ -151,37 +169,87 @@ func (cw *channelWindow) renameChannel(newName string) {
})
}
-func (cw *channelWindow) loadMoreHistory() {
- if cw.loadingHistory || cw.endOfHistory {
+func (cw *channelWindow) addMessage(m proto.Object, after bool) {
+ cw.messageCache.Update(m.Id, m)
+ if !cw.messageCache.IsWatched(m.Id) {
+ cw.messageCache.Watch(m.Id)
+ }
+ cmsg := channelMsg {m, cw}
+ if after {
+ cw.Buf.Add(cmsg)
+ } else {
+ cw.Buf.AddTop(cmsg)
+ }
+ if m.Id == cw.location.jumpTo {
+ cw.jumpedTo = cmsg
+ cw.Buf.ScrollTo(cmsg.Id())
+ }
+}
+
+func (cw *channelWindow) loadMoreHistory(after bool) {
+ if cw.loadingHistory {
+ return
+ }
+ if !after && cw.endOfHistory {
+ return
+ }
+ if after && cw.startOfHistory {
return
}
cw.loadingHistory = true
rq := proto.Object {Fields: make(map[string]string)}
- top := cw.Buf.Top()
- if top != nil {
- rq.Kind = "before"
- rq.Fields[""] = top.Msg().(channelMsg).Object.Id
+ var last *buffer.List
+ if !after {
+ last = cw.Buf.Top()
} else {
+ last = cw.Buf.Bottom()
+ }
+ var lastId string
+ if last != nil {
+ lastId = last.Msg().(channelMsg).Object.Id
+ }
+
+ switch {
+ case last == nil && cw.location.jumpTo != "":
+ rq.Kind = "around"
+ rq.Fields[""] = cw.location.jumpTo
+ case last == nil:
rq.Kind = "latest"
+ case !after:
+ rq.Kind = "before"
+ rq.Fields[""] = lastId
+ case after:
+ rq.Kind = "after"
+ rq.Fields[""] = lastId
}
+
cb := func(response proto.Command) {
cw.loadingHistory = false
switch response.Kind {
case "history":
if len(response.Args) == 0 {
- cw.endOfHistory = true
+ if !after {
+ cw.endOfHistory = true
+ } else {
+ cw.startOfHistory = true
+ }
}
- for i := len(response.Args) - 1; i >= 0; i-- {
- m := response.Args[i]
- cw.messageCache.Update(m.Id, m)
- if !cw.messageCache.IsWatched(m.Id) {
- cw.messageCache.Watch(m.Id)
+ if !after {
+ for i := len(response.Args) - 1; i >= 0; i-- {
+ cw.addMessage(response.Args[i], after)
+ }
+ } else {
+ for i := 0; i < len(response.Args); i++ {
+ cw.addMessage(response.Args[i], after)
}
- cw.Buf.AddTop(channelMsg {m, cw})
}
case "fail":
- cw.endOfHistory = true
+ if !after {
+ cw.endOfHistory = true
+ } else {
+ cw.startOfHistory = true
+ }
}
}
globalApp.Request(proto.NewCmd("history", cw.location.id, rq), cb)
@@ -277,6 +345,12 @@ func (cw *channelWindow) userList(callback func(userListMsg)) {
}
func (cw *channelWindow) ShowStatusLine() {
+ if cw.location.jumpTo != "" {
+ tui.Text("viewing history", &tui.Style {
+ Fg: tui.Black, Bg: tui.White, Italic: true,
+ })
+ tui.Text(": ", nil)
+ }
ch := cw.getChannel()
if ch == nil {
return
@@ -295,7 +369,7 @@ func (cw *channelWindow) ShowComposingReply() {
Style: &tui.Style {Fg: 248, Bg: tui.Black},
})
tui.Text("> ", nil)
- tui.Text(msg.Fields[""], nil)
+ tui.Text(strings.TrimSpace(msg.Fields[""]), nil)
tui.Pop()
if mouse.Pressed && mouse.Button == 0 {
cw.replyingTo = ""
@@ -304,6 +378,12 @@ func (cw *channelWindow) ShowComposingReply() {
}
}
+func (cw *channelWindow) OnNavigate() {
+ if cw.jumpedTo != nil {
+ cw.Buf.ScrollTo(cw.jumpedTo.Id())
+ }
+}
+
func (m channelMsg) Id() string {
return "buffer." + m.Object.Id
}
@@ -365,10 +445,17 @@ func (m channelMsg) showName(bg int32, uid string, abbreviate bool) {
}
func (m channelMsg) showReply(bg int32, replyTo *proto.Object) {
- tui.Push(m.Id() + ".reply message", tui.Box {
+ mouse := tui.Push(m.Id() + ".reply message", tui.Box {
Width: tui.Children, Height: 1, Dir: tui.Right,
})
+ if replyTo != nil && mouse.Button == 0 && mouse.Pressed {
+ globalApp.goTo(channelLocation {
+ id: m.window.location.id, jumpTo: replyTo.Id,
+ })
+ globalApp.redraw = true
+ }
+
tui.Push("", tui.Box {
Width: tui.TextSize, Height: 1, NoWrap: true,
})
@@ -402,6 +489,13 @@ func (m channelMsg) showReply(bg int32, replyTo *proto.Object) {
func (m channelMsg) Show(odd bool) {
var bg int32 = colorDefault[odd]
+ if m.Object.Id == m.window.location.jumpTo {
+ bg = 17
+ }
+ tui.Push("", tui.Box {
+ Width: tui.Fill, Height: tui.Children,
+ Style: &tui.Style {Bg: bg, Fg: tui.White},
+ })
var (
isReply bool
@@ -472,4 +566,6 @@ func (m channelMsg) Show(odd bool) {
tui.Text("]", nil)
tui.Pop()
}
+
+ tui.Pop()
}
diff --git a/client/cmd_window.go b/client/cmd_window.go
index 379743e..dccbaba 100644
--- a/client/cmd_window.go
+++ b/client/cmd_window.go
@@ -57,7 +57,11 @@ func (m logMsg) Show(odd bool) {
}
func (l cmdWindowLocation) CreateWindow() window.Window {
- return &globalApp.cmdWindow
+ if globalApp != nil {
+ return &globalApp.cmdWindow
+ } else {
+ return nil
+ }
}
func (w *cmdWindow) Location() window.Location {
diff --git a/client/empty_window.go b/client/empty_window.go
index 690049c..424a64d 100644
--- a/client/empty_window.go
+++ b/client/empty_window.go
@@ -29,3 +29,5 @@ func (w emptyWindow) Send(text string) {}
func (w emptyWindow) ShowStatusLine() {}
func (w emptyWindow) ShowComposingReply() {}
+
+func (w emptyWindow) OnNavigate() {}
diff --git a/client/navigation.go b/client/navigation.go
index e418445..d71f257 100644
--- a/client/navigation.go
+++ b/client/navigation.go
@@ -28,6 +28,10 @@ func (a *application) traverseHistory(direction int) {
i += direction
if i >= 0 && i < len(a.windowHist) {
a.currentWindow = a.windowHist[i]
+ win := a.windowCache.Open(a.currentWindow)
+ if win != nil {
+ win.OnNavigate()
+ }
}
}
@@ -35,4 +39,8 @@ func (a *application) goTo(location window.Location) {
a.removeFromHistory(location)
a.windowHist = append(a.windowHist, location)
a.currentWindow = location
+ win := a.windowCache.Open(a.currentWindow)
+ if win != nil {
+ win.OnNavigate()
+ }
}
diff --git a/client/window/window.go b/client/window/window.go
index 80777cc..c768da9 100644
--- a/client/window/window.go
+++ b/client/window/window.go
@@ -15,6 +15,7 @@ type Window interface {
Kill()
Buffer() *buffer.Buffer
ShowComposingReply()
+ OnNavigate()
}
type Prompt interface {
@@ -49,6 +50,12 @@ func (wc *WindowCache) Get(l Location) Window {
return wc.windows[l]
}
+func (wc *WindowCache) ForAll(do func(Window)) {
+ for _, window := range wc.windows {
+ do(window)
+ }
+}
+
type DefaultWindow struct {
In tui.TextInput
Buf buffer.Buffer
@@ -73,3 +80,5 @@ func (w *DefaultWindow) Send(text string) {}
func (w *DefaultWindow) ShowStatusLine() {}
func (w *DefaultWindow) ShowComposingReply() {}
+
+func (w *DefaultWindow) OnNavigate() {}