@@ -218,36 +218,155 @@ 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 )
226
- {
227
- // We won't render, but most likely the tokens will be different, so make
228
- // sure we don't use old tokens, also allow garbage to get collected.
229
- _tokens = null ;
230
- _ast = null ;
231
- _parseErrors = null ;
232
- _waitingToRender = true ;
233
- return ;
225
+ if ( ! force )
226
+ {
227
+ if ( _queuedKeys . Count > 10 && elapsedMs < 50 )
228
+ {
229
+ // We won't render, but most likely the tokens will be different, so make
230
+ // sure we don't use old tokens, also allow garbage to get collected.
231
+ _tokens = null ;
232
+ _ast = null ;
233
+ _parseErrors = null ;
234
+ _waitingToRender = true ;
235
+ return ;
236
+ }
237
+
238
+ // If we've rendered very recently, skip the terminal window resizing check as it's unlikely
239
+ // to happen in such a short time interval.
240
+ // We try to avoid unnecessary resizing check because it requires getting the cursor position
241
+ // which would force a network round trip in an environment where front-end xtermjs talking to
242
+ // a server-side PTY via websocket. Without querying for cursor position, content written on
243
+ // the server side could be buffered, which is much more performant.
244
+ // See the following 2 GitHub issues for more context:
245
+ // - https://github.com/PowerShell/PSReadLine/issues/3879#issuecomment-2573996070
246
+ // - https://github.com/PowerShell/PowerShell/issues/24696
247
+ if ( elapsedMs < 50 )
248
+ {
249
+ _handlePotentialResizing = false ;
250
+ }
234
251
}
235
252
236
- // If we've rendered very recently, skip the terminal window resizing check as it's unlikely
237
- // to happen in such a short time interval.
238
- // We try to avoid unnecessary resizing check because it requires getting the cursor position
239
- // which would force a network round trip in an environment where front-end xtermjs talking to
240
- // a server-side PTY via websocket. Without querying for cursor position, content written on
241
- // the server side could be buffered, which is much more performant.
242
- // See the following 2 GitHub issues for more context:
243
- // - https://github.com/PowerShell/PSReadLine/issues/3879#issuecomment-2573996070
244
- // - https://github.com/PowerShell/PowerShell/issues/24696
245
- if ( elapsedMs < 50 )
253
+ // Use simplified rendering for screen readers
254
+ if ( Options . ScreenReader )
246
255
{
247
- _handlePotentialResizing = false ;
256
+ SafeRender ( ) ;
248
257
}
258
+ else
259
+ {
260
+ ForceRender ( ) ;
261
+ }
262
+ }
263
+
264
+ private void SafeRender ( )
265
+ {
266
+ int bufferWidth = _console . BufferWidth ;
267
+ int bufferHeight = _console . BufferHeight ;
268
+
269
+ static int FindCommonPrefixLength ( string leftStr , string rightStr )
270
+ {
271
+ if ( string . IsNullOrEmpty ( leftStr ) || string . IsNullOrEmpty ( rightStr ) )
272
+ {
273
+ return 0 ;
274
+ }
249
275
250
- ForceRender ( ) ;
276
+ int i = 0 ;
277
+ int minLength = Math . Min ( leftStr . Length , rightStr . Length ) ;
278
+
279
+ while ( i < minLength && leftStr [ i ] == rightStr [ i ] )
280
+ {
281
+ i ++ ;
282
+ }
283
+
284
+ return i ;
285
+ }
286
+
287
+ // For screen readers, we are just comparing the previous and current buffer text
288
+ // (without colors) and only writing the differences.
289
+ string currentBuffer = ParseInput ( ) ;
290
+ string previousBuffer = _previousRender . lines [ 0 ] . Line ;
291
+
292
+ // In case the buffer was resized.
293
+ RecomputeInitialCoords ( isTextBufferUnchanged : false ) ;
294
+
295
+ // Make cursor invisible while we're rendering.
296
+ _console . CursorVisible = false ;
297
+
298
+ // Calculate what to render and where to start the rendering.
299
+ // TODO: Short circuit optimization when currentBuffer == previousBuffer.
300
+ int commonPrefixLength = FindCommonPrefixLength ( previousBuffer , currentBuffer ) ;
301
+
302
+ if ( commonPrefixLength > 0 && commonPrefixLength == previousBuffer . Length )
303
+ {
304
+ // Previous buffer is a complete prefix of current buffer.
305
+ // Just append the new data.
306
+ var appendedData = currentBuffer . Substring ( commonPrefixLength ) ;
307
+ _console . Write ( appendedData ) ;
308
+ }
309
+ else if ( commonPrefixLength > 0 )
310
+ {
311
+ // Buffers share a common prefix but previous buffer has additional content.
312
+ // Move cursor to where the difference starts, clear forward, and write the data.
313
+ var diffPoint = ConvertOffsetToPoint ( commonPrefixLength ) ;
314
+ _console . SetCursorPosition ( diffPoint . X , diffPoint . Y ) ;
315
+ var changedData = currentBuffer . Substring ( commonPrefixLength ) ;
316
+ _console . Write ( "\x1b [0J" ) ;
317
+ _console . Write ( changedData ) ;
318
+ }
319
+ else
320
+ {
321
+ // No common prefix, rewrite entire buffer.
322
+ _console . SetCursorPosition ( _initialX , _initialY ) ;
323
+ _console . Write ( "\x1b [0J" ) ;
324
+ _console . Write ( currentBuffer ) ;
325
+ }
326
+
327
+ // If we had to wrap to render everything, update _initialY
328
+ var endPoint = ConvertOffsetToPoint ( currentBuffer . Length ) ;
329
+ int physicalLine = endPoint . Y - _initialY ;
330
+ if ( _initialY + physicalLine > bufferHeight )
331
+ {
332
+ // We had to scroll to render everything, update _initialY.
333
+ _initialY = bufferHeight - physicalLine ;
334
+ }
335
+
336
+ // Preserve the current render data.
337
+ var renderData = new RenderData
338
+ {
339
+ lines = new RenderedLineData [ ] { new ( currentBuffer , isFirstLogicalLine : true ) } ,
340
+ errorPrompt = ( _parseErrors != null && _parseErrors . Length > 0 ) // Not yet used.
341
+ } ;
342
+ _previousRender = renderData ;
343
+
344
+ // Calculate the coord to place the cursor for the next input.
345
+ var point = ConvertOffsetToPoint ( _current ) ;
346
+
347
+ if ( point . Y == bufferHeight )
348
+ {
349
+ // The cursor top exceeds the buffer height and it hasn't already wrapped,
350
+ // so we need to scroll up the buffer by 1 line.
351
+ if ( point . X == 0 )
352
+ {
353
+ _console . Write ( "\n " ) ;
354
+ }
355
+
356
+ // Adjust the initial cursor position and the to-be-set cursor position
357
+ // after scrolling up the buffer.
358
+ _initialY -= 1 ;
359
+ point . Y -= 1 ;
360
+ }
361
+
362
+ _console . SetCursorPosition ( point . X , point . Y ) ;
363
+ _console . CursorVisible = true ;
364
+
365
+ _previousRender . UpdateConsoleInfo ( bufferWidth , bufferHeight , point . X , point . Y ) ;
366
+ _previousRender . initialY = _initialY ;
367
+
368
+ _lastRenderTime . Restart ( ) ;
369
+ _waitingToRender = false ;
251
370
}
252
371
253
372
private void ForceRender ( )
@@ -261,7 +380,7 @@ private void ForceRender()
261
380
// and minimize writing more than necessary on the next render.)
262
381
263
382
var renderLines = new RenderedLineData [ logicalLineCount ] ;
264
- var renderData = new RenderData { lines = renderLines } ;
383
+ var renderData = new RenderData { lines = renderLines } ;
265
384
for ( var i = 0 ; i < logicalLineCount ; i ++ )
266
385
{
267
386
var line = _consoleBufferLines [ i ] . ToString ( ) ;
0 commit comments