Skip to content

Commit 9ceedf8

Browse files
authored
Merge pull request #139 from FRACerqueira/v5.0.0
Refinement of Ctrl+C / Ctrl+Break key monitoring. Documentation update.
2 parents e898e42 + ca1cc8c commit 9ceedf8

10 files changed

Lines changed: 168 additions & 63 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ Whenever a **critical error occurs, a log file is generated** before completion
178178

179179
### About console Ctrl+C / Ctrl+Break
180180

181+
The Prompt Plus library monitors the Ctrl+C / Ctrl+Break keys.
182+
183+
When it is not handled by the 'CancelKeyPress' command, every time the application is closed it restores the initial state of colors, cursor, Culture and returns to the primary console buffer.
184+
181185
The Prompt Plus library provides the ability to manipulate the behavior of Ctrl+C/Ctrl+Break using the command:
182186
- CancelKeyPress(AfterCancelKeyPress behaviorcontrols, Action\<object?, ConsoleCancelEventArgs\> actionhandle)
183187

@@ -278,7 +282,7 @@ PromptPlus adopts the keyboard **left/right arrows, Home, End** keys for navigat
278282

279283
### For list/colletion
280284

281-
PromptPlus adopts the keyboard **arrows, Ctrl+Home, Ctrl+End, PageUp and PageDown** keys items navigation.
285+
PromptPlus adopts the keyboard **Up/Down arrows, Ctrl+Home, Ctrl+End, PageUp and PageDown** keys items navigation.
282286

283287
For multiple-selection lists, a **space** is used to indicate a check mark. The **Shift+space** combination is used in text input to differentiate it from a check mark, when applicable.
284288

docs/api/assemblies/PromptPlus.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
| enum [ProgressBarStyles](./PromptPlusLibrary/ProgressBarStyles.md) | Represents The Styles ProgressBar control This enum defines various regions or components of the ProgressBar. |
8787
| enum [ProgressBarType](./PromptPlusLibrary/ProgressBarType.md) | Represents the Graphical-based of Progress Bar. |
8888
| static class [PromptPlus](./PromptPlusLibrary/PromptPlus.md) | Provides extension methods for ConsoleKeyInfo to evaluate specific key combinations, including standard keys and Emacs-style shortcuts. |
89+
| class [PromptPlusException](./PromptPlusLibrary/PromptPlusException.md) | Represents an exception thrown by PromptPlus for Press Ctrl+C or Ctrl+Break |
8990
| struct [ResultPrompt&lt;T&gt;](./PromptPlusLibrary/ResultPrompt-1.md) | Represents The Result *T* to Controls |
9091
| enum [SelectStyles](./PromptPlusLibrary/SelectStyles.md) | Represents the styles for the Select Control. This enum defines various regions or components of the Select Control. |
9192
| enum [SeparatorLine](./PromptPlusLibrary/SeparatorLine.md) | Represents Type Separation line |
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
![PromptPlus Logo](https://raw.githubusercontent.com/FRACerqueira/PromptPlus/refs/heads/main/icon.png)
2+
3+
### PromptPlusException class
4+
</br>
5+
6+
7+
#### Represents an exception thrown by PromptPlus for Press Ctrl+C or Ctrl+Break
8+
9+
```csharp
10+
public class PromptPlusException : Exception
11+
```
12+
13+
### Public Members
14+
15+
| name | description |
16+
| --- | --- |
17+
| [PromptPlusException](PromptPlusException/PromptPlusException.md)() | Initializes a new instance of the PromptPlusException class. |
18+
| [PromptPlusException](PromptPlusException/PromptPlusException.md)(…) | Initializes a new instance of the PromptPlusException class with message. |
19+
20+
### See Also
21+
22+
* namespace [PromptPlusLibrary](../PromptPlus.md)
23+
24+
<!-- DO NOT EDIT: generated by xmldocmd for PromptPlus.dll -->
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
![PromptPlus Logo](https://raw.githubusercontent.com/FRACerqueira/PromptPlus/refs/heads/main/icon.png)
2+
3+
### PromptPlusException constructor (1 of 2)
4+
</br>
5+
6+
7+
#### Initializes a new instance of the PromptPlusException class.
8+
9+
```csharp
10+
public PromptPlusException()
11+
```
12+
13+
### See Also
14+
15+
* class [PromptPlusException](../PromptPlusException.md)
16+
* namespace [PromptPlusLibrary](../../PromptPlus.md)
17+
18+
---
19+
20+
### PromptPlusException constructor (2 of 2)
21+
22+
#### Initializes a new instance of the PromptPlusException class with message.
23+
24+
```csharp
25+
public PromptPlusException(string message)
26+
```
27+
28+
| parameter | description |
29+
| --- | --- |
30+
| message | |
31+
32+
### See Also
33+
34+
* class [PromptPlusException](../PromptPlusException.md)
35+
* namespace [PromptPlusLibrary](../../PromptPlus.md)
36+
37+
<!-- DO NOT EDIT: generated by xmldocmd for PromptPlus.dll -->

src/Controls/BaseControlPrompt.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
namespace PromptPlusLibrary.Controls
1515
{
16-
1716
internal abstract class BaseControlPrompt<T>(bool isWidget, IConsoleExtend console, PromptConfig promptConfig, BaseControlOptions baseControlOptions, [CallerFilePath] string? filemecontrol = null)
1817
{
1918
private readonly BufferScreen _bufferScreen = new();
@@ -42,12 +41,16 @@ public void Show()
4241

4342
public ResultPrompt<T> Run(CancellationToken stoptoken = default)
4443
{
45-
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(stoptoken, console.TokenCancelPress);
44+
if (ConsolePlus.IsExitDefaultCancel && ConsolePlus.AbortedByCtrlC)
45+
{
46+
throw new PromptPlusException();
47+
}
48+
49+
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(stoptoken, ConsolePlus.TokenCancelPress);
4650

4751
if (isWidget)
4852
{
49-
50-
using (((IConsole)console).InternalExclusiveContext())
53+
using (ConsolePlus.InternalExclusiveContext())
5154
{
5255

5356
promptConfig.TraceBaseControlOptions = GeneralOptions;
@@ -80,6 +83,10 @@ public ResultPrompt<T> Run(CancellationToken stoptoken = default)
8083
catch
8184
{
8285
error = true;
86+
if (ConsolePlus.IsExitDefaultCancel && ConsolePlus.AbortedByCtrlC)
87+
{
88+
throw new PromptPlusException();
89+
}
8390
throw;
8491
}
8592
finally
@@ -103,7 +110,7 @@ public ResultPrompt<T> Run(CancellationToken stoptoken = default)
103110
#pragma warning restore CS8604 // Possible null reference argument.
104111
}
105112
}
106-
using (console.InternalExclusiveContext())
113+
using (ConsolePlus.InternalExclusiveContext())
107114
{
108115
promptConfig.TraceBaseControlOptions = GeneralOptions;
109116
promptConfig.TraceCurrentFileNameControl = filemecontrol;
@@ -160,6 +167,10 @@ public ResultPrompt<T> Run(CancellationToken stoptoken = default)
160167
catch
161168
{
162169
error = true;
170+
if (ConsolePlus.IsExitDefaultCancel && ConsolePlus.AbortedByCtrlC)
171+
{
172+
throw new PromptPlusException();
173+
}
163174
throw;
164175
}
165176
finally
@@ -221,11 +232,11 @@ public bool TryValidate(object input, IList<Func<object, ValidationResult>> vali
221232

222233
public ConsoleKeyInfo WaitKeypress(bool intercept, CancellationToken token)
223234
{
224-
while (!console.KeyAvailable && !token.IsCancellationRequested)
235+
while (!ConsolePlus.KeyAvailable && !token.IsCancellationRequested)
225236
{
226237
token.WaitHandle.WaitOne(2);
227238
}
228-
return console.KeyAvailable && !token.IsCancellationRequested ? console.ReadKey(intercept) : new ConsoleKeyInfo();
239+
return ConsolePlus.KeyAvailable && !token.IsCancellationRequested ? ConsolePlus.ReadKey(intercept) : new ConsoleKeyInfo();
229240
}
230241

231242
public bool IsTooltipToggerKeyPress(ConsoleKeyInfo keyInfo)

src/Core/Colors/ColorPalette.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
using System;
99
using System.Collections.Generic;
10-
using System.Collections.Immutable;
1110
using System.Linq;
1211

1312
namespace PromptPlusLibrary.Core.Colors

src/Drivers/ConsoleDriveWindows.cs

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ namespace PromptPlusLibrary.Drivers
1616
{
1717
internal class ConsoleDriveWindows : IConsole, IConsoleExtend
1818
{
19-
internal const int IdleReadKey = 2;
19+
private readonly SemaphoreSlim _exclusiveCAbortCtrlC = new(0, 1);
20+
private readonly SemaphoreSlim _exclusiveContext = new(1, 1);
21+
private readonly Task _CheckBackGroundCtrlC;
2022
private TargetScreen _currentBuffer = TargetScreen.Primary;
2123
private Color _consoleForegroundColor;
2224
private Color _consoleBackgroundColor;
23-
private readonly SemaphoreSlim _exclusiveContext = new(1, 1);
2425
private bool _disposed;
2526
private Action<object?, ConsoleCancelEventArgs>? _cancelKeyPressEvent;
2627
private AfterCancelKeyPress _behaviorAfterCancelKeyPress;
2728
private CancellationTokenSource _tokenCancelPress;
2829
private bool _isExistDefaultCancel;
29-
private static bool _isAbortCtrlC;
30-
private static Task? _CheckBackGroundCtrlC;
30+
private bool _isAbortCtrlC;
3131

3232
public ConsoleDriveWindows(IProfileDrive profile)
3333
{
@@ -39,29 +39,13 @@ public ConsoleDriveWindows(IProfileDrive profile)
3939
_isExistDefaultCancel = false;
4040
_CheckBackGroundCtrlC = Task.Run(() =>
4141
{
42-
bool found = false;
43-
while (!_disposed)
44-
{
45-
try
46-
{
47-
_tokenCancelPress.Token.WaitHandle.WaitOne(2);
48-
}
49-
catch (ObjectDisposedException)
50-
{
51-
break;
52-
}
53-
if (AbortedByCtrlC)
54-
{
55-
found = true;
56-
break;
57-
}
58-
}
59-
if (found)
42+
_exclusiveCAbortCtrlC.Wait();
43+
if (!_disposed)
6044
{
61-
throw new AppDomainUnloadedException();
45+
throw new PromptPlusException();
6246
}
63-
6447
});
48+
RemoveCancelKeyPress();
6549
}
6650

6751
public CancellationToken TokenCancelPress => _tokenCancelPress.Token;
@@ -100,7 +84,11 @@ private void ExitDefaultCancel(object? sender, ConsoleCancelEventArgs args)
10084
Console.CancelKeyPress -= ExitDefaultCancel;
10185
SetUserPressKeyAborted();
10286
_isAbortCtrlC = true;
103-
throw new AppDomainUnloadedException();
87+
if (!_disposed)
88+
{
89+
_exclusiveCAbortCtrlC.Release();
90+
}
91+
throw new PromptPlusException();
10492
}
10593

10694
public void CancelKeyPress(AfterCancelKeyPress behaviorcontrols, Action<object?, ConsoleCancelEventArgs> actionhandle)
@@ -142,6 +130,10 @@ public void SetUserPressKeyAborted()
142130

143131
public void UniqueContext(Action action)
144132
{
133+
if (IsExitDefaultCancel && AbortedByCtrlC)
134+
{
135+
throw new PromptPlusException();
136+
}
145137
bool exclusive = false;
146138
if (_exclusiveContext.CurrentCount == 1)
147139
{
@@ -851,23 +843,23 @@ public void Dispose()
851843
{
852844
if (!_disposed)
853845
{
854-
if (_cancelKeyPressEvent != null)
846+
_disposed = true;
847+
if (_isExistDefaultCancel)
855848
{
856-
Console.CancelKeyPress -= ConsoleCancelKeyPress;
849+
Console.CancelKeyPress -= ExitDefaultCancel;
857850
}
858-
_disposed = true;
859-
if (_CheckBackGroundCtrlC != null)
851+
if (_cancelKeyPressEvent != null)
860852
{
861-
while (!_CheckBackGroundCtrlC.IsCompleted)
862-
{
863-
Thread.Sleep(2);
864-
}
865-
_CheckBackGroundCtrlC.Dispose();
853+
Console.CancelKeyPress -= ConsoleCancelKeyPress;
866854
}
855+
_behaviorAfterCancelKeyPress = AfterCancelKeyPress.Default;
856+
_exclusiveCAbortCtrlC.Release();
857+
_CheckBackGroundCtrlC.Wait();
858+
_CheckBackGroundCtrlC.Dispose();
867859
_cancelKeyPressEvent = null;
868860
_tokenCancelPress?.Dispose();
869861
_exclusiveContext?.Dispose();
870-
_disposed = true;
862+
_exclusiveCAbortCtrlC?.Dispose();
871863
GC.SuppressFinalize(this);
872864
}
873865
}

src/PromptPlus.ExclusiveContextExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,28 @@ public static partial class PromptPlus
1818
/// <returns><see cref="IDisposable"/></returns>
1919
public static IDisposable ExclusiveContext(this IConsole console)
2020
{
21+
if (((IConsoleExtend)console).IsExitDefaultCancel && ((IConsoleExtend)console).AbortedByCtrlC)
22+
{
23+
throw new PromptPlusException();
24+
}
2125
return new ExclusiveContextOutput((IConsoleExtend)console, false);
2226
}
2327

2428
internal static IDisposable InternalExclusiveContext(this IConsole console)
2529
{
30+
if (((IConsoleExtend)console).IsExitDefaultCancel && ((IConsoleExtend)console).AbortedByCtrlC)
31+
{
32+
throw new PromptPlusException();
33+
}
2634
return new ExclusiveContextOutput((IConsoleExtend)console, true);
2735
}
2836

2937
internal static IDisposable InternalExclusiveContext(this IConsoleExtend console)
3038
{
39+
if (console.IsExitDefaultCancel && console.AbortedByCtrlC)
40+
{
41+
throw new PromptPlusException();
42+
}
3143
return new ExclusiveContextOutput(console, true);
3244
}
3345
}

src/PromptPlus.cs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using System.Globalization;
1414
using System.IO;
1515
using System.Reflection;
16-
using System.Runtime.ExceptionServices;
1716
using System.Runtime.InteropServices;
1817
using System.Text;
1918
using System.Text.Json;
@@ -111,9 +110,13 @@ static PromptPlus()
111110

112111
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
113112
{
113+
if (_promptConfig.ResetBasicStateAfterExist)
114+
{
115+
ResetState();
116+
}
114117
if (((IConsoleExtend)Console).AbortedByCtrlC)
115118
{
116-
AppDomainUnloadedException error = new("Press Ctrl+C or Ctrl+Break");
119+
PromptPlusException error = new("Press Ctrl+C or Ctrl+Break");
117120
try
118121
{
119122
WriteCrashLog(typeof(PromptPlus), error);
@@ -125,31 +128,28 @@ static PromptPlus()
125128
//none
126129
}
127130
}
128-
if (_promptConfig.ResetBasicStateAfterExist)
129-
{
130-
ResetState();
131-
}
132131
};
133132

134-
AppDomain.CurrentDomain.FirstChanceException += ((object? o, FirstChanceExceptionEventArgs e) =>
133+
AppDomain.CurrentDomain.FirstChanceException += ((o, e) =>
135134
{
136-
if (e.Exception.GetType() == typeof(AppDomainUnloadedException))
135+
if (e.Exception.GetType() == typeof(PromptPlusException))
137136
{
138-
if (_consoledrive.UserPressKeyAborted && ((IConsoleExtend)_consoledrive).IsExitDefaultCancel)
139-
{
140-
((IConsoleExtend)_consoledrive).ResetTokenCancelPress();
141-
Environment.Exit(1);
142-
}
137+
((IConsoleExtend)_consoledrive).ResetTokenCancelPress();
138+
Environment.Exit(1);
143139
}
144140
});
145141

146142
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
147143
{
144+
if (_promptConfig.ResetBasicStateAfterExist)
145+
{
146+
ResetState();
147+
}
148148
try
149149
{
150150
if (((IConsoleExtend)Console).AbortedByCtrlC)
151151
{
152-
AppDomainUnloadedException error = new("Press Ctrl+C or Ctrl+Break");
152+
PromptPlusException error = new("Press Ctrl+C or Ctrl+Break");
153153
WriteCrashLog(typeof(PromptPlus), error);
154154
_promptConfig.AfterError?.Invoke(error);
155155
System.Console.WriteLine($"{error}");
@@ -164,14 +164,9 @@ static PromptPlus()
164164
{
165165
//none
166166
}
167-
if (_promptConfig.ResetBasicStateAfterExist)
168-
{
169-
ResetState();
170-
}
171167
};
172168
_consoledrive.ResetColor();
173169
_consoledrive.Clear();
174-
_consoledrive.RemoveCancelKeyPress();
175170
}
176171

177172
/// <summary>

0 commit comments

Comments
 (0)