Skip to content

Commit ddd7b89

Browse files
authored
Merge pull request #177 from sourcery-ai/ben/sou-1485-feat-if-user-scrolls-up-during-a-code-generation-stop-auto
fix: scroll message window to bottom, unless the user has scrolled up
2 parents c56e1fb + 1504116 commit ddd7b89

File tree

3 files changed

+32
-8
lines changed

3 files changed

+32
-8
lines changed

media/chat.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
margin: 0;
2626
display: flex;
2727
flex-direction: column;
28+
overflow-y: scroll;
2829
}
2930

3031
.sidebar__chat-assistant--chat-bubble {

src/chat.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,10 @@ export class ChatProvider implements vscode.WebviewViewProvider {
252252
253253
</head>
254254
<body class="sidebar__chat-assistant-body">
255-
<section class="sidebar__section-container active" data-section="chat-assistant">
255+
<section id="message-container" class="sidebar__section-container active" data-section="chat-assistant">
256256
<ul class="sidebar__chat-assistant--dialogue-container">
257-
257+
258+
<li id="anchor"></li>
258259
</ul>
259260
</section>
260261
<footer class="sidebar__chat-assistant--footer">

src/webview/chat.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
99
<span class="sidebar__chat-assistant--agent-avatar-image">🧙🏻‍♂️</span>
1010
</div>`;
1111

12+
// This is a little larger than the (empirical) number of pixels the window might scroll by when
13+
// a new line is added. If the line is less than this, we should try to keep scrolling with the window.
14+
// See `stickyScrollToBottom` below.
15+
const LINE_HEIGHT = 36;
16+
1217
// This script will be run within the webview itself
1318
// It cannot access the main VS Code APIs directly.
1419
(function () {
@@ -28,6 +33,8 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
2833
cancelButton.onclick = sendCancelRequest;
2934
}
3035

36+
const messageContainer = document.getElementById("message-container");
37+
3138
// Hold the current assistant message so we can direct streaming responses to it
3239
let currentAssistantMessage;
3340
let thinkingMessage;
@@ -54,7 +61,7 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
5461
window.addEventListener("message", (event) => {
5562
const message = event.data;
5663
if (message.command === "add_result") {
57-
addMessageToUI(message.result);
64+
withStickyScroll(addMessageToUI)(message.result);
5865
} else if (message.command === "clear_chat") {
5966
clearAllMessages();
6067
} else if (message.command === "focus") {
@@ -104,7 +111,6 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
104111
);
105112
userMessageElement.innerHTML = templateMessage;
106113
chatContainer.append(userMessageElement);
107-
userMessageElement.scrollIntoView();
108114
}
109115

110116
function addMessageToUI(result) {
@@ -116,6 +122,26 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
116122
}
117123
}
118124

125+
function withStickyScroll(wrapped) {
126+
return function () {
127+
const { scrollHeight: scrollHeightBefore } = messageContainer;
128+
wrapped.apply(this, arguments);
129+
const { scrollHeight: scrollHeightAfter } = messageContainer;
130+
stickyScrollToBottom(scrollHeightAfter - scrollHeightBefore);
131+
};
132+
}
133+
134+
// If we're already at the bottom, scroll the bottom into view
135+
function stickyScrollToBottom(diff = LINE_HEIGHT) {
136+
const { scrollTop, clientHeight, scrollHeight } = messageContainer;
137+
const scrollDiff = Math.abs(scrollHeight - clientHeight - scrollTop);
138+
const isScrolledToBottom = scrollDiff <= diff + 1;
139+
140+
if (isScrolledToBottom) {
141+
messageContainer.scrollTop = scrollHeight - clientHeight;
142+
}
143+
}
144+
119145
// Function to add an assistant message or add to the existing one
120146
function addAssistantMessageToUI(message) {
121147
cancelButton.disabled = false;
@@ -126,9 +152,6 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
126152

127153
const replaceCurrentAssistantMessage = () => {
128154
currentAssistantMessage.innerHTML = message.textContent;
129-
130-
// Scroll the bottom into view
131-
currentAssistantMessage.scrollIntoView(false);
132155
};
133156

134157
if (currentAssistantMessage != null && message.outcome !== "error") {
@@ -170,7 +193,6 @@ const chatAvatar = `<div class="sidebar__chat-assistant--chat-avatar-container">
170193
}
171194
thinkingMessage = thinkingMessageElement;
172195
chatContainer.append(thinkingMessage);
173-
thinkingMessage.scrollIntoView();
174196
}
175197

176198
// Enable/Disable send button depending on whether text area is empty

0 commit comments

Comments
 (0)