From 52971d101184d5c52c2ae72eec7965b1f0c879f6 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:03:13 -0600 Subject: [PATCH 01/13] Target dotnet 8.0 --- PSReadLine.build.ps1 | 52 ++++++++++++------------------------ PSReadLine/PSReadLine.csproj | 11 ++++---- global.json | 7 +++++ 3 files changed, 29 insertions(+), 41 deletions(-) create mode 100644 global.json diff --git a/PSReadLine.build.ps1 b/PSReadLine.build.ps1 index e245dfae3..9eb25d003 100644 --- a/PSReadLine.build.ps1 +++ b/PSReadLine.build.ps1 @@ -19,33 +19,33 @@ param( [ValidateSet("Debug", "Release")] [string]$Configuration = (property Configuration Release), - [ValidateSet("net472", "net6.0")] - [string]$TestFramework, - [switch]$CheckHelpContent ) Import-Module "$PSScriptRoot/tools/helper.psm1" -# Final bits to release go here -$targetDir = "bin/$Configuration/PSReadLine" +# Dynamically read target framework from project file +$csprojPath = "$PSScriptRoot/PSReadLine/PSReadLine.csproj" +[xml]$csproj = Get-Content $csprojPath +$targetFramework = $csproj.Project.PropertyGroup.TargetFramework | Where-Object { $_ } | Select-Object -First 1 -if (-not $TestFramework) { - $TestFramework = $IsWindows ? "net472" : "net6.0" +if (-not $targetFramework) { + throw "Could not determine TargetFramework from $csprojPath" } +Write-Verbose "Target framework: $targetFramework" + +# Final bits to release go here +$targetDir = "bin/$Configuration/PSReadLine" +$TestFramework = $targetFramework + function ConvertTo-CRLF([string] $text) { $text.Replace("`r`n","`n").Replace("`n","`r`n") } -$polyFillerParams = @{ - Inputs = { Get-ChildItem Polyfill/*.cs, Polyfill/Polyfill.csproj } - Outputs = "Polyfill/bin/$Configuration/netstandard2.0/Microsoft.PowerShell.PSReadLine.Polyfiller.dll" -} - $binaryModuleParams = @{ - Inputs = { Get-ChildItem PSReadLine/*.cs, PSReadLine/PSReadLine.csproj, PSReadLine/PSReadLineResources.resx, Polyfill/*.cs, Polyfill/Polyfill.csproj } - Outputs = "PSReadLine/bin/$Configuration/netstandard2.0/Microsoft.PowerShell.PSReadLine.dll" + Inputs = { Get-ChildItem PSReadLine/*.cs, PSReadLine/PSReadLine.csproj, PSReadLine/PSReadLineResources.resx } + Outputs = "PSReadLine/bin/$Configuration/$targetFramework/Microsoft.PowerShell.PSReadLine.dll" } $xUnitTestParams = @{ @@ -53,14 +53,6 @@ $xUnitTestParams = @{ Outputs = "test/bin/$Configuration/$TestFramework/PSReadLine.Tests.dll" } -<# -Synopsis: Build the Polyfiller assembly -#> -task BuildPolyfiller @polyFillerParams { - exec { dotnet publish -c $Configuration -f 'netstandard2.0' Polyfill } - exec { dotnet publish -c $Configuration -f 'net6.0' Polyfill } -} - <# Synopsis: Build main binary module #> @@ -79,7 +71,7 @@ task BuildXUnitTests @xUnitTestParams { Synopsis: Run the unit tests #> task RunTests BuildMainModule, BuildXUnitTests, { - Write-Verbose "Run tests targeting '$TestFramework' ..." + Write-Verbose "Run tests targeting $targetFramework ..." Start-TestRun -Configuration $Configuration -Framework $TestFramework } @@ -97,7 +89,7 @@ task CheckHelpContent -If $CheckHelpContent { <# Synopsis: Copy all of the files that belong in the module to one place in the layout for installation #> -task LayoutModule BuildPolyfiller, BuildMainModule, { +task LayoutModule BuildMainModule, { if (-not (Test-Path $targetDir -PathType Container)) { New-Item $targetDir -ItemType Directory -Force > $null } @@ -115,17 +107,7 @@ task LayoutModule BuildPolyfiller, BuildMainModule, { Set-Content -Path (Join-Path $targetDir (Split-Path $file -Leaf)) -Value (ConvertTo-CRLF $content) -Force } - if (-not (Test-Path "$targetDir/netstd")) { - New-Item "$targetDir/netstd" -ItemType Directory -Force > $null - } - if (-not (Test-Path "$targetDir/net6plus")) { - New-Item "$targetDir/net6plus" -ItemType Directory -Force > $null - } - - Copy-Item "Polyfill/bin/$Configuration/netstandard2.0/Microsoft.PowerShell.PSReadLine.Polyfiller.dll" "$targetDir/netstd" -Force - Copy-Item "Polyfill/bin/$Configuration/net6.0/Microsoft.PowerShell.PSReadLine.Polyfiller.dll" "$targetDir/net6plus" -Force - - $binPath = "PSReadLine/bin/$Configuration/netstandard2.0/publish" + $binPath = "PSReadLine/bin/$Configuration/$targetFramework/publish" Copy-Item $binPath/Microsoft.PowerShell.PSReadLine.dll $targetDir Copy-Item $binPath/Microsoft.PowerShell.Pager.dll $targetDir diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index f67e29e4c..3246bb7a4 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -9,19 +9,18 @@ 2.4.5 2.4.5 true - netstandard2.0 + net8.0 true false 9.0 - - - - + + + - + diff --git a/global.json b/global.json new file mode 100644 index 000000000..7aeeefbf6 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.416", + "rollForward": "latestPatch", + "allowPrerelease": false + } +} From 7d992446bc0d7154962d46ed066be1bc242c3fee Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:21:05 -0600 Subject: [PATCH 02/13] bump module version to 3.0.0 and add error message for users --- PSReadLine/PSReadLine.csproj | 6 +++--- PSReadLine/PSReadLine.psd1 | 4 ++-- PSReadLine/PSReadLine.psm1 | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 3246bb7a4..8325714a1 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -5,9 +5,9 @@ Microsoft.PowerShell.PSReadLine Microsoft.PowerShell.PSReadLine $(NoWarn);CA1416 - 2.4.5.0 - 2.4.5 - 2.4.5 + 3.0.0.0 + 3.0.0 + 3.0.0 true net8.0 true diff --git a/PSReadLine/PSReadLine.psd1 b/PSReadLine/PSReadLine.psd1 index 0690e85cc..4ce7eff73 100644 --- a/PSReadLine/PSReadLine.psd1 +++ b/PSReadLine/PSReadLine.psd1 @@ -1,13 +1,13 @@ @{ RootModule = 'PSReadLine.psm1' NestedModules = @("Microsoft.PowerShell.PSReadLine.dll") -ModuleVersion = '2.4.5' +ModuleVersion = '3.0.0' GUID = '5714753b-2afd-4492-a5fd-01d9e2cff8b5' Author = 'Microsoft Corporation' CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation. All rights reserved.' Description = 'Great command line editing in the PowerShell console host' -PowerShellVersion = '5.1' +PowerShellVersion = '7.4' FormatsToProcess = 'PSReadLine.format.ps1xml' AliasesToExport = @() FunctionsToExport = 'PSConsoleHostReadLine' diff --git a/PSReadLine/PSReadLine.psm1 b/PSReadLine/PSReadLine.psm1 index 572aee80f..06194371d 100644 --- a/PSReadLine/PSReadLine.psm1 +++ b/PSReadLine/PSReadLine.psm1 @@ -1,3 +1,17 @@ +# Check PowerShell version compatibility +if ($PSVersionTable.PSVersion -lt [Version]'7.4.0') { + $errorMessage = @" +PSReadLine 3.0+ requires PowerShell 7.4 or later. +Current version: $($PSVersionTable.PSVersion) + +To use PSReadLine with your PowerShell version, install an older version: + Install-Module PSReadLine -RequiredVersion 2.4.5 -Force -SkipPublisherCheck + +To upgrade PowerShell: https://aka.ms/install-powershell +"@ + throw $errorMessage +} + function PSConsoleHostReadLine { [System.Diagnostics.DebuggerHidden()] From 704e5a6f352594d42e929ae5f408cb99856619e5 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:29:08 -0600 Subject: [PATCH 03/13] Remove PolyFill and update MockPSConsole --- .vscode/launch.json | 2 +- MockPSConsole/MockPSConsole.csproj | 10 +- Polyfill/CommandPrediction.cs | 232 ----------------------------- Polyfill/Polyfill.csproj | 22 --- test/PSReadLine.Tests.csproj | 11 +- 5 files changed, 7 insertions(+), 270 deletions(-) delete mode 100644 Polyfill/CommandPrediction.cs delete mode 100644 Polyfill/Polyfill.csproj diff --git a/.vscode/launch.json b/.vscode/launch.json index c01006234..f15ddc52f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "-NoProfile", "-NoExit", "-Command", - "Import-Module '${workspaceFolder}/PSReadLine/bin/Debug/netstandard2.0/PSReadLine.psd1'" + "Import-Module '${workspaceFolder}/PSReadLine/bin/Debug/net8.0/PSReadLine.psd1'" ], "console": "integratedTerminal", "justMyCode": false, diff --git a/MockPSConsole/MockPSConsole.csproj b/MockPSConsole/MockPSConsole.csproj index cebd3941d..7a1cfa3fa 100644 --- a/MockPSConsole/MockPSConsole.csproj +++ b/MockPSConsole/MockPSConsole.csproj @@ -4,18 +4,14 @@ Exe MockPSConsole MockPSConsole - net472;net6.0 + net8.0 512 Program.manifest true - - - - - - + + diff --git a/Polyfill/CommandPrediction.cs b/Polyfill/CommandPrediction.cs deleted file mode 100644 index e7b331a8b..000000000 --- a/Polyfill/CommandPrediction.cs +++ /dev/null @@ -1,232 +0,0 @@ -#if LEGACY - -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Management.Automation.Language; - -namespace System.Management.Automation.Subsystem.Prediction -{ - /// - /// Kinds of prediction clients. - /// - public enum PredictionClientKind - { - /// - /// A terminal client, representing the command-line experience. - /// - Terminal, - - /// - /// An editor client, representing the editor experience. - /// - Editor, - } - - /// - /// The class represents a client that interacts with predictors. - /// - public sealed class PredictionClient - { - /// - /// Gets the client name. - /// - [HiddenAttribute] - public string Name { get; } - - /// - /// Gets the client kind. - /// - [HiddenAttribute] - public PredictionClientKind Kind { get; } - - /// - /// Initializes a new instance of the class. - /// - /// Name of the interactive client. - /// Kind of the interactive client. - [HiddenAttribute] - public PredictionClient(string name, PredictionClientKind kind) - { - Name = name; - Kind = kind; - } - } - - /// - /// The class represents the prediction result from a predictor. - /// - public sealed class PredictionResult - { - /// - /// Gets the Id of the predictor. - /// - [HiddenAttribute] - public Guid Id { get; } - - /// - /// Gets the name of the predictor. - /// - [HiddenAttribute] - public string Name { get; } - - /// - /// Gets the mini-session id that represents a specific invocation that returns this result. - /// When it's not specified, it's considered by a client that the predictor doesn't expect feedback. - /// - [HiddenAttribute] - public uint? Session { get; } - - /// - /// Gets the suggestions. - /// - [HiddenAttribute] - public IReadOnlyList Suggestions { get; } - - internal PredictionResult(Guid id, string name, uint? session, List suggestions) - { - Id = id; - Name = name; - Session = session; - Suggestions = suggestions; - } - } - - /// - /// The class represents a predictive suggestion generated by a predictor. - /// - public sealed class PredictiveSuggestion - { - /// - /// Gets the suggestion. - /// - [HiddenAttribute] - public string SuggestionText { get; } - - /// - /// Gets the tooltip of the suggestion. - /// - [HiddenAttribute] - public string ToolTip { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The predictive suggestion text. - [HiddenAttribute] - public PredictiveSuggestion(string suggestion) - : this(suggestion, toolTip: null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The predictive suggestion text. - /// The tooltip of the suggestion. - [HiddenAttribute] - public PredictiveSuggestion(string suggestion, string toolTip) - { - if (string.IsNullOrEmpty(suggestion)) - { - throw new ArgumentNullException(nameof(suggestion)); - } - - SuggestionText = suggestion; - ToolTip = toolTip; - } - } - - /// - /// Provides a set of possible predictions for given input. - /// - public static class CommandPrediction - { - /// - /// Collect the predictive suggestions from registered predictors using the default timeout. - /// - /// Represents the client that initiates the call. - /// The object from parsing the current command line input. - /// The objects from parsing the current command line input. - /// A list of objects. - [HiddenAttribute] - public static Task> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens) - { - return null; - } - - /// - /// Collect the predictive suggestions from registered predictors using the specified timeout. - /// - /// Represents the client that initiates the call. - /// The object from parsing the current command line input. - /// The objects from parsing the current command line input. - /// The milliseconds to timeout. - /// A list of objects. - [HiddenAttribute] - public static Task> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout) - { - return null; - } - - /// - /// Allow registered predictors to do early processing when a command line is accepted. - /// - /// Represents the client that initiates the call. - /// History command lines provided as references for prediction. - [HiddenAttribute] - public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) - { - } - - /// - /// Allow registered predictors to know the execution result (success/failure) of the last accepted command line. - /// - /// Represents the client that initiates the call. - /// The last accepted command line. - /// Whether the execution of the last command line was successful. - [HiddenAttribute] - public static void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) - { - } - - /// - /// Send feedback to a predictor when one or more suggestions from it were displayed to the user. - /// - /// Represents the client that initiates the call. - /// The identifier of the predictor whose prediction result was accepted. - /// The mini-session where the displayed suggestions came from. - /// - /// When the value is > 0, it's the number of displayed suggestions from the list returned in , starting from the index 0. - /// When the value is <= 0, it means a single suggestion from the list got displayed, and the index is the absolute value. - /// - [HiddenAttribute] - public static void OnSuggestionDisplayed(PredictionClient client, Guid predictorId, uint session, int countOrIndex) - { - } - - /// - /// Send feedback to predictors about their last suggestions. - /// - /// Represents the client that initiates the call. - /// The identifier of the predictor whose prediction result was accepted. - /// The mini-session where the accepted suggestion came from. - /// The accepted suggestion text. - [HiddenAttribute] - public static void OnSuggestionAccepted(PredictionClient client, Guid predictorId, uint session, string suggestionText) - { - } - } -} - -#else - -using System.Management.Automation.Subsystem.Prediction; -using System.Runtime.CompilerServices; - -[assembly: TypeForwardedTo(typeof(PredictionClientKind))] -[assembly: TypeForwardedTo(typeof(PredictionClient))] -[assembly: TypeForwardedTo(typeof(PredictiveSuggestion))] -[assembly: TypeForwardedTo(typeof(PredictionResult))] -[assembly: TypeForwardedTo(typeof(CommandPrediction))] - -#endif diff --git a/Polyfill/Polyfill.csproj b/Polyfill/Polyfill.csproj deleted file mode 100644 index 2cdccfbba..000000000 --- a/Polyfill/Polyfill.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - Microsoft.PowerShell.PSReadLine.Polyfiller - 1.0.0.0 - netstandard2.0;net6.0 - true - - - - - - - - - - - - $(DefineConstants);LEGACY - - - diff --git a/test/PSReadLine.Tests.csproj b/test/PSReadLine.Tests.csproj index 34598cbe9..1ccc25432 100644 --- a/test/PSReadLine.Tests.csproj +++ b/test/PSReadLine.Tests.csproj @@ -5,7 +5,7 @@ library UnitTestPSReadLine PSReadLine.Tests - net472;net6.0 + net8.0 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} False @@ -14,13 +14,8 @@ 9.0 - - - - - - - + + From 15540d6334a77fcafb6ac3d35fe041e44bea9369 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:46:44 -0600 Subject: [PATCH 04/13] Point to latest 7.4 SDK, remove polyfill loading interface, clean up build.ps1 --- MockPSConsole/MockPSConsole.csproj | 2 +- PSReadLine.build.ps1 | 7 +++---- PSReadLine/OnImportAndRemove.cs | 21 ++------------------- PSReadLine/PSReadLine.csproj | 6 +++++- build.ps1 | 18 +++--------------- test/PSReadLine.Tests.csproj | 2 +- 6 files changed, 15 insertions(+), 41 deletions(-) diff --git a/MockPSConsole/MockPSConsole.csproj b/MockPSConsole/MockPSConsole.csproj index 7a1cfa3fa..f4407a3b2 100644 --- a/MockPSConsole/MockPSConsole.csproj +++ b/MockPSConsole/MockPSConsole.csproj @@ -11,7 +11,7 @@ - + diff --git a/PSReadLine.build.ps1 b/PSReadLine.build.ps1 index 9eb25d003..4d54be206 100644 --- a/PSReadLine.build.ps1 +++ b/PSReadLine.build.ps1 @@ -37,7 +37,6 @@ Write-Verbose "Target framework: $targetFramework" # Final bits to release go here $targetDir = "bin/$Configuration/PSReadLine" -$TestFramework = $targetFramework function ConvertTo-CRLF([string] $text) { $text.Replace("`r`n","`n").Replace("`n","`r`n") @@ -50,7 +49,7 @@ $binaryModuleParams = @{ $xUnitTestParams = @{ Inputs = { Get-ChildItem test/*.cs, test/*.json, test/PSReadLine.Tests.csproj } - Outputs = "test/bin/$Configuration/$TestFramework/PSReadLine.Tests.dll" + Outputs = "test/bin/$Configuration/$targetFramework/PSReadLine.Tests.dll" } <# @@ -64,7 +63,7 @@ task BuildMainModule @binaryModuleParams { Synopsis: Build xUnit tests #> task BuildXUnitTests @xUnitTestParams { - exec { dotnet publish -f $TestFramework -c $Configuration test } + exec { dotnet publish -f $targetFramework -c $Configuration test } } <# @@ -72,7 +71,7 @@ Synopsis: Run the unit tests #> task RunTests BuildMainModule, BuildXUnitTests, { Write-Verbose "Run tests targeting $targetFramework ..." - Start-TestRun -Configuration $Configuration -Framework $TestFramework + Start-TestRun -Configuration $Configuration -Framework $targetFramework } <# diff --git a/PSReadLine/OnImportAndRemove.cs b/PSReadLine/OnImportAndRemove.cs index 70208420a..cc91701c0 100644 --- a/PSReadLine/OnImportAndRemove.cs +++ b/PSReadLine/OnImportAndRemove.cs @@ -9,29 +9,12 @@ public class OnModuleImportAndRemove : IModuleAssemblyInitializer, IModuleAssemb { public void OnImport() { - AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly; + // Module initialization - reserved for future use } public void OnRemove(PSModuleInfo module) { - AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly; - } - - /// - /// Load the correct 'Polyfiller' assembly based on the runtime. - /// - private static Assembly ResolveAssembly(object sender, ResolveEventArgs args) - { - if (args.Name != "Microsoft.PowerShell.PSReadLine.Polyfiller, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") - { - return null; - } - - string root = Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location); - string subd = (Environment.Version.Major >= 6) ? "net6plus" : "netstd"; - string path = Path.Combine(root, subd, "Microsoft.PowerShell.PSReadLine.Polyfiller.dll"); - - return Assembly.LoadFrom(path); + // Module cleanup - reserved for future use } } } diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 8325714a1..69cd814d9 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -16,7 +16,11 @@ - + + + contentFiles + All + diff --git a/build.ps1 b/build.ps1 index 481f2a271..47adfcbfe 100644 --- a/build.ps1 +++ b/build.ps1 @@ -10,13 +10,13 @@ Check and install prerequisites for the build. .EXAMPLE PS > .\build.ps1 -Configuration Release - Build the main module with 'Release' configuration targeting 'netstandard2.0'. + Build the main module with 'Release' configuration targeting 'net8.0'. .EXAMPLE PS > .\build.ps1 - Build the main module with the default configuration (Debug) targeting 'netstandard2.0'. + Build the main module with the default configuration (Debug) targeting 'net8.0'. .EXAMPLE PS > .\build.ps1 -Test - Run xUnit tests with the default configuration (Debug) and the default target framework (net472 on Windows or net6.0 otherwise). + Run xUnit tests with the default configuration (Debug) targeting 'net8.0'. .PARAMETER Clean Clean the local repo, but keep untracked files. .PARAMETER Bootstrap @@ -25,13 +25,6 @@ Run tests. .PARAMETER Configuration The configuration setting for the build. The default value is 'Debug'. -.PARAMETER Framework - The target framework when testing: - - net472: run tests with .NET Framework - - net6.0: run tests with .NET 6.0 - When not specified, the target framework is determined by the current OS platform: - - use 'net472' on Windows - - use 'net6.0' on Unix platforms #> [CmdletBinding(DefaultParameterSetName = 'default')] param( @@ -47,10 +40,6 @@ param( [Parameter(ParameterSetName = 'test')] [switch] $CheckHelpContent, - [Parameter(ParameterSetName = 'test')] - [ValidateSet("net472", "net6.0")] - [string] $Framework, - [Parameter(ParameterSetName = 'default')] [Parameter(ParameterSetName = 'test')] [ValidateSet("Debug", "Release")] @@ -93,7 +82,6 @@ if (-not (Get-Module -Name InvokeBuild -ListAvailable)) { $buildTask = if ($Test) { "RunTests" } else { "ZipRelease" } $arguments = @{ Task = $buildTask; Configuration = $Configuration } -if ($Framework) { $arguments.Add("TestFramework", $Framework) } if ($CheckHelpContent) { $arguments.Add("CheckHelpContent", $true) } Invoke-Build @arguments diff --git a/test/PSReadLine.Tests.csproj b/test/PSReadLine.Tests.csproj index 1ccc25432..2c344b76c 100644 --- a/test/PSReadLine.Tests.csproj +++ b/test/PSReadLine.Tests.csproj @@ -15,7 +15,7 @@ - + From d45a4f48d897c4ff7a11638c8c83bf37087a3419 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:52:52 -0600 Subject: [PATCH 05/13] Use 7.4.13 --- MockPSConsole/MockPSConsole.csproj | 2 +- PSReadLine/PSReadLine.csproj | 4 ++-- test/PSReadLine.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MockPSConsole/MockPSConsole.csproj b/MockPSConsole/MockPSConsole.csproj index f4407a3b2..d0f91fa71 100644 --- a/MockPSConsole/MockPSConsole.csproj +++ b/MockPSConsole/MockPSConsole.csproj @@ -11,7 +11,7 @@ - + diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 69cd814d9..f77b56fc8 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -16,8 +16,8 @@ - - + + contentFiles All diff --git a/test/PSReadLine.Tests.csproj b/test/PSReadLine.Tests.csproj index 2c344b76c..3cf7aee80 100644 --- a/test/PSReadLine.Tests.csproj +++ b/test/PSReadLine.Tests.csproj @@ -15,7 +15,7 @@ - + From 6eca5aacb7916874d6708ee5a0cf6aee2605b5a9 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:29:27 -0600 Subject: [PATCH 06/13] use dotnet 8.0.415 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 7aeeefbf6..fc22515fc 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.416", + "version": "8.0.415", "rollForward": "latestPatch", "allowPrerelease": false } From 9c1f75d00e71a05bdee8f0a672028e4d9946eb96 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:32:08 -0600 Subject: [PATCH 07/13] Remove error message from psm1 --- PSReadLine/PSReadLine.psm1 | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/PSReadLine/PSReadLine.psm1 b/PSReadLine/PSReadLine.psm1 index 06194371d..94d094a1e 100644 --- a/PSReadLine/PSReadLine.psm1 +++ b/PSReadLine/PSReadLine.psm1 @@ -1,17 +1,3 @@ -# Check PowerShell version compatibility -if ($PSVersionTable.PSVersion -lt [Version]'7.4.0') { - $errorMessage = @" -PSReadLine 3.0+ requires PowerShell 7.4 or later. -Current version: $($PSVersionTable.PSVersion) - -To use PSReadLine with your PowerShell version, install an older version: - Install-Module PSReadLine -RequiredVersion 2.4.5 -Force -SkipPublisherCheck - -To upgrade PowerShell: https://aka.ms/install-powershell -"@ - throw $errorMessage -} - function PSConsoleHostReadLine { [System.Diagnostics.DebuggerHidden()] @@ -22,4 +8,4 @@ function PSConsoleHostReadLine $lastRunStatus = $? Microsoft.PowerShell.Core\Set-StrictMode -Off [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext, $lastRunStatus) -} +} \ No newline at end of file From 98dc583192706fae010716265650ac477f59150f Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:21:35 -0600 Subject: [PATCH 08/13] update appveryor --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0bc286805..0414d555b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,7 @@ cache: install: - pwsh: | + dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org Write-Host "PS Version: $($PSVersionTable.PSVersion)" ./build.ps1 -Bootstrap @@ -19,7 +20,7 @@ build_script: ./build.ps1 -Configuration Release test_script: - - pwsh: ./build.ps1 -Test -Configuration Release -Framework net472 + - pwsh: ./build.ps1 -Test -Configuration Release artifacts: - path: .\bin\Release\PSReadLine.zip From 3a614acd86f83dbd2973aa84e4ba0974206c96e3 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:28:48 -0600 Subject: [PATCH 09/13] remove ado feed in apppveyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 0414d555b..57bc98f24 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,7 @@ cache: install: - pwsh: | + dotnet nuget remove source PowerShell_PublicPackages dotnet nuget add source https://api.nuget.org/v3/index.json --name nuget.org Write-Host "PS Version: $($PSVersionTable.PSVersion)" ./build.ps1 -Bootstrap From ea6bc4f945f3cb117260342fb3cb08e825eede0f Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:55:08 -0600 Subject: [PATCH 10/13] Fix test infrastructure for 256-color sequences --- test/MockConsole.cs | 60 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/test/MockConsole.cs b/test/MockConsole.cs index 990698671..1c66348ef 100644 --- a/test/MockConsole.cs +++ b/test/MockConsole.cs @@ -233,8 +233,12 @@ public virtual void Write(string s) var endSequence = s.IndexOfAny(endEscapeChars, i); var len = endSequence - i - (s[endSequence] != 'm' ? 1 : 2); var escapeSequence = s.Substring(i + 2, len); - foreach (var subsequence in escapeSequence.Split(';')) + var parts = escapeSequence.Split(';'); + + for (int j = 0; j < parts.Length; j++) { + var subsequence = parts[j]; + if (subsequence is "1" or "2" or "3") { // Ignore the font effect sequence: 1 - bold; 2 - dimmed color; 3 - italics @@ -242,7 +246,29 @@ public virtual void Write(string s) continue; } - EscapeSequenceActions[subsequence](this); + // Handle 256-color/RGB sequences: 38 (FG) or 48 (BG) + // PSReadLine uses \x1b[38;5;238m and \x1b[48;5;238m for predictions + if (subsequence is "38" or "48") + { + if (j + 1 < parts.Length) + { + var mode = parts[++j]; + if (mode == "5" && j + 1 < parts.Length) + { + j++; // Skip 256-color index + } + else if (mode == "2" && j + 3 < parts.Length) + { + j += 3; // Skip RGB values + } + } + continue; + } + + if (EscapeSequenceActions.ContainsKey(subsequence)) + { + EscapeSequenceActions[subsequence](this); + } } i = endSequence; continue; @@ -462,8 +488,12 @@ public override void Write(string s) var endSequence = s.IndexOfAny(endEscapeChars, i); var len = endSequence - i - (s[endSequence] != 'm' ? 1 : 2); var escapeSequence = s.Substring(i + 2, len); - foreach (var subsequence in escapeSequence.Split(';')) + var parts = escapeSequence.Split(';'); + + for (int j = 0; j < parts.Length; j++) { + var subsequence = parts[j]; + if (subsequence is "1" or "2" or "3") { // Ignore the font effect sequence: 1 - bold; 2 - dimmed color; 3 - italics @@ -471,7 +501,29 @@ public override void Write(string s) continue; } - EscapeSequenceActions[subsequence](this); + // Handle 256-color/RGB sequences: 38 (FG) or 48 (BG) + // PSReadLine uses \x1b[38;5;238m and \x1b[48;5;238m for predictions + if (subsequence is "38" or "48") + { + if (j + 1 < parts.Length) + { + var mode = parts[++j]; + if (mode == "5" && j + 1 < parts.Length) + { + j++; // Skip 256-color index + } + else if (mode == "2" && j + 3 < parts.Length) + { + j += 3; // Skip RGB values + } + } + continue; + } + + if (EscapeSequenceActions.ContainsKey(subsequence)) + { + EscapeSequenceActions[subsequence](this); + } } i = endSequence; continue; From 2ea2cc95bfc0cf525538f99096c3b1ede77cbe48 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:15:58 -0600 Subject: [PATCH 11/13] Support 256-color ANSI sequences in MockConsole --- test/MockConsole.cs | 49 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/MockConsole.cs b/test/MockConsole.cs index 1c66348ef..f126a1ca1 100644 --- a/test/MockConsole.cs +++ b/test/MockConsole.cs @@ -250,12 +250,18 @@ public virtual void Write(string s) // PSReadLine uses \x1b[38;5;238m and \x1b[48;5;238m for predictions if (subsequence is "38" or "48") { + bool isForeground = subsequence == "38"; if (j + 1 < parts.Length) { var mode = parts[++j]; if (mode == "5" && j + 1 < parts.Length) { - j++; // Skip 256-color index + var colorIndex = parts[++j]; + var approxColor = Map256ColorToConsoleColor(colorIndex); + if (isForeground) + ForegroundColor = approxColor; + else + BackgroundColor = approxColor; } else if (mode == "2" && j + 3 < parts.Length) { @@ -417,6 +423,39 @@ private static void ToggleNegative(TestConsole c, bool b) { "0J", c => c.BlankRestOfBuffer() }, { "2J", c => c.SetCursorPosition(0, 0) }, }; + + protected static ConsoleColor Map256ColorToConsoleColor(string colorIndex) + { + // Map 256-color palette to nearest ConsoleColor + // PSReadLine commonly uses 238 (dark gray) + if (!int.TryParse(colorIndex, out var index)) + return ConsoleColor.White; + + return index switch + { + // Standard colors (0-15) + 0 => ConsoleColor.Black, + 1 => ConsoleColor.DarkRed, + 2 => ConsoleColor.DarkGreen, + 3 => ConsoleColor.DarkYellow, + 4 => ConsoleColor.DarkBlue, + 5 => ConsoleColor.DarkMagenta, + 6 => ConsoleColor.DarkCyan, + 7 => ConsoleColor.Gray, + 8 => ConsoleColor.DarkGray, + 9 => ConsoleColor.Red, + 10 => ConsoleColor.Green, + 11 => ConsoleColor.Yellow, + 12 => ConsoleColor.Blue, + 13 => ConsoleColor.Magenta, + 14 => ConsoleColor.Cyan, + 15 => ConsoleColor.White, + // 232-255: Grayscale ramp (238 is commonly used by PSReadLine) + >= 232 and <= 255 => ConsoleColor.DarkGray, + // Default for other 256 colors: approximate based on ranges + _ => ConsoleColor.White + }; + } } internal class BasicScrollingConsole : TestConsole @@ -505,12 +544,18 @@ public override void Write(string s) // PSReadLine uses \x1b[38;5;238m and \x1b[48;5;238m for predictions if (subsequence is "38" or "48") { + bool isForeground = subsequence == "38"; if (j + 1 < parts.Length) { var mode = parts[++j]; if (mode == "5" && j + 1 < parts.Length) { - j++; // Skip 256-color index + var colorIndex = parts[++j]; + var approxColor = Map256ColorToConsoleColor(colorIndex); + if (isForeground) + ForegroundColor = approxColor; + else + BackgroundColor = approxColor; } else if (mode == "2" && j + 3 < parts.Length) { From 835228f888659061380414e645af2dabc3899b45 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:23:22 -0600 Subject: [PATCH 12/13] Separately track 256-color codes --- test/InlinePredictionTest.cs | 12 +++++++- test/MockConsole.cs | 54 ++++++------------------------------ 2 files changed, 20 insertions(+), 46 deletions(-) diff --git a/test/InlinePredictionTest.cs b/test/InlinePredictionTest.cs index febf0ac1f..a22cd43fd 100644 --- a/test/InlinePredictionTest.cs +++ b/test/InlinePredictionTest.cs @@ -638,7 +638,7 @@ public void Inline_HistoryAndPluginSource_Acceptance() Assert.Equal(Guid.Empty, _mockedMethods.acceptedPredictorId); Assert.Null(_mockedMethods.acceptedSuggestion); Assert.NotNull(_mockedMethods.commandHistory); - Assert.Equal(1, _mockedMethods.commandHistory.Count); + Assert.Single(_mockedMethods.commandHistory); Assert.Equal("netsh show me", _mockedMethods.commandHistory[0]); _mockedMethods.ClearPredictionFields(); @@ -839,5 +839,15 @@ public void Inline_TruncateVeryLongSuggestion() TokenClassification.Command, "vv")) )); } + + // Example: How to validate 256-color emission for predictions + // When PSReadLine emits ANSI escape codes like \x1b[38;5;238m (256-color), + // the test console now tracks them in Emitted256Colors list. + // + // Usage in tests: + // CheckThat(() => { + // // Verify PSReadLine emitted color 238 for prediction text + // Assert.Contains(_console.Emitted256Colors, c => c.ColorIndex == 238); + // }) } } diff --git a/test/MockConsole.cs b/test/MockConsole.cs index f126a1ca1..ca1c1a78d 100644 --- a/test/MockConsole.cs +++ b/test/MockConsole.cs @@ -178,6 +178,9 @@ public virtual int WindowHeight public virtual int WindowTop { get; set; } + // Track 256-color/RGB escape sequences emitted by PSReadLine + public List<(int Position, bool IsForeground, int ColorIndex)> Emitted256Colors { get; } = new(); + public virtual ConsoleColor BackgroundColor { get => _backgroundColor; @@ -248,6 +251,7 @@ public virtual void Write(string s) // Handle 256-color/RGB sequences: 38 (FG) or 48 (BG) // PSReadLine uses \x1b[38;5;238m and \x1b[48;5;238m for predictions + // Track these sequences without changing color state if (subsequence is "38" or "48") { bool isForeground = subsequence == "38"; @@ -256,12 +260,8 @@ public virtual void Write(string s) var mode = parts[++j]; if (mode == "5" && j + 1 < parts.Length) { - var colorIndex = parts[++j]; - var approxColor = Map256ColorToConsoleColor(colorIndex); - if (isForeground) - ForegroundColor = approxColor; - else - BackgroundColor = approxColor; + var colorIndex = int.Parse(parts[++j]); + Emitted256Colors.Add((writePos, isForeground, colorIndex)); } else if (mode == "2" && j + 3 < parts.Length) { @@ -423,39 +423,6 @@ private static void ToggleNegative(TestConsole c, bool b) { "0J", c => c.BlankRestOfBuffer() }, { "2J", c => c.SetCursorPosition(0, 0) }, }; - - protected static ConsoleColor Map256ColorToConsoleColor(string colorIndex) - { - // Map 256-color palette to nearest ConsoleColor - // PSReadLine commonly uses 238 (dark gray) - if (!int.TryParse(colorIndex, out var index)) - return ConsoleColor.White; - - return index switch - { - // Standard colors (0-15) - 0 => ConsoleColor.Black, - 1 => ConsoleColor.DarkRed, - 2 => ConsoleColor.DarkGreen, - 3 => ConsoleColor.DarkYellow, - 4 => ConsoleColor.DarkBlue, - 5 => ConsoleColor.DarkMagenta, - 6 => ConsoleColor.DarkCyan, - 7 => ConsoleColor.Gray, - 8 => ConsoleColor.DarkGray, - 9 => ConsoleColor.Red, - 10 => ConsoleColor.Green, - 11 => ConsoleColor.Yellow, - 12 => ConsoleColor.Blue, - 13 => ConsoleColor.Magenta, - 14 => ConsoleColor.Cyan, - 15 => ConsoleColor.White, - // 232-255: Grayscale ramp (238 is commonly used by PSReadLine) - >= 232 and <= 255 => ConsoleColor.DarkGray, - // Default for other 256 colors: approximate based on ranges - _ => ConsoleColor.White - }; - } } internal class BasicScrollingConsole : TestConsole @@ -542,6 +509,7 @@ public override void Write(string s) // Handle 256-color/RGB sequences: 38 (FG) or 48 (BG) // PSReadLine uses \x1b[38;5;238m and \x1b[48;5;238m for predictions + // Track these sequences without changing color state if (subsequence is "38" or "48") { bool isForeground = subsequence == "38"; @@ -550,12 +518,8 @@ public override void Write(string s) var mode = parts[++j]; if (mode == "5" && j + 1 < parts.Length) { - var colorIndex = parts[++j]; - var approxColor = Map256ColorToConsoleColor(colorIndex); - if (isForeground) - ForegroundColor = approxColor; - else - BackgroundColor = approxColor; + var colorIndex = int.Parse(parts[++j]); + Emitted256Colors.Add((writePos, isForeground, colorIndex)); } else if (mode == "2" && j + 3 < parts.Length) { From 0870e63e333bd29cc1133e9a19b08578c0d79d60 Mon Sep 17 00:00:00 2001 From: Justin Chung <124807742+jshigetomi@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:22:39 -0600 Subject: [PATCH 13/13] Fix skipping when 38 --- test/MockConsole.cs | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/test/MockConsole.cs b/test/MockConsole.cs index ca1c1a78d..421a4ce22 100644 --- a/test/MockConsole.cs +++ b/test/MockConsole.cs @@ -255,17 +255,20 @@ public virtual void Write(string s) if (subsequence is "38" or "48") { bool isForeground = subsequence == "38"; - if (j + 1 < parts.Length) + if (j + 2 < parts.Length) { - var mode = parts[++j]; - if (mode == "5" && j + 1 < parts.Length) + var mode = parts[j + 1]; + if (mode == "5") { - var colorIndex = int.Parse(parts[++j]); - Emitted256Colors.Add((writePos, isForeground, colorIndex)); + if (int.TryParse(parts[j + 2], out var colorIndex)) + { + Emitted256Colors.Add((writePos, isForeground, colorIndex)); + } + j += 2; // Skip mode and color index } - else if (mode == "2" && j + 3 < parts.Length) + else if (mode == "2" && j + 4 < parts.Length) { - j += 3; // Skip RGB values + j += 4; // Skip mode and RGB values (r, g, b) } } continue; @@ -513,17 +516,20 @@ public override void Write(string s) if (subsequence is "38" or "48") { bool isForeground = subsequence == "38"; - if (j + 1 < parts.Length) + if (j + 2 < parts.Length) { - var mode = parts[++j]; - if (mode == "5" && j + 1 < parts.Length) + var mode = parts[j + 1]; + if (mode == "5") { - var colorIndex = int.Parse(parts[++j]); - Emitted256Colors.Add((writePos, isForeground, colorIndex)); + if (int.TryParse(parts[j + 2], out var colorIndex)) + { + Emitted256Colors.Add((writePos, isForeground, colorIndex)); + } + j += 2; // Skip mode and color index } - else if (mode == "2" && j + 3 < parts.Length) + else if (mode == "2" && j + 4 < parts.Length) { - j += 3; // Skip RGB values + j += 4; // Skip mode and RGB values (r, g, b) } } continue;