diff options
| -rw-r--r-- | client/application.go | 17 | ||||
| -rw-r--r-- | client/buffer/buffer.go | 8 | ||||
| -rw-r--r-- | client/channel_list.go | 2 | ||||
| -rw-r--r-- | client/channel_window.go | 142 | ||||
| -rw-r--r-- | client/cmd_window.go | 6 | ||||
| -rw-r--r-- | client/empty_window.go | 2 | ||||
| -rw-r--r-- | client/navigation.go | 8 | ||||
| -rw-r--r-- | client/window/window.go | 9 | ||||
| -rw-r--r-- | server/channel/command.go | 5 | ||||
| -rw-r--r-- | tui/layout.go | 2 | ||||
| -rw-r--r-- | tui/scroll_state.go | 5 |
11 files changed, 174 insertions, 32 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() {} diff --git a/server/channel/command.go b/server/channel/command.go index 05a1c7a..c4eb13b 100644 --- a/server/channel/command.go +++ b/server/channel/command.go @@ -140,7 +140,7 @@ func (c *Channel) SendRequest(r session.Request) { case "latest": max = len(c.messages) min = max - 20 - case "before", "around", "at": + case "before", "around", "after", "at": var id string for k, v := range h.Fields { switch k { @@ -163,6 +163,9 @@ func (c *Channel) SendRequest(r session.Request) { case "around": min = i - 9 max = i + 11 + case "after": + min = i + 1 + max = min + 20 case "at": min = i max = i + 1 diff --git a/tui/layout.go b/tui/layout.go index f8ea4bc..d230a87 100644 --- a/tui/layout.go +++ b/tui/layout.go @@ -342,7 +342,7 @@ func (b *Box) computePositions(axis int) { b.Scroll.offset = -p b.Scroll.at = first.id } - if b.Scroll.absolute == 0 { + if b.Scroll.absolute == 0 && !b.Scroll.NoSnap { b.Scroll.at = "" b.Scroll.offset = 0 } diff --git a/tui/scroll_state.go b/tui/scroll_state.go index 75797b1..a712634 100644 --- a/tui/scroll_state.go +++ b/tui/scroll_state.go @@ -6,6 +6,7 @@ type ScrollState struct { absolute int atFirst bool atLast bool + NoSnap bool } func (s *ScrollState) ToStart() { @@ -25,6 +26,10 @@ func (s *ScrollState) Scroll(amnt int) { s.absolute += amnt } +func (s *ScrollState) To(id string) { + s.at = id +} + func (s *ScrollState) ByMouse(ev MouseEvent, reverse bool) { scroll := ev.Scroll * 5 if reverse { |
