diff --git a/.gitignore b/.gitignore index ebf89a98e..14b214373 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ x64/ *.vspscc *.vssscc .builds +project.lock.json # Visual C++ cache files ipch/ diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 000000000..064fe4f00 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/PSReadLine/BasicEditing.cs b/PSReadLine/BasicEditing.cs index 2792f763f..4eb151dbf 100644 --- a/PSReadLine/BasicEditing.cs +++ b/PSReadLine/BasicEditing.cs @@ -428,8 +428,8 @@ private bool UnresolvedCommandCouldSucceed(string commandName, Ast rootAst) { // The following are debugger commands that should be accepted if we're debugging // because the console host will interpret these commands directly. - case 's': case 'v': case 'o': case 'c': case 'q': case'k': case 'l': - case 'S': case 'V': case 'O': case 'C': case 'Q': case'K': case 'L': + case 's': case 'v': case 'o': case 'c': case 'q': case 'k': case 'l': + case 'S': case 'V': case 'O': case 'C': case 'Q': case 'K': case 'L': case '?': case 'h': case 'H': // Ideally we would check $PSDebugContext, but it is set at function // scope, and because we're in a module, we can't find that variable @@ -523,7 +523,7 @@ public static void AddLine(ConsoleKeyInfo? key = null, object arg = null) [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void InsertLineAbove(ConsoleKeyInfo? key = null, object arg = null) { - // Move the current postion to the beginning of the current line and only the current line. + // Move the current position to the beginning of the current line and only the current line. if (_singleton.LineIsMultiLine()) { int i = Math.Max(0, _singleton._current - 1); @@ -554,7 +554,7 @@ public static void InsertLineAbove(ConsoleKeyInfo? key = null, object arg = null [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void InsertLineBelow(ConsoleKeyInfo? key = null, object arg = null) { - // Move the current postion to the end of the current line and only the current line. + // Move the current position to the end of the current line and only the current line. if (_singleton.LineIsMultiLine()) { int i = _singleton._current; diff --git a/PSReadLine/Changes.txt b/PSReadLine/Changes.txt index 0dcee86ee..31aa77d0c 100644 --- a/PSReadLine/Changes.txt +++ b/PSReadLine/Changes.txt @@ -154,7 +154,7 @@ New features: * SamplePSReadlineProfile.ps1 added with examples of custom key bindings * Word movement takes DigitArgument * HistoryNoDuplicates now works a little differently - - Dupicates are saved (it was a dubious memory optimization anyway) + - Duplicates are saved (it was a dubious memory optimization anyway) - Recall will recall the most recently executed item instead of the first * When at the last word, NextWord/ForwardWord now move to the end of line instead of the last character of the word. @@ -227,7 +227,7 @@ Bugs fixed: * Ctrl+R with long search lines no longer causes big problems Behavior change: -* Word functions now use delimiters. The previous behavior is availble +* Word functions now use delimiters. The previous behavior is available via a Shell*Word function, e.g. instead of KillWord, use ShellKillWord. ### Version 1.0.0.4 diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs index 8bad00ca0..77149b57f 100644 --- a/PSReadLine/Cmdlets.cs +++ b/PSReadLine/Cmdlets.cs @@ -10,6 +10,10 @@ using System.Management.Automation.Language; using System.Reflection; using System.Linq; +#if CORECLR +using System.Runtime.InteropServices; +using Microsoft.PowerShell.Internal; +#endif namespace Microsoft.PowerShell { @@ -69,20 +73,31 @@ public enum HistorySaveStyle public class PSConsoleReadlineOptions { +#if UNIX // TODO: why different schemes for LINUX? + public const ConsoleColor DefaultCommentForegroundColor = ConsoleColor.Magenta; + public const ConsoleColor DefaultOperatorForegroundColor = ConsoleColor.Gray; + public const ConsoleColor DefaultParameterForegroundColor = ConsoleColor.Green; +#else public const ConsoleColor DefaultCommentForegroundColor = ConsoleColor.DarkGreen; + public const ConsoleColor DefaultOperatorForegroundColor = ConsoleColor.DarkGray; + public const ConsoleColor DefaultParameterForegroundColor = ConsoleColor.DarkGray; +#endif public const ConsoleColor DefaultKeywordForegroundColor = ConsoleColor.Green; public const ConsoleColor DefaultStringForegroundColor = ConsoleColor.DarkCyan; - public const ConsoleColor DefaultOperatorForegroundColor = ConsoleColor.DarkGray; public const ConsoleColor DefaultVariableForegroundColor = ConsoleColor.Green; public const ConsoleColor DefaultCommandForegroundColor = ConsoleColor.Yellow; - public const ConsoleColor DefaultParameterForegroundColor = ConsoleColor.DarkGray; public const ConsoleColor DefaultTypeForegroundColor = ConsoleColor.Gray; public const ConsoleColor DefaultNumberForegroundColor = ConsoleColor.White; public const ConsoleColor DefaultMemberForegroundColor = ConsoleColor.Gray; public const ConsoleColor DefaultEmphasisForegroundColor = ConsoleColor.Cyan; public const ConsoleColor DefaultErrorForegroundColor = ConsoleColor.Red; - public const EditMode DefaultEditMode = EditMode.Windows; + public const EditMode DefaultEditMode = +#if UNIX + EditMode.Emacs; +#else + EditMode.Windows; +#endif public const string DefaultContinuationPrompt = ">> "; @@ -105,7 +120,7 @@ public class PSConsoleReadlineOptions /// /// When displaying possible completions, either display - /// tooltips or dipslay just the completions. + /// tooltips or display just the completions. /// public const bool DefaultShowToolTips = false; @@ -151,8 +166,32 @@ public PSConsoleReadlineOptions(string hostName) WordDelimiters = DefaultWordDelimiters; HistorySearchCaseSensitive = DefaultHistorySearchCaseSensitive; HistorySaveStyle = DefaultHistorySaveStyle; - HistorySavePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) - + @"\Microsoft\Windows\PowerShell\PSReadline\" + hostName + "_history.txt"; + + string historyFileName = hostName + "_history.txt"; +#if UNIX + // PSReadline does not have access to Utils.CorePSPlatform. Must set PSReadline path separately + string historyPath = System.Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + + if (!String.IsNullOrEmpty(historyPath)) + { + historyPath = System.IO.Path.Combine(historyPath, "powershell", "PSReadLine", historyFileName); + HistorySavePath = historyPath; + } + else + { + // History is data, so it goes into .local/share/powershell folder + HistorySavePath = System.IO.Path.Combine(Environment.GetEnvironmentVariable("HOME"), + ".local", + "share", + "powershell", + "PSReadLine", + historyFileName); + } +#else + HistorySavePath = System.IO.Path.Combine(Environment.GetEnvironmentVariable("APPDATA"), + @"Microsoft\Windows\PowerShell\PSReadline\", + historyFileName); +#endif CommandValidationHandler = null; CommandsToValidateScriptBlockArguments = new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -342,7 +381,7 @@ internal void SetBackgroundColor(TokenClassification tokenKind, ConsoleColor col } } - [Cmdlet("Get", "PSReadlineOption", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=528808")] + [Cmdlet("Get", "PSReadlineOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528808")] [OutputType(typeof(PSConsoleReadlineOptions))] public class GetPSReadlineOption : PSCmdlet { @@ -353,7 +392,7 @@ protected override void EndProcessing() } } - [Cmdlet("Set", "PSReadlineOption", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=528811")] + [Cmdlet("Set", "PSReadlineOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528811")] public class SetPSReadlineOption : PSCmdlet { [Parameter(ParameterSetName = "OptionsSet")] @@ -633,7 +672,7 @@ protected IDisposable UseRequestedDispatchTables() } } - [Cmdlet("Set", "PSReadlineKeyHandler", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=528810")] + [Cmdlet("Set", "PSReadlineKeyHandler", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528810")] public class SetPSReadlineKeyHandlerCommand : ChangePSReadlineKeyHandlerCommandBase, IDynamicParameters { [Parameter(Position = 1, Mandatory = true, ParameterSetName = "ScriptBlock")] @@ -658,9 +697,9 @@ protected override void EndProcessing() if (ParameterSetName.Equals(FunctionParameterSet)) { var function = (string)_dynamicParameters.Value[FunctionParameter].Value; + MethodInfo mi = typeof (PSConsoleReadLine).GetMethod(function); var keyHandler = (Action) - Delegate.CreateDelegate(typeof (Action), - typeof (PSConsoleReadLine).GetMethod(function)); + mi.CreateDelegate(typeof (Action)); BriefDescription = function; PSConsoleReadLine.SetKeyHandler(Chord, keyHandler, BriefDescription, Description); } @@ -708,7 +747,7 @@ public object GetDynamicParameters() } } - [Cmdlet("Get", "PSReadlineKeyHandler", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=528807")] + [Cmdlet("Get", "PSReadlineKeyHandler", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528807")] [OutputType(typeof(KeyHandler))] public class GetKeyHandlerCommand : PSCmdlet { @@ -752,7 +791,7 @@ protected override void EndProcessing() } } - [Cmdlet("Remove", "PSReadlineKeyHandler", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=528809")] + [Cmdlet("Remove", "PSReadlineKeyHandler", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528809")] public class RemoveKeyHandlerCommand : ChangePSReadlineKeyHandlerCommandBase { [ExcludeFromCodeCoverage] diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 402013386..3daa6b948 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -23,6 +23,9 @@ public partial class PSConsoleReadLine private CommandCompletion _tabCompletions; private Runspace _runspace; + // String helper for directory paths + private static string DirectorySeparatorString = System.IO.Path.DirectorySeparatorChar.ToString(); + // Stub helper method so completion can be mocked [ExcludeFromCodeCoverage] CommandCompletion IPSConsoleReadLineMockableMethods.CompleteInput(string input, int cursorIndex, Hashtable options, System.Management.Automation.PowerShell powershell) @@ -273,36 +276,36 @@ private void DoReplacementForCompletion(CompletionResult completionResult, Comma private static string GetReplacementTextForDirectory(string replacementText, ref int cursorAdjustment) { - if (!replacementText.EndsWith("\\", StringComparison.Ordinal)) + if (!replacementText.EndsWith(DirectorySeparatorString , StringComparison.Ordinal)) { - if (replacementText.EndsWith("\\'", StringComparison.Ordinal) || replacementText.EndsWith("\\\"", StringComparison.Ordinal)) + if (replacementText.EndsWith(String.Format("{0}\'", DirectorySeparatorString), StringComparison.Ordinal) || + replacementText.EndsWith(String.Format("{0}\"", DirectorySeparatorString), StringComparison.Ordinal)) { cursorAdjustment = -1; } - else if (replacementText.EndsWith("'", StringComparison.Ordinal) || replacementText.EndsWith("\"", StringComparison.Ordinal)) + else if (replacementText.EndsWith("'", StringComparison.Ordinal) || + replacementText.EndsWith("\"", StringComparison.Ordinal)) { var len = replacementText.Length; - replacementText = replacementText.Substring(0, len - 1) + '\\' + replacementText[len - 1]; + replacementText = replacementText.Substring(0, len - 1) + System.IO.Path.DirectorySeparatorChar + replacementText[len - 1]; cursorAdjustment = -1; } else { - replacementText = replacementText + '\\'; + replacementText = replacementText + System.IO.Path.DirectorySeparatorChar; } } return replacementText; } - private static void InvertSelectedCompletion(CHAR_INFO[] buffer, int selectedItem, int menuColumnWidth, int menuRows) + private static void InvertSelectedCompletion(BufferChar[] buffer, int selectedItem, int menuColumnWidth, int menuRows) { var selectedX = selectedItem / menuRows; var selectedY = selectedItem - (selectedX * menuRows); var start = selectedY * _singleton._console.BufferWidth + selectedX * menuColumnWidth; for (int i = 0; i < menuColumnWidth; i++) { - int j = i + start; - buffer[j].ForegroundColor = (ConsoleColor)((int)buffer[j].ForegroundColor ^ 7); - buffer[j].BackgroundColor = (ConsoleColor)((int)buffer[j].BackgroundColor ^ 7); + buffer[i + start].Inverse = true; } } diff --git a/PSReadLine/ConsoleBufferBuilder.cs b/PSReadLine/ConsoleBufferBuilder.cs index d839e4ed9..fad206fcc 100644 --- a/PSReadLine/ConsoleBufferBuilder.cs +++ b/PSReadLine/ConsoleBufferBuilder.cs @@ -10,18 +10,18 @@ namespace Microsoft.PowerShell { internal class ConsoleBufferBuilder { - private List buffer; + private List buffer; private IConsole _console; public ConsoleBufferBuilder(int capacity, IConsole console) { - buffer = new List(capacity); + buffer = new List(capacity); _console = console; } - CHAR_INFO NewCharInfo(char c) + BufferChar NewCharInfo(char c) { - return new CHAR_INFO + return new BufferChar { UnicodeChar = c, BackgroundColor = _console.BackgroundColor, @@ -45,7 +45,7 @@ public void Append(char c, int count) } } - public CHAR_INFO[] ToArray() + public BufferChar[] ToArray() { return buffer.ToArray(); } diff --git a/PSReadLine/ConsoleKeyChordConverter.cs b/PSReadLine/ConsoleKeyChordConverter.cs index 1cd308266..85178c6fa 100644 --- a/PSReadLine/ConsoleKeyChordConverter.cs +++ b/PSReadLine/ConsoleKeyChordConverter.cs @@ -53,6 +53,7 @@ private static ConsoleKeyInfo ConvertOneSequence(string sequence) Stack tokens = null; ConsoleModifiers modifiers = 0; ConsoleKey key = 0; + char keyChar = '\u0000'; bool valid = !String.IsNullOrEmpty(sequence); @@ -78,6 +79,9 @@ private static ConsoleKeyInfo ConvertOneSequence(string sequence) // key should be first token to be popped if (key == 0) { + // the keyChar is this token + keyChar = token[0]; + // Enum.TryParse accepts arbitrary integers. We shouldn't, // but single digits need to map to the correct key, e.g. // ConsoleKey.D1 @@ -149,8 +153,6 @@ private static ConsoleKeyInfo ConvertOneSequence(string sequence) throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, PSReadLineResources.InvalidSequence, sequence)); } - char keyChar = GetCharFromConsoleKey(key, modifiers); - return new ConsoleKeyInfo(keyChar, key, shift: ((modifiers & ConsoleModifiers.Shift) != 0), alt: ((modifiers & ConsoleModifiers.Alt) != 0), @@ -161,6 +163,27 @@ private static bool TryParseCharLiteral(char literal, ref ConsoleModifiers modif { bool valid = false; +#if UNIX + bool isShift; + bool isCtrl; + key = GetKeyFromCharValue(literal, out isShift, out isCtrl); + + // Failure to get a key for the char just means that the key is not + // special (in the ConsoleKey enum), so we return a default key. + // Thus this never fails. + valid = true; + failReason = null; + + if (isShift) + { + modifiers |= ConsoleModifiers.Shift; + } + if (isCtrl) + { + modifiers |= ConsoleModifiers.Control; + } + // alt is not possible to get +#else // shift state will be in MSB short virtualKey = NativeMethods.VkKeyScan(literal); int hresult = Marshal.GetLastWin32Error(); @@ -203,10 +226,84 @@ private static bool TryParseCharLiteral(char literal, ref ConsoleModifiers modif Exception e = Marshal.GetExceptionForHR(hresult); failReason = e.Message; } +#endif return valid; } +#if UNIX + // this is borrowed from the CoreFX internal System.IO.StdInReader class + // https://github.com/dotnet/corefx/blob/5b2ae6aa485773cd5569f56f446698633c9ad945/src/System.Console/src/System/IO/StdInReader.cs#L222 + private static ConsoleKey GetKeyFromCharValue(char x, out bool isShift, out bool isCtrl) + { + isShift = false; + isCtrl = false; + + switch (x) + { + case '\b': + return ConsoleKey.Backspace; + + case '\t': + return ConsoleKey.Tab; + + case '\n': + return ConsoleKey.Enter; + + case (char)(0x1B): + return ConsoleKey.Escape; + + case '*': + return ConsoleKey.Multiply; + + case '+': + return ConsoleKey.Add; + + case '-': + return ConsoleKey.Subtract; + + case '/': + return ConsoleKey.Divide; + + case (char)(0x7F): + return ConsoleKey.Delete; + + case ' ': + return ConsoleKey.Spacebar; + + default: + // 1. Ctrl A to Ctrl Z. + if (x >= 1 && x <= 26) + { + isCtrl = true; + return ConsoleKey.A + x - 1; + } + + // 2. Numbers from 0 to 9. + if (x >= '0' && x <= '9') + { + return ConsoleKey.D0 + x - '0'; + } + + //3. A to Z + if (x >= 'A' && x <= 'Z') + { + isShift = true; + return ConsoleKey.A + (x - 'A'); + } + + // 4. a to z. + if (x >= 'a' && x <= 'z') + { + return ConsoleKey.A + (x - 'a'); + } + + break; + } + + return default(ConsoleKey); + } +#else internal static char GetCharFromConsoleKey(ConsoleKey key, ConsoleModifiers modifiers) { // default for unprintables and unhandled @@ -224,18 +321,18 @@ internal static char GetCharFromConsoleKey(ConsoleKey key, ConsoleModifiers modi // get corresponding scan code uint scanCode = NativeMethods.MapVirtualKey(virtualKey, NativeMethods.MAPVK_VK_TO_VSC); - // get corresponding character - maybe be 0, 1 or 2 in length (diacriticals) + // get corresponding character - maybe be 0, 1 or 2 in length (diacritics) var chars = new char[2]; int charCount = NativeMethods.ToUnicode( virtualKey, scanCode, state, chars, chars.Length, NativeMethods.MENU_IS_INACTIVE); - // TODO: support diacriticals (charCount == 2) + // TODO: support diacritics (charCount == 2) if (charCount == 1) { keyChar = chars[0]; } - return keyChar; } +#endif } } diff --git a/PSReadLine/ConsoleLib.cs b/PSReadLine/ConsoleLib.cs index bea090828..ed04122a3 100644 --- a/PSReadLine/ConsoleLib.cs +++ b/PSReadLine/ConsoleLib.cs @@ -309,61 +309,63 @@ internal struct CONSOLE_FONT_INFO_EX internal string FontFace; } - public struct CHAR_INFO + public struct BufferChar { - public ushort UnicodeChar; - public ushort Attributes; - - public CHAR_INFO(char c, ConsoleColor foreground, ConsoleColor background) + public char UnicodeChar; + public ConsoleColor ForegroundColor; + public ConsoleColor BackgroundColor; +#if !UNIX + public bool IsLeadByte; + public bool IsTrailByte; +#endif + public bool Inverse; + +#if !UNIX + public CHAR_INFO ToCharInfo() { - UnicodeChar = c; - Attributes = (ushort)(((int)background << 4) | (int)foreground); - } + int fg = (int) ForegroundColor; + int bg = (int) BackgroundColor; - [ExcludeFromCodeCoverage] - public ConsoleColor ForegroundColor - { - get { return (ConsoleColor)(Attributes & 0xf); } - set { Attributes = (ushort)((Attributes & 0xfff0) | ((int)value & 0xf)); } - } + if (fg < 0 || fg > 0xf || bg < 0 || bg > 0xf) + throw new InvalidOperationException(); - [ExcludeFromCodeCoverage] - public ConsoleColor BackgroundColor - { - get { return (ConsoleColor)((Attributes & 0xf0) >> 4); } - set { Attributes = (ushort)((Attributes & 0xff0f) | (((int)value & 0xf) << 4)); } - } - - [ExcludeFromCodeCoverage] - public override string ToString() - { - var sb = new StringBuilder(); - sb.Append((char)UnicodeChar); - if (ForegroundColor != Console.ForegroundColor) - sb.AppendFormat(" fg: {0}", ForegroundColor); - if (BackgroundColor != Console.BackgroundColor) - sb.AppendFormat(" bg: {0}", BackgroundColor); - return sb.ToString(); - } - - [ExcludeFromCodeCoverage] - public override bool Equals(object obj) - { - if (!(obj is CHAR_INFO)) + if (Inverse) { - return false; + // TODO: check $host.UI.SupportsVirtualTerminal and maybe set Inverse instead (it does look weird) + fg = fg ^ 7; + bg = bg ^ 7; } + ushort attrs = (ushort)(fg | (bg << 4)); + if (IsLeadByte) + attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE; + if (IsTrailByte) + attrs |= (ushort)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE; + CHAR_INFO result = new CHAR_INFO + { + UnicodeChar = UnicodeChar, + Attributes = attrs, + }; - var other = (CHAR_INFO)obj; - return this.UnicodeChar == other.UnicodeChar && this.Attributes == other.Attributes; + return result; } - [ExcludeFromCodeCoverage] - public override int GetHashCode() + public static BufferChar FromCharInfo(CHAR_INFO charInfo) { - return UnicodeChar.GetHashCode() + Attributes.GetHashCode(); + BufferChar result = new BufferChar + { + UnicodeChar = (char) charInfo.UnicodeChar, + ForegroundColor = (ConsoleColor)(charInfo.Attributes & 0xf), + BackgroundColor = (ConsoleColor)((charInfo.Attributes & 0x00f0) >> 4), + }; + return result; } +#endif + } + public struct CHAR_INFO + { + public ushort UnicodeChar; + public ushort Attributes; } internal static class ConsoleKeyInfoExtension @@ -384,8 +386,13 @@ public static string ToGestureString(this ConsoleKeyInfo key) sb.Append("Alt"); } +#if UNIX + char c = key.KeyChar; +#else + // Windows cannot use KeyChar as some chords (like Ctrl+[) show up as control characters. char c = ConsoleKeyChordConverter.GetCharFromConsoleKey(key.Key, (mods & ConsoleModifiers.Shift) != 0 ? ConsoleModifiers.Shift : 0); +#endif if (char.IsControl(c) || char.IsWhiteSpace(c)) { if (key.Modifiers.HasFlag(ConsoleModifiers.Shift)) @@ -408,6 +415,7 @@ public static string ToGestureString(this ConsoleKeyInfo key) } } +#if !UNIX internal class ConhostConsole : IConsole { [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] @@ -435,7 +443,7 @@ internal class ConhostConsole : IConsole { int err = Marshal.GetLastWin32Error(); Win32Exception innerException = new Win32Exception(err); - throw new Exception("Failed to retreive the input console handle.", innerException); + throw new Exception("Failed to retrieve the input console handle.", innerException); } return new SafeFileHandle(handle, true); @@ -458,24 +466,48 @@ internal class ConhostConsole : IConsole { int err = Marshal.GetLastWin32Error(); Win32Exception innerException = new Win32Exception(err); - throw new Exception("Failed to retreive the input console handle.", innerException); + throw new Exception("Failed to retrieve the input console handle.", innerException); } return new SafeFileHandle(handle, true); }); - public uint GetConsoleInputMode() + public object GetConsoleInputMode() { + uint mode; var handle = _inputHandle.Value.DangerousGetHandle(); - uint result; - NativeMethods.GetConsoleMode(handle, out result); - return result; + NativeMethods.GetConsoleMode(handle, out mode); + return mode; } - public void SetConsoleInputMode(uint mode) + public void SetConsoleInputMode(object modeObj) { - var handle = _inputHandle.Value.DangerousGetHandle(); - NativeMethods.SetConsoleMode(handle, mode); + if (modeObj is uint) + { + // Clear a couple flags so we can actually receive certain keys: + // ENABLE_PROCESSED_INPUT - enables Ctrl+C + // ENABLE_LINE_INPUT - enables Ctrl+S + // Also clear a couple flags so we don't mask the input that we ignore: + // ENABLE_MOUSE_INPUT - mouse events + // ENABLE_WINDOW_INPUT - window resize events + var mode = (uint)modeObj & + ~(NativeMethods.ENABLE_PROCESSED_INPUT | + NativeMethods.ENABLE_LINE_INPUT | + NativeMethods.ENABLE_WINDOW_INPUT | + NativeMethods.ENABLE_MOUSE_INPUT); + + var handle = _inputHandle.Value.DangerousGetHandle(); + NativeMethods.SetConsoleMode(handle, mode); + } + } + + public void RestoreConsoleInputMode(object modeObj) + { + if (modeObj is uint) + { + var handle = _inputHandle.Value.DangerousGetHandle(); + NativeMethods.SetConsoleMode(handle, (uint)modeObj); + } } public ConsoleKeyInfo ReadKey() @@ -568,15 +600,13 @@ public void WriteLine(string value) Console.WriteLine(value); } - public void WriteBufferLines(CHAR_INFO[] buffer, ref int top) + public void WriteBufferLines(BufferChar[] buffer, ref int top) { WriteBufferLines(buffer, ref top, true); } - public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible) + public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible) { - var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output); - int bufferWidth = Console.BufferWidth; int bufferLineCount = buffer.Length / bufferWidth; if ((top + bufferLineCount) > Console.BufferHeight) @@ -585,6 +615,8 @@ public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomL ScrollBuffer(scrollCount); top -= scrollCount; } + + var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output); var bufferSize = new COORD { X = (short) bufferWidth, @@ -599,7 +631,7 @@ public void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomL Bottom = (short) bottom, Right = (short) (bufferWidth - 1) }; - NativeMethods.WriteConsoleOutput(handle, buffer, + NativeMethods.WriteConsoleOutput(handle, ToCharInfoBuffer(buffer), bufferSize, bufferCoord, ref writeRegion); // Now make sure the bottom line is visible @@ -622,11 +654,15 @@ public void ScrollBuffer(int lines) Right = (short)Console.BufferWidth }; var destinationOrigin = new COORD {X = 0, Y = 0}; - var fillChar = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor); + var fillChar = new CHAR_INFO + { + UnicodeChar = ' ', + Attributes = (ushort)((int)Console.ForegroundColor | ((int)Console.BackgroundColor << 4)) + }; NativeMethods.ScrollConsoleScreenBuffer(handle, ref scrollRectangle, IntPtr.Zero, destinationOrigin, ref fillChar); } - public CHAR_INFO[] ReadBufferLines(int top, int count) + public BufferChar[] ReadBufferLines(int top, int count) { var result = new CHAR_INFO[BufferWidth * count]; var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output); @@ -644,7 +680,8 @@ public CHAR_INFO[] ReadBufferLines(int top, int count) }; NativeMethods.ReadConsoleOutput(handle, result, readBufferSize, readBufferCoord, ref readRegion); - return result; + + return ToBufferCharBuffer(result); } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", @@ -676,10 +713,10 @@ public int LengthInBufferCells(char c) internal static bool IsAnyDBCSCharSet(uint charSet) { const uint SHIFTJIS_CHARSET = 128; - const uint HANGEUL_CHARSET = 129; + const uint HANGUL_CHARSET = 129; const uint CHINESEBIG5_CHARSET = 136; const uint GB2312_CHARSET = 134; - return charSet == SHIFTJIS_CHARSET || charSet == HANGEUL_CHARSET || + return charSet == SHIFTJIS_CHARSET || charSet == HANGUL_CHARSET || charSet == CHINESEBIG5_CHARSET || charSet == GB2312_CHARSET; } @@ -761,7 +798,7 @@ internal int LengthInBufferCellsFE(char c) (0xffd2 <= c && c <= 0xffd7) || (0xffda <= c && c <= 0xffdc)) { - /* Halfwidth Hangule variants */ + /* Halfwidth Hangul variants */ return 1; } else if (0xffe0 <= c && c <= 0xffe6) @@ -838,7 +875,231 @@ public void EndRender() NativeMethods.ReleaseDC(_hwnd, _hDC); } } + + private CHAR_INFO[] cachedBuffer; + private CHAR_INFO[] ToCharInfoBuffer(BufferChar[] buffer) + { + if (cachedBuffer == null || cachedBuffer.Length != buffer.Length) + { + cachedBuffer = new CHAR_INFO[buffer.Length]; + } + + for (int i = 0; i < buffer.Length; i++) + { + cachedBuffer[i] = buffer[i].ToCharInfo(); + } + + return cachedBuffer; + } + + private BufferChar[] ToBufferCharBuffer(CHAR_INFO[] buffer) + { + var result = new BufferChar[buffer.Length]; + + for (int i = 0; i < buffer.Length; i++) + { + result[i] = BufferChar.FromCharInfo(buffer[i]); + } + + return result; + } + + } + +#else + + internal class TTYConsole : IConsole + { + public object GetConsoleInputMode() + { + return Console.TreatControlCAsInput; + } + + public void SetConsoleInputMode(object modeObj) + { + Console.TreatControlCAsInput = true; + } + + public void RestoreConsoleInputMode(object modeObj) + { + if (modeObj is bool) + { + Console.TreatControlCAsInput = (bool)modeObj; + } + } + + public ConsoleKeyInfo ReadKey() + { + return Console.ReadKey(true); + } + + public bool KeyAvailable + { + get { return Console.KeyAvailable; } + } + + public int CursorLeft + { + get { return Console.CursorLeft; } + set { Console.CursorLeft = value; } + } + + public int CursorTop + { + get { return Console.CursorTop; } + set { Console.CursorTop = value; } + } + + public int CursorSize + { + get { return Console.CursorSize; } + set { Console.CursorSize = value; } + } + + public int BufferWidth + { + get { return Console.BufferWidth; } + set { Console.BufferWidth = value; } + } + + public int BufferHeight + { + get { return Console.BufferHeight; } + set { Console.BufferHeight = value; } + } + + public int WindowWidth + { + get { return Console.WindowWidth; } + set { Console.WindowWidth = value; } + } + + public int WindowHeight + { + get { return Console.WindowHeight; } + set { Console.WindowHeight = value; } + } + + public int WindowTop + { + get { return Console.WindowTop; } + set { Console.WindowTop = value; } + } + + public ConsoleColor BackgroundColor + { + get { return Console.BackgroundColor; } + set { Console.BackgroundColor = value; } + } + + public ConsoleColor ForegroundColor + { + get { return Console.ForegroundColor; } + set { Console.ForegroundColor = value; } + } + + public void SetWindowPosition(int left, int top) + { + Console.SetWindowPosition(left, top); + } + + public void SetCursorPosition(int left, int top) + { + Console.SetCursorPosition(left, top); + } + + public void Write(string value) + { + Console.Write(value); + } + + public void WriteLine(string value) + { + Console.WriteLine(value); + } + + public void WriteBufferLines(BufferChar[] buffer, ref int top) + { + WriteBufferLines(buffer, ref top, true); + } + + public void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible) + { + int bufferWidth = Console.BufferWidth; + int bufferLineCount = buffer.Length / bufferWidth; + if ((top + bufferLineCount) > Console.BufferHeight) + { + var scrollCount = (top + bufferLineCount) - Console.BufferHeight; + ScrollBuffer(scrollCount); + top -= scrollCount; + } + + ConsoleColor foregroundColor = Console.ForegroundColor; + ConsoleColor backgroundColor = Console.BackgroundColor; + + Console.SetCursorPosition(0, (top>=0) ? top : 0); + + for (int i = 0; i < buffer.Length; ++i) + { + // TODO: use escape sequences for better perf + Console.ForegroundColor = buffer[i].ForegroundColor; + Console.BackgroundColor = buffer[i].BackgroundColor; + + Console.Write(buffer[i].UnicodeChar); + } + + Console.BackgroundColor = backgroundColor; + Console.ForegroundColor = foregroundColor; + } + + public void ScrollBuffer(int lines) + { + for (int i=0; i { public bool Equals(ConsoleKeyInfo x, ConsoleKeyInfo y) { - return x.Key == y.Key && x.KeyChar == y.KeyChar && x.Modifiers == y.Modifiers; + // We *must not* compare the KeyChar field as its value is platform-dependent. + // We compare exactly the ConsoleKey enum field (which is platform-agnostic) + // and the modifiers. + + return x.Key == y.Key && x.Modifiers == y.Modifiers; } public int GetHashCode(ConsoleKeyInfo obj) { - return obj.GetHashCode(); + // Because a comparison of two ConsoleKeyInfo objects is a comparison of the + // combination of the ConsoleKey and Modifiers, we must combine their hashes. + // Note that if the ConsoleKey is default, we must fall back to the KeyChar, + // otherwise every non-special key will compare as the same. + int h1 = obj.Key == default(ConsoleKey) + ? obj.KeyChar.GetHashCode() + : obj.Key.GetHashCode(); + int h2 = obj.Modifiers.GetHashCode(); + // This is based on Tuple.GetHashCode + return unchecked(((h1 << 5) + h1) ^ h2); } } @@ -105,6 +118,25 @@ static KeyHandler MakeKeyHandler(Action action, string private Dictionary _dispatchTable; private Dictionary> _chordDispatchTable; + /// + /// Helper to set bindings based on EditMode + /// + void SetDefaultBindings(EditMode editMode) + { + switch (editMode) + { + case EditMode.Emacs: + SetDefaultEmacsBindings(); + break; + case EditMode.Vi: + SetDefaultViBindings(); + break; + case EditMode.Windows: + SetDefaultWindowsBindings(); + break; + } + } + void SetDefaultWindowsBindings() { _dispatchTable = new Dictionary(new ConsoleKeyInfoComparer()) @@ -133,9 +165,11 @@ void SetDefaultWindowsBindings() { Keys.CtrlSpace, MakeKeyHandler(MenuComplete, "MenuComplete") }, { Keys.Tab, MakeKeyHandler(TabCompleteNext, "TabCompleteNext") }, { Keys.ShiftTab, MakeKeyHandler(TabCompletePrevious, "TabCompletePrevious") }, +#if !UNIX { Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") }, +#endif { Keys.CtrlA, MakeKeyHandler(SelectAll, "SelectAll") }, { Keys.CtrlC, MakeKeyHandler(CopyOrCancelLine, "CopyOrCancelLine") }, { Keys.CtrlShiftC, MakeKeyHandler(Copy, "Copy") }, @@ -170,10 +204,12 @@ void SetDefaultWindowsBindings() { Keys.ShiftF3, MakeKeyHandler(CharacterSearchBackward, "CharacterSearchBackward") }, { Keys.F8, MakeKeyHandler(HistorySearchBackward, "HistorySearchBackward") }, { Keys.ShiftF8, MakeKeyHandler(HistorySearchForward, "HistorySearchForward") }, +#if !UNIX { Keys.PageUp, MakeKeyHandler(ScrollDisplayUp, "ScrollDisplayUp") }, { Keys.PageDown, MakeKeyHandler(ScrollDisplayDown, "ScrollDisplayDown") }, { Keys.CtrlPageUp, MakeKeyHandler(ScrollDisplayUpLine, "ScrollDisplayUpLine") }, { Keys.CtrlPageDown, MakeKeyHandler(ScrollDisplayDownLine, "ScrollDisplayDownLine") }, +#endif }; _chordDispatchTable = new Dictionary>(); @@ -252,6 +288,7 @@ void SetDefaultEmacsBindings() { Keys.AltPeriod, MakeKeyHandler(YankLastArg, "YankLastArg") }, { Keys.AltUnderbar, MakeKeyHandler(YankLastArg, "YankLastArg") }, { Keys.AltCtrlY, MakeKeyHandler(YankNthArg, "YankNthArg") }, +#if !UNIX { Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") }, @@ -261,6 +298,7 @@ void SetDefaultEmacsBindings() { Keys.CtrlPageDown, MakeKeyHandler(ScrollDisplayDownLine,"ScrollDisplayDownLine") }, { Keys.CtrlHome, MakeKeyHandler(ScrollDisplayTop, "ScrollDisplayTop") }, { Keys.CtrlEnd, MakeKeyHandler(ScrollDisplayToCursor,"ScrollDisplayToCursor") }, +#endif }; _chordDispatchTable = new Dictionary>(); diff --git a/PSReadLine/KeyBindings.vi.cs b/PSReadLine/KeyBindings.vi.cs index 0c6529a5b..eb6f76da6 100644 --- a/PSReadLine/KeyBindings.vi.cs +++ b/PSReadLine/KeyBindings.vi.cs @@ -70,9 +70,11 @@ private void SetDefaultViBindings() { Keys.Tab, MakeKeyHandler(ViTabCompleteNext, "ViTabCompleteNext") }, { Keys.ShiftTab, MakeKeyHandler(ViTabCompletePrevious, "ViTabCompletePrevious") }, { Keys.CtrlV, MakeKeyHandler(Paste, "Paste") }, +#if !UNIX { Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") }, +#endif { Keys.CtrlC, MakeKeyHandler(CancelLine, "CancelLine") }, { Keys.CtrlL, MakeKeyHandler(ClearScreen, "ClearScreen") }, { Keys.CtrlY, MakeKeyHandler(Redo, "Redo") }, @@ -107,9 +109,11 @@ private void SetDefaultViBindings() { Keys.Tab, MakeKeyHandler(TabCompleteNext, "TabCompleteNext") }, { Keys.ShiftTab, MakeKeyHandler(TabCompletePrevious, "TabCompletePrevious") }, { Keys.CtrlV, MakeKeyHandler(Paste, "Paste") }, +#if !UNIX { Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") }, +#endif { Keys.CtrlC, MakeKeyHandler(CancelLine, "CancelLine") }, { Keys.CtrlL, MakeKeyHandler(ClearScreen, "ClearScreen") }, { Keys.CtrlT, MakeKeyHandler(SwapCharacters, "SwapCharacters") }, diff --git a/PSReadLine/Keys.cs b/PSReadLine/Keys.cs index 57f2248f3..25b594441 100644 --- a/PSReadLine/Keys.cs +++ b/PSReadLine/Keys.cs @@ -290,8 +290,10 @@ internal static class Keys public static ConsoleKeyInfo ShiftF8 = new ConsoleKeyInfo((char)0, ConsoleKey.F8, true, false, false); // Keys to ignore - public static ConsoleKeyInfo VolumeUp = new ConsoleKeyInfo((char)0, ConsoleKey.VolumeUp, false, false, false); - public static ConsoleKeyInfo VolumeDown = new ConsoleKeyInfo((char)0, ConsoleKey.VolumeDown, false, false, false); - public static ConsoleKeyInfo VolumeMute = new ConsoleKeyInfo((char)0, ConsoleKey.VolumeMute, false, false, false); +#if !UNIX + public static ConsoleKeyInfo VolumeUp = new ConsoleKeyInfo((char)0, /*ConsoleKey.VolumeUp*/(ConsoleKey)175, false, false, false); + public static ConsoleKeyInfo VolumeDown = new ConsoleKeyInfo((char)0, /*ConsoleKey.VolumeDown*/(ConsoleKey)174, false, false, false); + public static ConsoleKeyInfo VolumeMute = new ConsoleKeyInfo((char)0, /*ConsoleKey.VolumeMute*/(ConsoleKey)173, false, false, false); +#endif } } diff --git a/PSReadLine/KillYank.cs b/PSReadLine/KillYank.cs index 86de83a7f..be46011a7 100644 --- a/PSReadLine/KillYank.cs +++ b/PSReadLine/KillYank.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Language; +#if !CORECLR // TODO: clipboard using System.Windows.Forms; +#endif namespace Microsoft.PowerShell { @@ -30,7 +32,7 @@ class YankLastArgState private int _visualSelectionCommandCount; /// - /// Mark the current loction of the cursor for use in a subsequent editing command. + /// Mark the current location of the cursor for use in a subsequent editing command. /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void SetMark(ConsoleKeyInfo? key = null, object arg = null) @@ -476,6 +478,7 @@ public static void SelectBackwardsLine(ConsoleKeyInfo? key = null, object arg = [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void Paste(ConsoleKeyInfo? key = null, object arg = null) { +#if !CORECLR // TODO: clipboard string textToPaste = null; ExecuteOnSTAThread(() => { if (Clipboard.ContainsText()) @@ -498,6 +501,7 @@ public static void Paste(ConsoleKeyInfo? key = null, object arg = null) Insert(textToPaste); } } +#endif } /// @@ -506,6 +510,7 @@ public static void Paste(ConsoleKeyInfo? key = null, object arg = null) [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void Copy(ConsoleKeyInfo? key = null, object arg = null) { +#if !CORECLR // TODO: clipboard string textToSet; if (_singleton._visualSelectionCommandCount > 0) { @@ -521,6 +526,7 @@ public static void Copy(ConsoleKeyInfo? key = null, object arg = null) { ExecuteOnSTAThread(() => Clipboard.SetText(textToSet)); } +#endif } /// @@ -549,7 +555,9 @@ public static void Cut(ConsoleKeyInfo? key = null, object arg = null) { int start, length; _singleton.GetRegion(out start, out length); +#if !CORECLR // TODO: clipboard ExecuteOnSTAThread(() => Clipboard.SetText(_singleton._buffer.ToString(start, length))); +#endif Delete(start, length); } } diff --git a/PSReadLine/Movement.cs b/PSReadLine/Movement.cs index f7d7b74ad..a413ef63d 100644 --- a/PSReadLine/Movement.cs +++ b/PSReadLine/Movement.cs @@ -434,6 +434,11 @@ public static void GotoBrace(ConsoleKeyInfo? key = null, object arg = null) public static void ClearScreen(ConsoleKeyInfo? key = null, object arg = null) { var console = _singleton._console; +#if UNIX // TODO: this is not correct, it should only scroll + console.Clear(); + _singleton._initialY = 0; + _singleton.Render(); +#else if (_singleton._initialY + console.WindowHeight > console.BufferHeight) { var scrollCount = _singleton._initialY - console.WindowTop; @@ -445,6 +450,7 @@ public static void ClearScreen(ConsoleKeyInfo? key = null, object arg = null) { console.SetWindowPosition(0, _singleton._initialY); } +#endif } // Try to convert the arg to a char, return 0 for failure diff --git a/PSReadLine/Options.cs b/PSReadLine/Options.cs index f200d735e..b927282e2 100644 --- a/PSReadLine/Options.cs +++ b/PSReadLine/Options.cs @@ -96,18 +96,7 @@ private void SetOptionsInternal(SetPSReadlineOption options) // Switching/resetting modes - clear out chord dispatch table _chordDispatchTable.Clear(); - switch (options._editMode) - { - case EditMode.Emacs: - SetDefaultEmacsBindings(); - break; - case EditMode.Vi: - SetDefaultViBindings(); - break; - case EditMode.Windows: - SetDefaultWindowsBindings(); - break; - } + SetDefaultBindings(Options.EditMode); } if (options._showToolTips.HasValue) { @@ -297,7 +286,7 @@ public static void RemoveKeyHandler(string[] key) public static IEnumerable GetKeyHandlers(bool includeBound = true, bool includeUnbound = false) { var boundFunctions = new HashSet(StringComparer.OrdinalIgnoreCase); - + foreach (var entry in _singleton._dispatchTable) { if (entry.Value.BriefDescription == "Ignore" diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj index 63431248c..a9845be9f 100644 --- a/PSReadLine/PSReadLine.csproj +++ b/PSReadLine/PSReadLine.csproj @@ -34,6 +34,12 @@ true false + + bin\UNIX\ + + + bin\CORECLR\ + @@ -140,4 +146,4 @@ --> - + \ No newline at end of file diff --git a/PSReadLine/PSReadLine.psd1 b/PSReadLine/PSReadLine.psd1 index 65daec419..6af2eea6f 100644 --- a/PSReadLine/PSReadLine.psd1 +++ b/PSReadLine/PSReadLine.psd1 @@ -13,5 +13,5 @@ CLRVersion = '4.0' FunctionsToExport = 'PSConsoleHostReadline' CmdletsToExport = 'Get-PSReadlineKeyHandler','Set-PSReadlineKeyHandler','Remove-PSReadlineKeyHandler', 'Get-PSReadlineOption','Set-PSReadlineOption' -HelpInfoURI = 'http://go.microsoft.com/fwlink/?LinkId=528806' +HelpInfoURI = 'https://go.microsoft.com/fwlink/?LinkId=528806' } diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs index 1470cd00f..b6ed66993 100644 --- a/PSReadLine/PSReadLineResources.Designer.cs +++ b/PSReadLine/PSReadLineResources.Designer.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell { using System; - + using System.Reflection; /// /// A strongly-typed resource class, for looking up localized strings, etc. @@ -39,7 +39,7 @@ internal PSReadLineResources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerShell.PSReadLineResources", typeof(PSReadLineResources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerShell.PSReadLine.PSReadLineResources", typeof(PSReadLineResources).GetTypeInfo().Assembly); resourceMan = temp; } return resourceMan; @@ -106,7 +106,7 @@ internal static string BackwardCharDescription { } /// - /// Looks up a localized string similar to Delete the charcter before the cursor. + /// Looks up a localized string similar to Delete the character before the cursor. /// internal static string BackwardDeleteCharDescription { get { @@ -322,7 +322,7 @@ internal static string DeleteBraceDescription { } /// - /// Looks up a localized string similar to Delete the character under the cusor. + /// Looks up a localized string similar to Delete the character under the cursor. /// internal static string DeleteCharDescription { get { @@ -331,7 +331,7 @@ internal static string DeleteCharDescription { } /// - /// Looks up a localized string similar to Delete the character under the cusor, or if the line is empty, exit the process.. + /// Looks up a localized string similar to Delete the character under the cursor, or if the line is empty, exit the process.. /// internal static string DeleteCharOrExitDescription { get { @@ -502,7 +502,7 @@ internal static string GotoBraceDescription { } /// - /// Looks up a localized string similar to Moves the cursor to the perscribed column.. + /// Looks up a localized string similar to Moves the cursor to the prescribed column.. /// internal static string GotoColumnDescription { get { @@ -990,7 +990,7 @@ internal static string ScrollDisplayUpLineDescription { } /// - /// Looks up a localized string similar to Searches backward for the perscribed character.. + /// Looks up a localized string similar to Searches backward for the prescribed character.. /// internal static string SearchBackwardCharDescription { get { @@ -999,7 +999,7 @@ internal static string SearchBackwardCharDescription { } /// - /// Looks up a localized string similar to Move to the previous occurance of the specified character.. + /// Looks up a localized string similar to Move to the previous occurrence of the specified character.. /// internal static string SearchCharBackwardDescription { get { @@ -1008,7 +1008,7 @@ internal static string SearchCharBackwardDescription { } /// - /// Looks up a localized string similar to Move to the previous occurance of the specified character and then forward one character.. + /// Looks up a localized string similar to Move to the previous occurrence of the specified character and then forward one character.. /// internal static string SearchCharBackwardWithBackoffDescription { get { @@ -1017,7 +1017,7 @@ internal static string SearchCharBackwardWithBackoffDescription { } /// - /// Looks up a localized string similar to Move to the next occurance of the specified character.. + /// Looks up a localized string similar to Move to the next occurrence of the specified character.. /// internal static string SearchCharDescription { get { @@ -1026,7 +1026,7 @@ internal static string SearchCharDescription { } /// - /// Looks up a localized string similar to Move to he next occurance of the specified character and then back one character.. + /// Looks up a localized string similar to Move to he next occurrence of the specified character and then back one character.. /// internal static string SearchCharWithBackoffDescription { get { @@ -1035,7 +1035,7 @@ internal static string SearchCharWithBackoffDescription { } /// - /// Looks up a localized string similar to Searches for the perscribed character in the perscribed direction.. + /// Looks up a localized string similar to Searches for the prescribed character in the prescribed direction.. /// internal static string SearchDescription { get { @@ -1488,7 +1488,7 @@ internal static string ViGotoBraceDescription { /// /// Looks up a localized string similar to Moves the cursor to the beginning of the line and switches to insert mode.. /// - internal static string ViInsertAtBeginingDescription { + internal static string ViInsertAtBeginningDescription { get { return ResourceManager.GetString("ViInsertAtBeginingDescription", resourceCulture); } @@ -1585,7 +1585,7 @@ internal static string ViReplaceEndOfGlobDescription { } /// - /// Looks up a localized string similar to Delete to the end of the word, as delimited by white space and common delimters, and enter insert mode.. + /// Looks up a localized string similar to Delete to the end of the word, as delimited by white space and common delimiters, and enter insert mode.. /// internal static string ViReplaceEndOfWordDescription { get { @@ -1639,7 +1639,7 @@ internal static string ViReplaceWordDescription { } /// - /// Looks up a localized string similar to Starts a new seach backward in the history.. + /// Looks up a localized string similar to Starts a new search backward in the history.. /// internal static string ViSearchHistoryBackwardDescription { get { @@ -1756,7 +1756,7 @@ internal static string ViYankPreviousGlobDescription { } /// - /// Looks up a localized string similar to Place all characters from before the cursor to the beginning of the previous word, as delimted by white space and common delimiters, into the local clipboard.. + /// Looks up a localized string similar to Place all characters from before the cursor to the beginning of the previous word, as delimited by white space and common delimiters, into the local clipboard.. /// internal static string ViYankPreviousWordDescription { get { diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx index 6348877b3..ca33545cc 100644 --- a/PSReadLine/PSReadLineResources.resx +++ b/PSReadLine/PSReadLineResources.resx @@ -127,7 +127,7 @@ Move the cursor back one character - Delete the charcter before the cursor + Delete the character before the cursor Delete text from the cursor to the start of the line @@ -154,7 +154,7 @@ Complete the input if there is a single completion, otherwise complete the input with common prefix for all completions. Show possible completions if pressed a second time. - Delete the character under the cusor + Delete the character under the cursor Move the cursor to the beginning of the current or previous token or start of the line @@ -412,7 +412,7 @@ Exception: If there are other parse errors, unresolved commands, or incorrect parameters, show the error and continue editing. - Delete the character under the cusor, or if the line is empty, exit the process. + Delete the character under the cursor, or if the line is empty, exit the process. Unable to translate '{0}' to virtual key code: {1}. @@ -451,10 +451,10 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s Repeats the last search. - Searches backward for the perscribed character. + Searches backward for the prescribed character. - Searches for the perscribed character in the perscribed direction. + Searches for the prescribed character in the prescribed direction. Switches to insert mode after positioning the cursor past the end of the line. @@ -496,7 +496,7 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s Moves the cursor forward to the end of the next word. - Moves the cursor to the perscribed column. + Moves the cursor to the prescribed column. Switches to insert mode. @@ -547,7 +547,7 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s Delete the current character and insert the next character. - Starts a new seach backward in the history. + Starts a new search backward in the history. Transposes the current character with the next character in the line. @@ -565,16 +565,16 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s Repeat the last recorded character search. - Move to the previous occurance of the specified character. + Move to the previous occurrence of the specified character. - Move to the previous occurance of the specified character and then forward one character. + Move to the previous occurrence of the specified character and then forward one character. - Move to the next occurance of the specified character. + Move to the next occurrence of the specified character. - Move to he next occurance of the specified character and then back one character. + Move to he next occurrence of the specified character and then back one character. Replace the previous word. @@ -622,7 +622,7 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s Delete to the beginning of the next word, as delimited by white space, and enter insert mode. - Delete to the end of the word, as delimited by white space and common delimters, and enter insert mode. + Delete to the end of the word, as delimited by white space and common delimiters, and enter insert mode. Delete to the end of the word, as delimited by white space, and enter insert mode. @@ -634,7 +634,7 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s Place all characters at and after the cursor into the local clipboard. - Place all characters from before the cursor to the beginning of the previous word, as delimted by white space and common delimiters, into the local clipboard. + Place all characters from before the cursor to the beginning of the previous word, as delimited by white space and common delimiters, into the local clipboard. Place all characters from before the cursor to the beginning of the previous word, as delimited by white space, into the local clipboard. diff --git a/PSReadLine/PublicAPI.cs b/PSReadLine/PublicAPI.cs index f245ec49a..3f0163a78 100644 --- a/PSReadLine/PublicAPI.cs +++ b/PSReadLine/PublicAPI.cs @@ -28,8 +28,9 @@ public interface IPSConsoleReadLineMockableMethods [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public interface IConsole { - uint GetConsoleInputMode(); - void SetConsoleInputMode(uint mode); + object GetConsoleInputMode(); + void SetConsoleInputMode(object mode); + void RestoreConsoleInputMode(object mode); ConsoleKeyInfo ReadKey(); bool KeyAvailable { get; } int CursorLeft { get; set; } @@ -46,14 +47,17 @@ public interface IConsole void SetCursorPosition(int left, int top); void WriteLine(string s); void Write(string s); - void WriteBufferLines(CHAR_INFO[] buffer, ref int top); - void WriteBufferLines(CHAR_INFO[] buffer, ref int top, bool ensureBottomLineVisible); + void WriteBufferLines(BufferChar[] buffer, ref int top); + void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible); void ScrollBuffer(int lines); - CHAR_INFO[] ReadBufferLines(int top, int count); + BufferChar[] ReadBufferLines(int top, int count); void StartRender(); int LengthInBufferCells(char c); void EndRender(); +#if UNIX + void Clear(); +#endif } #pragma warning restore 1591 @@ -121,7 +125,7 @@ public static void Delete(int start, int length) /// The length to replace /// The replacement text /// The action that initiated the replace (used for undo) - /// The argument to the action that initiaed the replace (used for undo) + /// The argument to the action that initiated the replace (used for undo) public static void Replace(int start, int length, string replacement, Action instigator = null, object instigatorArg = null) { if (start < 0 || start > _singleton._buffer.Length) @@ -220,7 +224,7 @@ public static void SetCursorPosition(int cursor) /// /// A helper method when your function expects an optional int argument (e.g. from DigitArgument) - /// If there is not argument (it's null), returns true and sets numericArg to derfaultNumericArg. + /// If there is not argument (it's null), returns true and sets numericArg to defaultNumericArg. /// Dings and returns false if the argument is not an int (no conversion is attempted) /// Otherwise returns true, and numericArg has the result. /// diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index d8e0c2657..f3805676a 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -34,14 +34,16 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods private IConsole _console; private EngineIntrinsics _engineIntrinsics; +#if !UNIX private static GCHandle _breakHandlerGcHandle; +#endif private Thread _readKeyThread; private AutoResetEvent _readKeyWaitHandle; private AutoResetEvent _keyReadWaitHandle; private ManualResetEvent _closingWaitHandle; private WaitHandle[] _threadProcWaitHandles; private WaitHandle[] _requestKeyWaitHandles; - private uint _prePSReadlineConsoleMode; + private object _savedConsoleInputMode; private readonly StringBuilder _buffer; private readonly StringBuilder _statusBuffer; @@ -156,7 +158,7 @@ private static ConsoleKeyInfo ReadKey() // There is an OnIdle event. We're idle because we timed out. Normally // PowerShell generates this event, but PowerShell assumes the engine is not // idle because it called PSConsoleHostReadline which isn't returning. - // So we generate the event intstead. + // So we generate the event instead. _singleton._engineIntrinsics.Events.GenerateEvent("PowerShell.OnIdle", null, null, null); runPipelineForEventProcessing = true; break; @@ -252,24 +254,21 @@ private bool BreakHandler(ConsoleBreakSignal signal) public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics) { var console = _singleton._console; - _singleton._prePSReadlineConsoleMode = console.GetConsoleInputMode(); + + // If either stdin or stdout is redirected, PSReadline doesn't really work, so throw + // and let PowerShell call Console.ReadLine or do whatever else it decides to do. + if (Console.IsInputRedirected || Console.IsOutputRedirected) + { + throw new NotSupportedException(); + } + + _singleton._savedConsoleInputMode = _singleton._console.GetConsoleInputMode(); bool firstTime = true; while (true) { try { - // Clear a couple flags so we can actually receive certain keys: - // ENABLE_PROCESSED_INPUT - enables Ctrl+C - // ENABLE_LINE_INPUT - enables Ctrl+S - // Also clear a couple flags so we don't mask the input that we ignore: - // ENABLE_MOUSE_INPUT - mouse events - // ENABLE_WINDOW_INPUT - window resize events - var mode = _singleton._prePSReadlineConsoleMode & - ~(NativeMethods.ENABLE_PROCESSED_INPUT | - NativeMethods.ENABLE_LINE_INPUT | - NativeMethods.ENABLE_WINDOW_INPUT | - NativeMethods.ENABLE_MOUSE_INPUT); - console.SetConsoleInputMode(mode); + _singleton._console.SetConsoleInputMode(_singleton._savedConsoleInputMode); if (firstTime) { @@ -340,7 +339,7 @@ public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsi } finally { - console.SetConsoleInputMode(_singleton._prePSReadlineConsoleMode); + _singleton._console.RestoreConsoleInputMode(_singleton._savedConsoleInputMode); } } } @@ -428,15 +427,15 @@ private string InputLoop() T CalloutUsingDefaultConsoleMode(Func func) { - uint psReadlineConsoleMode = _console.GetConsoleInputMode(); + var currentMode = _console.GetConsoleInputMode(); try { - _console.SetConsoleInputMode(_prePSReadlineConsoleMode); + _console.RestoreConsoleInputMode(_savedConsoleInputMode); return func(); } finally { - _console.SetConsoleInputMode(psReadlineConsoleMode); + _console.RestoreConsoleInputMode(currentMode); } } @@ -448,6 +447,7 @@ void CalloutUsingDefaultConsoleMode(Action action) void ProcessOneKey(ConsoleKeyInfo key, Dictionary dispatchTable, bool ignoreIfNoAction, object arg) { KeyHandler handler; + if (!dispatchTable.TryGetValue(key, out handler)) { // If we see a control character where Ctrl wasn't used but shift was, treat that like @@ -458,6 +458,7 @@ void ProcessOneKey(ConsoleKeyInfo key, Dictionary di dispatchTable.TryGetValue(key, out handler); } } + if (handler != null) { if (handler.ScriptBlock != null) @@ -478,9 +479,11 @@ void ProcessOneKey(ConsoleKeyInfo key, Dictionary di private PSConsoleReadLine() { _mockableMethods = this; +#if UNIX + _console = new TTYConsole(); +#else _console = new ConhostConsole(); - - SetDefaultWindowsBindings(); +#endif _buffer = new StringBuilder(8 * 1024); _statusBuffer = new StringBuilder(256); @@ -511,6 +514,7 @@ private PSConsoleReadLine() hostName = "PSReadline"; } _options = new PSConsoleReadlineOptions(hostName); + SetDefaultBindings(_options.EditMode); } private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) @@ -539,7 +543,12 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) _initialY = _console.CursorTop - Options.ExtraPromptLineCount; _initialBackgroundColor = _console.BackgroundColor; _initialForegroundColor = _console.ForegroundColor; - _space = new CHAR_INFO(' ', _initialForegroundColor, _initialBackgroundColor); + _space = new BufferChar + { + UnicodeChar = ' ', + BackgroundColor = _initialBackgroundColor, + ForegroundColor = _initialForegroundColor + }; _bufferWidth = _console.BufferWidth; _killCommandCount = 0; _yankCommandCount = 0; @@ -548,7 +557,19 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) _visualSelectionCommandCount = 0; _statusIsErrorMessage = false; + +#if UNIX // TODO: not necessary if ReadBufferLines worked, or if rendering worked on spans instead of complete lines + string newPrompt = GetPrompt(); + var bufferLineCount = (newPrompt.Length) / (_console.BufferWidth) + 1; + _consoleBuffer = ReadBufferLines(_initialY, bufferLineCount); + + for (int i=0; i(Options.MaximumHistoryCount); _currentHistoryIndex = 0; @@ -629,14 +654,17 @@ private void DelayedOneTimeInitialize() _killIndex = -1; // So first add indexes 0. _killRing = new List(Options.MaximumKillRingCount); +#if !UNIX _breakHandlerGcHandle = GCHandle.Alloc(new BreakHandler(_singleton.BreakHandler)); NativeMethods.SetConsoleCtrlHandler((BreakHandler)_breakHandlerGcHandle.Target, true); +#endif _singleton._readKeyWaitHandle = new AutoResetEvent(false); _singleton._keyReadWaitHandle = new AutoResetEvent(false); _singleton._closingWaitHandle = new ManualResetEvent(false); _singleton._requestKeyWaitHandles = new WaitHandle[] {_singleton._keyReadWaitHandle, _singleton._closingWaitHandle}; _singleton._threadProcWaitHandles = new WaitHandle[] {_singleton._readKeyWaitHandle, _singleton._closingWaitHandle}; +#if !CORECLR // This is for a "being hosted in an alternate appdomain scenario" (the // DomainUnload event is not raised for the default appdomain). It allows us // to exit cleanly when the appdomain is unloaded but the process is not going @@ -649,6 +677,7 @@ private void DelayedOneTimeInitialize() _singleton._readKeyThread.Join(); // may need to wait for history to be written }; } +#endif _singleton._readKeyThread = new Thread(_singleton.ReadKeyThreadProc) {IsBackground = true}; _singleton._readKeyThread.Start(); @@ -677,6 +706,9 @@ private static void Ignore(ConsoleKeyInfo? key = null, object arg = null) private static void ExecuteOnSTAThread(Action action) { +#if CORECLR + action(); +#else if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { action(); @@ -704,6 +736,7 @@ private static void ExecuteOnSTAThread(Action action) { throw exception; } +#endif } #region Miscellaneous bindable functions @@ -728,13 +761,11 @@ public static void DigitArgument(ConsoleKeyInfo? key = null, object arg = null) return; } - #region VI special case if (_singleton._options.EditMode == EditMode.Vi && key.Value.KeyChar == '0') { BeginningOfLine(); return; } - #endregion VI special case bool sawDigit = false; _singleton._statusLinePrompt = "digit-argument: "; @@ -834,23 +865,71 @@ public static void InvokePrompt(ConsoleKeyInfo? key = null, object arg = null) _singleton._console.CursorLeft = 0; _singleton._console.CursorTop = _singleton._initialY - _singleton.Options.ExtraPromptLineCount; + string newPrompt = GetPrompt(); + _singleton._console.Write(newPrompt); + + _singleton._initialX = _singleton._console.CursorLeft; + _singleton._consoleBuffer = ReadBufferLines(_singleton._initialY, 1 + _singleton.Options.ExtraPromptLineCount); +#if UNIX // TODO: ReadBufferLines only needed for extra line, but doesn't work on Linux + for (int i=0; i + /// Gets the current prompt as possibly defined by the user through the + /// prompt function, and returns a default prompt if no other is + /// available. Also handles remote prompts. + /// + public static string GetPrompt() + { + string newPrompt; var runspaceIsRemote = _singleton._mockableMethods.RunspaceIsRemote(_singleton._runspace); - System.Management.Automation.PowerShell ps; - if (!runspaceIsRemote) + + if ((_singleton._runspace.Debugger != null) && _singleton._runspace.Debugger.InBreakpoint) { - ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + // Run prompt command in debugger API to ensure it is run correctly on the runspace. + // This handles remote runspace debugging and nested debugger scenarios. + PSDataCollection results = new PSDataCollection(); + var command = new PSCommand(); + command.AddCommand(PromptCommand); + _singleton._runspace.Debugger.ProcessCommand( + command, + results); + + newPrompt = (results.Count == 1) ? (results[0].BaseObject as string) : DefaultPrompt; } else { - ps = System.Management.Automation.PowerShell.Create(); - ps.Runspace = _singleton._runspace; + System.Management.Automation.PowerShell ps; + if (!runspaceIsRemote) + { + ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + } + else + { + ps = System.Management.Automation.PowerShell.Create(); + ps.Runspace = _singleton._runspace; + } + using (ps) + { + ps.AddCommand(PromptCommand); + var result = ps.Invoke(); + newPrompt = result.Count == 1 ? result[0] : DefaultPrompt; + } } - string newPrompt; - using (ps) + + if (string.IsNullOrEmpty(newPrompt)) { - ps.AddCommand("prompt"); - var result = ps.Invoke(); - newPrompt = result.Count == 1 ? result[0] : "PS>"; + newPrompt = DefaultPrompt; } if (runspaceIsRemote) @@ -861,13 +940,8 @@ public static void InvokePrompt(ConsoleKeyInfo? key = null, object arg = null) newPrompt = string.Format(CultureInfo.InvariantCulture, "[{0}]: {1}", connectionInfo.ComputerName, newPrompt); } } - _singleton._console.Write(newPrompt); - _singleton._initialX = _singleton._console.CursorLeft; - _singleton._consoleBuffer = ReadBufferLines(_singleton._initialY, 1 + _singleton.Options.ExtraPromptLineCount); - _singleton._buffer.Append(currentBuffer); - _singleton._current = currentPos; - _singleton.Render(); + return newPrompt; } #endregion Miscellaneous bindable functions diff --git a/PSReadLine/ReadLine.vi.cs b/PSReadLine/ReadLine.vi.cs index bb0410a3d..ce3a30392 100644 --- a/PSReadLine/ReadLine.vi.cs +++ b/PSReadLine/ReadLine.vi.cs @@ -197,7 +197,7 @@ public static void SearchChar(ConsoleKeyInfo? key = null, object arg = null) } /// - /// Read the next character and then find it, going backard, and then back off a character. + /// Read the next character and then find it, going backward, and then back off a character. /// This is for 'T' functionality. /// public static void SearchCharBackward(ConsoleKeyInfo? key = null, object arg = null) @@ -219,7 +219,7 @@ public static void SearchCharWithBackoff(ConsoleKeyInfo? key = null, object arg } /// - /// Read the next character and then find it, going backard, and then back off a character. + /// Read the next character and then find it, going backward, and then back off a character. /// This is for 'T' functionality. /// public static void SearchCharBackwardWithBackoff(ConsoleKeyInfo? key = null, object arg = null) @@ -570,7 +570,7 @@ private void ViIndicateInsertMode() } /// - /// Switch to Insert mode and position the cursor at the begining of the line. + /// Switch to Insert mode and position the cursor at the beginning of the line. /// public static void ViInsertAtBegining(ConsoleKeyInfo? key = null, object arg = null) { @@ -887,7 +887,7 @@ public static void RepeatSearchBackward(ConsoleKeyInfo? key = null, object arg = /// /// Prompts for a string for history searching. /// - /// True for seaching backward in the history. + /// True for searching backward in the history. private void StartSearch(bool backward) { _statusLinePrompt = "find: "; diff --git a/PSReadLine/Render.cs b/PSReadLine/Render.cs index c54a20ba4..f6807bf19 100644 --- a/PSReadLine/Render.cs +++ b/PSReadLine/Render.cs @@ -13,13 +13,16 @@ namespace Microsoft.PowerShell { public partial class PSConsoleReadLine { - private CHAR_INFO[] _consoleBuffer; +#if UNIX + private const ConsoleColor UnknownColor = (ConsoleColor) (-1); +#endif + private BufferChar[] _consoleBuffer; private int _initialX; private int _initialY; private int _bufferWidth; private ConsoleColor _initialBackgroundColor; private ConsoleColor _initialForegroundColor; - private CHAR_INFO _space; + private BufferChar _space; private int _current; private int _emphasisStart; private int _emphasisLength; @@ -101,11 +104,10 @@ private void ReallyRender() try { _console.StartRender(); - bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount; if (_consoleBuffer.Length != bufferLineCount * bufferWidth) { - var newBuffer = new CHAR_INFO[bufferLineCount * bufferWidth]; + var newBuffer = new BufferChar[bufferLineCount * bufferWidth]; Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); if (_consoleBuffer.Length > bufferLineCount * bufferWidth) { @@ -129,6 +131,7 @@ private void ReallyRender() // use the tokens color otherwise use the initial color. var state = tokenStack.Peek(); var token = state.Tokens[state.Index]; + if (i == token.Extent.EndOffset) { if (token == state.Tokens[state.Tokens.Length - 1]) @@ -151,7 +154,6 @@ private void ReallyRender() { foregroundColor = state.ForegroundColor; backgroundColor = state.BackgroundColor; - token = state.Tokens[++state.Index]; } } @@ -226,17 +228,17 @@ private void ReallyRender() MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); } +#if !UNIX else if (size > 1) { _consoleBuffer[j].UnicodeChar = charToRender; - _consoleBuffer[j].Attributes = (ushort)(_consoleBuffer[j].Attributes | - (uint)CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE); + _consoleBuffer[j].IsLeadByte = true; MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); _consoleBuffer[j].UnicodeChar = charToRender; - _consoleBuffer[j].Attributes = (ushort)(_consoleBuffer[j].Attributes | - (uint)CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE); + _consoleBuffer[j].IsTrailByte = true; MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); } +#endif else { _consoleBuffer[j].UnicodeChar = charToRender; @@ -286,7 +288,7 @@ private void ReallyRender() while (promptChar >= 0) { - var c = (char)_consoleBuffer[promptChar].UnicodeChar; + var c = _consoleBuffer[promptChar].UnicodeChar; if (char.IsWhiteSpace(c)) { promptChar -= 1; @@ -311,7 +313,9 @@ private void ReallyRender() if ((_initialY + bufferLineCount) > (_console.WindowTop + _console.WindowHeight)) { +#if !UNIX // TODO: verify this isn't necessary for LINUX _console.WindowTop = _initialY + bufferLineCount - _console.WindowHeight; +#endif } _lastRenderTime.Restart(); @@ -330,7 +334,7 @@ private int LengthInBufferCells(char c) private static void WriteBlankLines(int count, int top) { var console = _singleton._console; - var blanks = new CHAR_INFO[count * console.BufferWidth]; + var blanks = new BufferChar[count * console.BufferWidth]; for (int i = 0; i < blanks.Length; i++) { blanks[i].BackgroundColor = console.BackgroundColor; @@ -340,7 +344,7 @@ private static void WriteBlankLines(int count, int top) console.WriteBufferLines(blanks, ref top); } - private static CHAR_INFO[] ReadBufferLines(int top, int count) + private static BufferChar[] ReadBufferLines(int top, int count) { return _singleton._console.ReadBufferLines(top, count); } @@ -448,7 +452,7 @@ private bool InRegion(int i) return i >= start && i < end; } - private void MaybeEmphasize(ref CHAR_INFO charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor) + private void MaybeEmphasize(ref BufferChar charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor) { if (i >= _emphasisStart && i < (_emphasisStart + _emphasisLength)) { @@ -458,12 +462,18 @@ private void MaybeEmphasize(ref CHAR_INFO charInfo, int i, ConsoleColor foregrou else if (_visualSelectionCommandCount > 0 && InRegion(i)) { // We can't quite emulate real console selection because it inverts - // based on actual screen colors, our pallete is limited. The choice + // based on actual screen colors, our palette is limited. The choice // to invert only the lower 3 bits to change the color is somewhat // but looks best with the 2 default color schemes - starting PowerShell // from it's shortcut or from a cmd shortcut. +#if UNIX // TODO: set Inverse attribute and let render choose what to do. + ConsoleColor tempColor = (foregroundColor == UnknownColor) ? ConsoleColor.White : foregroundColor; + foregroundColor = (backgroundColor == UnknownColor) ? ConsoleColor.Black : backgroundColor; + backgroundColor = tempColor; +#else foregroundColor = (ConsoleColor)((int)foregroundColor ^ 7); backgroundColor = (ConsoleColor)((int)backgroundColor ^ 7); +#endif } charInfo.ForegroundColor = foregroundColor; @@ -646,7 +656,11 @@ void IPSConsoleReadLineMockableMethods.Ding() case BellStyle.None: break; case BellStyle.Audible: +#if UNIX + Console.Beep(); +#else Console.Beep(Options.DingTone, Options.DingDuration); +#endif break; case BellStyle.Visual: // TODO: flash prompt? command line? @@ -674,8 +688,9 @@ private bool PromptYesOrNo(string s) return key.Key == ConsoleKey.Y; } - #region Screen scrolling +#region Screen scrolling +#if !UNIX /// /// Scroll the display up one screen. /// @@ -794,6 +809,7 @@ public static void ScrollDisplayToCursor(ConsoleKeyInfo? key = null, object arg console.SetWindowPosition(0, newTop); } - #endregion Screen scrolling +#endif +#endregion Screen scrolling } } diff --git a/PSReadLine/ScreenCapture.cs b/PSReadLine/ScreenCapture.cs index 0ab20b752..e35d83a5c 100644 --- a/PSReadLine/ScreenCapture.cs +++ b/PSReadLine/ScreenCapture.cs @@ -2,11 +2,11 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ +#if !UNIX using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; -using System.Windows.Forms; using Microsoft.PowerShell.Internal; namespace Microsoft.PowerShell @@ -194,7 +194,7 @@ private static string GetColorTable() var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output); var csbe = new NativeMethods.CONSOLE_SCREEN_BUFFER_INFO_EX { - cbSize = Marshal.SizeOf(typeof(NativeMethods.CONSOLE_SCREEN_BUFFER_INFO_EX)) + cbSize = Marshal.SizeOf() }; if (NativeMethods.GetConsoleScreenBufferInfoEx(handle, ref csbe)) { @@ -228,7 +228,6 @@ private static void DumpScreenToClipboard(int top, int count) var buffer = ReadBufferLines(top, count); var bufferWidth = _singleton._console.BufferWidth; - var dataObject = new DataObject(); var textBuffer = new StringBuilder(buffer.Length + count); var rtfBuffer = new StringBuilder(); @@ -298,9 +297,13 @@ private static void DumpScreenToClipboard(int top, int count) } rtfBuffer.Append("}}"); - dataObject.SetData(DataFormats.Text, textBuffer.ToString()); - dataObject.SetData(DataFormats.Rtf, rtfBuffer.ToString()); - ExecuteOnSTAThread(() => Clipboard.SetDataObject(dataObject, copy: true)); +#if !CORECLR // TODO: break dependency on Window.Forms w/ p/invokes to clipboard directly, for now, just silently skip the copy. + var dataObject = new System.Windows.Forms.DataObject(); + dataObject.SetData(System.Windows.Forms.DataFormats.Text, textBuffer.ToString()); + dataObject.SetData(System.Windows.Forms.DataFormats.Rtf, rtfBuffer.ToString()); + ExecuteOnSTAThread(() => System.Windows.Forms.Clipboard.SetDataObject(dataObject, copy: true)); +#endif } } } +#endif diff --git a/PSReadLine/UndoRedo.cs b/PSReadLine/UndoRedo.cs index 128e6548e..b451231e1 100644 --- a/PSReadLine/UndoRedo.cs +++ b/PSReadLine/UndoRedo.cs @@ -76,7 +76,6 @@ public static void Undo(ConsoleKeyInfo? key = null, object arg = null) _singleton.ClearStatusMessage(render: false); } _singleton._edits[_singleton._undoEditIndex - 1].Undo(); - _singleton._edits.RemoveAt(_singleton._undoEditIndex - 1); _singleton._undoEditIndex--; if (_singleton._options.EditMode == EditMode.Vi && _singleton._current >= _singleton._buffer.Length) { diff --git a/PSReadLine/VisualEditing.vi.cs b/PSReadLine/VisualEditing.vi.cs index 0aa51a339..59a14053e 100644 --- a/PSReadLine/VisualEditing.vi.cs +++ b/PSReadLine/VisualEditing.vi.cs @@ -96,20 +96,12 @@ private void ProcessViVisualEditing() private static string GetPreferredEditor() { string[] names = {"VISUAL", "EDITOR"}; - EnvironmentVariableTarget[] targets = { - EnvironmentVariableTarget.Machine, - EnvironmentVariableTarget.Process, - EnvironmentVariableTarget.User - }; foreach (string name in names) { - foreach (EnvironmentVariableTarget target in targets) + string editor = Environment.GetEnvironmentVariable(name); + if (!string.IsNullOrWhiteSpace(editor)) { - string editor = Environment.GetEnvironmentVariable(name, target); - if (!string.IsNullOrWhiteSpace(editor)) - { - return editor; - } + return editor; } } diff --git a/PSReadLine/Words.vi.cs b/PSReadLine/Words.vi.cs index 265378ed9..182ebc27c 100644 --- a/PSReadLine/Words.vi.cs +++ b/PSReadLine/Words.vi.cs @@ -129,7 +129,7 @@ private int ViFindPreviousWordPoint(string wordDelimiters) /// Returns the beginning of the current/next word as defined by wordDelimiters and whitespace. /// /// Current cursor location. - /// Characters used to deliminate words. + /// Characters used to delimit words. /// Location of the beginning of the previous word. private int ViFindPreviousWordPoint(int i, string wordDelimiters) { diff --git a/PSReadLine/en-US/PSReadline.md b/PSReadLine/en-US/PSReadline.md index 1b2a491ea..9e86dc535 100644 --- a/PSReadLine/en-US/PSReadline.md +++ b/PSReadLine/en-US/PSReadline.md @@ -10,9 +10,9 @@ If neither -Bound nor -Unbound is specified, returns all bound keys and unbound If -Bound is specified and -Unbound is not specified, only bound keys are returned. -If -Unound is specified and -Bound is not specified, only unbound keys are returned. +If -Unbound is specified and -Bound is not specified, only unbound keys are returned. -If both -Bound and -Unound are specified, returns all bound keys and unbound functions. +If both -Bound and -Unbound are specified, returns all bound keys and unbound functions. ## PARAMETERS diff --git a/PSReadLine/project.json b/PSReadLine/project.json new file mode 100644 index 000000000..ee8287783 --- /dev/null +++ b/PSReadLine/project.json @@ -0,0 +1,38 @@ +{ + "name": "Microsoft.PowerShell.PSReadLine", + "version": "1.0.0-*", + + "buildOptions": { + "xmlDoc": true, + "warningsAsErrors": true + }, + + "configurations": { + "Linux": { + "buildOptions": { + "define": [ "UNIX" ] + } + } + }, + + "frameworks": { + "netstandard1.6": { + "buildOptions": { + "define": [ "CORECLR" ], + "debugType": "portable" + }, + "imports": [ "dnxcore50", "portable-net45+win8" ], + "dependencies": { + "System.Management.Automation": "1.0.0-alpha10" + } + + }, + "net451": { + "frameworkAssemblies": { + "System.Windows.Forms": { + "type": "build" + } + } + } + } +}