diff --git a/internal/tui/chat.go b/internal/tui/chat.go index a379171..b5b036d 100644 --- a/internal/tui/chat.go +++ b/internal/tui/chat.go @@ -28,26 +28,40 @@ func updateChatView(v *gocui.View) { clear := cleanimage.NewKittyImageCleaner() 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 if len(users) == 0 || selectedUserIdx >= len(users) { 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() + // 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 w, h, err := cell_size.GetTerminalCellSizePixels() if err != nil { @@ -62,7 +76,10 @@ func updateChatView(v *gocui.View) { 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) if err != nil { log.Printf("Error decoding message: %v", err) @@ -90,12 +107,20 @@ func updateChatView(v *gocui.View) { iconPath = fmt.Sprintf(contactIconPathFmt, strings.ToLower(msg.Sender)) } - // Print message text - fmt.Fprintf(v, "%s", "\t\t\t\t\t"+senderLabel+"\n\t\t\t\t\t"+string(decoded)+"\n\n") + // Highlight selected message + 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 - // Each message takes 3 lines (messageRowIncrement) - rowPosition := i*messageRowIncrement + messageRowOffset - chatScrollOffset + // Print message text + fmt.Fprintf(v, "%s", prefix+senderLabel+"\n"+prefix+string(decoded)+"\n\n") + + // Calculate row position for image rendering + rowPosition := i*messageRowIncrement + messageRowOffset - scrollOffset // Only render images that are visible in the view if rowPosition >= 0 && rowPosition < viewHeight { @@ -126,36 +151,45 @@ func sendMessage(g *gocui.Gui, v *gocui.View) error { return err } - // Reset scroll to bottom when sending a new message - chatScrollOffset = 0 + // Select newest message (at the end) + if len(chatData.Messages) > 0 { + selectedMessageIdx = len(chatData.Messages) - 1 + } updateChatView(chatView) return nil } func scrollChatUp(g *gocui.Gui, v *gocui.View) error { + if len(chatData.Messages) == 0 { + return nil + } + chatView, err := g.View("chat") if err != nil { return err } - // Scroll up by 1 line - if chatScrollOffset > 0 { - chatScrollOffset-- + // Move selection up (to older messages) + if selectedMessageIdx > 0 { + selectedMessageIdx-- updateChatView(chatView) } return nil } func scrollChatDown(g *gocui.Gui, v *gocui.View) error { + if len(chatData.Messages) == 0 { + return nil + } + chatView, err := g.View("chat") if err != nil { return err } - // Scroll down by 1 line - maxScroll := len(chatData.Messages) * messageRowIncrement - if chatScrollOffset < maxScroll { - chatScrollOffset++ + // Move selection down (to newer messages) + if selectedMessageIdx < len(chatData.Messages)-1 { + selectedMessageIdx++ updateChatView(chatView) } return nil diff --git a/internal/tui/sidebar.go b/internal/tui/sidebar.go index aa8614f..22c048f 100644 --- a/internal/tui/sidebar.go +++ b/internal/tui/sidebar.go @@ -74,8 +74,15 @@ func nextContact(g *gocui.Gui, v *gocui.View) error { selectedUserIdx = 0 } - // Reset scroll offset when changing contacts - chatScrollOffset = 0 + // Load messages for new contact + 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 { return err @@ -99,8 +106,15 @@ func prevContact(g *gocui.Gui, v *gocui.View) error { selectedUserIdx = len(users) - 1 } - // Reset scroll offset when changing contacts - chatScrollOffset = 0 + // Load messages for new contact + 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 { return err diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 3871920..f35c880 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -10,7 +10,7 @@ var users []string var prevWidth, prevHeight int var chatData ChatData var selectedUserIdx int = 0 -var chatScrollOffset int = 0 +var selectedMessageIdx int = 0 func Run() { LoadContacts(defaultServerPath) @@ -18,6 +18,10 @@ func Run() { // Load initial messages if there are any contacts if len(users) > 0 && selectedUserIdx < len(users) { LoadMessages(users[selectedUserIdx]) + // Initialize to newest message (bottom) + if len(chatData.Messages) > 0 { + selectedMessageIdx = len(chatData.Messages) - 1 + } } g, err := gocui.NewGui(gocui.OutputNormal)