From 3e340e41cd5891ff8c8589eb8294244d16c236b7 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 10 Apr 2025 07:55:58 -0700 Subject: [PATCH 1/3] Avoid reading cursor position when rendering the posted code --- shell/AIShell.Integration/Channel.cs | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/shell/AIShell.Integration/Channel.cs b/shell/AIShell.Integration/Channel.cs index 47069c5a..811609af 100644 --- a/shell/AIShell.Integration/Channel.cs +++ b/shell/AIShell.Integration/Channel.cs @@ -16,6 +16,8 @@ public class Channel : IDisposable private readonly Type _psrlType; private readonly Runspace _runspace; private readonly MethodInfo _psrlInsert, _psrlRevertLine, _psrlAcceptLine; + private readonly FieldInfo _psrlHandleResizing; + private readonly object _psrlSingleton; private readonly ManualResetEvent _connSetupWaitHandler; private readonly Predictor _predictor; private readonly ScriptBlock _onIdleAction; @@ -45,6 +47,10 @@ private Channel(Runspace runspace, Type psConsoleReadLineType) _psrlRevertLine = _psrlType.GetMethod("RevertLine", bindingFlags); _psrlAcceptLine = _psrlType.GetMethod("AcceptLine", bindingFlags); + FieldInfo singletonInfo = _psrlType.GetField("_singleton", BindingFlags.Static | BindingFlags.NonPublic); + _psrlSingleton = singletonInfo.GetValue(null); + _psrlHandleResizing = _psrlType.GetField("_handlePotentialResizing", BindingFlags.Instance | BindingFlags.NonPublic); + _predictor = new Predictor(); _onIdleAction = ScriptBlock.Create("[AIShell.Integration.Channel]::Singleton.OnIdleHandler()"); } @@ -268,18 +274,44 @@ private void OnAskConnection(ShellClientPipe clientPipe, Exception exception) private void PSRLInsert(string text) { + using var _ = new NoWindowResizingCheck(); _psrlInsert.Invoke(null, [text]); } private void PSRLRevertLine() { + using var _ = new NoWindowResizingCheck(); _psrlRevertLine.Invoke(null, [null, null]); } private void PSRLAcceptLine() { + using var _ = new NoWindowResizingCheck(); _psrlAcceptLine.Invoke(null, [null, null]); } + + /// + /// We assume the terminal window will not resize during the code-post operation and hence disable the window resizing check. + /// This is to avoid reading console cursor positions while PSReadLine is already blocked on 'Console.ReadKey'. + /// On Unix system, when we are already blocked on key input, reading cursor position on another thread will block too. + /// + private class NoWindowResizingCheck : IDisposable + { + private readonly object _originalValue; + + internal NoWindowResizingCheck() + { + Channel channel = Singleton; + _originalValue = channel._psrlHandleResizing.GetValue(channel._psrlSingleton); + channel._psrlHandleResizing.SetValue(channel._psrlSingleton, false); + } + + public void Dispose() + { + Channel channel = Singleton; + channel._psrlHandleResizing.SetValue(channel._psrlSingleton, _originalValue); + } + } } internal record CodePostData(string CodeToInsert, List PredictionCandidates); From 733e43e99eb1d5fa25d1942513385245e6cce927 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 10 Apr 2025 16:53:34 -0700 Subject: [PATCH 2/3] Be deterministic about if PSReadLine is initialized and ready --- shell/AIShell.Integration/Channel.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/shell/AIShell.Integration/Channel.cs b/shell/AIShell.Integration/Channel.cs index 811609af..5bc412ef 100644 --- a/shell/AIShell.Integration/Channel.cs +++ b/shell/AIShell.Integration/Channel.cs @@ -16,7 +16,7 @@ public class Channel : IDisposable private readonly Type _psrlType; private readonly Runspace _runspace; private readonly MethodInfo _psrlInsert, _psrlRevertLine, _psrlAcceptLine; - private readonly FieldInfo _psrlHandleResizing; + private readonly FieldInfo _psrlHandleResizing, _psrlReadLineReady; private readonly object _psrlSingleton; private readonly ManualResetEvent _connSetupWaitHandler; private readonly Predictor _predictor; @@ -42,14 +42,17 @@ private Channel(Runspace runspace, Type psConsoleReadLineType) .Append(Path.GetFileNameWithoutExtension(Environment.ProcessPath)) .ToString(); - BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Public; - _psrlInsert = _psrlType.GetMethod("Insert", bindingFlags, [typeof(string)]); - _psrlRevertLine = _psrlType.GetMethod("RevertLine", bindingFlags); - _psrlAcceptLine = _psrlType.GetMethod("AcceptLine", bindingFlags); + BindingFlags methodFlags = BindingFlags.Static | BindingFlags.Public; + _psrlInsert = _psrlType.GetMethod("Insert", methodFlags, [typeof(string)]); + _psrlRevertLine = _psrlType.GetMethod("RevertLine", methodFlags); + _psrlAcceptLine = _psrlType.GetMethod("AcceptLine", methodFlags); FieldInfo singletonInfo = _psrlType.GetField("_singleton", BindingFlags.Static | BindingFlags.NonPublic); _psrlSingleton = singletonInfo.GetValue(null); - _psrlHandleResizing = _psrlType.GetField("_handlePotentialResizing", BindingFlags.Instance | BindingFlags.NonPublic); + + BindingFlags fieldFlags = BindingFlags.Instance | BindingFlags.NonPublic; + _psrlReadLineReady = _psrlType.GetField("_readLineReady", fieldFlags); + _psrlHandleResizing = _psrlType.GetField("_handlePotentialResizing", fieldFlags); _predictor = new Predictor(); _onIdleAction = ScriptBlock.Create("[AIShell.Integration.Channel]::Singleton.OnIdleHandler()"); @@ -223,10 +226,9 @@ private void OnPostCode(PostCodeMessage postCodeMessage) codeToInsert = sb.ToString(); } - // When PSReadLine is actively running, 'TreatControlCAsInput' would be set to 'true' because - // it handles 'Ctrl+c' as regular input. + // When PSReadLine is actively running, its '_readLineReady' field should be set to 'true'. // When the value is 'false', it means PowerShell is still busy running scripts or commands. - if (Console.TreatControlCAsInput) + if (_psrlReadLineReady.GetValue(_psrlSingleton) is true) { PSRLRevertLine(); PSRLInsert(codeToInsert); From bf8b64b2841153c201720abd6e64f00f22117fbe Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 15 Apr 2025 17:43:54 -0700 Subject: [PATCH 3/3] Keep window resizing check on Windows --- shell/AIShell.Integration/AIShell.psm1 | 4 ++-- shell/AIShell.Integration/Channel.cs | 30 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/shell/AIShell.Integration/AIShell.psm1 b/shell/AIShell.Integration/AIShell.psm1 index bac0bc66..aeb02f7e 100644 --- a/shell/AIShell.Integration/AIShell.psm1 +++ b/shell/AIShell.Integration/AIShell.psm1 @@ -1,6 +1,6 @@ $module = Get-Module -Name PSReadLine -if ($null -eq $module -or $module.Version -lt [version]"2.4.1") { - throw "The PSReadLine v2.4.1-beta1 or higher is required for the AIShell module to work properly." +if ($null -eq $module -or $module.Version -lt [version]"2.4.2") { + throw "The PSReadLine v2.4.2-beta2 or higher is required for the AIShell module to work properly." } ## Create the channel singleton when loading the module. diff --git a/shell/AIShell.Integration/Channel.cs b/shell/AIShell.Integration/Channel.cs index 5bc412ef..6dfe2b68 100644 --- a/shell/AIShell.Integration/Channel.cs +++ b/shell/AIShell.Integration/Channel.cs @@ -293,9 +293,17 @@ private void PSRLAcceptLine() } /// - /// We assume the terminal window will not resize during the code-post operation and hence disable the window resizing check. - /// This is to avoid reading console cursor positions while PSReadLine is already blocked on 'Console.ReadKey'. - /// On Unix system, when we are already blocked on key input, reading cursor position on another thread will block too. + /// We assume the terminal window will not resize during the code-post operation and hence disable the window resizing check on macOS. + /// This is to avoid reading console cursor positions while PSReadLine is already blocked on 'Console.ReadKey', because on Unix system, + /// when we are already blocked on key input, reading cursor position on another thread will be blocked too until a key is pressed. + /// + /// We do need window resizing check on Windows due to how 'Start-AIShell' works differently: + /// - On Windows, 'Start-AIShell' returns way BEFORE the current tab gets splitted for the sidecar pane, and PowerShell has already + /// called into PSReadLine when the splitting actually happens. So, it's literally a window resizing for PSReadLine at that point + /// and hence we need the window resizing check to correct the initial coordinates ('_initialX' and '_initialY'). + /// - On macOS, however, 'Start-AIShell' returns AFTER the current tab gets splitted for the sidecar pane. So, window resizing will + /// be done before PowerShell calls into PSReadLine and hence there is no need for window resizing check on macOS. + /// Also, On Windows we can read cursor position without blocking even if another thread is blocked on calling 'ReadKey'. /// private class NoWindowResizingCheck : IDisposable { @@ -303,15 +311,21 @@ private class NoWindowResizingCheck : IDisposable internal NoWindowResizingCheck() { - Channel channel = Singleton; - _originalValue = channel._psrlHandleResizing.GetValue(channel._psrlSingleton); - channel._psrlHandleResizing.SetValue(channel._psrlSingleton, false); + if (OperatingSystem.IsMacOS()) + { + Channel channel = Singleton; + _originalValue = channel._psrlHandleResizing.GetValue(channel._psrlSingleton); + channel._psrlHandleResizing.SetValue(channel._psrlSingleton, false); + } } public void Dispose() { - Channel channel = Singleton; - channel._psrlHandleResizing.SetValue(channel._psrlSingleton, _originalValue); + if (OperatingSystem.IsMacOS()) + { + Channel channel = Singleton; + channel._psrlHandleResizing.SetValue(channel._psrlSingleton, _originalValue); + } } } }