@@ -28,8 +28,16 @@ class ExitException : Exception { }
28
28
29
29
public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
30
30
{
31
+ private const int ConsoleExiting = 1 ;
32
+
33
+ private const int CancellationRequested = 2 ;
34
+
35
+ private const int EventProcessingRequested = 3 ;
36
+
31
37
private static readonly PSConsoleReadLine _singleton = new PSConsoleReadLine ( ) ;
32
38
39
+ private static readonly CancellationToken _defaultCancellationToken = new CancellationTokenSource ( ) . Token ;
40
+
33
41
private bool _delayedOneTimeInitCompleted ;
34
42
35
43
private IPSConsoleReadLineMockableMethods _mockableMethods ;
@@ -41,6 +49,8 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
41
49
private Thread _readKeyThread ;
42
50
private AutoResetEvent _readKeyWaitHandle ;
43
51
private AutoResetEvent _keyReadWaitHandle ;
52
+ private AutoResetEvent _forceEventWaitHandle ;
53
+ private CancellationToken _cancelReadCancellationToken ;
44
54
internal ManualResetEvent _closingWaitHandle ;
45
55
private WaitHandle [ ] _threadProcWaitHandles ;
46
56
private WaitHandle [ ] _requestKeyWaitHandles ;
@@ -139,7 +149,12 @@ private void ReadKeyThreadProc()
139
149
if ( handleId == 1 ) // It was the _closingWaitHandle that was signaled.
140
150
break ;
141
151
152
+ var localCancellationToken = _singleton . _cancelReadCancellationToken ;
142
153
ReadOneOrMoreKeys ( ) ;
154
+ if ( localCancellationToken . IsCancellationRequested )
155
+ {
156
+ continue ;
157
+ }
143
158
144
159
// One or more keys were read - let ReadKey know we're done.
145
160
_keyReadWaitHandle . Set ( ) ;
@@ -174,9 +189,10 @@ internal static ConsoleKeyInfo ReadKey()
174
189
// - a key is pressed
175
190
// - the console is exiting
176
191
// - 300ms - to process events if we're idle
177
-
192
+ // - processing of events is requested externally
193
+ // - ReadLine cancellation is requested externally
178
194
handleId = WaitHandle . WaitAny ( _singleton . _requestKeyWaitHandles , 300 ) ;
179
- if ( handleId != WaitHandle . WaitTimeout )
195
+ if ( handleId != WaitHandle . WaitTimeout && handleId != EventProcessingRequested )
180
196
break ;
181
197
182
198
// If we timed out, check for event subscribers (which is just
@@ -236,7 +252,7 @@ internal static ConsoleKeyInfo ReadKey()
236
252
ps ? . Dispose ( ) ;
237
253
}
238
254
239
- if ( handleId == 1 )
255
+ if ( handleId == ConsoleExiting )
240
256
{
241
257
// The console is exiting - throw an exception to unwind the stack to the point
242
258
// where we can return from ReadLine.
@@ -249,6 +265,18 @@ internal static ConsoleKeyInfo ReadKey()
249
265
throw new OperationCanceledException ( ) ;
250
266
}
251
267
268
+ if ( handleId == CancellationRequested )
269
+ {
270
+ // ReadLine was cancelled. Save the current line to be restored next time ReadLine
271
+ // is called, clear the buffer and throw an exception so we can return an empty string.
272
+ _singleton . SaveCurrentLine ( ) ;
273
+ _singleton . _getNextHistoryIndex = _singleton . _history . Count ;
274
+ _singleton . _current = 0 ;
275
+ _singleton . _buffer . Clear ( ) ;
276
+ _singleton . Render ( ) ;
277
+ throw new OperationCanceledException ( ) ;
278
+ }
279
+
252
280
var key = _singleton . _queuedKeys . Dequeue ( ) ;
253
281
return key ;
254
282
}
@@ -275,6 +303,18 @@ private void PrependQueuedKeys(ConsoleKeyInfo key)
275
303
/// </summary>
276
304
/// <returns>The complete command line.</returns>
277
305
public static string ReadLine ( Runspace runspace , EngineIntrinsics engineIntrinsics )
306
+ {
307
+ // Use a default cancellation token instead of CancellationToken.None because the
308
+ // WaitHandle is shared and could be triggered accidently.
309
+ return ReadLine ( runspace , engineIntrinsics , _defaultCancellationToken ) ;
310
+ }
311
+
312
+ /// <summary>
313
+ /// Entry point - called by custom PSHost implementations that require the
314
+ /// ability to cancel ReadLine.
315
+ /// </summary>
316
+ /// <returns>The complete command line.</returns>
317
+ public static string ReadLine ( Runspace runspace , EngineIntrinsics engineIntrinsics , CancellationToken cancellationToken )
278
318
{
279
319
var console = _singleton . _console ;
280
320
@@ -313,11 +353,14 @@ public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsi
313
353
_singleton . Initialize ( runspace , engineIntrinsics ) ;
314
354
}
315
355
356
+ _singleton . _cancelReadCancellationToken = cancellationToken ;
357
+ _singleton . _requestKeyWaitHandles [ 2 ] = _singleton . _cancelReadCancellationToken . WaitHandle ;
316
358
return _singleton . InputLoop ( ) ;
317
359
}
318
360
catch ( OperationCanceledException )
319
361
{
320
- // Console is exiting - return value isn't too critical - null or 'exit' could work equally well.
362
+ // Console is either exiting or the cancellation of ReadLine has been requested
363
+ // by a custom PSHost implementation.
321
364
return "" ;
322
365
}
323
366
catch ( ExitException )
@@ -720,8 +763,9 @@ private void DelayedOneTimeInitialize()
720
763
721
764
_singleton . _readKeyWaitHandle = new AutoResetEvent ( false ) ;
722
765
_singleton . _keyReadWaitHandle = new AutoResetEvent ( false ) ;
766
+ _singleton . _forceEventWaitHandle = new AutoResetEvent ( false ) ;
723
767
_singleton . _closingWaitHandle = new ManualResetEvent ( false ) ;
724
- _singleton . _requestKeyWaitHandles = new WaitHandle [ ] { _singleton . _keyReadWaitHandle , _singleton . _closingWaitHandle } ;
768
+ _singleton . _requestKeyWaitHandles = new WaitHandle [ ] { _singleton . _keyReadWaitHandle , _singleton . _closingWaitHandle , _defaultCancellationToken . WaitHandle , _singleton . _forceEventWaitHandle } ;
725
769
_singleton . _threadProcWaitHandles = new WaitHandle [ ] { _singleton . _readKeyWaitHandle , _singleton . _closingWaitHandle } ;
726
770
727
771
// This is for a "being hosted in an alternate appdomain scenario" (the
@@ -741,6 +785,17 @@ private void DelayedOneTimeInitialize()
741
785
_singleton . _readKeyThread . Start ( ) ;
742
786
}
743
787
788
+ /// <summary>
789
+ /// Used by PowerShellEditorServices to force immediate
790
+ /// event handling during the <see cref="PSConsoleReadLine.ReadKey" />
791
+ /// method. This is not a public API, but it is part of a private contract
792
+ /// with that project.
793
+ /// </summary>
794
+ private static void ForcePSEventHandling ( )
795
+ {
796
+ _singleton . _forceEventWaitHandle . Set ( ) ;
797
+ }
798
+
744
799
private static void Chord ( ConsoleKeyInfo ? key = null , object arg = null )
745
800
{
746
801
if ( ! key . HasValue )
0 commit comments