From 49071163a16b550da71206b3c931d7eae8d9c086 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 14 Aug 2025 19:47:37 -0700 Subject: [PATCH 1/5] Fix ellipsis / continuation character encoding issue in tests This character was always meant to be an ellipsis. I'm unsure exactly why Visual Studio interprets it so, but VS Code and GitHub do not. This commit replaces it with the actual Unicode character which ensures the tests continue to pass when these files are edited and saved in VS Code. --- test/ListPredictionTest.cs | 122 ++++++++++++++++++------------------ test/ListViewTooltipTest.cs | 8 +-- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/test/ListPredictionTest.cs b/test/ListPredictionTest.cs index 4931867..1b28339 100644 --- a/test/ListPredictionTest.cs +++ b/test/ListPredictionTest.cs @@ -1355,8 +1355,8 @@ namespace Test TokenClassification.Command, "ec", NextLine, TokenClassification.ListPrediction, "<-/3>", - TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME TEXT BEFORE ", @@ -1377,9 +1377,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1395,10 +1395,10 @@ namespace Test TokenClassification.None, " TEXT BEFORE ec", NextLine, TokenClassification.ListPrediction, "<1/3>", - TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<1/3>' plus ''. + TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<1/3>' plus ''. dimmedColors, '<', TokenClassification.ListPrediction, "TestPredictor(1/2) ", - dimmedColors, "LongNamePredic…(1)>", + dimmedColors, "LongNamePredic…(1)>", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.ListPredictionSelected, " SOME TEXT BEFORE ", @@ -1419,9 +1419,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1434,10 +1434,10 @@ namespace Test TokenClassification.Selection, "SOME TEXT BEFORE ec", NextLine, TokenClassification.ListPrediction, "<1/3>", - TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<1/3>' plus ''. + TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<1/3>' plus ''. dimmedColors, '<', TokenClassification.ListPrediction, "TestPredictor(1/2) ", - dimmedColors, "LongNamePredic…(1)>", + dimmedColors, "LongNamePredic…(1)>", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.ListPredictionSelected, " SOME TEXT BEFORE ", @@ -1458,9 +1458,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1473,8 +1473,8 @@ namespace Test TokenClassification.Command, "j", NextLine, TokenClassification.ListPrediction, "<-/3>", - TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME TEXT BEFORE ", @@ -1495,9 +1495,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1518,9 +1518,9 @@ namespace Test TokenClassification.None, " NEW TEXT", NextLine, TokenClassification.ListPrediction, "<3/3>", - TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<1/3>' plus ''. + TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<1/3>' plus ''. dimmedColors, "', NextLine, TokenClassification.ListPrediction, '>', @@ -1542,9 +1542,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.ListPredictionSelected, " SOME NEW TEXT", - TokenClassification.ListPredictionSelected, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.ListPredictionSelected, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.ListPredictionSelected, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.ListPredictionSelected, ']', // List view is done, no more list item following. NextLine, @@ -1558,8 +1558,8 @@ namespace Test TokenClassification.None, " NEW TEX", NextLine, TokenClassification.ListPrediction, "<-/3>", - TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME TEXT BEFORE ", @@ -1582,9 +1582,9 @@ namespace Test TokenClassification.None, ' ', emphasisColors, "SOME NEW TEX", TokenClassification.None, 'T', - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1604,10 +1604,10 @@ namespace Test TokenClassification.None, " NEW TEX SOME TEXT AFTER", NextLine, TokenClassification.ListPrediction, "<2/3>", - TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<2/3>' plus ''. + TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<2/3>' plus ''. dimmedColors, '<', TokenClassification.ListPrediction, "TestPredictor(2/2) ", - dimmedColors, "LongNamePredic…(1)>", + dimmedColors, "LongNamePredic…(1)>", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME TEXT BEFORE ", @@ -1630,9 +1630,9 @@ namespace Test TokenClassification.None, ' ', emphasisColors, "SOME NEW TEX", TokenClassification.None, 'T', - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1676,8 +1676,8 @@ namespace Test TokenClassification.Command, "ec", NextLine, TokenClassification.ListPrediction, "<-/5>", - TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/5>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/5>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, ' ', @@ -1716,9 +1716,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1733,10 +1733,10 @@ namespace Test TokenClassification.Selection, "eca -zoo", NextLine, TokenClassification.ListPrediction, "<1/5>", - TokenClassification.None, new string(' ', listWidth - 38), // 38 is the length of '<1/5>' plus ''. + TokenClassification.None, new string(' ', listWidth - 38), // 38 is the length of '<1/5>' plus ''. dimmedColors, '<', TokenClassification.ListPrediction, "History(1/2) ", - dimmedColors, "TestPredictor(2) …>", + dimmedColors, "TestPredictor(2) …>", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.ListPredictionSelected, ' ', @@ -1775,9 +1775,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1789,8 +1789,8 @@ namespace Test TokenClassification.Command, "j", NextLine, TokenClassification.ListPrediction, "<-/4>", - TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/4>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/4>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, ' ', @@ -1820,9 +1820,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1842,9 +1842,9 @@ namespace Test TokenClassification.None, " NEW TEXT", NextLine, TokenClassification.ListPrediction, "<4/4>", - TokenClassification.None, new string(' ', listWidth - 46), // 46 is the length of '<4/4>' plus '<… TestPredictor(2) LongNamePredic…(1/1)>'. - dimmedColors, "<… TestPredictor(2) ", - TokenClassification.ListPrediction, "LongNamePredic…(1/1)", + TokenClassification.None, new string(' ', listWidth - 46), // 46 is the length of '<4/4>' plus '<… TestPredictor(2) LongNamePredic…(1/1)>'. + dimmedColors, "<… TestPredictor(2) ", + TokenClassification.ListPrediction, "LongNamePredic…(1/1)", dimmedColors, '>', NextLine, TokenClassification.ListPrediction, '>', @@ -1875,9 +1875,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.ListPredictionSelected, " SOME NEW TEXT", - TokenClassification.ListPredictionSelected, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.ListPredictionSelected, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.ListPredictionSelected, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.ListPredictionSelected, ']', // List view is done, no more list item following. NextLine, @@ -1891,8 +1891,8 @@ namespace Test TokenClassification.None, " NEW TEX", NextLine, TokenClassification.ListPrediction, "<-/3>", - TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 42), // 42 is the length of '<-/3>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME TEXT BEFORE ", @@ -1915,9 +1915,9 @@ namespace Test TokenClassification.None, ' ', emphasisColors, "SOME NEW TEX", TokenClassification.None, 'T', - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -1937,10 +1937,10 @@ namespace Test TokenClassification.None, " NEW TEX SOME TEXT AFTER", NextLine, TokenClassification.ListPrediction, "<2/3>", - TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<2/3>' plus ''. + TokenClassification.None, new string(' ', listWidth - 44), // 44 is the length of '<2/3>' plus ''. dimmedColors, '<', TokenClassification.ListPrediction, "TestPredictor(2/2) ", - dimmedColors, "LongNamePredic…(1)>", + dimmedColors, "LongNamePredic…(1)>", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME TEXT BEFORE ", @@ -1963,9 +1963,9 @@ namespace Test TokenClassification.None, ' ', emphasisColors, "SOME NEW TEX", TokenClassification.None, 'T', - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -2011,8 +2011,8 @@ namespace Test TokenClassification.Command, "de-dup", NextLine, TokenClassification.ListPrediction, "<-/4>", - TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/4>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/4>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, ' ', @@ -2042,9 +2042,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, @@ -2075,8 +2075,8 @@ namespace Test TokenClassification.Command, "de-dup", NextLine, TokenClassification.ListPrediction, "<-/4>", - TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/4>' plus ''. - dimmedColors, "", + TokenClassification.None, new string(' ', listWidth - 36), // 36 is the length of '<-/4>' plus ''. + dimmedColors, "", NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, ' ', @@ -2105,9 +2105,9 @@ namespace Test NextLine, TokenClassification.ListPrediction, '>', TokenClassification.None, " SOME NEW TEXT", - TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' + TokenClassification.None, new string(' ', listWidth - 32), // 32 is the length of '> SOME NEW TEXT' plus '[LongNamePredic…]' TokenClassification.None, '[', - TokenClassification.ListPrediction, "LongNamePredic…", + TokenClassification.ListPrediction, "LongNamePredic…", TokenClassification.None, ']', // List view is done, no more list item following. NextLine, diff --git a/test/ListViewTooltipTest.cs b/test/ListViewTooltipTest.cs index 624ec0c..3d93be4 100644 --- a/test/ListViewTooltipTest.cs +++ b/test/ListViewTooltipTest.cs @@ -134,7 +134,7 @@ namespace Test dimmedColors, " >> Hello", NextLine, dimmedColors, " Binary", NextLine, dimmedColors, " World", NextLine, - dimmedColors, " PowerShell is a task automation an… ", + dimmedColors, " PowerShell is a task automation an… ", TokenClassification.ListPrediction, "( to view all)", NextLine, TokenClassification.ListPrediction, '>', @@ -287,7 +287,7 @@ namespace Test TokenClassification.ListPredictionSelected, ']', NextLine, dimmedColors, " >> Hello", NextLine, - dimmedColors, " Binary … ", + dimmedColors, " Binary … ", TokenClassification.ListPrediction, "( to view all)", NextLine, TokenClassification.ListPrediction, '>', @@ -406,7 +406,7 @@ namespace Test TokenClassification.ListPrediction, "Tooltip", TokenClassification.ListPredictionSelected, ']', NextLine, - dimmedColors, " >> Hello … ", + dimmedColors, " >> Hello … ", TokenClassification.ListPrediction, "( to view all)", NextLine, TokenClassification.ListPrediction, '>', @@ -462,7 +462,7 @@ namespace Test TokenClassification.ListPrediction, "Tooltip", TokenClassification.ListPredictionSelected, ']', NextLine, - dimmedColors, " >> Hello … ", + dimmedColors, " >> Hello … ", TokenClassification.ListPrediction, "( to view all)", // List view is done, no more list item following. NextLine From 5e72587cca7bd07e0d51aeb75db6967526739d5c Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:57:12 -0700 Subject: [PATCH 2/5] Improve `IsScreenReaderActive()` This supports checking for the built-in screen readers VoiceOver on macOS and Windows Narrator, as well as the popular open-source option, NVDA. The VoiceOver check spawns a quick `defaults` process since in .NET using the macOS events is difficult, but this is quick and easy. The Windows Narrator check inspects a system mutex. Notably though this screen reader handles re-rendering better than others. The check for NVDA et. al. inspects the system parameter information. While this approach is known to be buggy, the preferable and commonly used algorithm (as implemented by Electron) which checks for loaded libraries was tested and found to be unsupported for a non-windowed program like PowerShell. It's unknown if the SPI check will detect JAWS, Window-Eyes, or ZoomText, so a command-line option for the upcoming screen reader mode should also be provided. Linux is not yet supported. --- PSReadLine/Accessibility.cs | 77 +++++++++++++++++++++++++++++++++-- PSReadLine/PlatformWindows.cs | 15 +++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/PSReadLine/Accessibility.cs b/PSReadLine/Accessibility.cs index 4938da2..aae6c0f 100644 --- a/PSReadLine/Accessibility.cs +++ b/PSReadLine/Accessibility.cs @@ -2,6 +2,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ +using System.Diagnostics; using System.Runtime.InteropServices; namespace Microsoft.PowerShell.Internal @@ -10,14 +11,82 @@ namespace Microsoft.PowerShell.Internal { internal static bool IsScreenReaderActive() { - bool returnValue = false; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - PlatformWindows.SystemParametersInfo(PlatformWindows.SPI_GETSCREENREADER, 0, ref returnValue, 0); + return IsAnyWindowsScreenReaderEnabled(); } - return returnValue; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return IsVoiceOverEnabled(); + } + + // TODO: Support Linux per https://code.visualstudio.com/docs/configure/accessibility/accessibility + return false; + } + + private static bool IsAnyWindowsScreenReaderEnabled() + { + // The supposedly official way to check for a screen reader on + // Windows is SystemParametersInfo(SPI_GETSCREENREADER, ...) but it + // doesn't detect the in-box Windows Narrator and is otherwise known + // to be problematic. + // + // Unfortunately, the alternative method used by Electron and + // Chromium, where the relevant screen reader libraries (modules) + // are checked for does not work in the context of PowerShell + // because it relies on those applications injecting themselves into + // the app. Which they do not because PowerShell is not a windowed + // app, so we're stuck using the known-to-be-buggy way. + bool spiScreenReader = false; + PlatformWindows.SystemParametersInfo(PlatformWindows.SPI_GETSCREENREADER, 0, ref spiScreenReader, 0); + if (spiScreenReader) + { + return true; + } + + // At least we can correctly check for Windows Narrator using the + // NarratorRunning mutex. Windows Narrator is mostly not broken with + // PSReadLine, not in the way that NVDA and VoiceOver are. + if (PlatformWindows.IsMutexPresent("NarratorRunning")) + { + return true; + } + + return false; + } + + private static bool IsVoiceOverEnabled() + { + try + { + // Use the 'defaults' command to check if VoiceOver is enabled + // This checks the com.apple.universalaccess preference for voiceOverOnOffKey + ProcessStartInfo startInfo = new() + { + FileName = "defaults", + Arguments = "read com.apple.universalaccess voiceOverOnOffKey", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + using Process process = Process.Start(startInfo); + process.WaitForExit(250); + if (process.HasExited && process.ExitCode == 0) + { + string output = process.StandardOutput.ReadToEnd().Trim(); + // VoiceOver is enabled if the value is 1 + return output == "1"; + } + } + catch + { + // If we can't determine the status, assume VoiceOver is not enabled + } + + return false; } } } diff --git a/PSReadLine/PlatformWindows.cs b/PSReadLine/PlatformWindows.cs index c7e0313..32cf653 100644 --- a/PSReadLine/PlatformWindows.cs +++ b/PSReadLine/PlatformWindows.cs @@ -79,6 +79,21 @@ static class PlatformWindows [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern IntPtr GetStdHandle(uint handleId); + internal const int ERROR_ALREADY_EXISTS = 0xB7; + + internal static bool IsMutexPresent(string name) + { + try + { + using var mutex = new System.Threading.Mutex(false, name); + return Marshal.GetLastWin32Error() == ERROR_ALREADY_EXISTS; + } + catch + { + return false; + } + } + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add); From 7a5826bac3212f62e30a700fccb61ef977e1afa1 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 14 Aug 2025 18:52:38 -0700 Subject: [PATCH 3/5] Fix hard-coded continuation prompt in tests These tests were using a hard-coded continuation prompt (and its length) which blocks tests for the upcoming screen reader mode. This is also simply a reasonable refactor, thanks Claude (for trying). --- test/BasicEditingTest.VI.cs | 47 +++++-------- test/BasicEditingTest.cs | 38 +++++----- test/KillYankTest.cs | 26 +++---- test/MovementTest.VI.Multiline.cs | 68 +++++++----------- test/MovementTest.cs | 112 +++++++++++++++--------------- test/RenderTest.cs | 5 +- test/TextObjects.Vi.Tests.cs | 6 -- test/UnitTestReadLine.cs | 6 ++ test/YankPasteTest.VI.cs | 34 +++------ 9 files changed, 147 insertions(+), 195 deletions(-) diff --git a/test/BasicEditingTest.VI.cs b/test/BasicEditingTest.VI.cs index 1ab2bca..33c3ad1 100644 --- a/test/BasicEditingTest.VI.cs +++ b/test/BasicEditingTest.VI.cs @@ -494,8 +494,6 @@ namespace Test { TestSetup(KeyMode.Vi); - int continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\n\"", Keys(  _.DQuote, _.Enter, "one", _.Enter, @@ -503,7 +501,7 @@ namespace Test "three", _.Enter, _.DQuote, _.Escape, "kl", // go to the 'hree' portion of "three" - "2dk", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + "2dk", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); } @@ -512,8 +510,6 @@ namespace Test { TestSetup(KeyMode.Vi); - int continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\none\ntwo\n\"", Keys(  _.DQuote, _.Enter, "one", _.Enter, @@ -521,7 +517,7 @@ namespace Test "three", _.Enter, _.DQuote, _.Escape, "dk", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), CheckThat(() => AssertCursorTopIs(2)), CheckThat(() => AssertLineIs("\"\none\ntwo")), // finish the buffer to close the multiline string @@ -571,8 +567,6 @@ namespace Test { TestSetup(KeyMode.Vi); - int continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\no\nthree\n\"", Keys( _.DQuote, _.Enter, "one", _.Enter, @@ -582,7 +576,7 @@ namespace Test "kkkl", // go to the 'ne' portion of "one" // delete to the end of the next line "2d$", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); } @@ -591,18 +585,16 @@ namespace Test { TestSetup(KeyMode.Vi); - int continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\nsome words\n\"", Keys( _.DQuote, _.Enter, " this is a line with some words", _.Enter, _.DQuote, _.Escape, "k6W", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 23)), + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 23)), // delete from first non blank of line "d0", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength)) + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength)) )); } @@ -611,17 +603,15 @@ namespace Test { TestSetup(KeyMode.Vi); - int continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\n some spaces\n\"", Keys( _.DQuote, _.Enter, " this is a line with some spaces", _.Enter, _.DQuote, _.Escape, "k6W", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 23)), + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 23)), // delete from first non blank of line "d^", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)) + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)) )); } @@ -630,8 +620,6 @@ namespace Test { TestSetup(KeyMode.Vi); - int continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\n\"", Keys(  _.DQuote, _.Enter, "one", _.Enter, @@ -639,7 +627,7 @@ namespace Test "three", _.Enter, _.DQuote, _.Escape, "kkkl", // go to the 'ne' portion of "one" - "2dj", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + "2dj", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); } @@ -648,8 +636,6 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\nthree\n\"", Keys( _.DQuote, _.Enter, "one", _.Enter, @@ -659,7 +645,7 @@ namespace Test "kkl", // go to the 'wo' portion of "two" // delete from line 2 to the current line (3) "2dgg", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); Test("\"\none\nthree\n\"", Keys( @@ -671,7 +657,7 @@ namespace Test "kkl", // go to the 'wo' portion of "two" // delete the current line (3) "3dgg", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); Test("\"\none\n\"", Keys( @@ -683,7 +669,7 @@ namespace Test "kkl", // go to the 'wo' portion of "two" // delete from the current line (3) to line 4 "4dgg", - CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); } @@ -977,7 +963,6 @@ namespace Test [SkippableFact] public void ViInsertLine() { - int adder = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; TestSetup(KeyMode.Vi); Test("line1\n", Keys( @@ -985,8 +970,8 @@ namespace Test )); Test("\nline1", Keys( - _.Escape, "oline1", CheckThat(() => AssertCursorLeftIs(5 + adder)), CheckThat(() => AssertLineIs("\nline1")), - _.Escape, CheckThat(() => AssertCursorLeftIs(4 + adder)) + _.Escape, "oline1", CheckThat(() => AssertCursorLeftIs(5 + ContinuationPromptLength)), CheckThat(() => AssertLineIs("\nline1")), + _.Escape, CheckThat(() => AssertCursorLeftIs(4 + ContinuationPromptLength)) )); Test("", Keys( @@ -1010,10 +995,10 @@ namespace Test )); Test("", Keys( - _.Escape, "oline4", CheckThat(() => AssertLineIs("\nline4")), CheckThat(() => AssertCursorLeftIs(5 + adder)), - _.Escape, "Oline2", CheckThat(() => AssertLineIs("\nline2\nline4")), CheckThat(() => AssertCursorLeftIs(5 + adder)), + _.Escape, "oline4", CheckThat(() => AssertLineIs("\nline4")), CheckThat(() => AssertCursorLeftIs(5 + ContinuationPromptLength)), + _.Escape, "Oline2", CheckThat(() => AssertLineIs("\nline2\nline4")), CheckThat(() => AssertCursorLeftIs(5 + ContinuationPromptLength)), _.Escape, "oline3", CheckThat(() => AssertLineIs("\nline2\nline3\nline4")), - _.Escape, CheckThat(() => AssertLineIs("\nline2\nline3\nline4")), CheckThat(() => AssertCursorLeftIs(4 + adder)), + _.Escape, CheckThat(() => AssertLineIs("\nline2\nline3\nline4")), CheckThat(() => AssertCursorLeftIs(4 + ContinuationPromptLength)), 'u', CheckThat(() => AssertLineIs("\nline2\nline4")), 'u', CheckThat(() => AssertLineIs("\nline4")), 'u' diff --git a/test/BasicEditingTest.cs b/test/BasicEditingTest.cs index 16d8d27..ac5c783 100644 --- a/test/BasicEditingTest.cs +++ b/test/BasicEditingTest.cs @@ -340,8 +340,6 @@ namespace Test { TestSetup(KeyMode.Cmd); - var continutationPromptLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - // Test case - start with single line, cursor at end Test("56\n1234", Keys("1234", _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "56")); @@ -356,13 +354,13 @@ namespace Test // Test case - start with multi-line, cursor at end of second line (end of input) Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", - _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - start with multi-line, cursor at beginning of second line Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", - _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), - _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - start with multi-line, cursor at end of first line @@ -380,15 +378,15 @@ namespace Test // Test case - insert multiple blank lines Test("1234\n9ABC\n\n5678", Keys("1234", _.Shift_Enter, "5678", - _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), - _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - create leading blank line, cursor to stay on same line Test("\n\n1234", Keys("1234", _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(0,0)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), - _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)))); + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.Ctrl_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)))); } [SkippableFact] @@ -396,51 +394,49 @@ namespace Test { TestSetup(KeyMode.Cmd); - var continutationPromptLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - // Test case - start with single line, cursor at end Test("1234\n56", Keys("1234", - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "56")); // Test case - start with single line, cursor in home position Test("1234\n56", Keys("1234", - _.Home, _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.Home, _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "56")); // Test case - start with single line, cursor in middle Test("1234\n56", Keys("1234", - _.LeftArrow, _.LeftArrow, _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.LeftArrow, _.LeftArrow, _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "56")); // Test case - start with multi-line, cursor at end of second line (end of input) Test("1234\n5678\n9ABC", Keys("1234", _.Shift_Enter, "5678", - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), "9ABC")); // Test case - start with multi-line, cursor at beginning of second line Test("1234\n5678\n9ABC", Keys("1234", _.Shift_Enter, "5678", - _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)), + _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), "9ABC")); // Test case - start with multi-line, cursor at end of first line Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", _.UpArrow, _.LeftArrow, _.End, CheckThat(() => AssertCursorLeftTopIs(4, 0)), - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - start with multi-line, cursor at beginning of first line - temporarily having to press Home twice to // work around bug in home handler. Test("1234\n9ABC\n5678", Keys("1234", _.Shift_Enter, "5678", _.UpArrow, _.LeftArrow, _.Home, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)), - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), "9ABC")); // Test case - insert multiple blank lines Test("1234\n5678\n\n9ABC", Keys("1234", _.Shift_Enter, "5678", - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)), - _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 3)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), + _.Ctrl_Shift_Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 3)), "9ABC")); } diff --git a/test/KillYankTest.cs b/test/KillYankTest.cs index 8705faa..e884fcf 100644 --- a/test/KillYankTest.cs +++ b/test/KillYankTest.cs @@ -674,6 +674,8 @@ namespace Test { TestSetup(KeyMode.Cmd); + var continuationPrompt = PSConsoleReadLine.GetOptions().ContinuationPrompt; + Test("", Keys( "& Test-Sca a1 @'\nabc\n'@ -p1 \"$false\"", // Command name or command expression should be skipped. @@ -685,10 +687,10 @@ namespace Test TokenClassification.None, ' ', TokenClassification.String, "@'", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "abc", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "'@", TokenClassification.None, ' ', TokenClassification.Parameter, "-p1", @@ -704,10 +706,10 @@ namespace Test TokenClassification.None, " a1 ", TokenClassification.String, "@'", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.Selection, "abc", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "'@", TokenClassification.None, ' ', TokenClassification.Parameter, "-p1", @@ -723,10 +725,10 @@ namespace Test TokenClassification.None, " a1 ", TokenClassification.String, "@'", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "abc", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "'@", TokenClassification.None, ' ', TokenClassification.Parameter, "-p1", @@ -744,10 +746,10 @@ namespace Test TokenClassification.None, ' ', TokenClassification.String, "@'", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "abc", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "'@", TokenClassification.None, ' ', TokenClassification.Parameter, "-p1", @@ -766,10 +768,10 @@ namespace Test TokenClassification.None, ' ', TokenClassification.String, "@'", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "abc", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "'@", TokenClassification.None, ' ', TokenClassification.Parameter, "-p1", @@ -786,10 +788,10 @@ namespace Test TokenClassification.None, " a1 ", TokenClassification.String, "@'", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "abc", NextLine, - TokenClassification.None, ">> ", + TokenClassification.None, continuationPrompt, TokenClassification.String, "'@", TokenClassification.None, ' ', TokenClassification.Parameter, "-p1", diff --git a/test/MovementTest.VI.Multiline.cs b/test/MovementTest.VI.Multiline.cs index 68e49ba..0754efd 100644 --- a/test/MovementTest.VI.Multiline.cs +++ b/test/MovementTest.VI.Multiline.cs @@ -12,8 +12,6 @@ namespace Test const string buffer = "\"\n12345\n1234\n123\n12\n1\n\""; - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test(buffer, Keys( _.DQuote, _.Enter, "12345", _.Enter, @@ -25,22 +23,22 @@ namespace Test _.Escape, // move to second line at column 4 - "ggj3l", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)), + "ggj3l", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)), // moving down on shorter lines will position the cursor at the end of each logical line - _.j, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)), - _.j, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 2)), + _.j, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)), + _.j, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 2)), // moving back up will position the cursor at the end of shorter lines or at the desired column number - _.k, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)), - _.k, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)), + _.k, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)), + _.k, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)), // move at end of line (column 5) - _.Dollar, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 4)), + _.Dollar, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 4)), // moving down on shorter lines will position the cursor at the end of each logical line - _.j, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)), - _.j, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 2)), + _.j, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)), + _.j, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 2)), // moving back up will position the cursor at the end of each logical line - _.k, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 3)), - _.k, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 4)) + _.k, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 3)), + _.k, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 4)) )); } @@ -52,19 +50,17 @@ namespace Test const string buffer = "\"\nline2\nline3\n\""; - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test(buffer, Keys( _.DQuote, _.Enter, "line2", _.Enter, "line3", _.Enter, _.DQuote, _.Escape, - _.k, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), + _.k, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), // move left - _.h, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), - _.l, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 1)), - "2h", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + _.h, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), + _.l, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 1)), + "2h", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); } @@ -75,18 +71,16 @@ namespace Test const string buffer = "\"\nline2\nline3\n\""; - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test(buffer, Keys( _.DQuote, _.Enter, "line2", _.Enter, "line3", _.Enter, _.DQuote, _.Escape, - _.k, _.k, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), + _.k, _.k, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), // move right - _.l, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 1)), - "10l", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 4)) + _.l, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 1)), + "10l", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 4)) )); } @@ -97,8 +91,6 @@ namespace Test const string buffer = "\"Multiline buffer\n containing an empty line\n\nand text aligned on the left\n\""; - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test(buffer, Keys( _.DQuote, "Multiline buffer", _.Enter, " containing an empty line", _.Enter, @@ -107,7 +99,7 @@ namespace Test _.DQuote, _.Escape, CheckThat(() => AssertCursorTopIs(4)), "gg", CheckThat(() => AssertCursorLeftTopIs(0, 0)), - 'G', CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength, 4)) + 'G', CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 4)) )); } @@ -148,17 +140,15 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - const string buffer = "\"\n line\""; Test(buffer, Keys( - _.DQuote, _.Enter, " line", _.DQuote, _.Escape, CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 6)), - _.Underbar, CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 2, 1)), - _.Dollar, CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 6, 1)), + _.DQuote, _.Enter, " line", _.DQuote, _.Escape, CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 6)), + _.Underbar, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 2, 1)), + _.Dollar, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 6, 1)), // also works forward - '0', CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength, 1)), - _.Underbar, CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 2, 1)) + '0', CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.Underbar, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 2, 1)) )); } @@ -167,14 +157,12 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - const string buffer = "\"\n\n\""; Test(buffer, Keys( _.DQuote, _.Enter, _.Enter, _.DQuote, _.Escape, _.k, - CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 0, 1)), - _.Underbar, CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 0, 1)) + CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 0, 1)), + _.Underbar, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 0, 1)) )); } @@ -193,14 +181,12 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - const string buffer = "\"\n\n\""; Test(buffer, Keys( _.DQuote, _.Enter, _.Enter, _.DQuote, _.Escape, _.k, - CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 0, 1)), - _.Dollar, CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 0, 1)) + CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 0, 1)), + _.Dollar, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 0, 1)) )); } diff --git a/test/MovementTest.cs b/test/MovementTest.cs index 0c687cd..4bf4b99 100644 --- a/test/MovementTest.cs +++ b/test/MovementTest.cs @@ -35,7 +35,6 @@ namespace Test { TestSetup(KeyMode.Cmd); - int continutationPromptLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; string line_0 = "4444"; string line_1 = "33"; string line_2 = "666666"; @@ -43,8 +42,8 @@ namespace Test int wrappedLength_1 = 9; int wrappedLength_2 = 2; - string wrappedLine_1 = new string('8', _console.BufferWidth - continutationPromptLength + wrappedLength_1); // Take 2 physical lines - string wrappedLine_2 = new string('6', _console.BufferWidth - continutationPromptLength + wrappedLength_2); // Take 2 physical lines + string wrappedLine_1 = new string('8', _console.BufferWidth - ContinuationPromptLength + wrappedLength_1); // Take 2 physical lines + string wrappedLine_2 = new string('6', _console.BufferWidth - ContinuationPromptLength + wrappedLength_2); // Take 2 physical lines Test("", Keys( "", _.Shift_Enter, // physical line 0 @@ -57,45 +56,45 @@ namespace Test // Starting at the end of the last line. // Verify that UpArrow goes to the end of the previous logical line. - CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_3.Length, 8)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_3.Length, 8)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_3.Length, 8)), + CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_3.Length, 8)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_3.Length, 8)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_3.Length, 8)), // Press Up/Down/Up _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_2, 7)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_3.Length, 8)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_3.Length, 8)), _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_2, 7)), // Press Up/Down/Up _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_1, 5)), _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_2, 7)), _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_1, 5)), // Press Up/Down/Up - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_2.Length, 3)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_2.Length, 3)), _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_1, 5)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_2.Length, 3)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_2.Length, 3)), // Press Up/Up - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_1.Length, 2)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length, 1)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_1.Length, 2)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length, 1)), // Move to left for 1 character, so the cursor now is not at the end of line. // Verify that DownArrow/UpArrow goes to the previous logical line at the same column. - _.LeftArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 1)), + _.LeftArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 1)), // Press Down all the way to the end - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_1.Length, 2)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 3)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 4)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 5)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 6)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_1.Length, 2)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 3)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 4)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 5)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 6)), _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_2, 7)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_3.Length, 8)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_3.Length, 8)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_3.Length, 8)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_3.Length, 8)), // Press Up all the way to the physical line 1 _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(wrappedLength_2, 7)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 6)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 5)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 4)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 3)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_1.Length, 2)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength + line_0.Length - 1, 1)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 6)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 5)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 4)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 3)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_1.Length, 2)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + line_0.Length - 1, 1)), // Clear the input, we were just testing movement _.Escape @@ -107,7 +106,6 @@ namespace Test { TestSetup(KeyMode.Cmd); - var continutationPromptLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; Test("", Keys( "4444", _.Shift_Enter, "666666", _.Shift_Enter, @@ -118,39 +116,39 @@ namespace Test // Starting at the end of the next to last line (because it's not blank) // Verify that Home first goes to the start of the line, then the start of the input. _.LeftArrow, - _.Home, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 4)), + _.Home, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 4)), _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)), // Now (because we're at the start), verify first End goes to end of the line // and the second End goes to the end of the input. _.End, CheckThat(() => AssertCursorLeftTopIs(4, 0)), - _.End, CheckThat(() => AssertCursorLeftTopIs(0 + continutationPromptLength, 5)), + _.End, CheckThat(() => AssertCursorLeftTopIs(0 + ContinuationPromptLength, 5)), _.Home, _.Home, - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 3)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 4)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 5)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 3)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 4)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 5)), _.LeftArrow, _.Home, - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 3)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 0)), // was (4,0), but seems wrong + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 3)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 2)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 0)), // was (4,0), but seems wrong // Make sure that movement between lines stays at the end of a line if it starts // at the end of a line _.End, _.End, - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(4 + continutationPromptLength, 4)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 3)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(8 + continutationPromptLength, 2)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 1)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(4 + ContinuationPromptLength, 4)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 3)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(8 + ContinuationPromptLength, 2)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 1)), _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(4, 0)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 1)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(8 + continutationPromptLength, 2)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 3)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(4 + continutationPromptLength, 4)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(0 + continutationPromptLength, 5)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 1)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(8 + ContinuationPromptLength, 2)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 3)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(4 + ContinuationPromptLength, 4)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(0 + ContinuationPromptLength, 5)), _.Escape, _.Shift_Enter, @@ -160,22 +158,22 @@ namespace Test "55555", _.Shift_Enter, "88888888", _.LeftArrow, _.LeftArrow, - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(5 + continutationPromptLength, 4)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(2 + continutationPromptLength, 3)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(5 + continutationPromptLength, 2)), - _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 1)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(5 + ContinuationPromptLength, 4)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(2 + ContinuationPromptLength, 3)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(5 + ContinuationPromptLength, 2)), + _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 1)), _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(0, 0)), _.UpArrow, CheckThat(() => AssertCursorLeftTopIs(0, 0)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 1)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(5 + continutationPromptLength, 2)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(2 + continutationPromptLength, 3)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(5 + continutationPromptLength, 4)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + continutationPromptLength, 5)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 1)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(5 + ContinuationPromptLength, 2)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(2 + ContinuationPromptLength, 3)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(5 + ContinuationPromptLength, 4)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(6 + ContinuationPromptLength, 5)), // Using the input previously entered, check for correct cursor movements when first line is blank _.Home, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)), - _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(8 + continutationPromptLength, 1)), - _.Home, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)), + _.DownArrow, CheckThat(() => AssertCursorLeftTopIs(8 + ContinuationPromptLength, 1)), + _.Home, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), _.Home, CheckThat(() => AssertCursorLeftTopIs(0,0)), // Clear the input, we were just testing movement diff --git a/test/RenderTest.cs b/test/RenderTest.cs index a1222c1..cb4943a 100644 --- a/test/RenderTest.cs +++ b/test/RenderTest.cs @@ -182,7 +182,6 @@ namespace Test // Make sure when input is incomplete actually puts a newline // wherever the cursor is. - var continationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; Test("{\n\nd\n}", Keys( '{', _.Enter, CheckThat(() => AssertCursorTopIs(1)), @@ -190,8 +189,8 @@ namespace Test _.Enter, CheckThat(() => AssertCursorTopIs(2)), _.Home, _.RightArrow, CheckThat(() => AssertCursorLeftTopIs(1, 0)), - _.Enter, CheckThat(() => AssertCursorLeftTopIs(continationPrefixLength, 1)), - _.End, CheckThat(() => AssertCursorLeftTopIs(continationPrefixLength, 3)), + _.Enter, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 1)), + _.End, CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength, 3)), '}')); // Make sure when input successfully parses accepts the input regardless diff --git a/test/TextObjects.Vi.Tests.cs b/test/TextObjects.Vi.Tests.cs index f819b38..d38e44b 100644 --- a/test/TextObjects.Vi.Tests.cs +++ b/test/TextObjects.Vi.Tests.cs @@ -82,8 +82,6 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\nhello, world!\n\noh, bitter world!\n\"", Keys( _.DQuote, _.Enter, "hello, world!", _.Enter, @@ -105,8 +103,6 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("", Keys( _.DQuote, "hello, world!", _.Enter, @@ -143,8 +139,6 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\ntwo\n\"", Keys( _.DQuote, _.Enter, "one", _.Enter, diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index d09e2ab..71b8a94 100644 --- a/test/UnitTestReadLine.cs +++ b/test/UnitTestReadLine.cs @@ -131,6 +131,8 @@ namespace Test internal virtual bool KeyboardHasCtrlRBracket => true; internal virtual bool KeyboardHasCtrlAt => true; + internal int ContinuationPromptLength => _continuationPromptLength; + static ReadLine() { var iss = InitialSessionState.CreateDefault(); @@ -540,6 +542,7 @@ namespace Test private bool _oneTimeInitCompleted; private object _psrlInstance; private FieldInfo _psrlConsole, _psrlMockableMethods; + private int _continuationPromptLength; private static string MakeCombinedColor(ConsoleColor fg, ConsoleColor bg) => VTColorUtils.AsEscapeSequence(fg) + VTColorUtils.AsEscapeSequence(bg, isBackground: true); @@ -633,6 +636,9 @@ namespace Test var colorOptions = new SetPSReadLineOption {Colors = colors}; PSConsoleReadLine.SetOptions(colorOptions); + // Cache the continuation prompt length for use in tests + _continuationPromptLength = PSConsoleReadLine.GetOptions().ContinuationPrompt.Length; + if (!_oneTimeInitCompleted) { typeof(PSConsoleReadLine).GetMethod("Initialize", BindingFlags.Instance | BindingFlags.NonPublic) diff --git a/test/YankPasteTest.VI.cs b/test/YankPasteTest.VI.cs index 590f04b..56261d5 100644 --- a/test/YankPasteTest.VI.cs +++ b/test/YankPasteTest.VI.cs @@ -48,13 +48,11 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("abcd", Keys( "abcd", _.Escape, "dd", CheckThat(() => AssertLineIs("")), CheckThat(() => AssertCursorLeftIs(0)), - 'p', CheckThat(() => AssertLineIs("\nabcd")), CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), - 'P', CheckThat(() => AssertLineIs("\nabcd\nabcd")), CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), + 'p', CheckThat(() => AssertLineIs("\nabcd")), CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), + 'P', CheckThat(() => AssertLineIs("\nabcd\nabcd")), CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), "uuu" )); @@ -206,12 +204,10 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("abc def", Keys( "abc def", _.Escape, "dd", CheckThat(() => AssertLineIs("")), CheckThat(() => AssertCursorLeftIs(0)), - 'p', CheckThat(() => AssertLineIs("\nabc def")), CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), + 'p', CheckThat(() => AssertLineIs("\nabc def")), CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), "dd", CheckThat(() => AssertLineIs("")), CheckThat(() => AssertCursorLeftIs(0)), 'P', CheckThat(() => AssertLineIs("abc def\n")), CheckThat(() => AssertCursorLeftIs(0)), "uuuu" @@ -223,13 +219,11 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("012 456", Keys( "012 456", _.Escape, "byyP", CheckThat(() => AssertLineIs("012 456\n012 456")), CheckThat(() => AssertCursorLeftIs(0)), "u", CheckThat(() => AssertLineIs("012 456")), CheckThat(() => AssertCursorLeftIs(4)), - "p", CheckThat(() => AssertLineIs("012 456\n012 456")), CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), + "p", CheckThat(() => AssertLineIs("012 456\n012 456")), CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), "u", CheckThat(() => AssertLineIs("012 456")), CheckThat(() => AssertCursorLeftIs(4)) )); } @@ -338,8 +332,6 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("012", Keys( "012", _.Escape, "y0P", CheckThat(() => AssertLineIs("01012")), CheckThat(() => AssertCursorLeftIs(1)), @@ -358,9 +350,9 @@ namespace Test " World!", _.Enter, _.DQuote, _.Escape, _.k, "5l", // move the cursor to the 'd' character of "World!" - "y0", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)), - "P", CheckThat(() => AssertLineIs("\"\nHello\n Worl World!\n\"")), CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 4)), - "u", CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 0)) + "y0", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)), + "P", CheckThat(() => AssertLineIs("\"\nHello\n Worl World!\n\"")), CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 4)), + "u", CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 0)) )); } @@ -369,15 +361,13 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\nHello\nWorld!\n\"", Keys( _.DQuote, _.Enter, "Hello", _.Enter, "World!", _.Enter, _.DQuote, _.Escape, _.k, _.l, // move to the 'o' character of 'World!' - "y$P", CheckThat(() => AssertLineIs("\"\nHello\nWorld!orld!\n\"")), CheckThat(() => AssertCursorLeftIs(continuationPrefixLength + 5)), + "y$P", CheckThat(() => AssertLineIs("\"\nHello\nWorld!orld!\n\"")), CheckThat(() => AssertCursorLeftIs(ContinuationPromptLength + 5)), "u" )); } @@ -542,15 +532,13 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\nline1\nline1\nline2\nline2\n\"", Keys( _.DQuote, _.Enter, "line1", _.Enter, "line2", _.Enter, _.DQuote, _.Escape, _.k, _.k, - "2dd", 'P', 'p', CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 0, 2)) + "2dd", 'P', 'p', CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 0, 2)) )); } @@ -559,15 +547,13 @@ namespace Test { TestSetup(KeyMode.Vi); - var continuationPrefixLength = PSConsoleReadLineOptions.DefaultContinuationPrompt.Length; - Test("\"\nline1\nline1\nline2\nline2\n\"", Keys( _.DQuote, _.Enter, "line1", _.Enter, "line2", _.Enter, _.DQuote, _.Escape, _.k, _.k, - "2d", _.Underbar, 'P', 'p', CheckThat(() => AssertCursorLeftTopIs(continuationPrefixLength + 0, 2)) + "2d", _.Underbar, 'P', 'p', CheckThat(() => AssertCursorLeftTopIs(ContinuationPromptLength + 0, 2)) )); } From 7b6f6024052c4292ec31440669affe77b8f62744 Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:40:23 -0700 Subject: [PATCH 4/5] Add accessible screen reader mode This adds the `EnableScreenReaderMode` command-line switch which defaults to true if an active screen reader is detected. In screen reader mode, the existing `ForceRender()` function is replaced by `RenderForScreenReader()` which uses a differential rendering approach to minimize extraneous output to the terminal, allowing the use of screen readers better than ever before. The differential rendering relies on calculating the common prefix of the `buffer` and `previousRender` strings. Nearly all necessary changes are consolidated in the new rendering function. Features known not to be supported: * Colors: as this necessitates redrawing to insert color sequences after the input is received and the AST parsed. * Inline predictions: as this by definition changes the suffix and thus requires endless redrawing. * List view predictions: since the render implementation never calls into the prediction engine, this is not available either. * Menu completion: well, it "works" since it's not disabled and does its own rendering, but no effort has been made to improve `DrawMenu()`, so it's not accessible (and I'm not sure it could be given our current constraints). Features known to be partially supported: * Text selection: mark and select commands work as intended, but provide no visual indication. * Multiple lines: as in newlines work fine, but there is no continuation prompt. * Visually wrapped lines: editing above a wrapped line redraws all subsequent lines and hence is noisy. * Status prompt based commands: what-is-key, digit-argument, and most notably, forward/backward incremental history search all render a "status prompt" on the line below the user's input buffer. This _is_ supported; however, it can be noisy since it necessarily has to render the whole buffer when the input buffer changes, including the status prompt (and search text). But what it's reading is almost always going to be relevant. Everything else should generally work, even Vi mode, and the tests pass. That said, this isn't perfect, and moreover the approach specifically doesn't attempt to enable things from the ground up. There may be features that are available but turn out not to be accessible (like `MenuComplete`) and I believe they should be left as-is. Specifically tested with NVDA on Windows and VoiceOver on macOS within VS Code's integrated terminal, with shell integration loaded, and Code's screen reader optimizations enabled. --- PSReadLine/BasicEditing.cs | 4 +- PSReadLine/Cmdlets.cs | 17 ++- PSReadLine/Options.cs | 4 + PSReadLine/Render.cs | 237 +++++++++++++++++++++++++++++++------ 4 files changed, 221 insertions(+), 41 deletions(-) diff --git a/PSReadLine/BasicEditing.cs b/PSReadLine/BasicEditing.cs index 33445ed..74c76ac 100644 --- a/PSReadLine/BasicEditing.cs +++ b/PSReadLine/BasicEditing.cs @@ -86,7 +86,7 @@ namespace Microsoft.PowerShell _singleton._current = _singleton._buffer.Length; using var _ = _singleton._prediction.DisableScoped(); - _singleton.ForceRender(); + _singleton.Render(force: true); _singleton._console.Write("\x1b[91m^C\x1b[0m"); @@ -335,7 +335,7 @@ namespace Microsoft.PowerShell if (renderNeeded) { - ForceRender(); + Render(force: true); } // Only run validation if we haven't before. If we have and status line shows an error, diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs index 7fecf4b..5633a69 100644 --- a/PSReadLine/Cmdlets.cs +++ b/PSReadLine/Cmdlets.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Microsoft.PowerShell.PSReadLine; +using Microsoft.PowerShell.Internal; using AllowNull = System.Management.Automation.AllowNullAttribute; namespace Microsoft.PowerShell @@ -150,11 +151,6 @@ namespace Microsoft.PowerShell public const HistorySaveStyle DefaultHistorySaveStyle = HistorySaveStyle.SaveIncrementally; - /// - /// The predictive suggestion feature is disabled by default. - /// - public const PredictionSource DefaultPredictionSource = PredictionSource.None; - public const PredictionViewStyle DefaultPredictionViewStyle = PredictionViewStyle.InlineView; /// @@ -201,6 +197,7 @@ namespace Microsoft.PowerShell { ResetColors(); EditMode = DefaultEditMode; + ScreenReaderModeEnabled = Accessibility.IsScreenReaderActive(); ContinuationPrompt = DefaultContinuationPrompt; ContinuationPromptColor = Console.ForegroundColor; ExtraPromptLineCount = DefaultExtraPromptLineCount; @@ -533,6 +530,8 @@ namespace Microsoft.PowerShell public bool TerminateOrphanedConsoleApps { get; set; } + public bool ScreenReaderModeEnabled { get; set; } + internal string _defaultTokenColor; internal string _commentColor; internal string _keywordColor; @@ -847,6 +846,14 @@ namespace Microsoft.PowerShell } internal SwitchParameter? _terminateOrphanedConsoleApps; + [Parameter] + public SwitchParameter EnableScreenReaderMode + { + get => _enableScreenReaderMode.GetValueOrDefault(); + set => _enableScreenReaderMode = value; + } + internal SwitchParameter? _enableScreenReaderMode; + [ExcludeFromCodeCoverage] protected override void EndProcessing() { diff --git a/PSReadLine/Options.cs b/PSReadLine/Options.cs index 7485154..ffdf024 100644 --- a/PSReadLine/Options.cs +++ b/PSReadLine/Options.cs @@ -185,6 +185,10 @@ namespace Microsoft.PowerShell nameof(Options.TerminateOrphanedConsoleApps))); } } + if (options._enableScreenReaderMode.HasValue) + { + Options.ScreenReaderModeEnabled = options.EnableScreenReaderMode; + } } private void SetKeyHandlerInternal(string[] keys, Action handler, string briefDescription, string longDescription, ScriptBlock scriptBlock) diff --git a/PSReadLine/Render.cs b/PSReadLine/Render.cs index fe68848..253f7fb 100644 --- a/PSReadLine/Render.cs +++ b/PSReadLine/Render.cs @@ -218,36 +218,197 @@ namespace Microsoft.PowerShell Render(); } - private void Render() + private void Render(bool force = false) { - // If there are a bunch of keys queued up, skip rendering if we've rendered very recently. - long elapsedMs = _lastRenderTime.ElapsedMilliseconds; - if (_queuedKeys.Count > 10 && elapsedMs < 50) + if (!force) { - // We won't render, but most likely the tokens will be different, so make - // sure we don't use old tokens, also allow garbage to get collected. - _tokens = null; - _ast = null; - _parseErrors = null; - _waitingToRender = true; - return; + // If there are a bunch of keys queued up, skip rendering if we've rendered very recently. + long elapsedMs = _lastRenderTime.ElapsedMilliseconds; + if (_queuedKeys.Count > 10 && elapsedMs < 50) + { + // We won't render, but most likely the tokens will be different, so make + // sure we don't use old tokens, also allow garbage to get collected. + _tokens = null; + _ast = null; + _parseErrors = null; + _waitingToRender = true; + return; + } + + // If we've rendered very recently, skip the terminal window resizing check as it's unlikely + // to happen in such a short time interval. + // We try to avoid unnecessary resizing check because it requires getting the cursor position + // which would force a network round trip in an environment where front-end xtermjs talking to + // a server-side PTY via websocket. Without querying for cursor position, content written on + // the server side could be buffered, which is much more performant. + // See the following 2 GitHub issues for more context: + // - https://github.com/PowerShell/PSReadLine/issues/3879#issuecomment-2573996070 + // - https://github.com/PowerShell/PowerShell/issues/24696 + if (elapsedMs < 50) + { + _handlePotentialResizing = false; + } } - // If we've rendered very recently, skip the terminal window resizing check as it's unlikely - // to happen in such a short time interval. - // We try to avoid unnecessary resizing check because it requires getting the cursor position - // which would force a network round trip in an environment where front-end xtermjs talking to - // a server-side PTY via websocket. Without querying for cursor position, content written on - // the server side could be buffered, which is much more performant. - // See the following 2 GitHub issues for more context: - // - https://github.com/PowerShell/PSReadLine/issues/3879#issuecomment-2573996070 - // - https://github.com/PowerShell/PowerShell/issues/24696 - if (elapsedMs < 50) + // Use simplified rendering for screen readers + if (Options.ScreenReaderModeEnabled) { - _handlePotentialResizing = false; + RenderForScreenReader(); + } + else + { + ForceRender(); + } + } + + private void RenderForScreenReader() + { + int bufferWidth = _console.BufferWidth; + int bufferHeight = _console.BufferHeight; + + static int FindCommonPrefixLength(string leftStr, string rightStr) + { + if (string.IsNullOrEmpty(leftStr) || string.IsNullOrEmpty(rightStr)) + { + return 0; + } + + int i = 0; + int minLength = Math.Min(leftStr.Length, rightStr.Length); + + while (i < minLength && leftStr[i] == rightStr[i]) + { + i++; + } + + return i; } - ForceRender(); + // For screen readers, we are just comparing the previous and current buffer text + // (without colors) and only writing the differences. + // + // Note that we don't call QueryForSuggestion() which is the only + // entry into the prediction logic, so while it could be enabled, it + // won't do anything in this rendering implementation. + string parsedInput = ParseInput(); + StringBuilder buffer = new(parsedInput); + + // Really simple handling of a status line: append it! + if (!string.IsNullOrEmpty(_statusLinePrompt)) + { + buffer.Append("\n"); + buffer.Append(_statusLinePrompt); + buffer.Append(_statusBuffer); + } + + string currentBuffer = buffer.ToString(); + string previousBuffer = _previousRender.lines[0].Line; + + // In case the buffer was resized. + RecomputeInitialCoords(isTextBufferUnchanged: false); + + // Make cursor invisible while we're rendering. + _console.CursorVisible = false; + + if (currentBuffer == previousBuffer) + { + // No-op, such as when selecting text or otherwise re-entering. + } + else if (previousBuffer.Length == 0) + { + // Previous buffer was empty so we just render the current buffer, + // and we don't need to move the cursor. + _console.Write(currentBuffer); + } + else + { + // Calculate what to render and where to start the rendering. + int commonPrefixLength = FindCommonPrefixLength(previousBuffer, currentBuffer); + + // If we're scrolling through history we always want to re-render. + // Writing only the diff in this scenario is a weird UX. + if (commonPrefixLength > 0 && _anyHistoryCommandCount == 0) + { + // We need to differentially render, possibly with a partial rewrite. + if (commonPrefixLength != previousBuffer.Length) + { + // The buffers share a common prefix but the previous buffer has additional content. + // Move cursor to where the difference starts and clear so we can rewrite. + var diffPoint = ConvertOffsetToPoint(commonPrefixLength, buffer); + _console.SetCursorPosition(diffPoint.X, diffPoint.Y); + _console.Write("\x1b[0J"); + } // Otherwise the previous buffer is a complete prefix and we just write. + + // TODO: There is a rare edge case where the common prefix can be incorrectly + // calculated because the incoming replacement text matches the text at the current + // cursor position. Unfortunately we don't have a solution yet. For example: + // + // 1. Previous line is "abcdef" and cursor is at (before) the letter "d" + // 2. Paste "defghi" so currentBuffer is "abcdefghidef" + // 3. The diff is "ghidef" and because commonPrefixLength == previousBuffer.Length + // 4. The terminal will incorrectly display "abcghidef" instead of "abcdefghidef" + + // Finally, write the diff. + var diffData = currentBuffer.Substring(commonPrefixLength); + _console.Write(diffData); + } + else + { + // The buffers are completely different so we need to rewrite from the start. + _console.SetCursorPosition(_initialX, _initialY); + _console.Write("\x1b[0J"); + _console.Write(currentBuffer); + } + } + + // If we had to wrap to render everything, update _initialY + var endPoint = ConvertOffsetToPoint(currentBuffer.Length, buffer); + if (endPoint.Y >= bufferHeight) + { + // We had to scroll to render everything, update _initialY. + int offset = 1; // Base case to handle zero-indexing. + if (endPoint.X == 0 && !currentBuffer.EndsWith("\n")) + { + // The line hasn't actually wrapped yet because we have exactly filled the line. + offset -= 1; + } + int scrolledLines = endPoint.Y - bufferHeight + offset; + _initialY -= scrolledLines; + } + + // Calculate the coord to place the cursor for the next input. + var point = ConvertOffsetToPoint(_current, buffer); + + if (point.Y == bufferHeight) + { + // The cursor top exceeds the buffer height and it hasn't already wrapped, + // (because we have exactly filled the line) so we need to scroll up the buffer by 1 line. + if (point.X == 0) + { + _console.Write("\n"); + } + + // Adjust the initial cursor position and the to-be-set cursor position + // after scrolling up the buffer. + _initialY -= 1; + point.Y -= 1; + } + + _console.SetCursorPosition(point.X, point.Y); + _console.CursorVisible = true; + + // Preserve the current render data. + var renderData = new RenderData + { + lines = new RenderedLineData[] { new(currentBuffer, isFirstLogicalLine: true) }, + errorPrompt = (_parseErrors != null && _parseErrors.Length > 0) // Not yet used. + }; + _previousRender = renderData; + _previousRender.UpdateConsoleInfo(bufferWidth, bufferHeight, point.X, point.Y); + _previousRender.initialY = _initialY; + + _lastRenderTime.Restart(); + _waitingToRender = false; } private void ForceRender() @@ -261,7 +422,7 @@ namespace Microsoft.PowerShell // and minimize writing more than necessary on the next render.) var renderLines = new RenderedLineData[logicalLineCount]; - var renderData = new RenderData {lines = renderLines}; + var renderData = new RenderData { lines = renderLines }; for (var i = 0; i < logicalLineCount; i++) { var line = _consoleBufferLines[i].ToString(); @@ -872,9 +1033,6 @@ namespace Microsoft.PowerShell WriteBlankLines(lineCount); } - // Preserve the current render data. - _previousRender = renderData; - // If we counted pseudo physical lines, deduct them to get the real physical line counts // before updating '_initialY'. physicalLine -= pseudoPhysicalLineOffset; @@ -950,6 +1108,8 @@ namespace Microsoft.PowerShell _console.SetCursorPosition(point.X, point.Y); _console.CursorVisible = true; + // Preserve the current render data. + _previousRender = renderData; _previousRender.UpdateConsoleInfo(bufferWidth, bufferHeight, point.X, point.Y); _previousRender.initialY = _initialY; @@ -1201,17 +1361,23 @@ namespace Microsoft.PowerShell return ConvertOffsetToPoint(_buffer.Length); } - internal Point ConvertOffsetToPoint(int offset) + internal Point ConvertOffsetToPoint(int offset, StringBuilder buffer = null) { + // This lets us re-use the logic in the screen reader rendering implementation + // where the status line is added to the buffer without modifying the local state. + buffer ??= _buffer; + int x = _initialX; int y = _initialY; int bufferWidth = _console.BufferWidth; - var continuationPromptLength = LengthInBufferCells(Options.ContinuationPrompt); + var continuationPromptLength = Options.ScreenReaderModeEnabled + ? 0 + : LengthInBufferCells(Options.ContinuationPrompt); for (int i = 0; i < offset; i++) { - char c = _buffer[i]; + char c = buffer[i]; if (c == '\n') { y += 1; @@ -1229,7 +1395,7 @@ namespace Microsoft.PowerShell // If cursor is at column 0 and the next character is newline, let the next loop // iteration increment y. - if (x != 0 || !(i + 1 < offset && _buffer[i + 1] == '\n')) + if (x != 0 || !(i + 1 < offset && buffer[i + 1] == '\n')) { y += 1; } @@ -1238,9 +1404,9 @@ namespace Microsoft.PowerShell } // If next character actually exists, and isn't newline, check if wider than the space left on the current line. - if (_buffer.Length > offset && _buffer[offset] != '\n') + if (buffer.Length > offset && buffer[offset] != '\n') { - int size = LengthInBufferCells(_buffer[offset]); + int size = LengthInBufferCells(buffer[offset]); if (x + size > bufferWidth) { // Character was wider than remaining space, so character, and cursor, appear on next line. @@ -1259,7 +1425,10 @@ namespace Microsoft.PowerShell int y = _initialY; int bufferWidth = _console.BufferWidth; - var continuationPromptLength = LengthInBufferCells(Options.ContinuationPrompt); + var continuationPromptLength = Options.ScreenReaderModeEnabled + ? 0 + : LengthInBufferCells(Options.ContinuationPrompt); + for (offset = 0; offset < _buffer.Length; offset++) { // If we are on the correct line, return when we find From 06e3b4bcb6058f6174115d8298617056f34c516e Mon Sep 17 00:00:00 2001 From: Andy Jordan <2226434+andyleejordan@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:47:21 -0700 Subject: [PATCH 5/5] Add fixture to enable existing tests for screen reader mode Tests that depend on supported features such as menu completions, list view, inline predictions, continuation prompt, and colors are skipped. The "helper" module for running tests from the command-line and in AppVeyor is minimally updated to not skip the new fixture. A `BlankRestOfBuffer()` function implements the semantics of the `0J` control sequence in the mock console. --- test/CompletionTest.cs | 18 ++++++++++++++++++ test/InlinePredictionTest.cs | 22 ++++++++++++++++++++++ test/KillYankTest.cs | 2 ++ test/ListPredictionTest.cs | 22 ++++++++++++++++++++++ test/ListScrollableViewTest.cs | 6 ++++++ test/ListViewTooltipTest.cs | 6 ++++++ test/MockConsole.cs | 26 +++++++++++++++++++++++++- test/OptionsTest.cs | 16 ++++++++++++++++ test/RenderTest.cs | 10 ++++++++++ test/UnitTestReadLine.cs | 25 ++++++++++++++++++++++--- tools/helper.psm1 | 11 +++++++++-- 11 files changed, 158 insertions(+), 6 deletions(-) diff --git a/test/CompletionTest.cs b/test/CompletionTest.cs index 7a3ea51..8287e58 100644 --- a/test/CompletionTest.cs +++ b/test/CompletionTest.cs @@ -177,6 +177,8 @@ namespace Test [SkippableFact] public void MenuCompletions_Navigation1() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // Test 'RightArrow' and 'LeftArrow' with the following menu: // Get-Many0 Get-Many3 Get-Many6 Get-Many9 Get-Many12 // Get-Many1 Get-Many4 Get-Many7 Get-Many10 Get-Many13 @@ -304,6 +306,8 @@ namespace Test [SkippableFact] public void MenuCompletions_Navigation2() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // Test 'RightArrow' with the following menu: // Get-Less0 Get-Less3 Get-Less6 Get-Less9 Get-Less12 // Get-Less1 Get-Less4 Get-Less7 Get-Less10 @@ -387,6 +391,8 @@ namespace Test [SkippableFact] public void MenuCompletions_Navigation3() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // Test 'LeftArrow' with the following menu: // Get-Less0 Get-Less3 Get-Less6 Get-Less9 Get-Less12 // Get-Less1 Get-Less4 Get-Less7 Get-Less10 @@ -462,6 +468,8 @@ namespace Test [SkippableFact] public void MenuCompletions_Navigation4() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // Test 'UpArrow' and 'DownArrow' with the following menu: // Get-Less0 Get-Less3 Get-Less6 Get-Less9 Get-Less12 // Get-Less1 Get-Less4 Get-Less7 Get-Less10 @@ -633,6 +641,8 @@ namespace Test [SkippableFact] public void MenuCompletions_Navigation6() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // Test 'UpArrow', 'DownArrow', 'LeftArrow', and 'RightArrow' with the following menu: // Get-NewDynamicParameters Get-NewStyle // Get-NewIdea @@ -736,6 +746,8 @@ namespace Test [SkippableFact] public void MenuCompletions_Navigation7() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // Trigger the menu completion from the last line in the screen buffer, which will cause the screen // to scroll up. Then test 'DownArrow' and 'UpArrow' with the following menu to verify if scrolling // was handled correctly: @@ -968,6 +980,8 @@ namespace Test [SkippableFact] public void MenuCompletions_WorkWithListView() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+Spacebar", PSConsoleReadLine.MenuComplete)); int listWidth = CheckWindowSize(); @@ -1032,6 +1046,8 @@ namespace Test [SkippableFact] public void MenuCompletions_HandleScrolling1() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // This test case covers the fix to https://github.com/PowerShell/PSReadLine/issues/2928. var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 133, height: 10); TestSetup(basicScrollingConsole, KeyMode.Cmd, new KeyHandler("Ctrl+Spacebar", PSConsoleReadLine.MenuComplete)); @@ -1127,6 +1143,8 @@ namespace Test [SkippableFact] public void MenuCompletions_HandleScrolling2() { + Skip.If(ScreenReaderModeEnabled, "Menu completions are not supported in screen reader mode."); + // This test case covers the fix to https://github.com/PowerShell/PSReadLine/issues/2948. var basicScrollingConsole = new BasicScrollingConsole(keyboardLayout: _, width: 133, height: 10); TestSetup(basicScrollingConsole, KeyMode.Cmd, new KeyHandler("Ctrl+Spacebar", PSConsoleReadLine.MenuComplete)); diff --git a/test/InlinePredictionTest.cs b/test/InlinePredictionTest.cs index d9729c6..febf0ac 100644 --- a/test/InlinePredictionTest.cs +++ b/test/InlinePredictionTest.cs @@ -14,6 +14,8 @@ namespace Test [SkippableFact] public void Inline_RenderSuggestion() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord)); using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView); @@ -92,6 +94,8 @@ namespace Test [SkippableFact] public void Inline_CustomKeyBindingsToAcceptSuggestion() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Alt+g", PSConsoleReadLine.AcceptSuggestion), new KeyHandler("Alt+f", PSConsoleReadLine.AcceptNextSuggestionWord)); @@ -156,6 +160,8 @@ namespace Test [SkippableFact] public void Inline_AcceptNextSuggestionWordCanAcceptMoreThanOneWords() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord), new KeyHandler("Alt+f", PSConsoleReadLine.AcceptNextSuggestionWord)); @@ -192,6 +198,8 @@ namespace Test [SkippableFact] public void Inline_AcceptSuggestionWithSelection() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord)); using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView); @@ -261,6 +269,8 @@ namespace Test [SkippableFact] public void Inline_SetPredictionColor() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); var predictionColor = MakeCombinedColor(ConsoleColor.DarkYellow, ConsoleColor.Yellow); var predictionColorToCheck = Tuple.Create(ConsoleColor.DarkYellow, ConsoleColor.Yellow); @@ -288,6 +298,8 @@ namespace Test [SkippableFact] public void Inline_HistoryEditsCanUndoProperly() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord)); SetHistory("git checkout -b branch origin/bbbb"); @@ -320,6 +332,8 @@ namespace Test [SkippableFact] public void Inline_AcceptSuggestionInVIMode() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Vi); using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView); @@ -367,6 +381,8 @@ namespace Test [SkippableFact] public void ViDefect2408() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(KeyMode.Vi); using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView); @@ -460,6 +476,8 @@ namespace Test [SkippableFact] public void Inline_PluginSource_Acceptance() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + // Using the 'Plugin' source will make PSReadLine get prediction from the plugin only. TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+f", PSConsoleReadLine.ForwardWord)); @@ -534,6 +552,8 @@ namespace Test [SkippableFact] public void Inline_HistoryAndPluginSource_Acceptance() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + // Using the 'HistoryAndPlugin' source will make PSReadLine get prediction from the plugin and history, // and plugin takes precedence. TestSetup(KeyMode.Cmd, @@ -798,6 +818,8 @@ namespace Test [SkippableFact] public void Inline_TruncateVeryLongSuggestion() { + Skip.If(ScreenReaderModeEnabled, "Inline predictions are not supported in screen reader mode."); + TestSetup(new TestConsole(keyboardLayout: _, width: 10, height: 2), KeyMode.Cmd); using var disp = SetPrediction(PredictionSource.History, PredictionViewStyle.InlineView); diff --git a/test/KillYankTest.cs b/test/KillYankTest.cs index e884fcf..381e788 100644 --- a/test/KillYankTest.cs +++ b/test/KillYankTest.cs @@ -672,6 +672,8 @@ namespace Test [SkippableFact] public void SelectCommandArgument_HereStringArgs() { + Skip.If(ScreenReaderModeEnabled, "We're still investigating exactly why this test fails in screen reader mode."); + TestSetup(KeyMode.Cmd); var continuationPrompt = PSConsoleReadLine.GetOptions().ContinuationPrompt; diff --git a/test/ListPredictionTest.cs b/test/ListPredictionTest.cs index 1b28339..a0a4269 100644 --- a/test/ListPredictionTest.cs +++ b/test/ListPredictionTest.cs @@ -96,6 +96,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_ListUpdatesWhileTyping() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); // The font effect sequences of the dimmed color used in list view metadata line @@ -184,6 +186,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_NavigateInList_DefaultUpArrowDownArrow() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -401,6 +405,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_NavigateInList_HistorySearchBackwardForward() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+p", PSConsoleReadLine.HistorySearchBackward), new KeyHandler("Ctrl+l", PSConsoleReadLine.HistorySearchForward)); @@ -620,6 +626,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_Escape() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -783,6 +791,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_DigitArgument() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -943,6 +953,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_CtrlZ() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -1076,6 +1088,8 @@ namespace Test [SkippableFact] public void List_RenderSuggestion_Selection() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -1276,6 +1290,8 @@ namespace Test [SkippableFact] public void List_HistorySource_NoAcceptanceCallback() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -1340,6 +1356,8 @@ namespace Test [SkippableFact] public void List_PluginSource_Acceptance() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -1661,6 +1679,8 @@ namespace Test [SkippableFact] public void List_HistoryAndPluginSource_Acceptance() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); @@ -1994,6 +2014,8 @@ namespace Test [SkippableFact] public void List_HistoryAndPluginSource_Deduplication() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); int listWidth = CheckWindowSize(); var dimmedColors = Tuple.Create(ConsoleColor.White, _console.BackgroundColor); diff --git a/test/ListScrollableViewTest.cs b/test/ListScrollableViewTest.cs index 11452bb..3452c46 100644 --- a/test/ListScrollableViewTest.cs +++ b/test/ListScrollableViewTest.cs @@ -9,6 +9,8 @@ namespace Test [SkippableFact] public void List_MetaLine_And_Paging_Navigation() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + int listWidth = 100; TestSetup(new TestConsole(keyboardLayout: _, width: listWidth, height: 15), KeyMode.Cmd); @@ -514,6 +516,8 @@ namespace Test [SkippableFact] public void ListView_AdapteTo_ConsoleSize() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + // Console size is very small (h: 6, w: 50), and thus the list view will adjust to use 3-line height, // and the metadata line will be reduced to only show the (index/total) info. int listWidth = 50; @@ -850,6 +854,8 @@ namespace Test [SkippableFact] public void ListView_TermSize_Warning() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + // Console size is very small (h: 6, w: 50), and thus the list view will adjust to use 3-line height, // and the metadata line will be reduced to only show the (index/total) info. int listWidth = 40; diff --git a/test/ListViewTooltipTest.cs b/test/ListViewTooltipTest.cs index 3d93be4..79a6ba5 100644 --- a/test/ListViewTooltipTest.cs +++ b/test/ListViewTooltipTest.cs @@ -9,6 +9,8 @@ namespace Test [SkippableFact] public void List_Item_Tooltip_4_Lines() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + // Set the terminal height to 22 and width to 60, so the metadata line will be fully rendered // and maximum 4 lines can be used for tooltip for a selected list item. int listWidth = 60; @@ -206,6 +208,8 @@ namespace Test [SkippableFact] public void List_Item_Tooltip_2_Lines() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + // Set the terminal height to 15 and width to 60, so the metadata line will be fully rendered // and maximum 2 lines can be used for tooltip for a selected list item. int listWidth = 60; @@ -363,6 +367,8 @@ namespace Test [SkippableFact] public void List_Item_Tooltip_1_Line() { + Skip.If(ScreenReaderModeEnabled, "List view is not supported in screen reader mode."); + // Set the terminal height to 6 and width to 60, so the metadata line will be fully rendered // and maximum 2 lines can be used for tooltip for a selected list item. int listWidth = 60; diff --git a/test/MockConsole.cs b/test/MockConsole.cs index ee0a7c3..9906986 100644 --- a/test/MockConsole.cs +++ b/test/MockConsole.cs @@ -300,6 +300,17 @@ namespace Test buffer[writePos + i].ForegroundColor = ForegroundColor; } } + + public virtual void BlankRestOfBuffer() + { + var writePos = CursorTop * BufferWidth + CursorLeft; + for (; writePos < buffer.Length; writePos++) + { + buffer[writePos].UnicodeChar = ' '; + buffer[writePos].BackgroundColor = BackgroundColor; + buffer[writePos].ForegroundColor = ForegroundColor; + } + } public virtual void Clear() { @@ -334,6 +345,7 @@ namespace Test c.BackgroundColor = (ConsoleColor)((int)c.BackgroundColor ^ 7); c._negative = b; } + protected static readonly Dictionary> EscapeSequenceActions = new() { {"7", c => ToggleNegative(c, true) }, @@ -376,7 +388,8 @@ namespace Test c.ForegroundColor = DefaultForeground; c.BackgroundColor = DefaultBackground; }}, - {"2J", c => c.SetCursorPosition(0, 0) } + { "0J", c => c.BlankRestOfBuffer() }, + { "2J", c => c.SetCursorPosition(0, 0) }, }; } @@ -520,6 +533,17 @@ namespace Test } } + public override void BlankRestOfBuffer() + { + var writePos = (_offset + CursorTop) * BufferWidth + CursorLeft; + for (; writePos < buffer.Length; writePos++) + { + buffer[writePos].UnicodeChar = ' '; + buffer[writePos].BackgroundColor = BackgroundColor; + buffer[writePos].ForegroundColor = ForegroundColor; + } + } + public override void Clear() { _offset = 0; diff --git a/test/OptionsTest.cs b/test/OptionsTest.cs index 67e3653..5f67de7 100644 --- a/test/OptionsTest.cs +++ b/test/OptionsTest.cs @@ -11,6 +11,8 @@ namespace Test [SkippableFact] public void ContinuationPrompt() { + Skip.If(ScreenReaderModeEnabled, "Continuation prompt is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); Test("", Keys( @@ -55,6 +57,20 @@ namespace Test )); } + [SkippableFact] + public void ContinuationPromptForScreenReader() + { + Skip.IfNot(ScreenReaderModeEnabled); + TestSetup(KeyMode.Cmd); + + Test("", Keys( + "{\n}", + CheckThat(() => AssertScreenIs(2, TokenClassification.None, '{', NextLine, '}' )), + _.Ctrl_c, + InputAcceptedNow + )); + } + [SkippableFact] public void GetKeyHandlers() { diff --git a/test/RenderTest.cs b/test/RenderTest.cs index cb4943a..cc72f36 100644 --- a/test/RenderTest.cs +++ b/test/RenderTest.cs @@ -205,6 +205,8 @@ namespace Test [SkippableFact] public void MultiLine_ScreenCheck() { + Skip.If(ScreenReaderModeEnabled, "Continuation prompt is not supported in screen reader mode."); + TestSetup(KeyMode.Cmd); var defaultContinuationPrompt = PSConsoleReadLineOptions.DefaultContinuationPrompt; @@ -305,6 +307,14 @@ namespace Test CheckThat(() => AssertScreenIs(1, Tuple.Create(_console.ForegroundColor, _console.BackgroundColor), "PSREADLINE> ", TokenClassification.Command, "dir")))); + } + + [SkippableFact] + public void InvokeTrickyPrompt() + { + Skip.If(ScreenReaderModeEnabled, "We can't test the colors written in this prompt in screen reader mode."); + + TestSetup(KeyMode.Cmd, new KeyHandler("Ctrl+z", PSConsoleReadLine.InvokePrompt)); // Tricky prompt - writes to console directly with colors, uses ^H trick to eliminate trailing space. using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace)) diff --git a/test/UnitTestReadLine.cs b/test/UnitTestReadLine.cs index 71b8a94..81cae6c 100644 --- a/test/UnitTestReadLine.cs +++ b/test/UnitTestReadLine.cs @@ -130,6 +130,7 @@ namespace Test internal virtual bool KeyboardHasGreaterThan => true; internal virtual bool KeyboardHasCtrlRBracket => true; internal virtual bool KeyboardHasCtrlAt => true; + internal virtual bool ScreenReaderModeEnabled => false; internal int ContinuationPromptLength => _continuationPromptLength; @@ -446,8 +447,13 @@ namespace Test // that shouldn't be and aren't ever set by any code in PSReadLine, so we'll // ignore those bits and just check the stuff we do set. Assert.Equal(expectedBuffer[i].UnicodeChar, consoleBuffer[i].UnicodeChar); - Assert.Equal(expectedBuffer[i].ForegroundColor, consoleBuffer[i].ForegroundColor); - Assert.Equal(expectedBuffer[i].BackgroundColor, consoleBuffer[i].BackgroundColor); + if (!ScreenReaderModeEnabled) + { + // Changing colors is not supported in screen reader mode, + // and this is the simplest way to disable checking that in all the tests. + Assert.Equal(expectedBuffer[i].ForegroundColor, consoleBuffer[i].ForegroundColor); + Assert.Equal(expectedBuffer[i].BackgroundColor, consoleBuffer[i].BackgroundColor); + } } } @@ -585,6 +591,7 @@ namespace Test ContinuationPrompt = PSConsoleReadLineOptions.DefaultContinuationPrompt, DingDuration = 1, // Make tests virtually silent when they ding DingTone = 37, // Make tests virtually silent when they ding + EnableScreenReaderMode = ScreenReaderModeEnabled, ExtraPromptLineCount = PSConsoleReadLineOptions.DefaultExtraPromptLineCount, HistoryNoDuplicates = PSConsoleReadLineOptions.DefaultHistoryNoDuplicates, HistorySaveStyle = HistorySaveStyle.SaveNothing, @@ -637,7 +644,9 @@ namespace Test PSConsoleReadLine.SetOptions(colorOptions); // Cache the continuation prompt length for use in tests - _continuationPromptLength = PSConsoleReadLine.GetOptions().ContinuationPrompt.Length; + _continuationPromptLength = ScreenReaderModeEnabled + ? 0 + : PSConsoleReadLine.GetOptions().ContinuationPrompt.Length; if (!_oneTimeInitCompleted) { @@ -674,4 +683,14 @@ namespace Test internal override bool KeyboardHasCtrlRBracket => false; internal override bool KeyboardHasCtrlAt => false; } + + public class ScreenReader : Test.ReadLine, IClassFixture + { + public ScreenReader(ConsoleFixture fixture, ITestOutputHelper output) + : base(fixture, output, "en-US", "windows") + { + } + + internal override bool ScreenReaderModeEnabled => true; + } } diff --git a/tools/helper.psm1 b/tools/helper.psm1 index a3ee18a..a79532a 100644 --- a/tools/helper.psm1 +++ b/tools/helper.psm1 @@ -210,7 +210,14 @@ function Start-TestRun function RunXunitTestsInNewProcess ([string] $Layout, [string] $OperatingSystem) { - $filter = "FullyQualifiedName~Test.{0}_{1}" -f ($Layout -replace '-','_'), $OperatingSystem + $filter = if ($Layout) { + Write-Log "Testing $Layout on $OperatingSystem...`n" + "FullyQualifiedName~Test.{0}_{1}" -f ($Layout -replace '-','_'), $OperatingSystem + } else { + ## Today, tests for screen-reader mode only run on Windows with the 'en-US' layout. + Write-Log "Testing screen reader mode...`n" + "FullyQualifiedName~Test.ScreenReader" + } $testResultFile = "xUnitTestResults.{0}.xml" -f $Layout $testResultFile = Join-Path $testResultFolder $testResultFile @@ -263,7 +270,6 @@ function Start-TestRun { if (Test-Path "KeyInfo-${layout}-windows.json") { - Write-Log "Testing $layout ..." $null = [KeyboardLayoutHelper]::SetKeyboardLayout($layout) # We have to use Start-Process so it creates a new window, because the keyboard @@ -283,6 +289,7 @@ function Start-TestRun $null = [KeyboardLayoutHelper]::SetKeyboardLayout($savedLayout) } } + RunXunitTestsInNewProcess } else {