@@ -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