@@ -218,11 +218,11 @@ private void RenderWithPredictionQueryPaused()
218
218
Render ( ) ;
219
219
}
220
220
221
- private void Render ( )
221
+ private void Render ( bool force = false )
222
222
{
223
223
// If there are a bunch of keys queued up, skip rendering if we've rendered very recently.
224
224
long elapsedMs = _lastRenderTime . ElapsedMilliseconds ;
225
- if ( _queuedKeys . Count > 10 && elapsedMs < 50 )
225
+ if ( ! force && _queuedKeys . Count > 10 && elapsedMs < 50 )
226
226
{
227
227
// We won't render, but most likely the tokens will be different, so make
228
228
// sure we don't use old tokens, also allow garbage to get collected.
@@ -242,12 +242,123 @@ private void Render()
242
242
// See the following 2 GitHub issues for more context:
243
243
// - https://github.com/PowerShell/PSReadLine/issues/3879#issuecomment-2573996070
244
244
// - https://github.com/PowerShell/PowerShell/issues/24696
245
- if ( elapsedMs < 50 )
245
+ if ( ! force && elapsedMs < 50 )
246
246
{
247
247
_handlePotentialResizing = false ;
248
248
}
249
249
250
- ForceRender ( ) ;
250
+ // Use simplified rendering for screen readers
251
+ if ( Options . ScreenReader )
252
+ {
253
+ SafeRender ( ) ;
254
+ }
255
+ else
256
+ {
257
+ ForceRender ( ) ;
258
+ }
259
+ }
260
+
261
+ private void SafeRender ( )
262
+ {
263
+ int bufferWidth = _console . BufferWidth ;
264
+ int bufferHeight = _console . BufferHeight ;
265
+
266
+ static int FindCommonPrefixLength ( string leftStr , string rightStr )
267
+ {
268
+ if ( string . IsNullOrEmpty ( leftStr ) || string . IsNullOrEmpty ( rightStr ) )
269
+ {
270
+ return 0 ;
271
+ }
272
+
273
+ int i = 0 ;
274
+ int minLength = Math . Min ( leftStr . Length , rightStr . Length ) ;
275
+
276
+ while ( i < minLength && leftStr [ i ] == rightStr [ i ] )
277
+ {
278
+ i ++ ;
279
+ }
280
+
281
+ return i ;
282
+ }
283
+
284
+ // For screen readers, we are just comparing the previous and current buffer text
285
+ // (without colors) and only writing the differences.
286
+ string currentBuffer = ParseInput ( ) ;
287
+ string previousBuffer = _previousRender . lines [ 0 ] . Line ;
288
+
289
+ // In case the buffer was resized.
290
+ RecomputeInitialCoords ( isTextBufferUnchanged : false ) ;
291
+
292
+ // Make cursor invisible while we're rendering.
293
+ _console . CursorVisible = false ;
294
+
295
+ // Calculate what to render and where to start the rendering.
296
+ // TODO: Short circuit optimization when currentBuffer == previousBuffer.
297
+ int commonPrefixLength = FindCommonPrefixLength ( previousBuffer , currentBuffer ) ;
298
+
299
+ if ( commonPrefixLength > 0 && commonPrefixLength == previousBuffer . Length )
300
+ {
301
+ // Previous buffer is a complete prefix of current buffer.
302
+ // Just append the new data.
303
+ var appendedData = currentBuffer . Substring ( commonPrefixLength ) ;
304
+ _console . Write ( appendedData ) ;
305
+ }
306
+ else if ( commonPrefixLength > 0 )
307
+ {
308
+ // Buffers share a common prefix but previous buffer has additional content.
309
+ // Move cursor to where the difference starts, clear forward, and write the data.
310
+ var diffPoint = ConvertOffsetToPoint ( commonPrefixLength ) ;
311
+ _console . SetCursorPosition ( diffPoint . X , diffPoint . Y ) ;
312
+ var changedData = currentBuffer . Substring ( commonPrefixLength ) ;
313
+ _console . Write ( "\x1b [0J" ) ;
314
+ _console . Write ( changedData ) ;
315
+ }
316
+ else
317
+ {
318
+ // No common prefix, rewrite entire buffer.
319
+ _console . SetCursorPosition ( _initialX , _initialY ) ;
320
+ _console . Write ( "\x1b [0J" ) ;
321
+ _console . Write ( currentBuffer ) ;
322
+ }
323
+
324
+ // If we had to wrap to render everything, update _initialY
325
+ var endPoint = ConvertOffsetToPoint ( currentBuffer . Length ) ;
326
+ int physicalLine = endPoint . Y - _initialY ;
327
+ if ( _initialY + physicalLine > bufferHeight )
328
+ {
329
+ // We had to scroll to render everything, update _initialY.
330
+ _initialY = bufferHeight - physicalLine ;
331
+ }
332
+
333
+ // Preserve the current render data.
334
+ var renderData = new RenderData
335
+ {
336
+ lines = new RenderedLineData [ ] { new ( currentBuffer , isFirstLogicalLine : true ) } ,
337
+ errorPrompt = ( _parseErrors != null && _parseErrors . Length > 0 ) // Not yet used.
338
+ } ;
339
+ _previousRender = renderData ;
340
+
341
+ // Calculate the coord to place the cursor for the next input.
342
+ var point = ConvertOffsetToPoint ( _current ) ;
343
+ if ( point . X == 0 && point . Y == bufferHeight )
344
+ {
345
+ // The cursor top exceeds the buffer height and it hasn't already wrapped,
346
+ // so we need to scroll up the buffer by 1 line.
347
+ _console . Write ( "\n " ) ;
348
+
349
+ // Adjust the initial cursor position and the to-be-set cursor position
350
+ // after scrolling up the buffer.
351
+ _initialY -= 1 ;
352
+ point . Y -= 1 ;
353
+ }
354
+ _console . SetCursorPosition ( point . X , point . Y ) ;
355
+ _console . CursorVisible = true ;
356
+
357
+ _previousRender . UpdateConsoleInfo ( bufferWidth , bufferHeight , point . X , point . Y ) ;
358
+ _previousRender . initialY = _initialY ;
359
+
360
+ _lastRenderTime . Restart ( ) ;
361
+ _waitingToRender = false ;
251
362
}
252
363
253
364
private void ForceRender ( )
@@ -261,7 +372,7 @@ private void ForceRender()
261
372
// and minimize writing more than necessary on the next render.)
262
373
263
374
var renderLines = new RenderedLineData [ logicalLineCount ] ;
264
- var renderData = new RenderData { lines = renderLines } ;
375
+ var renderData = new RenderData { lines = renderLines } ;
265
376
for ( var i = 0 ; i < logicalLineCount ; i ++ )
266
377
{
267
378
var line = _consoleBufferLines [ i ] . ToString ( ) ;
@@ -910,7 +1021,6 @@ void UpdateColorsIfNecessary(string newColor)
910
1021
911
1022
// Calculate the coord to place the cursor for the next input.
912
1023
var point = ConvertOffsetToPoint ( _current ) ;
913
-
914
1024
if ( point . Y == bufferHeight )
915
1025
{
916
1026
// The cursor top exceeds the buffer height, so we need to
@@ -946,7 +1056,6 @@ void UpdateColorsIfNecessary(string newColor)
946
1056
_current = ConvertLineAndColumnToOffset ( point ) ;
947
1057
point = ConvertOffsetToPoint ( _current ) ;
948
1058
}
949
-
950
1059
_console . SetCursorPosition ( point . X , point . Y ) ;
951
1060
_console . CursorVisible = true ;
952
1061
@@ -1827,6 +1936,6 @@ public static void ScrollDisplayToCursor(ConsoleKeyInfo? key = null, object arg
1827
1936
newTop = ( console . BufferHeight - console . WindowHeight ) ;
1828
1937
}
1829
1938
console . SetWindowPosition ( 0 , newTop ) ;
1830
- }
1939
+ }
1831
1940
}
1832
1941
}
0 commit comments