@@ -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,142 @@ 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 Point CalculateNextInputPoint ( int _current )
262
+ {
263
+ var point = ConvertOffsetToPoint ( _current ) ;
264
+
265
+ if ( point . Y == _console . BufferHeight )
266
+ {
267
+ // The cursor top exceeds the buffer height, so we need to
268
+ // scroll up the buffer by 1 line.
269
+ _console . Write ( "\n " ) ;
270
+
271
+ // Adjust the initial cursor position and the to-be-set cursor position
272
+ // after scrolling up the buffer.
273
+ _initialY -= 1 ;
274
+ point . Y -= 1 ;
275
+ }
276
+ else if ( point . Y < 0 )
277
+ {
278
+ // This could happen in at least 3 cases:
279
+ //
280
+ // 1. when you are adding characters to the first line in the buffer (top = 0) to make the logical line
281
+ // wrap to one extra physical line. This would cause the buffer to scroll up and push the line being
282
+ // edited up-off the buffer.
283
+ // 2. when you are deleting characters (Backspace) from the first line in the buffer without changing the
284
+ // number of physical lines (either editing the same logical line or causing the current logical line
285
+ // to merge in the previous but still span to the current physical line). The cursor is supposed to
286
+ // appear in the previous line (which is off the buffer).
287
+ // 3. Both 'bck-i-search' and 'fwd-i-search' may find a history command with multi-line text, and the
288
+ // matching string in the text, where the cursor is supposed to be moved to, will be scrolled up-off
289
+ // the buffer after rendering.
290
+ //
291
+ // In these case, we move the cursor to the left-most position of the first line, where it's closest to
292
+ // the real position it should be in the ideal world.
293
+
294
+ // First update '_current' to the index of the first character that appears on the line 0,
295
+ // then we call 'ConvertOffsetToPoint' again to get the right cursor position to use.
296
+ point . X = point . Y = 0 ;
297
+ _current = ConvertLineAndColumnToOffset ( point ) ;
298
+ point = ConvertOffsetToPoint ( _current ) ;
299
+ }
300
+
301
+ return point ;
302
+ }
303
+
304
+ private void SafeRender ( )
305
+ {
306
+ static int FindCommonPrefixLength ( string leftStr , string rightStr )
307
+ {
308
+ if ( string . IsNullOrEmpty ( leftStr ) || string . IsNullOrEmpty ( rightStr ) )
309
+ {
310
+ return 0 ;
311
+ }
312
+
313
+ int i = 0 ;
314
+ int minLength = Math . Min ( leftStr . Length , rightStr . Length ) ;
315
+
316
+ while ( i < minLength && leftStr [ i ] == rightStr [ i ] )
317
+ {
318
+ i ++ ;
319
+ }
320
+
321
+ return i ;
322
+ }
323
+
324
+ // For screen readers, we don't need complex tokenization or coloring.
325
+ // Just compare the raw buffer content and output differences.
326
+ // (Though we do have to adjust the cursor aftewards.)
327
+ string currentBuffer = _buffer . ToString ( ) ; // Just like GenerateRender().
328
+ string previousBuffer = _previousRender . lines [ 0 ] . Line ;
329
+
330
+ // In case the buffer was resized.
331
+ RecomputeInitialCoords ( isTextBufferUnchanged : false ) ;
332
+
333
+ // Make cursor invisible while we're rendering.
334
+ _console . CursorVisible = false ;
335
+
336
+ // Calculate what to render and where to start the rendering.
337
+ int commonPrefixLength = FindCommonPrefixLength ( previousBuffer , currentBuffer ) ;
338
+
339
+ if ( commonPrefixLength > 0 && commonPrefixLength == previousBuffer . Length )
340
+ {
341
+ // Previous buffer is a complete prefix of current buffer.
342
+ // Just append the new data.
343
+ var appendedData = currentBuffer . Substring ( commonPrefixLength ) ;
344
+ _console . Write ( appendedData ) ;
345
+ }
346
+ else if ( commonPrefixLength > 0 )
347
+ {
348
+ // Buffers share a common prefix but previous buffer has additional content.
349
+ // Move cursor to where the difference starts, clear forward, and write the data.
350
+ var diffPoint = ConvertOffsetToPoint ( commonPrefixLength ) ;
351
+ _console . SetCursorPosition ( diffPoint . X , diffPoint . Y ) ;
352
+ var changedData = currentBuffer . Substring ( commonPrefixLength ) ;
353
+ _console . Write ( "\x1b [0J" ) ;
354
+ _console . Write ( changedData ) ;
355
+ }
356
+ else
357
+ {
358
+ // No common prefix, rewrite entire buffer.
359
+ _console . SetCursorPosition ( _initialX , _initialY ) ;
360
+ _console . Write ( "\x1b [0J" ) ;
361
+ _console . Write ( currentBuffer ) ;
362
+ }
363
+
364
+ // Preserve the current render data.
365
+ var renderData = new RenderData
366
+ {
367
+ lines = new RenderedLineData [ ] { new ( currentBuffer , isFirstLogicalLine : true ) }
368
+ } ;
369
+ _previousRender = renderData ;
370
+
371
+ // Calculate the coord to place the cursor for the next input.
372
+ var point = CalculateNextInputPoint ( _current ) ;
373
+ _console . SetCursorPosition ( point . X , point . Y ) ;
374
+ _console . CursorVisible = true ;
375
+
376
+ renderData . UpdateConsoleInfo ( _console . BufferWidth , _console . BufferHeight , point . X , point . Y ) ;
377
+ renderData . initialY = _initialY ;
378
+
379
+ _lastRenderTime . Restart ( ) ;
380
+ _waitingToRender = false ;
251
381
}
252
382
253
383
private void ForceRender ( )
@@ -261,7 +391,7 @@ private void ForceRender()
261
391
// and minimize writing more than necessary on the next render.)
262
392
263
393
var renderLines = new RenderedLineData [ logicalLineCount ] ;
264
- var renderData = new RenderData { lines = renderLines } ;
394
+ var renderData = new RenderData { lines = renderLines } ;
265
395
for ( var i = 0 ; i < logicalLineCount ; i ++ )
266
396
{
267
397
var line = _consoleBufferLines [ i ] . ToString ( ) ;
@@ -909,44 +1039,7 @@ void UpdateColorsIfNecessary(string newColor)
909
1039
}
910
1040
911
1041
// Calculate the coord to place the cursor for the next input.
912
- var point = ConvertOffsetToPoint ( _current ) ;
913
-
914
- if ( point . Y == bufferHeight )
915
- {
916
- // The cursor top exceeds the buffer height, so we need to
917
- // scroll up the buffer by 1 line.
918
- _console . Write ( "\n " ) ;
919
-
920
- // Adjust the initial cursor position and the to-be-set cursor position
921
- // after scrolling up the buffer.
922
- _initialY -= 1 ;
923
- point . Y -= 1 ;
924
- }
925
- else if ( point . Y < 0 )
926
- {
927
- // This could happen in at least 3 cases:
928
- //
929
- // 1. when you are adding characters to the first line in the buffer (top = 0) to make the logical line
930
- // wrap to one extra physical line. This would cause the buffer to scroll up and push the line being
931
- // edited up-off the buffer.
932
- // 2. when you are deleting characters (Backspace) from the first line in the buffer without changing the
933
- // number of physical lines (either editing the same logical line or causing the current logical line
934
- // to merge in the previous but still span to the current physical line). The cursor is supposed to
935
- // appear in the previous line (which is off the buffer).
936
- // 3. Both 'bck-i-search' and 'fwd-i-search' may find a history command with multi-line text, and the
937
- // matching string in the text, where the cursor is supposed to be moved to, will be scrolled up-off
938
- // the buffer after rendering.
939
- //
940
- // In these case, we move the cursor to the left-most position of the first line, where it's closest to
941
- // the real position it should be in the ideal world.
942
-
943
- // First update '_current' to the index of the first character that appears on the line 0,
944
- // then we call 'ConvertOffsetToPoint' again to get the right cursor position to use.
945
- point . X = point . Y = 0 ;
946
- _current = ConvertLineAndColumnToOffset ( point ) ;
947
- point = ConvertOffsetToPoint ( _current ) ;
948
- }
949
-
1042
+ var point = CalculateNextInputPoint ( _current ) ;
950
1043
_console . SetCursorPosition ( point . X , point . Y ) ;
951
1044
_console . CursorVisible = true ;
952
1045
@@ -1178,8 +1271,11 @@ private void MoveCursor(int newCursor)
1178
1271
// to issuing a newline to scroll the buffer.
1179
1272
_console . SetCursorPosition ( point . X , point . Y ) ;
1180
1273
1181
- // Scroll up the buffer by 1 line.
1182
- _console . Write ( "\n " ) ;
1274
+ // Scroll up the buffer by 1 line, except under a screen reader which naturally wraps.
1275
+ if ( ! Options . ScreenReader )
1276
+ {
1277
+ _console . Write ( "\n " ) ;
1278
+ }
1183
1279
}
1184
1280
else
1185
1281
{
@@ -1827,6 +1923,6 @@ public static void ScrollDisplayToCursor(ConsoleKeyInfo? key = null, object arg
1827
1923
newTop = ( console . BufferHeight - console . WindowHeight ) ;
1828
1924
}
1829
1925
console . SetWindowPosition ( 0 , newTop ) ;
1830
- }
1926
+ }
1831
1927
}
1832
1928
}
0 commit comments