Implement message selection-based scrolling with j/k keys
Co-authored-by: foglar <82380203+foglar@users.noreply.github.com>
This commit is contained in:
parent
113fe93f58
commit
c2607b2860
@ -28,26 +28,40 @@ func updateChatView(v *gocui.View) {
|
|||||||
clear := cleanimage.NewKittyImageCleaner()
|
clear := cleanimage.NewKittyImageCleaner()
|
||||||
fmt.Print(clear.DeleteByColumn(chatViewColumn, false))
|
fmt.Print(clear.DeleteByColumn(chatViewColumn, false))
|
||||||
|
|
||||||
// Reset scroll offset when switching contacts or if out of bounds
|
|
||||||
if chatScrollOffset < 0 {
|
|
||||||
chatScrollOffset = 0
|
|
||||||
}
|
|
||||||
if chatScrollOffset > len(chatData.Messages) {
|
|
||||||
chatScrollOffset = len(chatData.Messages)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set view origin based on scroll offset
|
|
||||||
ox, _ := v.Origin()
|
|
||||||
v.SetOrigin(ox, chatScrollOffset)
|
|
||||||
|
|
||||||
// Check if we have valid users
|
// Check if we have valid users
|
||||||
if len(users) == 0 || selectedUserIdx >= len(users) {
|
if len(users) == 0 || selectedUserIdx >= len(users) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get view dimensions to check if images are visible
|
// Ensure selectedMessageIdx is within bounds
|
||||||
|
if len(chatData.Messages) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selectedMessageIdx < 0 {
|
||||||
|
selectedMessageIdx = 0
|
||||||
|
}
|
||||||
|
if selectedMessageIdx >= len(chatData.Messages) {
|
||||||
|
selectedMessageIdx = len(chatData.Messages) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get view dimensions
|
||||||
_, viewHeight := v.Size()
|
_, viewHeight := v.Size()
|
||||||
|
|
||||||
|
// Calculate which messages should be visible
|
||||||
|
// We want to show the selected message in the middle of the view when possible
|
||||||
|
messagesPerView := viewHeight / messageRowIncrement
|
||||||
|
if messagesPerView <= 0 {
|
||||||
|
messagesPerView = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate start index - show newest messages at bottom
|
||||||
|
startIdx := max(0, min(selectedMessageIdx-(messagesPerView/2), len(chatData.Messages)-messagesPerView))
|
||||||
|
|
||||||
|
// Calculate scroll offset to position view correctly
|
||||||
|
scrollOffset := startIdx * messageRowIncrement
|
||||||
|
ox, _ := v.Origin()
|
||||||
|
v.SetOrigin(ox, scrollOffset)
|
||||||
|
|
||||||
// Get terminal cell size once for all messages
|
// Get terminal cell size once for all messages
|
||||||
w, h, err := cell_size.GetTerminalCellSizePixels()
|
w, h, err := cell_size.GetTerminalCellSizePixels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,7 +76,10 @@ func updateChatView(v *gocui.View) {
|
|||||||
h = 0
|
h = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, msg := range chatData.Messages {
|
// Render only visible messages
|
||||||
|
endIdx := min(startIdx+messagesPerView+1, len(chatData.Messages))
|
||||||
|
for i := startIdx; i < endIdx; i++ {
|
||||||
|
msg := chatData.Messages[i]
|
||||||
decoded, err := base64.StdEncoding.DecodeString(msg.Content)
|
decoded, err := base64.StdEncoding.DecodeString(msg.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error decoding message: %v", err)
|
log.Printf("Error decoding message: %v", err)
|
||||||
@ -90,12 +107,20 @@ func updateChatView(v *gocui.View) {
|
|||||||
iconPath = fmt.Sprintf(contactIconPathFmt, strings.ToLower(msg.Sender))
|
iconPath = fmt.Sprintf(contactIconPathFmt, strings.ToLower(msg.Sender))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print message text
|
// Highlight selected message
|
||||||
fmt.Fprintf(v, "%s", "\t\t\t\t\t"+senderLabel+"\n\t\t\t\t\t"+string(decoded)+"\n\n")
|
prefix := "\t\t\t\t\t"
|
||||||
|
if i == selectedMessageIdx {
|
||||||
|
prefix = "\x1b[7m\t\t\t\t\t"
|
||||||
|
senderLabel = senderLabel + "\x1b[0m\x1b[7m"
|
||||||
|
decoded = append([]byte("\x1b[0m\x1b[7m"), decoded...)
|
||||||
|
decoded = append(decoded, []byte("\x1b[0m")...)
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate row position accounting for scroll offset
|
// Print message text
|
||||||
// Each message takes 3 lines (messageRowIncrement)
|
fmt.Fprintf(v, "%s", prefix+senderLabel+"\n"+prefix+string(decoded)+"\n\n")
|
||||||
rowPosition := i*messageRowIncrement + messageRowOffset - chatScrollOffset
|
|
||||||
|
// Calculate row position for image rendering
|
||||||
|
rowPosition := i*messageRowIncrement + messageRowOffset - scrollOffset
|
||||||
|
|
||||||
// Only render images that are visible in the view
|
// Only render images that are visible in the view
|
||||||
if rowPosition >= 0 && rowPosition < viewHeight {
|
if rowPosition >= 0 && rowPosition < viewHeight {
|
||||||
@ -126,36 +151,45 @@ func sendMessage(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset scroll to bottom when sending a new message
|
// Select newest message (at the end)
|
||||||
chatScrollOffset = 0
|
if len(chatData.Messages) > 0 {
|
||||||
|
selectedMessageIdx = len(chatData.Messages) - 1
|
||||||
|
}
|
||||||
updateChatView(chatView)
|
updateChatView(chatView)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollChatUp(g *gocui.Gui, v *gocui.View) error {
|
func scrollChatUp(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if len(chatData.Messages) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
chatView, err := g.View("chat")
|
chatView, err := g.View("chat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll up by 1 line
|
// Move selection up (to older messages)
|
||||||
if chatScrollOffset > 0 {
|
if selectedMessageIdx > 0 {
|
||||||
chatScrollOffset--
|
selectedMessageIdx--
|
||||||
updateChatView(chatView)
|
updateChatView(chatView)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollChatDown(g *gocui.Gui, v *gocui.View) error {
|
func scrollChatDown(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if len(chatData.Messages) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
chatView, err := g.View("chat")
|
chatView, err := g.View("chat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll down by 1 line
|
// Move selection down (to newer messages)
|
||||||
maxScroll := len(chatData.Messages) * messageRowIncrement
|
if selectedMessageIdx < len(chatData.Messages)-1 {
|
||||||
if chatScrollOffset < maxScroll {
|
selectedMessageIdx++
|
||||||
chatScrollOffset++
|
|
||||||
updateChatView(chatView)
|
updateChatView(chatView)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -74,8 +74,15 @@ func nextContact(g *gocui.Gui, v *gocui.View) error {
|
|||||||
selectedUserIdx = 0
|
selectedUserIdx = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset scroll offset when changing contacts
|
// Load messages for new contact
|
||||||
chatScrollOffset = 0
|
LoadMessages(users[selectedUserIdx])
|
||||||
|
|
||||||
|
// Select newest message (scroll to bottom)
|
||||||
|
if len(chatData.Messages) > 0 {
|
||||||
|
selectedMessageIdx = len(chatData.Messages) - 1
|
||||||
|
} else {
|
||||||
|
selectedMessageIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
if err := updateContactsView(g); err != nil {
|
if err := updateContactsView(g); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -99,8 +106,15 @@ func prevContact(g *gocui.Gui, v *gocui.View) error {
|
|||||||
selectedUserIdx = len(users) - 1
|
selectedUserIdx = len(users) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset scroll offset when changing contacts
|
// Load messages for new contact
|
||||||
chatScrollOffset = 0
|
LoadMessages(users[selectedUserIdx])
|
||||||
|
|
||||||
|
// Select newest message (scroll to bottom)
|
||||||
|
if len(chatData.Messages) > 0 {
|
||||||
|
selectedMessageIdx = len(chatData.Messages) - 1
|
||||||
|
} else {
|
||||||
|
selectedMessageIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
if err := updateContactsView(g); err != nil {
|
if err := updateContactsView(g); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -10,7 +10,7 @@ var users []string
|
|||||||
var prevWidth, prevHeight int
|
var prevWidth, prevHeight int
|
||||||
var chatData ChatData
|
var chatData ChatData
|
||||||
var selectedUserIdx int = 0
|
var selectedUserIdx int = 0
|
||||||
var chatScrollOffset int = 0
|
var selectedMessageIdx int = 0
|
||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
LoadContacts(defaultServerPath)
|
LoadContacts(defaultServerPath)
|
||||||
@ -18,6 +18,10 @@ func Run() {
|
|||||||
// Load initial messages if there are any contacts
|
// Load initial messages if there are any contacts
|
||||||
if len(users) > 0 && selectedUserIdx < len(users) {
|
if len(users) > 0 && selectedUserIdx < len(users) {
|
||||||
LoadMessages(users[selectedUserIdx])
|
LoadMessages(users[selectedUserIdx])
|
||||||
|
// Initialize to newest message (bottom)
|
||||||
|
if len(chatData.Messages) > 0 {
|
||||||
|
selectedMessageIdx = len(chatData.Messages) - 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user