diff --git a/Terminal.Gui/Configuration/KeyJsonConverter.cs b/Terminal.Gui/Configuration/KeyJsonConverter.cs index d9c17996e6..9c1a3fa50e 100644 --- a/Terminal.Gui/Configuration/KeyJsonConverter.cs +++ b/Terminal.Gui/Configuration/KeyJsonConverter.cs @@ -8,7 +8,9 @@ namespace Terminal.Gui; /// Support for in JSON in the form of "Ctrl-X" or "Alt-Shift-F1". /// public class KeyJsonConverter : JsonConverter { + /// public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Key.TryParse (reader.GetString (), out var key) ? key : Key.Empty; + /// public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options) => writer.WriteStringValue (value.ToString ()); } \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 0b5bc9cc62..5e67ebb517 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; +using Terminal.Gui.ConsoleDrivers; namespace Terminal.Gui; @@ -31,7 +32,6 @@ public abstract class ConsoleDriver { internal static bool RunningUnitTests { get; set; } #region Setup & Teardown - /// /// Initializes the driver /// @@ -42,7 +42,6 @@ public abstract class ConsoleDriver { /// Ends the execution of the console driver. /// internal abstract void End (); - #endregion /// @@ -129,10 +128,7 @@ public virtual void Move (int col, int row) /// /// if the rune can be properly presented; if the driver /// does not support displaying this rune. - public virtual bool IsRuneSupported (Rune rune) - { - return Rune.IsValid (rune.Value); - } + public virtual bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value); /// /// Adds the specified rune to the display at the current cursor position. @@ -151,7 +147,7 @@ public virtual bool IsRuneSupported (Rune rune) public void AddRune (Rune rune) { int runeWidth = -1; - var validLocation = IsValidLocation (Col, Row); + bool validLocation = IsValidLocation (Col, Row); if (validLocation) { rune = rune.MakePrintable (); runeWidth = rune.GetColumns (); @@ -275,7 +271,7 @@ public void AddRune (Rune rune) public void AddStr (string str) { var runes = str.EnumerateRunes ().ToList (); - for (var i = 0; i < runes.Count; i++) { + for (int i = 0; i < runes.Count; i++) { //if (runes [i].IsCombiningMark()) { // // Attempt to normalize @@ -361,8 +357,8 @@ public void ClearContents () lock (Contents) { // Can raise an exception while is still resizing. try { - for (var row = 0; row < Rows; row++) { - for (var c = 0; c < Cols; c++) { + for (int row = 0; row < Rows; row++) { + for (int c = 0; c < Cols; c++) { Contents [row, c] = new Cell () { Rune = (Rune)' ', Attribute = new Attribute (Color.White, Color.Black), @@ -381,11 +377,10 @@ public void ClearContents () public abstract void UpdateScreen (); #region Color Handling - /// /// Gets whether the supports TrueColor output. /// - public virtual bool SupportsTrueColor { get => true; } + public virtual bool SupportsTrueColor => true; /// /// Gets or sets whether the should use 16 colors instead of the default TrueColors. See @@ -399,7 +394,7 @@ public void ClearContents () /// internal virtual bool Force16Colors { get => Application.Force16Colors || !SupportsTrueColor; - set => Application.Force16Colors = (value || !SupportsTrueColor); + set => Application.Force16Colors = value || !SupportsTrueColor; } Attribute _currentAttribute; @@ -447,17 +442,13 @@ public Attribute SetAttribute (Attribute c) /// The foreground color. /// The background color. /// The attribute for the foreground and background colors. - public virtual Attribute MakeColor (Color foreground, Color background) - { + public virtual Attribute MakeColor (Color foreground, Color background) => // Encode the colors into the int value. - return new Attribute ( - platformColor: 0, // only used by cursesdriver! - foreground: foreground, - background: background + new ( + 0, // only used by cursesdriver! + foreground, + background ); - } - - #endregion #region Mouse and Keyboard @@ -509,7 +500,6 @@ public virtual Attribute MakeColor (Color foreground, Color background) /// If simulates the Alt key being pressed. /// If simulates the Ctrl key being pressed. public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); - #endregion /// @@ -521,16 +511,18 @@ public enum DiagnosticFlags : uint { /// All diagnostics off /// Off = 0b_0000_0000, + /// /// When enabled, will draw a /// ruler in the frame for any side with a padding value greater than 0. /// FrameRuler = 0b_0000_0001, + /// /// When enabled, will draw a /// 'L', 'R', 'T', and 'B' when clearing 's instead of ' '. /// - FramePadding = 0b_0000_0010, + FramePadding = 0b_0000_0010 } /// @@ -552,8 +544,8 @@ public enum DiagnosticFlags : uint { /// public void FillRect (Rect rect, Rune rune = default) { - for (var r = rect.Y; r < rect.Y + rect.Height; r++) { - for (var c = rect.X; c < rect.X + rect.Width; c++) { + for (int r = rect.Y; r < rect.Y + rect.Height; r++) { + for (int c = rect.X; c < rect.X + rect.Width; c++) { Application.Driver.Move (c, r); Application.Driver.AddRune (rune == default ? new Rune (' ') : rune); } @@ -575,7 +567,6 @@ public void FillRect (Rect rect, Rune rune = default) public virtual string GetVersionInfo () => GetType ().Name; } - /// /// Terminal Cursor Visibility settings. /// @@ -631,10 +622,9 @@ public enum CursorVisibility { /// Cursor caret is displayed a block ▉ /// /// Works under Xterm-like terminal otherwise this is equivalent to - BoxFix = 0x02020164, + BoxFix = 0x02020164 } - /// /// The enumeration encodes key information from s and provides a consistent /// way for application code to specify keys and receive key events. @@ -671,72 +661,59 @@ public enum CursorVisibility { [Flags] public enum KeyCode : uint { /// - /// Mask that indicates that this is a character value, values outside this range - /// indicate special characters like Alt-key combinations or special keys on the - /// keyboard like function keys, arrows keys and so on. + /// Mask that indicates that the key is a unicode codepoint. Values outside this range + /// indicate the key has shift modifiers or is a special key like function keys, arrows keys and so on. /// - CharMask = 0xfffff, + CharMask = 0x_f_ffff, /// /// If the is set, then the value is that of the special mask, /// otherwise, the value is in the the lower bits (as extracted by ). /// - SpecialMask = 0xfff00000, + SpecialMask = 0x_fff0_0000, /// - /// The key code representing null or empty - /// - Null = '\0', - - /// - /// Backspace key. + /// When this value is set, the Key encodes the sequence Shift-KeyValue. + /// The actual value must be extracted by removing the ShiftMask. /// - Backspace = 8, + ShiftMask = 0x_1000_0000, /// - /// The key code for the tab key (forwards tab key). + /// When this value is set, the Key encodes the sequence Alt-KeyValue. + /// The actual value must be extracted by removing the AltMask. /// - Tab = 9, + AltMask = 0x_8000_0000, /// - /// The key code for the return key. + /// When this value is set, the Key encodes the sequence Ctrl-KeyValue. + /// The actual value must be extracted by removing the CtrlMask. /// - Enter = '\n', + CtrlMask = 0x_4000_0000, /// - /// The key code for the clear key. + /// The key code representing an invalid or empty key. /// - Clear = 12, + Null = 0, /// - /// The key code for the Shift key. + /// Backspace key. /// - ShiftKey = 16, + Backspace = 8, /// - /// The key code for the Ctrl key. + /// The key code for the tab key (forwards tab key). /// - CtrlKey = 17, + Tab = 9, /// - /// The key code for the Alt key. + /// The key code for the return key. /// - AltKey = 18, + Enter = ConsoleKey.Enter, /// - /// The key code for the CapsLock key. + /// The key code for the clear key. /// - CapsLock = 20, - - ///// - ///// The key code for the NumLock key. - ///// - //NumLock = 144, - - ///// - ///// The key code for the ScrollLock key. - ///// - //ScrollLock = 145, + Clear = 12, /// /// The key code for the escape key. @@ -752,38 +729,47 @@ public enum KeyCode : uint { /// Digit 0. /// D0 = 48, + /// /// Digit 1. /// D1, + /// /// Digit 2. /// D2, + /// /// Digit 3. /// D3, + /// /// Digit 4. /// D4, + /// /// Digit 5. /// D5, + /// /// Digit 6. /// D6, + /// /// Digit 7. /// D7, + /// /// Digit 8. /// D8, + /// /// Digit 9. /// @@ -793,271 +779,321 @@ public enum KeyCode : uint { /// The key code for the A key /// A = 65, + /// /// The key code for the B key /// B, + /// /// The key code for the C key /// C, + /// /// The key code for the D key /// D, + /// /// The key code for the E key /// E, + /// /// The key code for the F key /// F, + /// /// The key code for the G key /// G, + /// /// The key code for the H key /// H, + /// /// The key code for the I key /// I, + /// /// The key code for the J key /// J, + /// /// The key code for the K key /// K, + /// /// The key code for the L key /// L, + /// /// The key code for the M key /// M, + /// /// The key code for the N key /// N, + /// /// The key code for the O key /// O, + /// /// The key code for the P key /// P, + /// /// The key code for the Q key /// Q, + /// /// The key code for the R key /// R, + /// /// The key code for the S key /// S, + /// /// The key code for the T key /// T, + /// /// The key code for the U key /// U, + /// /// The key code for the V key /// V, + /// /// The key code for the W key /// W, + /// /// The key code for the X key /// X, + /// /// The key code for the Y key /// Y, + /// /// The key code for the Z key /// Z, - /// - /// The key code for the Delete key. - /// - Delete = 127, - /// - /// When this value is set, the Key encodes the sequence Shift-KeyValue. - /// - ShiftMask = 0x10000000, + ///// + ///// The key code for the Delete key. + ///// + //Delete = 127, - /// - /// When this value is set, the Key encodes the sequence Alt-KeyValue. - /// And the actual value must be extracted by removing the AltMask. - /// - AltMask = 0x80000000, + // --- Special keys --- + // The values below are common non-alphanum keys. Their values are + // based on the .NET ConsoleKey values, which, in-turn are based on the + // VK_ values from the Windows API. + // We add MaxCodePoint to avoid conflicts with the Unicode values. /// - /// When this value is set, the Key encodes the sequence Ctrl-KeyValue. - /// And the actual value must be extracted by removing the CtrlMask. + /// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control + /// keys. /// - CtrlMask = 0x40000000, + MaxCodePoint = 0x10FFFF, /// /// Cursor up key /// - CursorUp = 0x100000, + CursorUp = MaxCodePoint + ConsoleKey.UpArrow, + /// /// Cursor down key. /// - CursorDown, + CursorDown = MaxCodePoint + ConsoleKey.DownArrow, + /// /// Cursor left key. /// - CursorLeft, + CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow, + /// /// Cursor right key. /// - CursorRight, + CursorRight = MaxCodePoint + ConsoleKey.RightArrow, + /// /// Page Up key. /// - PageUp, + PageUp = MaxCodePoint + ConsoleKey.PageUp, + /// /// Page Down key. /// - PageDown, + PageDown = MaxCodePoint + ConsoleKey.PageDown, + /// /// Home key. /// - Home, + Home = MaxCodePoint + ConsoleKey.Home, + /// /// End key. /// - End, + End = MaxCodePoint + ConsoleKey.End, /// - /// Insert character key. + /// Insert (INS) key. /// - InsertChar, + Insert = MaxCodePoint + ConsoleKey.Insert, /// - /// Delete character key. + /// Delete (DEL) key. /// - DeleteChar, + Delete = MaxCodePoint + ConsoleKey.Delete, /// /// Print screen character key. /// - PrintScreen, + PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen, /// /// F1 key. /// - F1, + F1 = MaxCodePoint + ConsoleKey.F1, + /// /// F2 key. /// - F2, + F2 = MaxCodePoint + ConsoleKey.F2, + /// /// F3 key. /// - F3, + F3 = MaxCodePoint + ConsoleKey.F3, + /// /// F4 key. /// - F4, + F4 = MaxCodePoint + ConsoleKey.F4, + /// /// F5 key. /// - F5, + F5 = MaxCodePoint + ConsoleKey.F5, + /// /// F6 key. /// - F6, + F6 = MaxCodePoint + ConsoleKey.F6, + /// /// F7 key. /// - F7, + F7 = MaxCodePoint + ConsoleKey.F7, + /// /// F8 key. /// - F8, + F8 = MaxCodePoint + ConsoleKey.F8, + /// /// F9 key. /// - F9, + F9 = MaxCodePoint + ConsoleKey.F9, + /// /// F10 key. /// - F10, + F10 = MaxCodePoint + ConsoleKey.F10, + /// /// F11 key. /// - F11, + F11 = MaxCodePoint + ConsoleKey.F11, + /// /// F12 key. /// - F12, + F12 = MaxCodePoint + ConsoleKey.F12, + /// /// F13 key. /// - F13, + F13 = MaxCodePoint + ConsoleKey.F13, + /// /// F14 key. /// - F14, + F14 = MaxCodePoint + ConsoleKey.F14, + /// /// F15 key. /// - F15, + F15 = MaxCodePoint + ConsoleKey.F15, + /// /// F16 key. /// - F16, + F16 = MaxCodePoint + ConsoleKey.F16, + /// /// F17 key. /// - F17, + F17 = MaxCodePoint + ConsoleKey.F17, + /// /// F18 key. /// - F18, + F18 = MaxCodePoint + ConsoleKey.F18, + /// /// F19 key. /// - F19, + F19 = MaxCodePoint + ConsoleKey.F19, + /// /// F20 key. /// - F20, + F20 = MaxCodePoint + ConsoleKey.F20, + /// /// F21 key. /// - F21, + F21 = MaxCodePoint + ConsoleKey.F21, + /// /// F22 key. /// - F22, + F22 = MaxCodePoint + ConsoleKey.F22, + /// /// F23 key. /// - F23, + F23 = MaxCodePoint + ConsoleKey.F23, + /// /// F24 key. /// - F24, -} - + F24 = MaxCodePoint + ConsoleKey.F24, +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index 142a03b1b2..f18420c424 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -2,597 +2,1721 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; -namespace Terminal.Gui.ConsoleDrivers { +namespace Terminal.Gui.ConsoleDrivers; + +/// +/// Helper class to handle the scan code and virtual key from a . +/// +public static class ConsoleKeyMapping { + +#if !WT_ISSUE_8871_FIXED // https://github.com/microsoft/terminal/issues/8871 /// - /// Helper class to handle the scan code and virtual key from a . + /// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code. /// - public static class ConsoleKeyMapping { - class ScanCodeMapping : IEquatable { - public uint ScanCode; - public uint VirtualKey; - public ConsoleModifiers Modifiers; - public uint UnicodeChar; - - public ScanCodeMapping (uint scanCode, uint virtualKey, ConsoleModifiers modifiers, uint unicodeChar) - { - ScanCode = scanCode; - VirtualKey = virtualKey; - Modifiers = modifiers; - UnicodeChar = unicodeChar; - } + /// + /// + /// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an un-shifted + /// character value in the low order word of the return value. + /// + /// + /// An un-shifted character value in the low order word of the return value. Dead keys (diacritics) + /// are indicated by setting the top bit of the return value. If there is no translation, + /// the function returns 0. See Remarks. + [DllImport ("user32.dll", EntryPoint = "MapVirtualKeyExW", CharSet = CharSet.Unicode)] + extern static uint MapVirtualKeyEx (VK vk, uint uMapType, IntPtr dwhkl); - public bool Equals (ScanCodeMapping other) - { - return ScanCode.Equals (other.ScanCode) && - VirtualKey.Equals (other.VirtualKey) && - Modifiers.Equals (other.Modifiers) && - UnicodeChar.Equals (other.UnicodeChar); - } - } + /// + /// Retrieves the active input locale identifier (formerly called the keyboard layout). + /// + /// 0 for current thread + /// The return value is the input locale identifier for the thread. + /// The low word contains a Language Identifier for the input language + /// and the high word contains a device handle to the physical layout of the keyboard. + /// + [DllImport ("user32.dll", EntryPoint = "GetKeyboardLayout", CharSet = CharSet.Unicode)] + extern static IntPtr GetKeyboardLayout (IntPtr idThread); - static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers) - { - if (modifiers.HasFlag (ConsoleModifiers.Shift) - && !modifiers.HasFlag (ConsoleModifiers.Alt) - && !modifiers.HasFlag (ConsoleModifiers.Control)) { - return ConsoleModifiers.Shift; - } else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) { - return modifiers; - } + //[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayoutNameW", CharSet = CharSet.Unicode)] + //extern static uint GetKeyboardLayoutName (uint idThread); + + [DllImport ("user32.dll")] + extern static IntPtr GetForegroundWindow (); + [DllImport ("user32.dll")] + extern static IntPtr GetWindowThreadProcessId (IntPtr hWnd, IntPtr ProcessId); + + /// + /// Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters using + /// the Win32 API MapVirtualKey. + /// + /// + /// An un-shifted character value in the low order word of the return value. Dead keys (diacritics) + /// are indicated by setting the top bit of the return value. If there is no translation, + /// the function returns 0. + public static uint MapVKtoChar (VK vk) + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { return 0; } + var tid = GetWindowThreadProcessId (GetForegroundWindow (), 0); + var hkl = GetKeyboardLayout (tid); + return MapVirtualKeyEx (vk, 2, hkl); + } +#else + /// + /// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code. + /// + /// + /// + /// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an unshifted + /// character value in the low order word of the return value. + /// + /// An unshifted character value in the low order word of the return value. Dead keys (diacritics) + /// are indicated by setting the top bit of the return value. If there is no translation, + /// the function returns 0. See Remarks. + [DllImport ("user32.dll", EntryPoint = "MapVirtualKeyW", CharSet = CharSet.Unicode)] + extern static uint MapVirtualKey (VK vk, uint uMapType = 2); + + uint MapVKtoChar (VK vk) => MapVirtualKeyToCharEx (vk); +#endif + /// + /// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread. + /// + /// + /// + [DllImport ("user32.dll")] + extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID); + + public static string GetKeyboardLayoutName () + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { + return "none"; + } - static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers) - { - switch (propName) { - case "UnicodeChar": - var sCode = scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers); - if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) { - return scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0); - } - return sCode; - case "VirtualKey": - sCode = scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == modifiers); - if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) { - return scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == 0); - } - return sCode; - } + StringBuilder klidSB = new StringBuilder (); + GetKeyboardLayoutName (klidSB); + return klidSB.ToString (); + } - return null; + class ScanCodeMapping : IEquatable { + public uint ScanCode; + public VK VirtualKey; + public ConsoleModifiers Modifiers; + public uint UnicodeChar; + + public ScanCodeMapping (uint scanCode, VK virtualKey, ConsoleModifiers modifiers, uint unicodeChar) + { + ScanCode = scanCode; + VirtualKey = virtualKey; + Modifiers = modifiers; + UnicodeChar = unicodeChar; } - /// - /// Gets the from the provided . - /// - /// - /// - public static ConsoleKeyInfo GetConsoleKeyFromKey (KeyCode key) + public bool Equals (ScanCodeMapping other) { - var mod = new ConsoleModifiers (); - if (key.HasFlag (KeyCode.ShiftMask)) { - mod |= ConsoleModifiers.Shift; - } - if (key.HasFlag (KeyCode.AltMask)) { - mod |= ConsoleModifiers.Alt; + return ScanCode.Equals (other.ScanCode) && + VirtualKey.Equals (other.VirtualKey) && + Modifiers.Equals (other.Modifiers) && + UnicodeChar.Equals (other.UnicodeChar); + } + } + + static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers) + { + if (modifiers.HasFlag (ConsoleModifiers.Shift) + && !modifiers.HasFlag (ConsoleModifiers.Alt) + && !modifiers.HasFlag (ConsoleModifiers.Control)) { + return ConsoleModifiers.Shift; + } else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) { + return modifiers; + } + + return 0; + } + + static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers) + { + switch (propName) { + case "UnicodeChar": + var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers); + if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) { + return _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0); } - if (key.HasFlag (KeyCode.CtrlMask)) { - mod |= ConsoleModifiers.Control; + return sCode; + case "VirtualKey": + sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == modifiers); + if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) { + return _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == 0); } - return GetConsoleKeyFromKey ((uint)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask), mod, out _); + return sCode; } - /// - /// Get the from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask). - /// - /// The key as a unicode codepoint. - /// The modifier keys. - /// The resulting scan code. - /// The . - public static ConsoleKeyInfo GetConsoleKeyFromKey (uint keyValue, ConsoleModifiers modifiers, out uint scanCode) - { - scanCode = 0; - uint outputChar = keyValue; - if (keyValue == 0) { - return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift), + return null; + } + + // BUGBUG: This API is not correct. It is only used by WindowsDriver in VKPacket scenarios + /// + /// Get the scan code from a . + /// + /// The console key info. + /// The value if apply. + public static uint GetScanCodeFromConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + var mod = GetModifiers (consoleKeyInfo.Modifiers); + ScanCodeMapping scode = GetScanCode ("VirtualKey", (uint)consoleKeyInfo.Key, mod); + if (scode != null) { + return scode.ScanCode; + } + + return 0; + } + + // BUGBUG: This API is not correct. It is only used by FakeDriver and VkeyPacketSimulator + /// + /// Gets the from the provided . + /// + /// The key code. + /// The console key info. + public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key) + { + var modifiers = MapToConsoleModifiers (key); + var keyValue = MapKeyCodeToConsoleKey (key, out bool isConsoleKey); + if (isConsoleKey) { + var mod = GetModifiers (modifiers); + var scode = GetScanCode ("VirtualKey", (uint)keyValue, mod); + if (scode != null) { + return new ConsoleKeyInfo ((char)scode.UnicodeChar, (ConsoleKey)scode.VirtualKey, modifiers.HasFlag (ConsoleModifiers.Shift), + modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control)); + } + } else { + var keyChar = GetKeyCharFromUnicodeChar ((uint)keyValue, modifiers, out uint consoleKey, out _, isConsoleKey); + if (consoleKey != 0) { + return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift), modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control)); } + } + + return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift), + modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control)); + } + + /// + /// Map existing modifiers to . + /// + /// The key code. + /// The console modifiers. + public static ConsoleModifiers MapToConsoleModifiers (KeyCode key) + { + var modifiers = new ConsoleModifiers (); + if (key.HasFlag (KeyCode.ShiftMask)) { + modifiers |= ConsoleModifiers.Shift; + } + if (key.HasFlag (KeyCode.AltMask)) { + modifiers |= ConsoleModifiers.Alt; + } + if (key.HasFlag (KeyCode.CtrlMask)) { + modifiers |= ConsoleModifiers.Control; + } - uint consoleKey = (uint)MapKeyToConsoleKey ((KeyCode)keyValue, modifiers, out bool mappable); - if (mappable) { - var mod = GetModifiers (modifiers); - var scode = GetScanCode ("UnicodeChar", keyValue, mod); - if (scode != null) { - consoleKey = scode.VirtualKey; - scanCode = scode.ScanCode; - outputChar = scode.UnicodeChar; + return modifiers; + } + + /// + /// Gets from modifiers. + /// + /// The shift key. + /// The alt key. + /// The control key. + /// The console modifiers. + public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control) + { + var modifiers = new ConsoleModifiers (); + if (shift) { + modifiers |= ConsoleModifiers.Shift; + } + if (alt) { + modifiers |= ConsoleModifiers.Alt; + } + if (control) { + modifiers |= ConsoleModifiers.Control; + } + + return modifiers; + } + + /// + /// Get the from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask). + /// + /// The key as a unicode codepoint. + /// The modifier keys. + /// The resulting scan code. + /// The . + static ConsoleKeyInfo GetConsoleKeyInfoFromKeyChar (uint keyValue, ConsoleModifiers modifiers, out uint scanCode) + { + scanCode = 0; + if (keyValue == 0) { + return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift), + modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control)); + } + + uint outputChar = keyValue; + uint consoleKey; + if (keyValue > byte.MaxValue) { + var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue); + if (sCode == null) { + consoleKey = (byte)(keyValue & byte.MaxValue); + sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)consoleKey); + if (sCode == null) { + consoleKey = 0; + outputChar = keyValue; } else { - // If the consoleKey is < 255, retain the lower 8 bits of the key value and set the upper bits to 0xff. - // This is a shifted value that will be used by the GetKeyCharFromConsoleKey to do the correct action - // because keyValue maybe a UnicodeChar or a ConsoleKey, e.g. for PageUp is passed the ConsoleKey.PageUp - consoleKey = consoleKey < 0xff ? consoleKey & 0xff | 0xff << 8 : consoleKey; - outputChar = GetKeyCharFromConsoleKey (consoleKey, modifiers, out consoleKey, out scanCode); + outputChar = (char)(keyValue >> 8); } } else { - var mod = GetModifiers (modifiers); - var scode = GetScanCode ("VirtualKey", consoleKey, mod); - if (scode != null) { - consoleKey = scode.VirtualKey; - scanCode = scode.ScanCode; - outputChar = scode.UnicodeChar; - } + consoleKey = (byte)sCode.VirtualKey; + outputChar = keyValue; } + } else { + consoleKey = (byte)keyValue; + outputChar = '\0'; + } - return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift), - modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control)); + return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift), + modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control)); + } + + // Used only by unit tests + internal static uint GetKeyChar (uint keyValue, ConsoleModifiers modifiers) + { + if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'A' and <= 'Z') { + return keyValue - 32; + } else if (modifiers == ConsoleModifiers.None && keyValue is >= 'A' and <= 'Z') { + return keyValue + 32; } - /// - /// Get the output character from the , the correct - /// and the scan code used on . - /// - /// The unicode character. - /// The modifiers keys. - /// The resulting console key. - /// The resulting scan code. - /// The output character or the . - static uint GetKeyCharFromConsoleKey (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode) - { - uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar; - uint keyChar = decodedChar; - consoleKey = 0; - var mod = GetModifiers (modifiers); - scanCode = 0; - var scode = unicodeChar != 0 && unicodeChar >> 8 != 0xff ? GetScanCode ("VirtualKey", decodedChar, mod) : null; + if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'À' and <= 'Ý') { + return keyValue - 32; + } else if (modifiers == ConsoleModifiers.None && keyValue is >= 'À' and <= 'Ý') { + return keyValue + 32; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '0') { + return keyValue + 13; + } else if (modifiers == ConsoleModifiers.None && keyValue - 13 is '0') { + return keyValue - 13; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is >= '1' and <= '9' and not '7') { + return keyValue - 16; + } else if (modifiers == ConsoleModifiers.None && keyValue + 16 is >= '1' and <= '9' and not '7') { + return keyValue + 16; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '7') { + return keyValue - 8; + } else if (modifiers == ConsoleModifiers.None && keyValue + 8 is '7') { + return keyValue + 8; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\'') { + return keyValue + 24; + } else if (modifiers == ConsoleModifiers.None && keyValue - 24 is '\'') { + return keyValue - 24; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '«') { + return keyValue + 16; + } else if (modifiers == ConsoleModifiers.None && keyValue - 16 is '«') { + return keyValue - 16; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\\') { + return keyValue + 32; + } else if (modifiers == ConsoleModifiers.None && keyValue - 32 is '\\') { + return keyValue - 32; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '+') { + return keyValue - 1; + } else if (modifiers == ConsoleModifiers.None && keyValue + 1 is '+') { + return keyValue + 1; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '´') { + return keyValue - 84; + } else if (modifiers == ConsoleModifiers.None && keyValue + 84 is '´') { + return keyValue + 84; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is 'º') { + return keyValue - 16; + } else if (modifiers == ConsoleModifiers.None && keyValue + 16 is 'º') { + return keyValue + 16; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '~') { + return keyValue - 32; + } else if (modifiers == ConsoleModifiers.None && keyValue + 32 is '~') { + return keyValue + 32; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '<') { + return keyValue + 2; + } else if (modifiers == ConsoleModifiers.None && keyValue - 2 is '<') { + return keyValue - 2; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is ',') { + return keyValue + 15; + } else if (modifiers == ConsoleModifiers.None && keyValue - 15 is ',') { + return keyValue - 15; + } + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '.') { + return keyValue + 12; + } else if (modifiers == ConsoleModifiers.None && keyValue - 12 is '.') { + return keyValue - 12; + } + + if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '-') { + return keyValue + 50; + } else if (modifiers == ConsoleModifiers.None && keyValue - 50 is '-') { + return keyValue - 50; + } + + return keyValue; + } + + /// + /// Get the output character from the , with the correct + /// and the scan code used on . + /// + /// The unicode character. + /// The modifiers keys. + /// The resulting console key. + /// The resulting scan code. + /// Indicates if the is a . + /// The output character or the . + /// This is only used by the and by unit tests. + internal static uint GetKeyCharFromUnicodeChar (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode, bool isConsoleKey = false) + { + uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar; + uint keyChar = decodedChar; + consoleKey = 0; + var mod = GetModifiers (modifiers); + scanCode = 0; + ScanCodeMapping scode = null; + if (unicodeChar != 0 && unicodeChar >> 8 != 0xff && isConsoleKey) { + scode = GetScanCode ("VirtualKey", decodedChar, mod); + } + if (isConsoleKey && scode != null) { + consoleKey = (uint)scode.VirtualKey; + keyChar = scode.UnicodeChar; + scanCode = scode.ScanCode; + } + if (scode == null) { + scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null; if (scode != null) { - consoleKey = scode.VirtualKey; + consoleKey = (uint)scode.VirtualKey; keyChar = scode.UnicodeChar; scanCode = scode.ScanCode; } - if (scode == null) { - scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null; - if (scode != null) { - consoleKey = scode.VirtualKey; - keyChar = scode.UnicodeChar; - scanCode = scode.ScanCode; - } - } - if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) { - string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD); - for (int i = 0; i < stFormD.Length; i++) { - var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]); - if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) { - consoleKey = char.ToUpper (stFormD [i]); - scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0); - if (scode != null) { - scanCode = scode.ScanCode; - } + } + if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) { + string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD); + for (int i = 0; i < stFormD.Length; i++) { + var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]); + if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) { + consoleKey = char.ToUpper (stFormD [i]); + scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0); + if (scode != null) { + scanCode = scode.ScanCode; } } } + } + if (keyChar < 255 && consoleKey == 0 && scanCode == 0) { + scode = GetScanCode ("VirtualKey", keyChar, mod); + if (scode != null) { + consoleKey = (uint)scode.VirtualKey; + keyChar = scode.UnicodeChar; + scanCode = scode.ScanCode; + } + } + + return keyChar; + } + + /// + /// Maps a unicode character (e.g. (Key)'a') to a uint representing a . + /// + /// The key value. + /// Indicates if the is a . + /// means the return value is in the ConsoleKey enum. + /// means the return value can be mapped to a valid unicode character. + /// + /// The or the . + /// This is only used by the and by unit tests. + internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsoleKey) + { + isConsoleKey = true; + keyValue = keyValue & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask; + + switch (keyValue) { + case KeyCode.Enter: + return (uint)ConsoleKey.Enter; + case KeyCode.CursorUp: + return (uint)ConsoleKey.UpArrow; + case KeyCode.CursorDown: + return (uint)ConsoleKey.DownArrow; + case KeyCode.CursorLeft: + return (uint)ConsoleKey.LeftArrow; + case KeyCode.CursorRight: + return (uint)ConsoleKey.RightArrow; + case KeyCode.PageUp: + return (uint)ConsoleKey.PageUp; + case KeyCode.PageDown: + return (uint)ConsoleKey.PageDown; + case KeyCode.Home: + return (uint)ConsoleKey.Home; + case KeyCode.End: + return (uint)ConsoleKey.End; + case KeyCode.Insert: + return (uint)ConsoleKey.Insert; + case KeyCode.Delete: + return (uint)ConsoleKey.Delete; + case KeyCode.F1: + return (uint)ConsoleKey.F1; + case KeyCode.F2: + return (uint)ConsoleKey.F2; + case KeyCode.F3: + return (uint)ConsoleKey.F3; + case KeyCode.F4: + return (uint)ConsoleKey.F4; + case KeyCode.F5: + return (uint)ConsoleKey.F5; + case KeyCode.F6: + return (uint)ConsoleKey.F6; + case KeyCode.F7: + return (uint)ConsoleKey.F7; + case KeyCode.F8: + return (uint)ConsoleKey.F8; + case KeyCode.F9: + return (uint)ConsoleKey.F9; + case KeyCode.F10: + return (uint)ConsoleKey.F10; + case KeyCode.F11: + return (uint)ConsoleKey.F11; + case KeyCode.F12: + return (uint)ConsoleKey.F12; + case KeyCode.F13: + return (uint)ConsoleKey.F13; + case KeyCode.F14: + return (uint)ConsoleKey.F14; + case KeyCode.F15: + return (uint)ConsoleKey.F15; + case KeyCode.F16: + return (uint)ConsoleKey.F16; + case KeyCode.F17: + return (uint)ConsoleKey.F17; + case KeyCode.F18: + return (uint)ConsoleKey.F18; + case KeyCode.F19: + return (uint)ConsoleKey.F19; + case KeyCode.F20: + return (uint)ConsoleKey.F20; + case KeyCode.F21: + return (uint)ConsoleKey.F21; + case KeyCode.F22: + return (uint)ConsoleKey.F22; + case KeyCode.F23: + return (uint)ConsoleKey.F23; + case KeyCode.F24: + return (uint)ConsoleKey.F24; + case KeyCode.Tab | KeyCode.ShiftMask: + return (uint)ConsoleKey.Tab; + } + + isConsoleKey = false; + return (uint)keyValue; + } + + /// + /// Maps a to a . + /// + /// The console key. + /// The or the . + public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo) + { + KeyCode keyCode; + + switch (consoleKeyInfo.Key) { + case ConsoleKey.Enter: + keyCode = KeyCode.Enter; + break; + case ConsoleKey.Delete: + keyCode = KeyCode.Delete; + break; + case ConsoleKey.UpArrow: + keyCode = KeyCode.CursorUp; + break; + case ConsoleKey.DownArrow: + keyCode = KeyCode.CursorDown; + break; + case ConsoleKey.LeftArrow: + keyCode = KeyCode.CursorLeft; + break; + case ConsoleKey.RightArrow: + keyCode = KeyCode.CursorRight; + break; + case ConsoleKey.PageUp: + keyCode = KeyCode.PageUp; + break; + case ConsoleKey.PageDown: + keyCode = KeyCode.PageDown; + break; + case ConsoleKey.Home: + keyCode = KeyCode.Home; + break; + case ConsoleKey.End: + keyCode = KeyCode.End; + break; + case ConsoleKey.Insert: + keyCode = KeyCode.Insert; + break; + case ConsoleKey.F1: + keyCode = KeyCode.F1; + break; + case ConsoleKey.F2: + keyCode = KeyCode.F2; + break; + case ConsoleKey.F3: + keyCode = KeyCode.F3; + break; + case ConsoleKey.F4: + keyCode = KeyCode.F4; + break; + case ConsoleKey.F5: + keyCode = KeyCode.F5; + break; + case ConsoleKey.F6: + keyCode = KeyCode.F6; + break; + case ConsoleKey.F7: + keyCode = KeyCode.F7; + break; + case ConsoleKey.F8: + keyCode = KeyCode.F8; + break; + case ConsoleKey.F9: + keyCode = KeyCode.F9; + break; + case ConsoleKey.F10: + keyCode = KeyCode.F10; + break; + case ConsoleKey.F11: + keyCode = KeyCode.F11; + break; + case ConsoleKey.F12: + keyCode = KeyCode.F12; + break; + case ConsoleKey.F13: + keyCode = KeyCode.F13; + break; + case ConsoleKey.F14: + keyCode = KeyCode.F14; + break; + case ConsoleKey.F15: + keyCode = KeyCode.F15; + break; + case ConsoleKey.F16: + keyCode = KeyCode.F16; + break; + case ConsoleKey.F17: + keyCode = KeyCode.F17; + break; + case ConsoleKey.F18: + keyCode = KeyCode.F18; + break; + case ConsoleKey.F19: + keyCode = KeyCode.F19; + break; + case ConsoleKey.F20: + keyCode = KeyCode.F20; + break; + case ConsoleKey.F21: + keyCode = KeyCode.F21; + break; + case ConsoleKey.F22: + keyCode = KeyCode.F22; + break; + case ConsoleKey.F23: + keyCode = KeyCode.F23; + break; + case ConsoleKey.F24: + keyCode = KeyCode.F24; + break; + case ConsoleKey.Tab: + keyCode = KeyCode.Tab; + break; + default: + keyCode = (KeyCode)consoleKeyInfo.KeyChar; + break; + } + keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode); + + return keyCode; + } - return keyChar; + /// + /// Maps a to a . + /// + /// The console modifiers. + /// The key code. + /// The with or the + public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key) + { + var keyMod = new KeyCode (); + if ((modifiers & ConsoleModifiers.Shift) != 0) { + keyMod = KeyCode.ShiftMask; + } + if ((modifiers & ConsoleModifiers.Control) != 0) { + keyMod |= KeyCode.CtrlMask; } + if ((modifiers & ConsoleModifiers.Alt) != 0) { + keyMod |= KeyCode.AltMask; + } + + return keyMod != KeyCode.Null ? keyMod | key : key; + } + /// + /// Generated from winuser.h. See https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + /// + public enum VK : ushort { /// - /// Maps a unicode character (e.g. (Key)'a') to a uint representing a . + /// Left mouse button. /// - /// The key value. - /// The modifiers keys. - /// - /// means the return value can be mapped to a valid unicode character. - /// means the return value is in the ConsoleKey enum. - /// - /// The or the . - public static ConsoleKey MapKeyToConsoleKey (KeyCode keyValue, ConsoleModifiers modifiers, out bool isMappable) - { - isMappable = false; - - switch (keyValue) { - case KeyCode.Delete: - return ConsoleKey.Delete; - case KeyCode.CursorUp: - return ConsoleKey.UpArrow; - case KeyCode.CursorDown: - return ConsoleKey.DownArrow; - case KeyCode.CursorLeft: - return ConsoleKey.LeftArrow; - case KeyCode.CursorRight: - return ConsoleKey.RightArrow; - case KeyCode.PageUp: - return ConsoleKey.PageUp; - case KeyCode.PageDown: - return ConsoleKey.PageDown; - case KeyCode.Home: - return ConsoleKey.Home; - case KeyCode.End: - return ConsoleKey.End; - case KeyCode.InsertChar: - return ConsoleKey.Insert; - case KeyCode.DeleteChar: - return ConsoleKey.Delete; - case KeyCode.F1: - return ConsoleKey.F1; - case KeyCode.F2: - return ConsoleKey.F2; - case KeyCode.F3: - return ConsoleKey.F3; - case KeyCode.F4: - return ConsoleKey.F4; - case KeyCode.F5: - return ConsoleKey.F5; - case KeyCode.F6: - return ConsoleKey.F6; - case KeyCode.F7: - return ConsoleKey.F7; - case KeyCode.F8: - return ConsoleKey.F8; - case KeyCode.F9: - return ConsoleKey.F9; - case KeyCode.F10: - return ConsoleKey.F10; - case KeyCode.F11: - return ConsoleKey.F11; - case KeyCode.F12: - return ConsoleKey.F12; - case KeyCode.F13: - return ConsoleKey.F13; - case KeyCode.F14: - return ConsoleKey.F14; - case KeyCode.F15: - return ConsoleKey.F15; - case KeyCode.F16: - return ConsoleKey.F16; - case KeyCode.F17: - return ConsoleKey.F17; - case KeyCode.F18: - return ConsoleKey.F18; - case KeyCode.F19: - return ConsoleKey.F19; - case KeyCode.F20: - return ConsoleKey.F20; - case KeyCode.F21: - return ConsoleKey.F21; - case KeyCode.F22: - return ConsoleKey.F22; - case KeyCode.F23: - return ConsoleKey.F23; - case KeyCode.F24: - return ConsoleKey.F24; - case KeyCode.Tab | KeyCode.ShiftMask: - return ConsoleKey.Tab; - } + LBUTTON = 0x01, - isMappable = true; + /// + /// Right mouse button. + /// + RBUTTON = 0x02, - if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= KeyCode.A and <= KeyCode.Z) { - return (ConsoleKey)(keyValue - 32); - } else if (modifiers == ConsoleModifiers.None && keyValue is >= KeyCode.A and <= KeyCode.Z) { - return (ConsoleKey)(keyValue + 32); - } - if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= (KeyCode)'À' and <= (KeyCode)'Ý') { - return (ConsoleKey)(keyValue - 32); - } else if (modifiers == ConsoleModifiers.None && keyValue is >= (KeyCode)'À' and <= (KeyCode)'Ý') { - return (ConsoleKey)(keyValue + 32); - } + /// + /// Control-break processing. + /// + CANCEL = 0x03, - return (ConsoleKey)keyValue; - } + /// + /// Middle mouse button (three-button mouse). + /// + MBUTTON = 0x04, /// - /// Maps a to a . + /// X1 mouse button. /// - /// The console key. - /// If is mapped to a valid character, otherwise . - /// The or the . - public static KeyCode MapConsoleKeyToKey (ConsoleKey consoleKey, out bool isMappable) - { - isMappable = false; - - switch (consoleKey) { - case ConsoleKey.Delete: - return KeyCode.Delete; - case ConsoleKey.UpArrow: - return KeyCode.CursorUp; - case ConsoleKey.DownArrow: - return KeyCode.CursorDown; - case ConsoleKey.LeftArrow: - return KeyCode.CursorLeft; - case ConsoleKey.RightArrow: - return KeyCode.CursorRight; - case ConsoleKey.PageUp: - return KeyCode.PageUp; - case ConsoleKey.PageDown: - return KeyCode.PageDown; - case ConsoleKey.Home: - return KeyCode.Home; - case ConsoleKey.End: - return KeyCode.End; - case ConsoleKey.Insert: - return KeyCode.InsertChar; - case ConsoleKey.F1: - return KeyCode.F1; - case ConsoleKey.F2: - return KeyCode.F2; - case ConsoleKey.F3: - return KeyCode.F3; - case ConsoleKey.F4: - return KeyCode.F4; - case ConsoleKey.F5: - return KeyCode.F5; - case ConsoleKey.F6: - return KeyCode.F6; - case ConsoleKey.F7: - return KeyCode.F7; - case ConsoleKey.F8: - return KeyCode.F8; - case ConsoleKey.F9: - return KeyCode.F9; - case ConsoleKey.F10: - return KeyCode.F10; - case ConsoleKey.F11: - return KeyCode.F11; - case ConsoleKey.F12: - return KeyCode.F12; - case ConsoleKey.F13: - return KeyCode.F13; - case ConsoleKey.F14: - return KeyCode.F14; - case ConsoleKey.F15: - return KeyCode.F15; - case ConsoleKey.F16: - return KeyCode.F16; - case ConsoleKey.F17: - return KeyCode.F17; - case ConsoleKey.F18: - return KeyCode.F18; - case ConsoleKey.F19: - return KeyCode.F19; - case ConsoleKey.F20: - return KeyCode.F20; - case ConsoleKey.F21: - return KeyCode.F21; - case ConsoleKey.F22: - return KeyCode.F22; - case ConsoleKey.F23: - return KeyCode.F23; - case ConsoleKey.F24: - return KeyCode.F24; - case ConsoleKey.Tab: - return KeyCode.Tab; - } - isMappable = true; + XBUTTON1 = 0x05, - if (consoleKey is >= ConsoleKey.A and <= ConsoleKey.Z) { - return (KeyCode)(consoleKey + 32); - } + /// + /// X2 mouse button. + /// + XBUTTON2 = 0x06, - return (KeyCode)consoleKey; - } + /// + /// BACKSPACE key. + /// + BACK = 0x08, /// - /// Maps a to a . + /// TAB key. /// - /// The console key info. - /// The key. - /// The with or the - public static KeyCode MapKeyModifiers (ConsoleKeyInfo keyInfo, KeyCode key) - { - var keyMod = new KeyCode (); - if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) { - keyMod = KeyCode.ShiftMask; - } - if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) { - keyMod |= KeyCode.CtrlMask; - } - if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) { - keyMod |= KeyCode.AltMask; - } + TAB = 0x09, - return keyMod != KeyCode.Null ? keyMod | key : key; - } - - static HashSet scanCodes = new HashSet { - new ScanCodeMapping (1, 27, 0, 27), // Escape - new ScanCodeMapping (1, 27, ConsoleModifiers.Shift, 27), - new ScanCodeMapping (2, 49, 0, 49), // D1 - new ScanCodeMapping (2, 49, ConsoleModifiers.Shift, 33), - new ScanCodeMapping (3, 50, 0, 50), // D2 - new ScanCodeMapping (3, 50, ConsoleModifiers.Shift, 34), - new ScanCodeMapping (3, 50, ConsoleModifiers.Alt | ConsoleModifiers.Control, 64), - new ScanCodeMapping (4, 51, 0, 51), // D3 - new ScanCodeMapping (4, 51, ConsoleModifiers.Shift, 35), - new ScanCodeMapping (4, 51, ConsoleModifiers.Alt | ConsoleModifiers.Control, 163), - new ScanCodeMapping (5, 52, 0, 52), // D4 - new ScanCodeMapping (5, 52, ConsoleModifiers.Shift, 36), - new ScanCodeMapping (5, 52, ConsoleModifiers.Alt | ConsoleModifiers.Control, 167), - new ScanCodeMapping (6, 53, 0, 53), // D5 - new ScanCodeMapping (6, 53, ConsoleModifiers.Shift, 37), - new ScanCodeMapping (6, 53, ConsoleModifiers.Alt | ConsoleModifiers.Control, 8364), - new ScanCodeMapping (7, 54, 0, 54), // D6 - new ScanCodeMapping (7, 54, ConsoleModifiers.Shift, 38), - new ScanCodeMapping (8, 55, 0, 55), // D7 - new ScanCodeMapping (8, 55, ConsoleModifiers.Shift, 47), - new ScanCodeMapping (8, 55, ConsoleModifiers.Alt | ConsoleModifiers.Control, 123), - new ScanCodeMapping (9, 56, 0, 56), // D8 - new ScanCodeMapping (9, 56, ConsoleModifiers.Shift, 40), - new ScanCodeMapping (9, 56, ConsoleModifiers.Alt | ConsoleModifiers.Control, 91), - new ScanCodeMapping (10, 57, 0, 57), // D9 - new ScanCodeMapping (10, 57, ConsoleModifiers.Shift, 41), - new ScanCodeMapping (10, 57, ConsoleModifiers.Alt | ConsoleModifiers.Control, 93), - new ScanCodeMapping (11, 48, 0, 48), // D0 - new ScanCodeMapping (11, 48, ConsoleModifiers.Shift, 61), - new ScanCodeMapping (11, 48, ConsoleModifiers.Alt | ConsoleModifiers.Control, 125), - new ScanCodeMapping (12, 219, 0, 39), // Oem4 - new ScanCodeMapping (12, 219, ConsoleModifiers.Shift, 63), - new ScanCodeMapping (13, 221, 0, 171), // Oem6 - new ScanCodeMapping (13, 221, ConsoleModifiers.Shift, 187), - new ScanCodeMapping (14, 8, 0, 8), // Backspace - new ScanCodeMapping (14, 8, ConsoleModifiers.Shift, 8), - new ScanCodeMapping (15, 9, 0, 9), // Tab - new ScanCodeMapping (15, 9, ConsoleModifiers.Shift, 15), - new ScanCodeMapping (16, 81, 0, 113), // Q - new ScanCodeMapping (16, 81, ConsoleModifiers.Shift, 81), - new ScanCodeMapping (17, 87, 0, 119), // W - new ScanCodeMapping (17, 87, ConsoleModifiers.Shift, 87), - new ScanCodeMapping (18, 69, 0, 101), // E - new ScanCodeMapping (18, 69, ConsoleModifiers.Shift, 69), - new ScanCodeMapping (19, 82, 0, 114), // R - new ScanCodeMapping (19, 82, ConsoleModifiers.Shift, 82), - new ScanCodeMapping (20, 84, 0, 116), // T - new ScanCodeMapping (20, 84, ConsoleModifiers.Shift, 84), - new ScanCodeMapping (21, 89, 0, 121), // Y - new ScanCodeMapping (21, 89, ConsoleModifiers.Shift, 89), - new ScanCodeMapping (22, 85, 0, 117), // U - new ScanCodeMapping (22, 85, ConsoleModifiers.Shift, 85), - new ScanCodeMapping (23, 73, 0, 105), // I - new ScanCodeMapping (23, 73, ConsoleModifiers.Shift, 73), - new ScanCodeMapping (24, 79, 0, 111), // O - new ScanCodeMapping (24, 79, ConsoleModifiers.Shift, 79), - new ScanCodeMapping (25, 80, 0, 112), // P - new ScanCodeMapping (25, 80, ConsoleModifiers.Shift, 80), - new ScanCodeMapping (26, 187, 0, 43), // OemPlus - new ScanCodeMapping (26, 187, ConsoleModifiers.Shift, 42), - new ScanCodeMapping (26, 187, ConsoleModifiers.Alt | ConsoleModifiers.Control, 168), - new ScanCodeMapping (27, 186, 0, 180), // Oem1 - new ScanCodeMapping (27, 186, ConsoleModifiers.Shift, 96), - new ScanCodeMapping (28, 13, 0, 13), // Enter - new ScanCodeMapping (28, 13, ConsoleModifiers.Shift, 13), - new ScanCodeMapping (29, 17, 0, 0), // Control - new ScanCodeMapping (29, 17, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (scanCode: 30, virtualKey: 65, modifiers: 0, unicodeChar: 97), // VK = A, UC = 'a' - new ScanCodeMapping (30, 65, ConsoleModifiers.Shift, 65), // VK = A | Shift, UC = 'A' - new ScanCodeMapping (31, 83, 0, 115), // S - new ScanCodeMapping (31, 83, ConsoleModifiers.Shift, 83), - new ScanCodeMapping (32, 68, 0, 100), // D - new ScanCodeMapping (32, 68, ConsoleModifiers.Shift, 68), - new ScanCodeMapping (33, 70, 0, 102), // F - new ScanCodeMapping (33, 70, ConsoleModifiers.Shift, 70), - new ScanCodeMapping (34, 71, 0, 103), // G - new ScanCodeMapping (34, 71, ConsoleModifiers.Shift, 71), - new ScanCodeMapping (35, 72, 0, 104), // H - new ScanCodeMapping (35, 72, ConsoleModifiers.Shift, 72), - new ScanCodeMapping (36, 74, 0, 106), // J - new ScanCodeMapping (36, 74, ConsoleModifiers.Shift, 74), - new ScanCodeMapping (37, 75, 0, 107), // K - new ScanCodeMapping (37, 75, ConsoleModifiers.Shift, 75), - new ScanCodeMapping (38, 76, 0, 108), // L - new ScanCodeMapping (38, 76, ConsoleModifiers.Shift, 76), - new ScanCodeMapping (39, 192, 0, 231), // Oem3 - new ScanCodeMapping (39, 192, ConsoleModifiers.Shift, 199), - new ScanCodeMapping (40, 222, 0, 186), // Oem7 - new ScanCodeMapping (40, 222, ConsoleModifiers.Shift, 170), - new ScanCodeMapping (41, 220, 0, 92), // Oem5 - new ScanCodeMapping (41, 220, ConsoleModifiers.Shift, 124), - new ScanCodeMapping (42, 16, 0, 0), // LShift - new ScanCodeMapping (42, 16, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (43, 191, 0, 126), // Oem2 - new ScanCodeMapping (43, 191, ConsoleModifiers.Shift, 94), - new ScanCodeMapping (44, 90, 0, 122), // Z - new ScanCodeMapping (44, 90, ConsoleModifiers.Shift, 90), - new ScanCodeMapping (45, 88, 0, 120), // X - new ScanCodeMapping (45, 88, ConsoleModifiers.Shift, 88), - new ScanCodeMapping (46, 67, 0, 99), // C - new ScanCodeMapping (46, 67, ConsoleModifiers.Shift, 67), - new ScanCodeMapping (47, 86, 0, 118), // V - new ScanCodeMapping (47, 86, ConsoleModifiers.Shift, 86), - new ScanCodeMapping (48, 66, 0, 98), // B - new ScanCodeMapping (48, 66, ConsoleModifiers.Shift, 66), - new ScanCodeMapping (49, 78, 0, 110), // N - new ScanCodeMapping (49, 78, ConsoleModifiers.Shift, 78), - new ScanCodeMapping (50, 77, 0, 109), // M - new ScanCodeMapping (50, 77, ConsoleModifiers.Shift, 77), - new ScanCodeMapping (51, 188, 0, 44), // OemComma - new ScanCodeMapping (51, 188, ConsoleModifiers.Shift, 59), - new ScanCodeMapping (52, 190, 0, 46), // OemPeriod - new ScanCodeMapping (52, 190, ConsoleModifiers.Shift, 58), - new ScanCodeMapping (53, 189, 0, 45), // OemMinus - new ScanCodeMapping (53, 189, ConsoleModifiers.Shift, 95), - new ScanCodeMapping (54, 16, 0, 0), // RShift - new ScanCodeMapping (54, 16, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (55, 44, 0, 0), // PrintScreen - new ScanCodeMapping (55, 44, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (56, 18, 0, 0), // Alt - new ScanCodeMapping (56, 18, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (57, 32, 0, 32), // Spacebar - new ScanCodeMapping (57, 32, ConsoleModifiers.Shift, 32), - new ScanCodeMapping (58, 20, 0, 0), // Caps - new ScanCodeMapping (58, 20, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (59, 112, 0, 0), // F1 - new ScanCodeMapping (59, 112, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (60, 113, 0, 0), // F2 - new ScanCodeMapping (60, 113, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (61, 114, 0, 0), // F3 - new ScanCodeMapping (61, 114, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (62, 115, 0, 0), // F4 - new ScanCodeMapping (62, 115, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (63, 116, 0, 0), // F5 - new ScanCodeMapping (63, 116, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (64, 117, 0, 0), // F6 - new ScanCodeMapping (64, 117, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (65, 118, 0, 0), // F7 - new ScanCodeMapping (65, 118, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (66, 119, 0, 0), // F8 - new ScanCodeMapping (66, 119, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (67, 120, 0, 0), // F9 - new ScanCodeMapping (67, 120, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (68, 121, 0, 0), // F10 - new ScanCodeMapping (68, 121, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (69, 144, 0, 0), // Num - new ScanCodeMapping (69, 144, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (70, 145, 0, 0), // Scroll - new ScanCodeMapping (70, 145, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (71, 36, 0, 0), // Home - new ScanCodeMapping (71, 36, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (72, 38, 0, 0), // UpArrow - new ScanCodeMapping (72, 38, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (73, 33, 0, 0), // PageUp - new ScanCodeMapping (73, 33, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (74, 109, 0, 45), // Subtract - new ScanCodeMapping (74, 109, ConsoleModifiers.Shift, 45), - new ScanCodeMapping (75, 37, 0, 0), // LeftArrow - new ScanCodeMapping (75, 37, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (76, 12, 0, 0), // Center - new ScanCodeMapping (76, 12, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (77, 39, 0, 0), // RightArrow - new ScanCodeMapping (77, 39, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (78, 107, 0, 43), // Add - new ScanCodeMapping (78, 107, ConsoleModifiers.Shift, 43), - new ScanCodeMapping (79, 35, 0, 0), // End - new ScanCodeMapping (79, 35, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (80, 40, 0, 0), // DownArrow - new ScanCodeMapping (80, 40, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (81, 34, 0, 0), // PageDown - new ScanCodeMapping (81, 34, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (82, 45, 0, 0), // Insert - new ScanCodeMapping (82, 45, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (83, 46, 0, 0), // Delete - new ScanCodeMapping (83, 46, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (86, 226, 0, 60), // OEM 102 - new ScanCodeMapping (86, 226, ConsoleModifiers.Shift, 62), - new ScanCodeMapping (87, 122, 0, 0), // F11 - new ScanCodeMapping (87, 122, ConsoleModifiers.Shift, 0), - new ScanCodeMapping (88, 123, 0, 0), // F12 - new ScanCodeMapping (88, 123, ConsoleModifiers.Shift, 0) - }; - - /// - /// Decode a that is using . - /// - /// The console key info. - /// The decoded or the . - /// If it's a the may be - /// a or a value. - /// - public static ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) - { - if (consoleKeyInfo.Key != ConsoleKey.Packet) { - return consoleKeyInfo; - } + /// + /// CLEAR key. + /// + CLEAR = 0x0C, - return GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _); - } + /// + /// ENTER key. + /// + RETURN = 0x0D, + + /// + /// SHIFT key. + /// + SHIFT = 0x10, + + /// + /// CTRL key. + /// + CONTROL = 0x11, + + /// + /// ALT key. + /// + MENU = 0x12, + + /// + /// PAUSE key. + /// + PAUSE = 0x13, + + /// + /// CAPS LOCK key. + /// + CAPITAL = 0x14, + + /// + /// IME Kana mode. + /// + KANA = 0x15, + + /// + /// IME Hangul mode. + /// + HANGUL = 0x15, + + /// + /// IME Junja mode. + /// + JUNJA = 0x17, + + /// + /// IME final mode. + /// + FINAL = 0x18, + + /// + /// IME Hanja mode. + /// + HANJA = 0x19, + + /// + /// IME Kanji mode. + /// + KANJI = 0x19, + + /// + /// ESC key. + /// + ESCAPE = 0x1B, + + /// + /// IME convert. + /// + CONVERT = 0x1C, + + /// + /// IME nonconvert. + /// + NONCONVERT = 0x1D, + + /// + /// IME accept. + /// + ACCEPT = 0x1E, + + /// + /// IME mode change request. + /// + MODECHANGE = 0x1F, + + /// + /// SPACEBAR. + /// + SPACE = 0x20, + + /// + /// PAGE UP key. + /// + PRIOR = 0x21, + + /// + /// PAGE DOWN key. + /// + NEXT = 0x22, + + /// + /// END key. + /// + END = 0x23, + + /// + /// HOME key. + /// + HOME = 0x24, + + /// + /// LEFT ARROW key. + /// + LEFT = 0x25, + + /// + /// UP ARROW key. + /// + UP = 0x26, + + /// + /// RIGHT ARROW key. + /// + RIGHT = 0x27, + + /// + /// DOWN ARROW key. + /// + DOWN = 0x28, + + /// + /// SELECT key. + /// + SELECT = 0x29, + + /// + /// PRINT key. + /// + PRINT = 0x2A, + + /// + /// EXECUTE key + /// + EXECUTE = 0x2B, + + /// + /// PRINT SCREEN key + /// + SNAPSHOT = 0x2C, + + /// + /// INS key + /// + INSERT = 0x2D, + + /// + /// DEL key + /// + DELETE = 0x2E, + + /// + /// HELP key + /// + HELP = 0x2F, + + /// + /// Left Windows key (Natural keyboard) + /// + LWIN = 0x5B, + + /// + /// Right Windows key (Natural keyboard) + /// + RWIN = 0x5C, + + /// + /// Applications key (Natural keyboard) + /// + APPS = 0x5D, + + /// + /// Computer Sleep key + /// + SLEEP = 0x5F, + + /// + /// Numeric keypad 0 key + /// + NUMPAD0 = 0x60, + + /// + /// Numeric keypad 1 key + /// + NUMPAD1 = 0x61, + + /// + /// Numeric keypad 2 key + /// + NUMPAD2 = 0x62, + + /// + /// Numeric keypad 3 key + /// + NUMPAD3 = 0x63, + + /// + /// Numeric keypad 4 key + /// + NUMPAD4 = 0x64, + + /// + /// Numeric keypad 5 key + /// + NUMPAD5 = 0x65, + + /// + /// Numeric keypad 6 key + /// + NUMPAD6 = 0x66, + + /// + /// Numeric keypad 7 key + /// + NUMPAD7 = 0x67, + + /// + /// Numeric keypad 8 key + /// + NUMPAD8 = 0x68, + + /// + /// Numeric keypad 9 key + /// + NUMPAD9 = 0x69, + + /// + /// Multiply key + /// + MULTIPLY = 0x6A, + + /// + /// Add key + /// + ADD = 0x6B, + + /// + /// Separator key + /// + SEPARATOR = 0x6C, + + /// + /// Subtract key + /// + SUBTRACT = 0x6D, + + /// + /// Decimal key + /// + DECIMAL = 0x6E, + + /// + /// Divide key + /// + DIVIDE = 0x6F, + + /// + /// F1 key + /// + F1 = 0x70, + + /// + /// F2 key + /// + F2 = 0x71, + + /// + /// F3 key + /// + F3 = 0x72, + + /// + /// F4 key + /// + F4 = 0x73, + + /// + /// F5 key + /// + F5 = 0x74, + + /// + /// F6 key + /// + F6 = 0x75, + + /// + /// F7 key + /// + F7 = 0x76, + + /// + /// F8 key + /// + F8 = 0x77, + + /// + /// F9 key + /// + F9 = 0x78, + + /// + /// F10 key + /// + F10 = 0x79, + + /// + /// F11 key + /// + F11 = 0x7A, + + /// + /// F12 key + /// + F12 = 0x7B, + + /// + /// F13 key + /// + F13 = 0x7C, + + /// + /// F14 key + /// + F14 = 0x7D, + + /// + /// F15 key + /// + F15 = 0x7E, + + /// + /// F16 key + /// + F16 = 0x7F, + + /// + /// F17 key + /// + F17 = 0x80, + + /// + /// F18 key + /// + F18 = 0x81, + + /// + /// F19 key + /// + F19 = 0x82, + + /// + /// F20 key + /// + F20 = 0x83, + + /// + /// F21 key + /// + F21 = 0x84, + + /// + /// F22 key + /// + F22 = 0x85, + + /// + /// F23 key + /// + F23 = 0x86, + + /// + /// F24 key + /// + F24 = 0x87, + + /// + /// NUM LOCK key + /// + NUMLOCK = 0x90, + + /// + /// SCROLL LOCK key + /// + SCROLL = 0x91, + + /// + /// NEC PC-9800 kbd definition: '=' key on numpad + /// + OEM_NEC_EQUAL = 0x92, + + /// + /// Fujitsu/OASYS kbd definition: 'Dictionary' key + /// + OEM_FJ_JISHO = 0x92, + + /// + /// Fujitsu/OASYS kbd definition: 'Unregister word' key + /// + OEM_FJ_MASSHOU = 0x93, + + /// + /// Fujitsu/OASYS kbd definition: 'Register word' key + /// + OEM_FJ_TOUROKU = 0x94, + + /// + /// Fujitsu/OASYS kbd definition: 'Left OYAYUBI' key + /// + OEM_FJ_LOYA = 0x95, + + /// + /// Fujitsu/OASYS kbd definition: 'Right OYAYUBI' key + /// + OEM_FJ_ROYA = 0x96, + + /// + /// Left SHIFT key + /// + LSHIFT = 0xA0, + + /// + /// Right SHIFT key + /// + RSHIFT = 0xA1, + + /// + /// Left CONTROL key + /// + LCONTROL = 0xA2, + + /// + /// Right CONTROL key + /// + RCONTROL = 0xA3, + + /// + /// Left MENU key (Left Alt key) + /// + LMENU = 0xA4, + + /// + /// Right MENU key (Right Alt key) + /// + RMENU = 0xA5, + + /// + /// Browser Back key + /// + BROWSER_BACK = 0xA6, + + /// + /// Browser Forward key + /// + BROWSER_FORWARD = 0xA7, + + /// + /// Browser Refresh key + /// + BROWSER_REFRESH = 0xA8, + + /// + /// Browser Stop key + /// + BROWSER_STOP = 0xA9, + + /// + /// Browser Search key + /// + BROWSER_SEARCH = 0xAA, + + /// + /// Browser Favorites key + /// + BROWSER_FAVORITES = 0xAB, + + /// + /// Browser Home key + /// + BROWSER_HOME = 0xAC, + + /// + /// Volume Mute key + /// + VOLUME_MUTE = 0xAD, + + /// + /// Volume Down key + /// + VOLUME_DOWN = 0xAE, + + /// + /// Volume Up key + /// + VOLUME_UP = 0xAF, + + /// + /// Next Track key + /// + MEDIA_NEXT_TRACK = 0xB0, + + /// + /// Previous Track key + /// + MEDIA_PREV_TRACK = 0xB1, + + /// + /// Stop Media key + /// + MEDIA_STOP = 0xB2, + + /// + /// Play/Pause Media key + /// + MEDIA_PLAY_PAUSE = 0xB3, + + /// + /// Start Mail key + /// + LAUNCH_MAIL = 0xB4, + + /// + /// Select Media key + /// + LAUNCH_MEDIA_SELECT = 0xB5, + + /// + /// Start Application 1 key + /// + LAUNCH_APP1 = 0xB6, + + /// + /// Start Application 2 key + /// + LAUNCH_APP2 = 0xB7, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key + /// + OEM_1 = 0xBA, + + /// + /// For any country/region, the '+' key + /// + OEM_PLUS = 0xBB, + + /// + /// For any country/region, the ',' key + /// + OEM_COMMA = 0xBC, + + /// + /// For any country/region, the '-' key + /// + OEM_MINUS = 0xBD, + + /// + /// For any country/region, the '.' key + /// + OEM_PERIOD = 0xBE, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key + /// + OEM_2 = 0xBF, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key + /// + OEM_3 = 0xC0, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key + /// + OEM_4 = 0xDB, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key + /// + OEM_5 = 0xDC, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key + /// + OEM_6 = 0xDD, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key + /// + OEM_7 = 0xDE, + + /// + /// Used for miscellaneous characters; it can vary by keyboard. + /// + OEM_8 = 0xDF, + + /// + /// 'AX' key on Japanese AX kbd + /// + OEM_AX = 0xE1, + + /// + /// Either the angle bracket key or the backslash key on the RT 102-key keyboard + /// + OEM_102 = 0xE2, + + /// + /// Help key on ICO + /// + ICO_HELP = 0xE3, + + /// + /// 00 key on ICO + /// + ICO_00 = 0xE4, + + /// + /// Process key + /// + PROCESSKEY = 0xE5, + + /// + /// Clear key on ICO + /// + ICO_CLEAR = 0xE6, + + /// + /// Packet key to be used to pass Unicode characters as if they were keystrokes + /// + PACKET = 0xE7, + + /// + /// Reset key + /// + OEM_RESET = 0xE9, + + /// + /// Jump key + /// + OEM_JUMP = 0xEA, + + /// + /// PA1 key + /// + OEM_PA1 = 0xEB, + + /// + /// PA2 key + /// + OEM_PA2 = 0xEC, + + /// + /// PA3 key + /// + OEM_PA3 = 0xED, + + /// + /// WsCtrl key + /// + OEM_WSCTRL = 0xEE, + + /// + /// CuSel key + /// + OEM_CUSEL = 0xEF, + + /// + /// Attn key + /// + OEM_ATTN = 0xF0, + + /// + /// Finish key + /// + OEM_FINISH = 0xF1, + + /// + /// Copy key + /// + OEM_COPY = 0xF2, + + /// + /// Auto key + /// + OEM_AUTO = 0xF3, + + /// + /// Enlw key + /// + OEM_ENLW = 0xF4, + + /// + /// BackTab key + /// + OEM_BACKTAB = 0xF5, + + /// + /// Attn key + /// + ATTN = 0xF6, + + /// + /// CrSel key + /// + CRSEL = 0xF7, + + /// + /// ExSel key + /// + EXSEL = 0xF8, + + /// + /// Erase EOF key + /// + EREOF = 0xF9, + + /// + /// Play key + /// + PLAY = 0xFA, + + /// + /// Zoom key + /// + ZOOM = 0xFB, + + /// + /// Reserved + /// + NONAME = 0xFC, + + /// + /// PA1 key + /// + PA1 = 0xFD, + + /// + /// Clear key + /// + OEM_CLEAR = 0xFE + } + + // BUGBUG: This database makes no sense. It is not possible to map a VK code to a character without knowing the keyboard layout + // It should be deleted. + static HashSet _scanCodes = new HashSet { + new (1, VK.ESCAPE, 0, '\u001B'), // Escape + new (1, VK.ESCAPE, ConsoleModifiers.Shift, '\u001B'), + new (2, (VK)'1', 0, '1'), // D1 + new (2, (VK)'1', ConsoleModifiers.Shift, '!'), + new (3, (VK)'2', 0, '2'), // D2 + new (3, (VK)'2', ConsoleModifiers.Shift, '\"'), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (") + new (3, (VK)'2', ConsoleModifiers.Alt | ConsoleModifiers.Control, '@'), + new (4, (VK)'3', 0, '3'), // D3 + new (4, (VK)'3', ConsoleModifiers.Shift, '#'), + new (4, (VK)'3', ConsoleModifiers.Alt | ConsoleModifiers.Control, '£'), + new (5, (VK)'4', 0, '4'), // D4 + new (5, (VK)'4', ConsoleModifiers.Shift, '$'), + new (5, (VK)'4', ConsoleModifiers.Alt | ConsoleModifiers.Control, '§'), + new (6, (VK)'5', 0, '5'), // D5 + new (6, (VK)'5', ConsoleModifiers.Shift, '%'), + new (6, (VK)'5', ConsoleModifiers.Alt | ConsoleModifiers.Control, '€'), + new (7, (VK)'6', 0, '6'), // D6 + new (7, (VK)'6', ConsoleModifiers.Shift, '&'), + new (8, (VK)'7', 0, '7'), // D7 + new (8, (VK)'7', ConsoleModifiers.Shift, '/'), + new (8, (VK)'7', ConsoleModifiers.Alt | ConsoleModifiers.Control, '{'), + new (9, (VK)'8', 0, '8'), // D8 + new (9, (VK)'8', ConsoleModifiers.Shift, '('), + new (9, (VK)'8', ConsoleModifiers.Alt | ConsoleModifiers.Control, '['), + new (10, (VK)'9', 0, '9'), // D9 + new (10, (VK)'9', ConsoleModifiers.Shift, ')'), + new (10, (VK)'9', ConsoleModifiers.Alt | ConsoleModifiers.Control, ']'), + new (11, (VK)'0', 0, '0'), // D0 + new (11, (VK)'0', ConsoleModifiers.Shift, '='), + new (11, (VK)'0', ConsoleModifiers.Alt | ConsoleModifiers.Control, '}'), + new (12, VK.OEM_4, 0, '\''), // Oem4 + new (12, VK.OEM_4, ConsoleModifiers.Shift, '?'), + new (13, VK.OEM_6, 0, '+'), // Oem6 + new (13, VK.OEM_6, ConsoleModifiers.Shift, '*'), + new (14, VK.BACK, 0, '\u0008'), // Backspace + new (14, VK.BACK, ConsoleModifiers.Shift, '\u0008'), + new (15, VK.TAB, 0, '\u0009'), // Tab + new (15, VK.TAB, ConsoleModifiers.Shift, '\u000F'), + new (16, (VK)'Q', 0, 'q'), // Q + new (16, (VK)'Q', ConsoleModifiers.Shift, 'Q'), + new (17, (VK)'W', 0, 'w'), // W + new (17, (VK)'W', ConsoleModifiers.Shift, 'W'), + new (18, (VK)'E', 0, 'e'), // E + new (18, (VK)'E', ConsoleModifiers.Shift, 'E'), + new (19, (VK)'R', 0, 'r'), // R + new (19, (VK)'R', ConsoleModifiers.Shift, 'R'), + new (20, (VK)'T', 0, 't'), // T + new (20, (VK)'T', ConsoleModifiers.Shift, 'T'), + new (21, (VK)'Y', 0, 'y'), // Y + new (21, (VK)'Y', ConsoleModifiers.Shift, 'Y'), + new (22, (VK)'U', 0, 'u'), // U + new (22, (VK)'U', ConsoleModifiers.Shift, 'U'), + new (23, (VK)'I', 0, 'i'), // I + new (23, (VK)'I', ConsoleModifiers.Shift, 'I'), + new (24, (VK)'O', 0, 'o'), // O + new (24, (VK)'O', ConsoleModifiers.Shift, 'O'), + new (25, (VK)'P', 0, 'p'), // P + new (25, (VK)'P', ConsoleModifiers.Shift, 'P'), + new (26, VK.OEM_PLUS, 0, '+'), // OemPlus + new (26, VK.OEM_PLUS, ConsoleModifiers.Shift, '*'), + new (26, VK.OEM_PLUS, ConsoleModifiers.Alt | ConsoleModifiers.Control, '¨'), + new (27, VK.OEM_1, 0, '´'), // Oem1 + new (27, VK.OEM_1, ConsoleModifiers.Shift, '`'), + new (28, VK.RETURN, 0, '\u000D'), // Enter + new (28, VK.RETURN, ConsoleModifiers.Shift, '\u000D'), + new (29, VK.CONTROL, 0, '\0'), // Control + new (29, VK.CONTROL, ConsoleModifiers.Shift, '\0'), + new (30, (VK)'A', 0, 'a'), // A + new (30, (VK)'A', ConsoleModifiers.Shift, 'A'), + new (31, (VK)'S', 0, 's'), // S + new (31, (VK)'S', ConsoleModifiers.Shift, 'S'), + new (32, (VK)'D', 0, 'd'), // D + new (32, (VK)'D', ConsoleModifiers.Shift, 'D'), + new (33, (VK)'F', 0, 'f'), // F + new (33, (VK)'F', ConsoleModifiers.Shift, 'F'), + new (34, (VK)'G', 0, 'g'), // G + new (34, (VK)'G', ConsoleModifiers.Shift, 'G'), + new (35, (VK)'H', 0, 'h'), // H + new (35, (VK)'H', ConsoleModifiers.Shift, 'H'), + new (36, (VK)'J', 0, 'j'), // J + new (36, (VK)'J', ConsoleModifiers.Shift, 'J'), + new (37, (VK)'K', 0, 'k'), // K + new (37, (VK)'K', ConsoleModifiers.Shift, 'K'), + new (38, (VK)'L', 0, 'l'), // L + new (38, (VK)'L', ConsoleModifiers.Shift, 'L'), + new (39, VK.OEM_3, 0, '`'), // Oem3 (Backtick/Grave) + new (39, VK.OEM_3, ConsoleModifiers.Shift, '~'), + new (40, VK.OEM_7, 0, '\''), // Oem7 (Single Quote) + new (40, VK.OEM_7, ConsoleModifiers.Shift, '\"'), + new (41, VK.OEM_5, 0, '\\'), // Oem5 (Backslash) + new (41, VK.OEM_5, ConsoleModifiers.Shift, '|'), + new (42, VK.LSHIFT, 0, '\0'), // Left Shift + new (42, VK.LSHIFT, ConsoleModifiers.Shift, '\0'), + new (43, VK.OEM_2, 0, '/'), // Oem2 (Forward Slash) + new (43, VK.OEM_2, ConsoleModifiers.Shift, '?'), + new (44, (VK)'Z', 0, 'z'), // Z + new (44, (VK)'Z', ConsoleModifiers.Shift, 'Z'), + new (45, (VK)'X', 0, 'x'), // X + new (45, (VK)'X', ConsoleModifiers.Shift, 'X'), + new (46, (VK)'C', 0, 'c'), // C + new (46, (VK)'C', ConsoleModifiers.Shift, 'C'), + new (47, (VK)'V', 0, 'v'), // V + new (47, (VK)'V', ConsoleModifiers.Shift, 'V'), + new (48, (VK)'B', 0, 'b'), // B + new (48, (VK)'B', ConsoleModifiers.Shift, 'B'), + new (49, (VK)'N', 0, 'n'), // N + new (49, (VK)'N', ConsoleModifiers.Shift, 'N'), + new (50, (VK)'M', 0, 'm'), // M + new (50, (VK)'M', ConsoleModifiers.Shift, 'M'), + new (51, VK.OEM_COMMA, 0, ','), // OemComma + new (51, VK.OEM_COMMA, ConsoleModifiers.Shift, '<'), + new (52, VK.OEM_PERIOD, 0, '.'), // OemPeriod + new (52, VK.OEM_PERIOD, ConsoleModifiers.Shift, '>'), + new (53, VK.OEM_MINUS, 0, '-'), // OemMinus + new (53, VK.OEM_MINUS, ConsoleModifiers.Shift, '_'), + new (54, VK.RSHIFT, 0, '\0'), // Right Shift + new (54, VK.RSHIFT, ConsoleModifiers.Shift, '\0'), + new (55, VK.PRINT, 0, '\0'), // Print Screen + new (55, VK.PRINT, ConsoleModifiers.Shift, '\0'), + new (56, VK.LMENU, 0, '\0'), // Alt + new (56, VK.LMENU, ConsoleModifiers.Shift, '\0'), + new (57, VK.SPACE, 0, ' '), // Spacebar + new (57, VK.SPACE, ConsoleModifiers.Shift, ' '), + new (58, VK.CAPITAL, 0, '\0'), // Caps Lock + new (58, VK.CAPITAL, ConsoleModifiers.Shift, '\0'), + new (59, VK.F1, 0, '\0'), // F1 + new (59, VK.F1, ConsoleModifiers.Shift, '\0'), + new (60, VK.F2, 0, '\0'), // F2 + new (60, VK.F2, ConsoleModifiers.Shift, '\0'), + new (61, VK.F3, 0, '\0'), // F3 + new (61, VK.F3, ConsoleModifiers.Shift, '\0'), + new (62, VK.F4, 0, '\0'), // F4 + new (62, VK.F4, ConsoleModifiers.Shift, '\0'), + new (63, VK.F5, 0, '\0'), // F5 + new (63, VK.F5, ConsoleModifiers.Shift, '\0'), + new (64, VK.F6, 0, '\0'), // F6 + new (64, VK.F6, ConsoleModifiers.Shift, '\0'), + new (65, VK.F7, 0, '\0'), // F7 + new (65, VK.F7, ConsoleModifiers.Shift, '\0'), + new (66, VK.F8, 0, '\0'), // F8 + new (66, VK.F8, ConsoleModifiers.Shift, '\0'), + new (67, VK.F9, 0, '\0'), // F9 + new (67, VK.F9, ConsoleModifiers.Shift, '\0'), + new (68, VK.F10, 0, '\0'), // F10 + new (68, VK.F10, ConsoleModifiers.Shift, '\0'), + new (69, VK.NUMLOCK, 0, '\0'), // Num Lock + new (69, VK.NUMLOCK, ConsoleModifiers.Shift, '\0'), + new (70, VK.SCROLL, 0, '\0'), // Scroll Lock + new (70, VK.SCROLL, ConsoleModifiers.Shift, '\0'), + new (71, VK.HOME, 0, '\0'), // Home + new (71, VK.HOME, ConsoleModifiers.Shift, '\0'), + new (72, VK.UP, 0, '\0'), // Up Arrow + new (72, VK.UP, ConsoleModifiers.Shift, '\0'), + new (73, VK.PRIOR, 0, '\0'), // Page Up + new (73, VK.PRIOR, ConsoleModifiers.Shift, '\0'), + new (74, VK.SUBTRACT, 0, '-'), // Subtract (Num Pad '-') + new (74, VK.SUBTRACT, ConsoleModifiers.Shift, '-'), + new (75, VK.LEFT, 0, '\0'), // Left Arrow + new (75, VK.LEFT, ConsoleModifiers.Shift, '\0'), + new (76, VK.CLEAR, 0, '\0'), // Center key (Num Pad 5 with Num Lock off) + new (76, VK.CLEAR, ConsoleModifiers.Shift, '\0'), + new (77, VK.RIGHT, 0, '\0'), // Right Arrow + new (77, VK.RIGHT, ConsoleModifiers.Shift, '\0'), + new (78, VK.ADD, 0, '+'), // Add (Num Pad '+') + new (78, VK.ADD, ConsoleModifiers.Shift, '+'), + new (79, VK.END, 0, '\0'), // End + new (79, VK.END, ConsoleModifiers.Shift, '\0'), + new (80, VK.DOWN, 0, '\0'), // Down Arrow + new (80, VK.DOWN, ConsoleModifiers.Shift, '\0'), + new (81, VK.NEXT, 0, '\0'), // Page Down + new (81, VK.NEXT, ConsoleModifiers.Shift, '\0'), + new (82, VK.INSERT, 0, '\0'), // Insert + new (82, VK.INSERT, ConsoleModifiers.Shift, '\0'), + new (83, VK.DELETE, 0, '\0'), // Delete + new (83, VK.DELETE, ConsoleModifiers.Shift, '\0'), + new (86, VK.OEM_102, 0, '<'), // OEM 102 (Typically '<' or '|' key next to Left Shift) + new (86, VK.OEM_102, ConsoleModifiers.Shift, '>'), + new (87, VK.F11, 0, '\0'), // F11 + new (87, VK.F11, ConsoleModifiers.Shift, '\0'), + new (88, VK.F12, 0, '\0'), // F12 + new (88, VK.F12, ConsoleModifiers.Shift, '\0') + }; + + /// + /// Decode a that is using . + /// + /// The console key info. + /// The decoded or the . + /// If it's a the may be + /// a or a value. + /// + public static ConsoleKeyInfo DecodeVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + if (consoleKeyInfo.Key != ConsoleKey.Packet) { + return consoleKeyInfo; + } + + return GetConsoleKeyInfoFromKeyChar (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _); + } + + /// + /// Encode the with the + /// if the first a byte length, otherwise only the KeyChar is considered and searched on the database. + /// + /// The console key info. + /// The encoded KeyChar with the Key if both can be shifted, otherwise only the KeyChar. + /// This is useful to use with the . + public static char EncodeKeyCharForVKPacket (ConsoleKeyInfo consoleKeyInfo) + { + char keyChar = consoleKeyInfo.KeyChar; + ConsoleKey consoleKey = consoleKeyInfo.Key; + if (keyChar != 0 && consoleKeyInfo.KeyChar < byte.MaxValue && consoleKey == ConsoleKey.None) { + // try to get the ConsoleKey + var scode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyChar); + if (scode != null) { + consoleKey = (ConsoleKey)scode.VirtualKey; + } + } + if (keyChar < byte.MaxValue && consoleKey != ConsoleKey.None) { + keyChar = (char)(consoleKeyInfo.KeyChar << 8 | (byte)consoleKey); + } + + return keyChar; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 5c39cadc61..c0b3db7daa 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -14,12 +14,12 @@ namespace Terminal.Gui; /// /// This is the Curses driver for the gui.cs/Terminal framework. /// -internal class CursesDriver : ConsoleDriver { - +class CursesDriver : ConsoleDriver { public override int Cols { get => Curses.Cols; internal set => Curses.Cols = value; } + public override int Rows { get => Curses.Lines; internal set => Curses.Lines = value; @@ -30,6 +30,7 @@ public override int Rows { public override string GetVersionInfo () => $"{Curses.curses_version ()}"; UnixMainLoop _mainLoopDriver = null; + public override bool SupportsTrueColor => false; object _processInputToken; @@ -132,11 +133,9 @@ public override void Move (int col, int row) } } - public override bool IsRuneSupported (Rune rune) - { + public override bool IsRuneSupported (Rune rune) => // See Issue #2615 - CursesDriver is broken with non-BMP characters - return base.IsRuneSupported (rune) && rune.IsBmp; - } + base.IsRuneSupported (rune) && rune.IsBmp; public override void Refresh () { @@ -153,7 +152,6 @@ internal void ProcessWinChange () } #region Color Handling - /// /// Creates an Attribute from the provided curses-based foreground and background color numbers /// @@ -162,16 +160,17 @@ internal void ProcessWinChange () /// static Attribute MakeColor (short foreground, short background) { - var v = (short)((int)foreground | background << 4); + short v = (short)((int)foreground | background << 4); // TODO: for TrueColor - Use InitExtendedPair Curses.InitColorPair (v, foreground, background); return new Attribute ( - platformColor: Curses.ColorPair (v), - foreground: CursesColorNumberToColorName (foreground), - background: CursesColorNumberToColorName (background)); + Curses.ColorPair (v), + CursesColorNumberToColorName (foreground), + CursesColorNumberToColorName (background)); } + /// /// /// In the CursesDriver, colors are encoded as an int. /// The foreground color is stored in the most significant 4 bits, @@ -184,9 +183,9 @@ public override Attribute MakeColor (Color foreground, Color background) return MakeColor (ColorNameToCursesColorNumber (foreground.ColorName), ColorNameToCursesColorNumber (background.ColorName)); } else { return new Attribute ( - platformColor: 0, - foreground: foreground, - background: background); + 0, + foreground, + background); } } @@ -267,7 +266,6 @@ static ColorName CursesColorNumberToColorName (short color) } throw new ArgumentException ("Invalid curses color code"); } - #endregion public override void UpdateCursor () @@ -367,8 +365,8 @@ static KeyCode MapCursesKey (int cursesKey) case Curses.KeyEnd: return KeyCode.End; case Curses.KeyNPage: return KeyCode.PageDown; case Curses.KeyPPage: return KeyCode.PageUp; - case Curses.KeyDeleteChar: return KeyCode.DeleteChar; - case Curses.KeyInsertChar: return KeyCode.InsertChar; + case Curses.KeyDeleteChar: return KeyCode.Delete; + case Curses.KeyInsertChar: return KeyCode.Insert; case Curses.KeyTab: return KeyCode.Tab; case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask; case Curses.KeyBackspace: return KeyCode.Backspace; @@ -423,12 +421,12 @@ static KeyCode MapCursesKey (int cursesKey) internal void ProcessInput () { int wch; - var code = Curses.get_wch (out wch); + int code = Curses.get_wch (out wch); //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}"); if (code == Curses.ERR) { return; } - KeyCode k = KeyCode.Null; + var k = KeyCode.Null; if (code == Curses.KEY_CODE_YES) { while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) { @@ -443,11 +441,11 @@ internal void ProcessInput () while (wch2 == Curses.KeyMouse) { Key kea = null; - ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { - new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false), - new ConsoleKeyInfo ('[', 0, false, false, false), - new ConsoleKeyInfo ('<', 0, false, false, false) - }; + var cki = new ConsoleKeyInfo [] { + new ((char)KeyCode.Esc, 0, false, false, false), + new ('[', 0, false, false, false), + new ('<', 0, false, false, false) + }; code = 0; HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki); } @@ -503,27 +501,27 @@ internal void ProcessInput () } else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) { k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0)); } else if (wch2 == Curses.KeyCSI) { - ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { - new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false), - new ConsoleKeyInfo ('[', 0, false, false, false) - }; + var cki = new ConsoleKeyInfo [] { + new ((char)KeyCode.Esc, 0, false, false, false), + new ('[', 0, false, false, false) + }; HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); return; } else { // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) { - k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~((int)KeyCode.CtrlMask))); + k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask)); } if (wch2 == 0) { k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space; } else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) { k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space; } else if (wch2 < 256) { - k = (KeyCode)wch2 | KeyCode.AltMask; + k = (KeyCode)wch2;// | KeyCode.AltMask; } else { k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2); } - } + } key = new Key (k); } else { key = new Key (KeyCode.Esc); @@ -545,9 +543,11 @@ internal void ProcessInput () } } else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) { k = (KeyCode)wch | KeyCode.ShiftMask; - } else if (wch <= 'z') { - k = (KeyCode)wch & ~KeyCode.Space; } + + if (wch == '\n' || wch == '\r') { + k = KeyCode.Enter; + } OnKeyDown (new Key (k)); OnKeyUp (new Key (k)); } @@ -561,7 +561,7 @@ void HandleEscSeqResponse (ref int code, ref KeyCode k, ref int wch2, ref Key ke code = Curses.get_wch (out wch2); var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false); if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) { - EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List mouseFlags, out Point pos, out _, ProcessMouseEvent); + EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out var mouseFlags, out var pos, out _, ProcessMouseEvent); if (isKeyMouse) { foreach (var mf in mouseFlags) { ProcessMouseEvent (mf, pos); @@ -572,9 +572,8 @@ void HandleEscSeqResponse (ref int code, ref KeyCode k, ref int wch2, ref Key ke false, false, false), cki); } } else { - k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _); - k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k); - keyEventArgs = new (k); + k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); + keyEventArgs = new Key (k); OnKeyDown (keyEventArgs); } } else { @@ -587,36 +586,27 @@ void HandleEscSeqResponse (ref int code, ref KeyCode k, ref int wch2, ref Key ke void ProcessMouseEvent (MouseFlags mouseFlag, Point pos) { - bool WasButtonReleased (MouseFlags flag) - { - return flag.HasFlag (MouseFlags.Button1Released) || - flag.HasFlag (MouseFlags.Button2Released) || - flag.HasFlag (MouseFlags.Button3Released) || - flag.HasFlag (MouseFlags.Button4Released); - } - - bool IsButtonNotPressed (MouseFlags flag) - { - return !flag.HasFlag (MouseFlags.Button1Pressed) && - !flag.HasFlag (MouseFlags.Button2Pressed) && - !flag.HasFlag (MouseFlags.Button3Pressed) && - !flag.HasFlag (MouseFlags.Button4Pressed); - } - - bool IsButtonClickedOrDoubleClicked (MouseFlags flag) - { - return flag.HasFlag (MouseFlags.Button1Clicked) || - flag.HasFlag (MouseFlags.Button2Clicked) || - flag.HasFlag (MouseFlags.Button3Clicked) || - flag.HasFlag (MouseFlags.Button4Clicked) || - flag.HasFlag (MouseFlags.Button1DoubleClicked) || - flag.HasFlag (MouseFlags.Button2DoubleClicked) || - flag.HasFlag (MouseFlags.Button3DoubleClicked) || - flag.HasFlag (MouseFlags.Button4DoubleClicked); - } - - if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || - (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) { + bool WasButtonReleased (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Released) || + flag.HasFlag (MouseFlags.Button2Released) || + flag.HasFlag (MouseFlags.Button3Released) || + flag.HasFlag (MouseFlags.Button4Released); + + bool IsButtonNotPressed (MouseFlags flag) => !flag.HasFlag (MouseFlags.Button1Pressed) && + !flag.HasFlag (MouseFlags.Button2Pressed) && + !flag.HasFlag (MouseFlags.Button3Pressed) && + !flag.HasFlag (MouseFlags.Button4Pressed); + + bool IsButtonClickedOrDoubleClicked (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Clicked) || + flag.HasFlag (MouseFlags.Button2Clicked) || + flag.HasFlag (MouseFlags.Button3Clicked) || + flag.HasFlag (MouseFlags.Button4Clicked) || + flag.HasFlag (MouseFlags.Button1DoubleClicked) || + flag.HasFlag (MouseFlags.Button2DoubleClicked) || + flag.HasFlag (MouseFlags.Button3DoubleClicked) || + flag.HasFlag (MouseFlags.Button4DoubleClicked); + + if (WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags) || + IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0) { return; } @@ -638,7 +628,7 @@ public static bool Is_WSL_Platform () // // If xclip is installed on Linux under WSL, this will return true. // return false; //} - var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true); + (int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true); if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) { return true; } @@ -675,8 +665,9 @@ public override bool GetCursorVisibility (out CursorVisibility visibility) { visibility = CursorVisibility.Invisible; - if (!_currentCursorVisibility.HasValue) + if (!_currentCursorVisibility.HasValue) { return false; + } visibility = _currentCursorVisibility.Value; @@ -691,11 +682,11 @@ public override bool SetCursorVisibility (CursorVisibility visibility) } if (!RunningUnitTests) { - Curses.curs_set (((int)visibility >> 16) & 0x000000FF); + Curses.curs_set ((int)visibility >> 16 & 0x000000FF); } if (visibility != CursorVisibility.Invisible) { - Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))); + Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)((int)visibility >> 24 & 0xFF))); } _currentCursorVisibility = visibility; @@ -704,17 +695,14 @@ public override bool SetCursorVisibility (CursorVisibility visibility) } /// - public override bool EnsureCursorVisibility () - { - return false; - } + public override bool EnsureCursorVisibility () => false; public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) { KeyCode key; if (consoleKey == ConsoleKey.Packet) { - ConsoleModifiers mod = new ConsoleModifiers (); + var mod = new ConsoleModifiers (); if (shift) { mod |= ConsoleModifiers.Shift; } @@ -724,11 +712,9 @@ public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, if (control) { mod |= ConsoleModifiers.Control; } - var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyChar, mod, out _); - key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)cKeyInfo.Key, out bool mappable); - if (mappable) { - key = (KeyCode)cKeyInfo.KeyChar; - } + var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); + cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); + key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo); } else { key = (KeyCode)keyChar; } @@ -737,16 +723,14 @@ public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, OnKeyUp (new Key (key)); //OnKeyPressed (new KeyEventArgsEventArgs (key)); } - - } -internal static class Platform { +static class Platform { [DllImport ("libc")] - static extern int uname (IntPtr buf); + extern static int uname (IntPtr buf); [DllImport ("libc")] - static extern int killpg (int pgrp, int pid); + extern static int killpg (int pgrp, int pid); static int _suspendSignal; @@ -793,7 +777,7 @@ static int GetSuspendSignal () /// Suspends the process by sending SIGTSTP to itself /// /// The suspend. - static public bool Suspend () + public static bool Suspend () { int signal = GetSuspendSignal (); if (signal == -1) { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs index 4cec5cb019..5828f96c94 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs @@ -800,7 +800,7 @@ public static void PushMockKeyPress (KeyCode key) { MockKeyPresses.Push (new ConsoleKeyInfo ( (char)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask), - ConsoleKeyMapping.GetConsoleKeyFromKey (key).Key, + ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key).Key, key.HasFlag (KeyCode.ShiftMask), key.HasFlag (KeyCode.AltMask), key.HasFlag (KeyCode.CtrlMask))); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 30ee5b5a4b..74a57dd805 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -205,39 +205,39 @@ KeyCode MapKey (ConsoleKeyInfo keyInfo) { switch (keyInfo.Key) { case ConsoleKey.Escape: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Esc); case ConsoleKey.Tab: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Tab); case ConsoleKey.Clear: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Clear); case ConsoleKey.Home: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Home); case ConsoleKey.End: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.End); case ConsoleKey.LeftArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorLeft); case ConsoleKey.RightArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorRight); case ConsoleKey.UpArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorUp); case ConsoleKey.DownArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorDown); case ConsoleKey.PageUp: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageUp); case ConsoleKey.PageDown: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageDown); case ConsoleKey.Enter: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Enter); case ConsoleKey.Spacebar: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar); case ConsoleKey.Backspace: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Backspace); case ConsoleKey.Delete: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Delete); case ConsoleKey.Insert: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Insert); case ConsoleKey.PrintScreen: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PrintScreen); case ConsoleKey.Oem1: case ConsoleKey.Oem2: @@ -256,25 +256,25 @@ KeyCode MapKey (ConsoleKeyInfo keyInfo) return KeyCode.Null; } - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar)); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar)); } var key = keyInfo.Key; if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { var delta = key - ConsoleKey.A; if (keyInfo.KeyChar != (uint)key) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)keyInfo.KeyChar); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); } if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta)); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta)); } var alphaBase = ((keyInfo.Modifiers != ConsoleModifiers.Shift)) ? 'A' : 'a'; return (KeyCode)((uint)alphaBase + delta); } - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar)); + return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar)); } private CursorVisibility _savedCursorVisibility; @@ -282,7 +282,7 @@ KeyCode MapKey (ConsoleKeyInfo keyInfo) void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo) { if (consoleKeyInfo.Key == ConsoleKey.Packet) { - consoleKeyInfo = ConsoleKeyMapping.FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); + consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); } var map = MapKey (consoleKeyInfo); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 5aaff3439c..58eae6264f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -7,13 +7,15 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Text; -using Terminal.Gui.ConsoleDrivers; +using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; using static Terminal.Gui.NetEvents; +using static Terminal.Gui.WindowsConsole; namespace Terminal.Gui; + class NetWinVTConsole { IntPtr _inputHandle, _outputHandle, _errorHandle; uint _originalInputConsoleMode, _originalOutputConsoleMode, _originalErrorConsoleMode; @@ -49,7 +51,7 @@ public NetWinVTConsole () throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}."); } _originalErrorConsoleMode = mode; - if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) { + if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN) { mode |= DISABLE_NEWLINE_AUTO_RETURN; if (!SetConsoleMode (_errorHandle, mode)) { throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}."); @@ -93,28 +95,28 @@ public void Cleanup () const uint ENABLE_LVB_GRID_WORLDWIDE = 10; [DllImport ("kernel32.dll", SetLastError = true)] - static extern IntPtr GetStdHandle (int nStdHandle); + extern static IntPtr GetStdHandle (int nStdHandle); [DllImport ("kernel32.dll")] - static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); + extern static bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); [DllImport ("kernel32.dll")] - static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); + extern static bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); [DllImport ("kernel32.dll")] - static extern uint GetLastError (); + extern static uint GetLastError (); } -internal class NetEvents : IDisposable { - readonly ManualResetEventSlim _inputReady = new ManualResetEventSlim (false); +class NetEvents : IDisposable { + readonly ManualResetEventSlim _inputReady = new (false); CancellationTokenSource _inputReadyCancellationTokenSource; - readonly ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false); + readonly ManualResetEventSlim _waitForStart = new (false); //CancellationTokenSource _waitForStartCancellationTokenSource; - readonly ManualResetEventSlim _winChange = new ManualResetEventSlim (false); + readonly ManualResetEventSlim _winChange = new (false); - readonly Queue _inputQueue = new Queue (); + readonly Queue _inputQueue = new (); readonly ConsoleDriver _consoleDriver; ConsoleKeyInfo [] _cki; @@ -123,7 +125,7 @@ internal class NetEvents : IDisposable { #if PROCESS_REQUEST bool _neededProcessRequest; #endif - public EscSeqRequests EscSeqRequests { get; } = new EscSeqRequests (); + public EscSeqRequests EscSeqRequests { get; } = new (); public NetEvents (ConsoleDriver consoleDriver) { @@ -209,17 +211,19 @@ void ProcessInputQueue () } catch (OperationCanceledException) { return; } - if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) - || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) { + if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq + || consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) { if (_cki == null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) { _cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.Esc, 0, - false, false, false), _cki); + false, false, false), _cki); } _isEscSeq = true; newConsoleKeyInfo = consoleKeyInfo; _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); - if (Console.KeyAvailable) continue; + if (Console.KeyAvailable) { + continue; + } ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; _isEscSeq = false; @@ -270,10 +274,10 @@ void RequestWindowSize (CancellationToken cancellationToken) buffWidth = _consoleDriver.Cols; } if (EnqueueWindowSizeEvent ( - Math.Max (Console.WindowHeight, 0), - Math.Max (Console.WindowWidth, 0), - buffHeight, - buffWidth)) { + Math.Max (Console.WindowHeight, 0), + Math.Max (Console.WindowWidth, 0), + buffHeight, + buffWidth)) { return; } @@ -306,9 +310,11 @@ void RequestWindowSize (CancellationToken cancellationToken) /// bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth) { - if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false; - var w = Math.Max (winWidth, 0); - var h = Math.Max (winHeight, 0); + if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) { + return false; + } + int w = Math.Max (winWidth, 0); + int h = Math.Max (winHeight, 0); _inputQueue.Enqueue (new InputResult () { EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent () { @@ -323,10 +329,10 @@ void ProcessRequestResponse (ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKe { // isMouse is true if it's CSI<, false otherwise EscSeqUtils.DecodeEscSeq (EscSeqRequests, ref newConsoleKeyInfo, ref key, cki, ref mod, - out var c1Control, out var code, out var values, out var terminating, - out var isMouse, out var mouseFlags, - out var pos, out var isReq, - (f, p) => HandleMouseEvent (MapMouseFlags (f), p)); + out string c1Control, out string code, out string [] values, out string terminating, + out bool isMouse, out var mouseFlags, + out var pos, out bool isReq, + (f, p) => HandleMouseEvent (MapMouseFlags (f), p)); if (isMouse) { foreach (var mf in mouseFlags) { @@ -343,7 +349,7 @@ void ProcessRequestResponse (ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKe MouseButtonState MapMouseFlags (MouseFlags mouseFlags) { MouseButtonState mbs = default; - foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) { + foreach (object flag in Enum.GetValues (mouseFlags.GetType ())) { if (mouseFlags.HasFlag ((MouseFlags)flag)) { switch (flag) { case MouseFlags.Button1Pressed: @@ -446,7 +452,7 @@ void HandleRequestResponseEvent (string c1Control, string code, string [] values switch (terminating) { // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator: - Point point = new Point { + var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 }; @@ -469,10 +475,10 @@ void HandleRequestResponseEvent (string c1Control, string code, string [] values switch (values [0]) { case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue: EnqueueWindowSizeEvent ( - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0), - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0)); + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0), + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0)); break; default: EnqueueRequestResponseEvent (c1Control, code, values, terminating); @@ -489,7 +495,7 @@ void HandleRequestResponseEvent (string c1Control, string code, string [] values void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) { - EventType eventType = EventType.RequestResponse; + var eventType = EventType.RequestResponse; var requestRespEv = new RequestResponseEvent () { ResultTuple = (c1Control, code, values, terminating) }; @@ -501,9 +507,9 @@ void EnqueueRequestResponseEvent (string c1Control, string code, string [] value void HandleMouseEvent (MouseButtonState buttonState, Point pos) { - MouseEvent mouseEvent = new MouseEvent () { + var mouseEvent = new MouseEvent () { Position = pos, - ButtonState = buttonState, + ButtonState = buttonState }; _inputQueue.Enqueue (new InputResult () { @@ -581,11 +587,40 @@ public struct InputResult { public WindowSizeEvent WindowSizeEvent; public WindowPositionEvent WindowPositionEvent; public RequestResponseEvent RequestResponseEvent; + + public override readonly string ToString () + { + return EventType switch { + EventType.Key => ToString (ConsoleKeyInfo), + EventType.Mouse => MouseEvent.ToString (), + //EventType.WindowSize => WindowSize.ToString (), + //EventType.RequestResponse => RequestResponse.ToString (), + _ => "Unknown event type: " + EventType + }; + } + + /// + /// Prints a ConsoleKeyInfoEx structure + /// + /// + /// + public readonly string ToString (ConsoleKeyInfo cki) + { + var ke = new Key ((KeyCode)cki.KeyChar); + var sb = new StringBuilder (); + sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})"); + sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); + sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) "); + var s = sb.ToString ().TrimEnd (',').TrimEnd (' '); + return $"[ConsoleKeyInfo({s})]"; + } } void HandleKeyboardEvent (ConsoleKeyInfo cki) { - InputResult inputResult = new InputResult { + var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki }; @@ -611,7 +646,7 @@ public void Dispose () } } -internal class NetDriver : ConsoleDriver { +class NetDriver : ConsoleDriver { const int COLOR_BLACK = 30; const int COLOR_RED = 31; const int COLOR_GREEN = 32; @@ -631,9 +666,10 @@ internal class NetDriver : ConsoleDriver { NetMainLoop _mainLoopDriver = null; - public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931); + public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || IsWinPlatform && Environment.OSVersion.Version.Build >= 14931; public NetWinVTConsole NetWinConsole { get; private set; } + public bool IsWinPlatform { get; private set; } internal override MainLoop Init () @@ -709,6 +745,21 @@ internal override void End () } } + + #region Size and Position Handling + volatile bool _winSizeChanging; + + void SetWindowPosition (int col, int row) + { + if (!RunningUnitTests) { + Top = Console.WindowTop; + Left = Console.WindowLeft; + } else { + Top = row; + Left = col; + } + } + public virtual void ResizeScreen () { // Not supported on Unix. @@ -727,7 +778,7 @@ public virtual void ResizeScreen () Console.SetBufferSize (Cols, Rows); } #pragma warning restore CA1416 - } catch (System.IO.IOException) { + } catch (IOException) { Clip = new Rect (0, 0, Cols, Rows); } catch (ArgumentOutOfRangeException) { Clip = new Rect (0, 0, Cols, Rows); @@ -739,6 +790,7 @@ public virtual void ResizeScreen () Clip = new Rect (0, 0, Cols, Rows); } + #endregion public override void Refresh () { @@ -752,18 +804,18 @@ public override void UpdateScreen () return; } - var top = 0; - var left = 0; - var rows = Rows; - var cols = Cols; - System.Text.StringBuilder output = new System.Text.StringBuilder (); - Attribute redrawAttr = new Attribute (); - var lastCol = -1; + int top = 0; + int left = 0; + int rows = Rows; + int cols = Cols; + var output = new StringBuilder (); + var redrawAttr = new Attribute (); + int lastCol = -1; var savedVisibitity = _cachedCursorVisibility; SetCursorVisibility (CursorVisibility.Invisible); - for (var row = top; row < rows; row++) { + for (int row = top; row < rows; row++) { if (Console.WindowHeight < 1) { return; } @@ -775,9 +827,9 @@ public override void UpdateScreen () } _dirtyLines [row] = false; output.Clear (); - for (var col = left; col < cols; col++) { + for (int col = left; col < cols; col++) { lastCol = -1; - var outputWidth = 0; + int outputWidth = 0; for (; col < cols; col++) { if (!Contents [row, col].IsDirty) { if (output.Length > 0) { @@ -795,7 +847,7 @@ public override void UpdateScreen () lastCol = col; } - Attribute attr = Contents [row, col].Attribute.Value; + var attr = Contents [row, col].Attribute.Value; // Performance: Only send the escape sequence if the attribute has changed. if (attr != redrawAttr) { redrawAttr = attr; @@ -823,7 +875,7 @@ public override void UpdateScreen () // output.Append (combMark); //} // WriteToConsole (output, ref lastCol, row, ref outputWidth); - } else if ((rune.IsSurrogatePair () && rune.GetColumns () < 2)) { + } else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) { WriteToConsole (output, ref lastCol, row, ref outputWidth); SetCursorPosition (col - 1, row); } @@ -850,37 +902,33 @@ void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int out } #region Color Handling - // Cache the list of ConsoleColor values. - private static readonly HashSet ConsoleColorValues = new HashSet ( - Enum.GetValues (typeof (ConsoleColor)).OfType ().Select (c => (int)c) + static readonly HashSet ConsoleColorValues = new ( + Enum.GetValues (typeof (ConsoleColor)).OfType ().Select (c => (int)c) ); // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console. - private static Dictionary colorMap = new Dictionary { - { ConsoleColor.Black, COLOR_BLACK }, - { ConsoleColor.DarkBlue, COLOR_BLUE }, - { ConsoleColor.DarkGreen, COLOR_GREEN }, - { ConsoleColor.DarkCyan, COLOR_CYAN }, - { ConsoleColor.DarkRed, COLOR_RED }, - { ConsoleColor.DarkMagenta, COLOR_MAGENTA }, - { ConsoleColor.DarkYellow, COLOR_YELLOW }, - { ConsoleColor.Gray, COLOR_WHITE }, - { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK }, - { ConsoleColor.Blue, COLOR_BRIGHT_BLUE }, - { ConsoleColor.Green, COLOR_BRIGHT_GREEN }, - { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN }, - { ConsoleColor.Red, COLOR_BRIGHT_RED }, - { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA }, - { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW }, - { ConsoleColor.White, COLOR_BRIGHT_WHITE } - }; + static Dictionary colorMap = new () { + { ConsoleColor.Black, COLOR_BLACK }, + { ConsoleColor.DarkBlue, COLOR_BLUE }, + { ConsoleColor.DarkGreen, COLOR_GREEN }, + { ConsoleColor.DarkCyan, COLOR_CYAN }, + { ConsoleColor.DarkRed, COLOR_RED }, + { ConsoleColor.DarkMagenta, COLOR_MAGENTA }, + { ConsoleColor.DarkYellow, COLOR_YELLOW }, + { ConsoleColor.Gray, COLOR_WHITE }, + { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK }, + { ConsoleColor.Blue, COLOR_BRIGHT_BLUE }, + { ConsoleColor.Green, COLOR_BRIGHT_GREEN }, + { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN }, + { ConsoleColor.Red, COLOR_BRIGHT_RED }, + { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA }, + { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW }, + { ConsoleColor.White, COLOR_BRIGHT_WHITE } + }; // Map a ConsoleColor to a platform dependent value. - int MapColors (ConsoleColor color, bool isForeground = true) - { - return colorMap.TryGetValue (color, out var colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; - } + int MapColors (ConsoleColor color, bool isForeground = true) => colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; ///// ///// In the NetDriver, colors are encoded as an int. @@ -938,7 +986,7 @@ public override bool GetCursorVisibility (out CursorVisibility visibility) public override bool SetCursorVisibility (CursorVisibility visibility) { _cachedCursorVisibility = visibility; - var isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default; + bool isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default; Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); return isVisible; } @@ -946,7 +994,7 @@ public override bool SetCursorVisibility (CursorVisibility visibility) public override bool EnsureCursorVisibility () { if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) { - GetCursorVisibility (out CursorVisibility cursorVisibility); + GetCursorVisibility (out var cursorVisibility); _cachedCursorVisibility = cursorVisibility; SetCursorVisibility (CursorVisibility.Invisible); return false; @@ -957,22 +1005,7 @@ public override bool EnsureCursorVisibility () } #endregion - #region Size and Position Handling - - void SetWindowPosition (int col, int row) - { - if (!RunningUnitTests) { - Top = Console.WindowTop; - Left = Console.WindowLeft; - } else { - Top = row; - Left = col; - } - } - - #endregion - - + #region Mouse Handling public void StartReportingMouseMoves () { if (!RunningUnitTests) { @@ -987,6 +1020,106 @@ public void StopReportingMouseMoves () } } + MouseEvent ToDriverMouse (NetEvents.MouseEvent me) + { + //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); + + MouseFlags mouseFlag = 0; + + if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0) { + mouseFlag |= MouseFlags.Button1Pressed; + } + if ((me.ButtonState & MouseButtonState.Button1Released) != 0) { + mouseFlag |= MouseFlags.Button1Released; + } + if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0) { + mouseFlag |= MouseFlags.Button1Clicked; + } + if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button1DoubleClicked; + } + if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button1TripleClicked; + } + if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0) { + mouseFlag |= MouseFlags.Button2Pressed; + } + if ((me.ButtonState & MouseButtonState.Button2Released) != 0) { + mouseFlag |= MouseFlags.Button2Released; + } + if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0) { + mouseFlag |= MouseFlags.Button2Clicked; + } + if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button2DoubleClicked; + } + if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button2TripleClicked; + } + if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0) { + mouseFlag |= MouseFlags.Button3Pressed; + } + if ((me.ButtonState & MouseButtonState.Button3Released) != 0) { + mouseFlag |= MouseFlags.Button3Released; + } + if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0) { + mouseFlag |= MouseFlags.Button3Clicked; + } + if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button3DoubleClicked; + } + if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button3TripleClicked; + } + if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0) { + mouseFlag |= MouseFlags.WheeledUp; + } + if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0) { + mouseFlag |= MouseFlags.WheeledDown; + } + if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0) { + mouseFlag |= MouseFlags.WheeledLeft; + } + if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0) { + mouseFlag |= MouseFlags.WheeledRight; + } + if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0) { + mouseFlag |= MouseFlags.Button4Pressed; + } + if ((me.ButtonState & MouseButtonState.Button4Released) != 0) { + mouseFlag |= MouseFlags.Button4Released; + } + if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0) { + mouseFlag |= MouseFlags.Button4Clicked; + } + if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button4DoubleClicked; + } + if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button4TripleClicked; + } + if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0) { + mouseFlag |= MouseFlags.ReportMousePosition; + } + if ((me.ButtonState & MouseButtonState.ButtonShift) != 0) { + mouseFlag |= MouseFlags.ButtonShift; + } + if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0) { + mouseFlag |= MouseFlags.ButtonCtrl; + } + if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0) { + mouseFlag |= MouseFlags.ButtonAlt; + } + + return new MouseEvent () { + X = me.Position.X, + Y = me.Position.Y, + Flags = mouseFlag + }; + } + #endregion Mouse Handling + + #region Keyboard Handling ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) { if (consoleKeyInfo.Key != ConsoleKey.Packet) { @@ -994,11 +1127,11 @@ ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) } var mod = consoleKeyInfo.Modifiers; - var shift = (mod & ConsoleModifiers.Shift) != 0; - var alt = (mod & ConsoleModifiers.Alt) != 0; - var control = (mod & ConsoleModifiers.Control) != 0; + bool shift = (mod & ConsoleModifiers.Shift) != 0; + bool alt = (mod & ConsoleModifiers.Alt) != 0; + bool control = (mod & ConsoleModifiers.Control) != 0; - var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _); + var cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); } @@ -1006,37 +1139,11 @@ ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) KeyCode MapKey (ConsoleKeyInfo keyInfo) { switch (keyInfo.Key) { - case ConsoleKey.Escape: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc); - case ConsoleKey.Tab: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab); - case ConsoleKey.Home: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home); - case ConsoleKey.End: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End); - case ConsoleKey.LeftArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft); - case ConsoleKey.RightArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight); - case ConsoleKey.UpArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp); - case ConsoleKey.DownArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown); - case ConsoleKey.PageUp: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp); - case ConsoleKey.PageDown: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown); - case ConsoleKey.Enter: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter); - case ConsoleKey.Spacebar: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar); - case ConsoleKey.Backspace: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace); - case ConsoleKey.Delete: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar); - case ConsoleKey.Insert: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar); - + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + case ConsoleKey.Packet: case ConsoleKey.Oem1: case ConsoleKey.Oem2: case ConsoleKey.Oem3: @@ -1046,99 +1153,78 @@ KeyCode MapKey (ConsoleKeyInfo keyInfo) case ConsoleKey.Oem7: case ConsoleKey.Oem8: case ConsoleKey.Oem102: - var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar)); - if (ret.HasFlag (KeyCode.ShiftMask)) { - ret &= ~KeyCode.ShiftMask; + if (keyInfo.KeyChar == 0) { + // If the keyChar is 0, keyInfo.Key value is not a printable character. + + return KeyCode.Null;// MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); + } else { + if (keyInfo.Modifiers != ConsoleModifiers.Shift) { + // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar)); + } + + // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç") + // and passing on Shift would be redundant. + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); } - return ret; + break; - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: - return (KeyCode)((uint)keyInfo.KeyChar); + + return (KeyCode)(uint)keyInfo.KeyChar; } var key = keyInfo.Key; - if (key is >= ConsoleKey.A and <= ConsoleKey.Z) { - var delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta)); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta)); - } + // A..Z are special cased: + // - Alone, they represent lowercase a...z + // - With ShiftMask they are A..Z + // - If CapsLock is on the above is reversed. + // - If Alt and/or Ctrl are present, treat as upper case + if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) { + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); } - if (((keyInfo.Modifiers == ConsoleModifiers.Shift) /*^ (keyInfoEx.CapsLock)*/)) { - if (keyInfo.KeyChar <= (uint)KeyCode.Z) { - return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask; + if (keyInfo.Modifiers == ConsoleModifiers.Shift) { + // If ShiftMask is on add the ShiftMask + if (char.IsUpper (keyInfo.KeyChar)) { + return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask; } } - // This is buggy because is converting a lower case to a upper case and mustn't - //if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == KeyCode.Space) { - // return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space; - //} return (KeyCode)(uint)keyInfo.KeyChar; } - if (key is >= ConsoleKey.D0 and <= ConsoleKey.D9) { - var delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta)); - } - } - return (KeyCode)((uint)keyInfo.KeyChar); - } - if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) { - var delta = key - ConsoleKey.F1; - if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta)); - } - return (KeyCode)((uint)KeyCode.F1 + delta); + // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC + if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.Key)); } - // Is it a key between a..z? - if ((char)keyInfo.KeyChar is >= 'a' and <= 'z') { - // 'a' should be Key.A - return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space; + // Handle control keys (e.g. CursorUp) + if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); } - // Is it a key between A..Z? - if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) { - // It's Key.A...Z. Make it Key.A | Key.ShiftMask - return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space | KeyCode.ShiftMask; - } return (KeyCode)(uint)keyInfo.KeyChar; } + #endregion Keyboard Handling - volatile bool _winSizeChanging; - - void ProcessInput (NetEvents.InputResult inputEvent) + void ProcessInput (InputResult inputEvent) { switch (inputEvent.EventType) { case NetEvents.EventType.Key: - ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo; - if (consoleKeyInfo.Key == ConsoleKey.Packet) { - consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); - } + var consoleKeyInfo = inputEvent.ConsoleKeyInfo; + //if (consoleKeyInfo.Key == ConsoleKey.Packet) { + // consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); + //} + + //Debug.WriteLine ($"event: {inputEvent}"); + var map = MapKey (consoleKeyInfo); + if (map == KeyCode.Null) { + break; + } + OnKeyDown (new Key (map)); OnKeyUp (new Key (map)); break; @@ -1150,16 +1236,14 @@ void ProcessInput (NetEvents.InputResult inputEvent) Top = 0; Left = 0; Cols = inputEvent.WindowSizeEvent.Size.Width; - Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); ; + Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); + ; ResizeScreen (); ClearContents (); _winSizeChanging = false; OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows))); break; case NetEvents.EventType.RequestResponse: - // BUGBUG: What is this for? It does not seem to be used anywhere. - // It is also not clear what it does. View.Data is documented as "This property is not used internally" - Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple; break; case NetEvents.EventType.WindowPosition: break; @@ -1168,107 +1252,9 @@ void ProcessInput (NetEvents.InputResult inputEvent) } } - MouseEvent ToDriverMouse (NetEvents.MouseEvent me) - { - //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); - - MouseFlags mouseFlag = 0; - - if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) { - mouseFlag |= MouseFlags.Button1Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) { - mouseFlag |= MouseFlags.Button1Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) { - mouseFlag |= MouseFlags.Button1Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button1DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button1TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) { - mouseFlag |= MouseFlags.Button2Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) { - mouseFlag |= MouseFlags.Button2Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) { - mouseFlag |= MouseFlags.Button2Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button2DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button2TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) { - mouseFlag |= MouseFlags.Button3Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) { - mouseFlag |= MouseFlags.Button3Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) { - mouseFlag |= MouseFlags.Button3Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button3DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button3TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) { - mouseFlag |= MouseFlags.WheeledUp; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) { - mouseFlag |= MouseFlags.WheeledDown; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) { - mouseFlag |= MouseFlags.WheeledLeft; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) { - mouseFlag |= MouseFlags.WheeledRight; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) { - mouseFlag |= MouseFlags.Button4Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) { - mouseFlag |= MouseFlags.Button4Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) { - mouseFlag |= MouseFlags.Button4Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button4DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button4TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) { - mouseFlag |= MouseFlags.ReportMousePosition; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) { - mouseFlag |= MouseFlags.ButtonShift; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) { - mouseFlag |= MouseFlags.ButtonCtrl; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) { - mouseFlag |= MouseFlags.ButtonAlt; - } - - return new MouseEvent () { - X = me.Position.X, - Y = me.Position.Y, - Flags = mouseFlag - }; - } - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) { - NetEvents.InputResult input = new NetEvents.InputResult { + var input = new InputResult { EventType = NetEvents.EventType.Key, ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control) }; @@ -1280,12 +1266,8 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al #region Not Implemented - public override void Suspend () - { - throw new NotImplementedException (); - } + public override void Suspend () => throw new NotImplementedException (); #endregion - } /// @@ -1296,19 +1278,19 @@ public override void Suspend () /// /// This implementation is used for NetDriver. /// -internal class NetMainLoop : IMainLoopDriver { - readonly ManualResetEventSlim _eventReady = new ManualResetEventSlim (false); - readonly ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false); - readonly Queue _resultQueue = new Queue (); +class NetMainLoop : IMainLoopDriver { + readonly ManualResetEventSlim _eventReady = new (false); + readonly ManualResetEventSlim _waitForProbe = new (false); + readonly Queue _resultQueue = new (); MainLoop _mainLoop; - CancellationTokenSource _eventReadyTokenSource = new CancellationTokenSource (); - readonly CancellationTokenSource _inputHandlerTokenSource = new CancellationTokenSource (); + CancellationTokenSource _eventReadyTokenSource = new (); + readonly CancellationTokenSource _inputHandlerTokenSource = new (); internal NetEvents _netEvents; /// /// Invoked when a Key is pressed. /// - internal Action ProcessInput; + internal Action ProcessInput; /// /// Initializes the class with the console driver. @@ -1367,16 +1349,13 @@ void IMainLoopDriver.Setup (MainLoop mainLoop) Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } - void IMainLoopDriver.Wakeup () - { - _eventReady.Set (); - } + void IMainLoopDriver.Wakeup () => _eventReady.Set (); bool IMainLoopDriver.EventsPending () { _waitForProbe.Set (); - if (_mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout)) { + if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) { return true; } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index f9915d7952..c6baab686b 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -24,6 +24,7 @@ using System.Diagnostics; using Terminal.Gui.ConsoleDrivers; using static Unix.Terminal.Delegates; +using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; namespace Terminal.Gui; @@ -371,7 +372,7 @@ public struct KeyEventRecord { [FieldOffset (4), MarshalAs (UnmanagedType.U2)] public ushort wRepeatCount; [FieldOffset (6), MarshalAs (UnmanagedType.U2)] - public ushort wVirtualKeyCode; + public VK wVirtualKeyCode; [FieldOffset (8), MarshalAs (UnmanagedType.U2)] public ushort wVirtualScanCode; [FieldOffset (10)] @@ -901,66 +902,31 @@ private void ChangeWin (Object s, SizeChangedEventArgs e) } #endif + KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) { var keyInfo = keyInfoEx.ConsoleKeyInfo; switch (keyInfo.Key) { - case ConsoleKey.Escape: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc); - case ConsoleKey.Tab: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab); - case ConsoleKey.Clear: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear); - case ConsoleKey.Home: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home); - case ConsoleKey.End: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End); - case ConsoleKey.LeftArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft); - case ConsoleKey.RightArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight); - case ConsoleKey.UpArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp); - case ConsoleKey.DownArrow: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown); - case ConsoleKey.PageUp: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp); - case ConsoleKey.PageDown: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown); - case ConsoleKey.Enter: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter); - case ConsoleKey.Spacebar: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar); - case ConsoleKey.Backspace: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace); - case ConsoleKey.Delete: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar); - case ConsoleKey.Insert: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar); - case ConsoleKey.PrintScreen: - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen); - - //case ConsoleKey.NumPad0: - // return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar; - //case ConsoleKey.NumPad1: - // return keyInfoEx.NumLock ? Key.D1 : Key.End; - //case ConsoleKey.NumPad2: - // return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown; - //case ConsoleKey.NumPad3: - // return keyInfoEx.NumLock ? Key.D3 : Key.PageDown; - //case ConsoleKey.NumPad4: - // return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft; - //case ConsoleKey.NumPad5: - // return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar); - //case ConsoleKey.NumPad6: - // return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight; - //case ConsoleKey.NumPad7: - // return keyInfoEx.NumLock ? Key.D7 : Key.Home; - //case ConsoleKey.NumPad8: - // return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp; - //case ConsoleKey.NumPad9: - // return keyInfoEx.NumLock ? Key.D9 : Key.PageUp; - + case ConsoleKey.D0: + case ConsoleKey.D1: + case ConsoleKey.D2: + case ConsoleKey.D3: + case ConsoleKey.D4: + case ConsoleKey.D5: + case ConsoleKey.D6: + case ConsoleKey.D7: + case ConsoleKey.D8: + case ConsoleKey.D9: + case ConsoleKey.NumPad0: + case ConsoleKey.NumPad1: + case ConsoleKey.NumPad2: + case ConsoleKey.NumPad3: + case ConsoleKey.NumPad4: + case ConsoleKey.NumPad5: + case ConsoleKey.NumPad6: + case ConsoleKey.NumPad7: + case ConsoleKey.NumPad8: + case ConsoleKey.NumPad9: case ConsoleKey.Oem1: case ConsoleKey.Oem2: case ConsoleKey.Oem3: @@ -970,148 +936,168 @@ KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) case ConsoleKey.Oem7: case ConsoleKey.Oem8: case ConsoleKey.Oem102: - var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar)); - if (ret.HasFlag (KeyCode.ShiftMask)) { - ret &= ~KeyCode.ShiftMask; - } - return ret; - + case ConsoleKey.Multiply: + case ConsoleKey.Add: + case ConsoleKey.Separator: + case ConsoleKey.Subtract: + case ConsoleKey.Decimal: + case ConsoleKey.Divide: case ConsoleKey.OemPeriod: case ConsoleKey.OemComma: case ConsoleKey.OemPlus: case ConsoleKey.OemMinus: - return (KeyCode)((uint)keyInfo.KeyChar); - } - - var key = keyInfo.Key; - - if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { - var delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta)); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta)); - } + // These virtual key codes are mapped differently depending on the keyboard layout in use. + // We use the Win32 API to map them to the correct character. + var mapResult = MapVKtoChar ((VK)keyInfo.Key); + if (mapResult == 0) { + // There is no mapping - this should not happen + Debug.Assert (mapResult != 0, $@"Unable to map the virtual key code {keyInfo.Key}."); + return KeyCode.Null; } - if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) { - if (keyInfo.KeyChar <= (uint)KeyCode.Z) { - return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask; + // An un-shifted character value is in the low order word of the return value. + var mappedChar = (char)(mapResult & 0x0000FFFF); + + if (keyInfo.KeyChar == 0) { + // If the keyChar is 0, keyInfo.Key value is not a printable character. + + // Dead keys (diacritics) are indicated by setting the top bit of the return value. + if ((mapResult & 0x80000000) != 0) { + // Dead key (e.g. Oem2 '~'/'^' on POR keyboard) + // Option 1: Throw it out. + // - Apps will never see the dead keys + // - If user presses a key that can be combined with the dead key ('a'), the right thing happens (app will see ''). + // - NOTE: With Dead Keys, KeyDown != KeyUp. The KeyUp event will have just the base char ('a'). + // - If user presses dead key again, the right thing happens (app will see `~~`) + // - This is what Notepad etc... appear to do + // Option 2: Expand the API to indicate the KeyCode is a dead key + // - Enables apps to do their own dead key processing + // - Adds complexity; no dev has asked for this (yet). + // We choose Option 1 for now. + return KeyCode.Null; + + // Note: Ctrl-Deadkey (like Oem3 '`'/'~` on ENG) can't be supported. + // Sadly, the charVal is just the deadkey and subsequent key events do not contain + // any info that the previous event was a deadkey. + // Note WT does not support Ctrl-Deadkey either. } - } - if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == 0) { - return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space; - } + if (keyInfo.Modifiers != 0) { + // These Oem keys have well defined chars. We ensure the representative char is used. + // If we don't do this, then on some keyboard layouts the wrong char is + // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important + // for key persistence ("Ctrl++" vs. "Ctrl+="). + mappedChar = keyInfo.Key switch { + ConsoleKey.OemPeriod => '.', + ConsoleKey.OemComma => ',', + ConsoleKey.OemPlus => '+', + ConsoleKey.OemMinus => '-', + _ => mappedChar + }; + } - if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) != 0) { - if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) == (KeyCode)keyInfo.Key) { - return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space; + // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down + // we should keep it + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar); + } else { + // KeyChar is printable + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) { + // AltGr support - AltGr is equivalent to Ctrl+Alt - the correct char is in KeyChar + return (KeyCode)keyInfo.KeyChar; } - return (KeyCode)((uint)keyInfo.KeyChar); - } - return (KeyCode)(uint)keyInfo.KeyChar; + if (keyInfo.Modifiers != ConsoleModifiers.Shift) { + // If Shift wasn't down we don't need to do anything but return the mappedChar + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(mappedChar)); + } + // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "") + // and passing on Shift would be redundant. + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); + } } - if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { - var delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta)); + // A..Z are special cased: + // - Alone, they represent lowercase a...z + // - With ShiftMask they are A..Z + // - If CapsLock is on the above is reversed. + // - If Alt and/or Ctrl are present, treat as upper case + if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) { + if (keyInfo.KeyChar == 0) { + // KeyChar is not printable - possibly an AltGr key? + // AltGr support - AltGr is equivalent to Ctrl+Alt + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); + } } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta)); + + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta)); + + if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) { + // If (ShiftMask is on and CapsLock is off) or (ShiftMask is off and CapsLock is on) add the ShiftMask + if (char.IsUpper (keyInfo.KeyChar)) { + return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask; } } - return (KeyCode)((uint)keyInfo.KeyChar); + return (KeyCode)(uint)keyInfo.KeyChar; } - if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { - var delta = key - ConsoleKey.F1; - if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta)); + // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC + if (Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) { + // If the key is JUST a modifier, return it as just that key + if (keyInfo.Key == (ConsoleKey)VK.SHIFT) { // Shift 16 + return KeyCode.ShiftMask; } - return (KeyCode)((uint)KeyCode.F1 + delta); - } + if (keyInfo.Key == (ConsoleKey)VK.CONTROL) { // Ctrl 17 + return KeyCode.CtrlMask; + } - // If the key is JUST a modifier, return it as that key - if (key == (ConsoleKey)16) { // Shift - return KeyCode.ShiftKey; - } + if (keyInfo.Key == (ConsoleKey)VK.MENU) { // Alt 18 + return KeyCode.AltMask; + } - if (key == (ConsoleKey)17) { // Ctrl - return KeyCode.CtrlKey; + if (keyInfo.KeyChar == 0) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar)); + } else { + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)(keyInfo.KeyChar)); + } } - if (key == (ConsoleKey)18) { // Alt - return KeyCode.AltKey; + // Handle control keys (e.g. CursorUp) + if (Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); } - return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar)); + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar)); } - bool _altDown = false; - internal void ProcessInput (WindowsConsole.InputRecord inputEvent) { switch (inputEvent.EventType) { case WindowsConsole.EventType.Key: - var fromPacketKey = inputEvent.KeyEvent.wVirtualKeyCode == (uint)ConsoleKey.Packet; - if (fromPacketKey) { + if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet) { + // Used to pass Unicode characters as if they were keystrokes. + // The VK_PACKET key is the low word of a 32-bit + // Virtual Key value used for non-keyboard input methods. inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent); } var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent); - //Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}"); - + //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}"); var map = MapKey (keyInfo); + if (map == KeyCode.Null) { + break; + } + if (inputEvent.KeyEvent.bKeyDown) { - _altDown = keyInfo.ConsoleKeyInfo.Modifiers == ConsoleModifiers.Alt; - // Avoid sending repeat keydowns + // Avoid sending repeat key down events OnKeyDown (new Key (map)); } else { - var keyPressedEventArgs = new Key (map); - - // PROTOTYPE: This logic enables `Alt` key presses (down, up, pressed). - // However, if while the 'Alt' key is down, if another key is pressed and - // released, there will be a keypressed event for that and the - // keypressed event for just `Alt` will be suppressed. - // This allows MenuBar to have `Alt` as a keybinding - if (map != KeyCode.AltMask) { - if (keyInfo.ConsoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)) { - if (_altDown) { - _altDown = false; - OnKeyUp (new Key (map)); - } - - } - _altDown = false; - // KeyUp of an Alt-key press. - OnKeyUp (keyPressedEventArgs); - } else { - OnKeyUp (keyPressedEventArgs); - if (_altDown) { - _altDown = false; - } - } + OnKeyUp (new Key (map)); } break; @@ -1422,7 +1408,7 @@ public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEve public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) { - if (keyEvent.wVirtualKeyCode != (uint)ConsoleKey.Packet) { + if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet) { return keyEvent; } @@ -1438,14 +1424,17 @@ public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsol keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) { mod |= ConsoleModifiers.Control; } - var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyEvent.UnicodeChar, mod, out uint scanCode); + var cKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, + mod.HasFlag (ConsoleModifiers.Shift), mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control)); + cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); + var scanCode = GetScanCodeFromConsoleKeyInfo (cKeyInfo); return new WindowsConsole.KeyEventRecord { UnicodeChar = cKeyInfo.KeyChar, bKeyDown = keyEvent.bKeyDown, dwControlKeyState = keyEvent.dwControlKeyState, wRepeatCount = keyEvent.wRepeatCount, - wVirtualKeyCode = (ushort)cKeyInfo.Key, + wVirtualKeyCode = (VK)cKeyInfo.Key, wVirtualScanCode = (ushort)scanCode }; } @@ -1591,19 +1580,19 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al if (shift) { controlKey |= WindowsConsole.ControlKeyState.ShiftPressed; keyEvent.UnicodeChar = '\0'; - keyEvent.wVirtualKeyCode = 16; + keyEvent.wVirtualKeyCode = VK.SHIFT; } if (alt) { controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed; controlKey |= WindowsConsole.ControlKeyState.RightAltPressed; keyEvent.UnicodeChar = '\0'; - keyEvent.wVirtualKeyCode = 18; + keyEvent.wVirtualKeyCode = VK.MENU; } if (control) { controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed; controlKey |= WindowsConsole.ControlKeyState.RightControlPressed; keyEvent.UnicodeChar = '\0'; - keyEvent.wVirtualKeyCode = 17; + keyEvent.wVirtualKeyCode = VK.CONTROL; } keyEvent.dwControlKeyState = controlKey; @@ -1614,11 +1603,12 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al } keyEvent.UnicodeChar = keyChar; - if ((uint)key < 255) { - keyEvent.wVirtualKeyCode = (ushort)key; - } else { - keyEvent.wVirtualKeyCode = '\0'; - } + //if ((uint)key < 255) { + // keyEvent.wVirtualKeyCode = (ushort)key; + //} else { + // keyEvent.wVirtualKeyCode = '\0'; + //} + keyEvent.wVirtualKeyCode = (VK)key; input.KeyEvent = keyEvent; diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index c3498931b8..63cb98b87b 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -132,7 +132,8 @@ public Key (string str) /// The encoded key value. /// /// - /// IMPORTANT: Lowercase alpha keys are encoded (in ) as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values + /// IMPORTANT: Lowercase alpha keys are encoded (in ) as values between 65 and + /// 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values /// are provided for these (e.g. , , etc.). Even though the values are the same as the ASCII /// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters. /// @@ -147,24 +148,36 @@ public Key (string str) public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused; /// - /// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. + /// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. Useful + /// for determining if a key represents is a printable character. /// /// /// - /// If the key is a letter (a-z or A-Z), the Rune be the upper or lower case letter depending on whether - /// the shift key was pressed. + /// Keys with Ctrl or Alt modifiers will return . + /// + /// + /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether + /// is set. + /// + /// /// If the key is outside of the range, the returned Rune will be . /// /// public Rune AsRune => ToRune (KeyCode); /// - /// Converts a to a . + /// Converts a to a . Useful + /// for determining if a key represents is a printable character. /// /// /// - /// If the key is a letter (a-z or A-Z), the Rune be the upper or lower case letter depending on whether - /// the shift key was pressed. + /// Keys with Ctrl or Alt modifiers will return . + /// + /// + /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether + /// is set. + /// + /// /// If the key is outside of the range, the returned Rune will be . /// /// @@ -176,23 +189,26 @@ public static Rune ToRune (KeyCode key) return default; } - // Extract the base key (removing modifier flags) - var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; + // Extract the base key code + var baseKey = key; + if (baseKey.HasFlag(KeyCode.ShiftMask)) { + baseKey &= ~KeyCode.ShiftMask; + } switch (baseKey) { case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask): - return new Rune ((char)(baseKey + 32)); - case >= KeyCode.A and <= KeyCode.Z: - return new Rune ((char)baseKey); + return new Rune ((uint)(baseKey + 32)); + case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask): + return new Rune ((uint)baseKey); case > KeyCode.Null and < KeyCode.A: - return new Rune ((char)baseKey); + return new Rune ((uint)baseKey); } if (Enum.IsDefined (typeof (KeyCode), baseKey)) { return default; } - return new Rune ((char)baseKey); + return new Rune ((uint)baseKey); } /// @@ -214,7 +230,9 @@ public static Rune ToRune (KeyCode key) public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0; /// - /// Gets a value indicating whether the KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key. + /// Gets a value indicating whether the key represents a key in the range of to , + /// regardless of the . This is useful for testing if a key is based on these keys which are + /// special cased. /// /// /// IMPORTANT: Lowercase alpha keys are encoded in as values between 65 and 90 corresponding to @@ -224,7 +242,9 @@ public static Rune ToRune (KeyCode key) public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode); /// - /// Tests if a KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key. + /// Tests if a KeyCode represents a key in the range of to , + /// regardless of the . This is useful for testing if a key is based on these keys which are + /// special cased. /// /// /// IMPORTANT: Lowercase alpha keys are encoded in as values between 65 and 90 corresponding to @@ -303,7 +323,8 @@ public static bool GetIsKeyCodeAtoZ (KeyCode keyCode) #region Operators /// - /// Explicitly cast a to a . The conversion is lossy. + /// Explicitly cast a to a . The conversion is lossy because properties + /// such as are not encoded in . /// /// /// Uses . @@ -311,14 +332,17 @@ public static bool GetIsKeyCodeAtoZ (KeyCode keyCode) /// public static explicit operator Rune (Key kea) => kea.AsRune; + // BUGBUG: (Tig) I do not think this cast operator is really needed. /// - /// Explicitly cast to a . The conversion is lossy. + /// Explicitly cast to a . The conversion is lossy because properties + /// such as are not encoded in . /// /// - public static explicit operator char (Key kea) => (char)kea.AsRune.Value; + public static explicit operator uint (Key kea) => (uint)kea.KeyCode; /// - /// Explicitly cast to a . The conversion is lossy. + /// Explicitly cast to a . The conversion is lossy because properties + /// such as are not encoded in . /// /// public static explicit operator KeyCode (Key key) => key.KeyCode; @@ -365,6 +389,7 @@ public static bool GetIsKeyCodeAtoZ (KeyCode keyCode) public override int GetHashCode () => (int)KeyCode; /// + /// Compares two s for equality. /// /// /// @@ -372,6 +397,7 @@ public static bool GetIsKeyCodeAtoZ (KeyCode keyCode) public static bool operator == (Key a, Key b) => a?.KeyCode == b?.KeyCode; /// + /// Compares two s for not equality. /// /// /// @@ -427,15 +453,15 @@ static string GetKeyString (KeyCode key) var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask; if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) { - return ((char)(key + 32)).ToString (); + return ((Rune)(uint)(key + 32)).ToString (); } - if (key is >= KeyCode.Space and < KeyCode.A) { - return ((char)key).ToString (); + if (key is > KeyCode.Space and < KeyCode.A) { + return ((Rune)(uint)key).ToString (); } string keyName = Enum.GetName (typeof (KeyCode), key); - return !string.IsNullOrEmpty (keyName) ? keyName : ((char)key).ToString (); + return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString (); } @@ -454,7 +480,7 @@ static string GetKeyString (KeyCode key) /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned. public static string ToString (KeyCode key, Rune separator) { - if (key is KeyCode.Null || (key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask) == 0) { + if (key is KeyCode.Null) { // Same as Key.IsValid return @"Null"; } @@ -489,21 +515,19 @@ public static string ToString (KeyCode key, Rune separator) } } - string result = sb.ToString (); - result = TrimEndRune (result, separator); - return result; + return TrimEndSeparator (sb.ToString (), separator); } - static string TrimEndRune (string input, Rune runeToTrim) + static string TrimEndSeparator (string input, Rune separator) { - // Convert the Rune to a string (which may be one or two chars) - string runeString = runeToTrim.ToString (); + // Trim the trailing separator (+). Unless there are two separators at the end. + // "+" (don't trim) + // "Ctrl+" (trim) + // "Ctrl++" (trim) - if (input.EndsWith (runeString)) { - // Remove the rune from the end of the string - return input.Substring (0, input.Length - runeString.Length); + if (input.Length > 1 && new Rune(input [^1]) == separator && new Rune(input [^2]) != separator) { + return input [..^1]; } - return input; } @@ -543,13 +567,13 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Key key) if (parts.Length == 1) { switch (parts [0]) { case "Ctrl": - key = new Key (KeyCode.CtrlKey); + key = new Key (KeyCode.CtrlMask); return true; case "Alt": - key = new Key (KeyCode.AltKey); + key = new Key (KeyCode.AltMask); return true; case "Shift": - key = new Key (KeyCode.ShiftKey); + key = new Key (KeyCode.ShiftMask); return true; } } @@ -655,26 +679,6 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Key key) /// public static Key Clear => new (KeyCode.Clear); - /// - /// The object for the Shift key. - /// - public static Key Shift => new (KeyCode.ShiftKey); - - /// - /// The object for the Ctrl key. - /// - public static Key Ctrl => new (KeyCode.CtrlKey); - - /// - /// The object for the Alt key. - /// - public static Key Alt => new (KeyCode.AltKey); - - /// - /// The object for the CapsLock key. - /// - public static Key CapsLock => new (KeyCode.CapsLock); - /// /// The object for the Escape key. /// @@ -913,12 +917,12 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Key key) /// /// The object for Insert Character key. /// - public static Key InsertChar => new (KeyCode.InsertChar); + public static Key InsertChar => new (KeyCode.Insert); /// /// The object for Delete Character key. /// - public static Key DeleteChar => new (KeyCode.DeleteChar); + public static Key DeleteChar => new (KeyCode.Delete); /// /// The object for Print Screen key. diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index a73a949234..af906ecf74 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -113,5 +113,6 @@ true Miguel de Icaza, Tig Kindel (@tig), @BDisp + true diff --git a/Terminal.Gui/Text/CollectionNavigatorBase.cs b/Terminal.Gui/Text/CollectionNavigatorBase.cs index 64161ebca5..165215fc0b 100644 --- a/Terminal.Gui/Text/CollectionNavigatorBase.cs +++ b/Terminal.Gui/Text/CollectionNavigatorBase.cs @@ -1,217 +1,214 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; + +namespace Terminal.Gui; + +/// +/// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. +/// The is used to find the next item in the collection that matches the search string +/// when is called. +/// +/// If the user types keystrokes that can't be found in the collection, +/// the search string is cleared and the next item is found that starts with the last keystroke. +/// +/// +/// If the user pauses keystrokes for a short time (see ), the search string is cleared. +/// +/// +public abstract class CollectionNavigatorBase { + DateTime _lastKeystroke = DateTime.Now; -namespace Terminal.Gui { /// - /// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. - /// The is used to find the next item in the collection that matches the search string - /// when is called. - /// - /// If the user types keystrokes that can't be found in the collection, - /// the search string is cleared and the next item is found that starts with the last keystroke. - /// - /// - /// If the user pauses keystrokes for a short time (see ), the search string is cleared. - /// + /// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is + /// reset on each call to . The default is 500ms. /// - public abstract class CollectionNavigatorBase { - - DateTime lastKeystroke = DateTime.Now; - /// - /// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is - /// reset on each call to . The default is 500ms. - /// - public int TypingDelay { get; set; } = 500; - - /// - /// The compararer function to use when searching the collection. - /// - public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase; - - /// - /// This event is invoked when changes. Useful for debugging. - /// - public event EventHandler SearchStringChanged; - - private string _searchString = ""; - /// - /// Gets the current search string. This includes the set of keystrokes that have been pressed - /// since the last unsuccessful match or after ) milliseconds. Useful for debugging. - /// - public string SearchString { - get => _searchString; - private set { - _searchString = value; - OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value)); - } - } + public int TypingDelay { get; set; } = 500; - /// - /// Invoked when the changes. Useful for debugging. Invokes the event. - /// - /// - public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) - { - SearchStringChanged?.Invoke (this, e); - } + /// + /// The comparer function to use when searching the collection. + /// + public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase; - /// - /// Gets the index of the next item in the collection that matches the current plus the provided character (typically - /// from a key press). - /// - /// The index in the collection to start the search from. - /// The character of the key the user pressed. - /// The index of the item that matches what the user has typed. - /// Returns if no item in the collection matched. - public int GetNextMatchingItem (int currentIndex, char keyStruck) - { - if (!char.IsControl (keyStruck)) { - - // maybe user pressed 'd' and now presses 'd' again. - // a candidate search is things that begin with "dd" - // but if we find none then we must fallback on cycling - // d instead and discard the candidate state - string candidateState = ""; - - // is it a second or third (etc) keystroke within a short time - if (SearchString.Length > 0 && DateTime.Now - lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) { - // "dd" is a candidate - candidateState = SearchString + keyStruck; - } else { - // its a fresh keystroke after some time - // or its first ever key press - SearchString = new string (keyStruck, 1); - } + /// + /// This event is invoked when changes. Useful for debugging. + /// + public event EventHandler SearchStringChanged; - var idxCandidate = GetNextMatchingItem (currentIndex, candidateState, - // prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart" - candidateState.Length > 1); + string _searchString = ""; - if (idxCandidate != -1) { - // found "dd" so candidate searchstring is accepted - lastKeystroke = DateTime.Now; - SearchString = candidateState; - return idxCandidate; - } + /// + /// Gets the current search string. This includes the set of keystrokes that have been pressed + /// since the last unsuccessful match or after ) milliseconds. Useful for debugging. + /// + public string SearchString { + get => _searchString; + private set { + _searchString = value; + OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value)); + } + } - //// nothing matches "dd" so discard it as a candidate - //// and just cycle "d" instead - lastKeystroke = DateTime.Now; - idxCandidate = GetNextMatchingItem (currentIndex, candidateState); - - // if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z' - // instead of "can" + 'd'). - if (SearchString.Length > 1 && idxCandidate == -1) { - // ignore it since we're still within the typing delay - // don't add it to SearchString either - return currentIndex; - } + /// + /// Invoked when the changes. Useful for debugging. Invokes the event. + /// + /// + public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) => SearchStringChanged?.Invoke (this, e); - // if no changes to current state manifested - if (idxCandidate == currentIndex || idxCandidate == -1) { - // clear history and treat as a fresh letter - ClearSearchString (); + /// + /// Gets the index of the next item in the collection that matches the current plus the provided character (typically + /// from a key press). + /// + /// The index in the collection to start the search from. + /// The character of the key the user pressed. + /// The index of the item that matches what the user has typed. + /// Returns if no item in the collection matched. + public int GetNextMatchingItem (int currentIndex, char keyStruck) + { + if (!char.IsControl (keyStruck)) { + + // maybe user pressed 'd' and now presses 'd' again. + // a candidate search is things that begin with "dd" + // but if we find none then we must fallback on cycling + // d instead and discard the candidate state + string candidateState = ""; + + // is it a second or third (etc) keystroke within a short time + if (SearchString.Length > 0 && DateTime.Now - _lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) { + // "dd" is a candidate + candidateState = SearchString + keyStruck; + } else { + // its a fresh keystroke after some time + // or its first ever key press + SearchString = new string (keyStruck, 1); + } - // match on the fresh letter alone - SearchString = new string (keyStruck, 1); - idxCandidate = GetNextMatchingItem (currentIndex, SearchString); - return idxCandidate == -1 ? currentIndex : idxCandidate; - } + int idxCandidate = GetNextMatchingItem (currentIndex, candidateState, + // prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart" + candidateState.Length > 1); - // Found another "d" or just leave index as it was + if (idxCandidate != -1) { + // found "dd" so candidate search string is accepted + _lastKeystroke = DateTime.Now; + SearchString = candidateState; return idxCandidate; + } - } else { - // clear state because keypress was a control char - ClearSearchString (); + //// nothing matches "dd" so discard it as a candidate + //// and just cycle "d" instead + _lastKeystroke = DateTime.Now; + idxCandidate = GetNextMatchingItem (currentIndex, candidateState); - // control char indicates no selection - return -1; + // if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z' + // instead of "can" + 'd'). + if (SearchString.Length > 1 && idxCandidate == -1) { + // ignore it since we're still within the typing delay + // don't add it to SearchString either + return currentIndex; } - } - /// - /// Gets the index of the next item in the collection that matches . - /// - /// The index in the collection to start the search from. - /// The search string to use. - /// Set to to stop the search on the first match - /// if there are multiple matches for . - /// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If (the default), - /// the next matching item will be returned, even if it is above in the collection. - /// - /// The index of the next matching item or if no match was found. - internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false) - { - if (string.IsNullOrEmpty (search)) { - return -1; + // if no changes to current state manifested + if (idxCandidate == currentIndex || idxCandidate == -1) { + // clear history and treat as a fresh letter + ClearSearchString (); + + // match on the fresh letter alone + SearchString = new string (keyStruck, 1); + idxCandidate = GetNextMatchingItem (currentIndex, SearchString); + return idxCandidate == -1 ? currentIndex : idxCandidate; } - var collectionLength = GetCollectionLength (); + // Found another "d" or just leave index as it was + return idxCandidate; - if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) { - // we are already at a match - if (minimizeMovement) { - // if we would rather not jump around (e.g. user is typing lots of text to get this match) - return currentIndex; - } + } else { + // clear state because keypress was a control char + ClearSearchString (); - for (int i = 1; i < collectionLength; i++) { - //circular - var idxCandidate = (i + currentIndex) % collectionLength; - if (IsMatch (search, ElementAt (idxCandidate))) { - return idxCandidate; - } - } + // control char indicates no selection + return -1; + } + } + + /// + /// Gets the index of the next item in the collection that matches . + /// + /// The index in the collection to start the search from. + /// The search string to use. + /// Set to to stop the search on the first match + /// if there are multiple matches for . + /// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If (the default), + /// the next matching item will be returned, even if it is above in the collection. + /// + /// The index of the next matching item or if no match was found. + internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false) + { + if (string.IsNullOrEmpty (search)) { + return -1; + } - // nothing else starts with the search term + int collectionLength = GetCollectionLength (); + + if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) { + // we are already at a match + if (minimizeMovement) { + // if we would rather not jump around (e.g. user is typing lots of text to get this match) return currentIndex; - } else { - // search terms no longer match the current selection or there is none - for (int i = 0; i < collectionLength; i++) { - if (IsMatch (search, ElementAt (i))) { - return i; - } + } + + for (int i = 1; i < collectionLength; i++) { + //circular + int idxCandidate = (i + currentIndex) % collectionLength; + if (IsMatch (search, ElementAt (idxCandidate))) { + return idxCandidate; } + } - // Nothing matches - return -1; + // nothing else starts with the search term + return currentIndex; + } else { + // search terms no longer match the current selection or there is none + for (int i = 0; i < collectionLength; i++) { + if (IsMatch (search, ElementAt (i))) { + return i; + } } + + // Nothing matches + return -1; } + } - /// - /// Return the number of elements in the collection - /// - protected abstract int GetCollectionLength (); + /// + /// Return the number of elements in the collection + /// + protected abstract int GetCollectionLength (); - private bool IsMatch (string search, object value) - { - return value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false; - } + bool IsMatch (string search, object value) => value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false; - /// - /// Returns the collection being navigated element at . - /// - /// - protected abstract object ElementAt (int idx); + /// + /// Returns the collection being navigated element at . + /// + /// + protected abstract object ElementAt (int idx); - private void ClearSearchString () - { - SearchString = ""; - lastKeystroke = DateTime.Now; - } + void ClearSearchString () + { + SearchString = ""; + _lastKeystroke = DateTime.Now; + } - /// - /// Returns true if is a searchable key - /// (e.g. letters, numbers, etc) that are valid to pass to this - /// class for search filtering. - /// - /// - /// - public static bool IsCompatibleKey (Key a) - { - return !a.IsAlt && !a.IsCtrl; - } + /// + /// Returns true if is a searchable key + /// (e.g. letters, numbers, etc) that are valid to pass to this + /// class for search filtering. + /// + /// + /// + public static bool IsCompatibleKey (Key a) + { + var rune = a.AsRune; + return rune != default && !Rune.IsControl (rune); } -} +} \ No newline at end of file diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 27b79fbcfb..6caccdac4b 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -330,6 +330,10 @@ public bool NewKeyDownEvent (Key keyEvent) } // During (this is what can be cancelled) + InvokingKeyBindings?.Invoke (this, keyEvent); + if (keyEvent.Handled) { + return true; + } var handled = OnInvokingKeyBindings (keyEvent); if (handled != null && (bool)handled) { return true; @@ -553,9 +557,11 @@ public virtual bool OnKeyUp (Key keyEvent) { // fire event // BUGBUG: KeyEventArgs doesn't include scope, so the event never sees it. - InvokingKeyBindings?.Invoke (this, keyEvent); - if (keyEvent.Handled) { - return true; + if (keyEvent.Scope == KeyBindingScope.Application || keyEvent.Scope == KeyBindingScope.HotKey) { + InvokingKeyBindings?.Invoke (this, keyEvent); + if (keyEvent.Handled) { + return true; + } } // * If no key binding was found, `InvokeKeyBindings` returns `null`. diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 56ed3d8b1a..4511f1bfd7 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -92,7 +92,7 @@ void Initialize (DateTime date, bool isShort = false) AddCommand (Command.Right, () => MoveRight ()); // Default keybindings for this view - KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight); KeyBindings.Add (Key.Delete, Command.DeleteCharLeft); diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 9a5daabe13..4f8bb4bf1a 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -963,7 +963,7 @@ private bool TableView_KeyUp (Key keyEvent) return this.history.Forward (); } - if (keyEvent.KeyCode == KeyCode.DeleteChar) { + if (keyEvent.KeyCode == KeyCode.Delete) { Delete (); return true; diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index ea737be7e9..acaf46fdbb 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -143,10 +143,9 @@ void SetInitialProperties (string text, int w) // Default keybindings for this view // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts - KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); - KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft); KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend); @@ -201,9 +200,9 @@ void SetInitialProperties (string text, int w) KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight); KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight); - KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards); + KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); - KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite); + KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite); KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste); diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs index 7f3f74d349..4b967eda02 100644 --- a/Terminal.Gui/Views/TextValidateField.cs +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -404,7 +404,7 @@ void Initialize () KeyBindings.Add (KeyCode.End, Command.RightEnd); KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); - KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); KeyBindings.Add (KeyCode.CursorLeft, Command.Left); diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index b5a2f71c9a..0626c6ea73 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -13,1611 +13,1601 @@ using System.Threading; using Terminal.Gui.Resources; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// Represents a single row/column within the . Includes the glyph and the foreground/background colors. +/// +[DebuggerDisplay ("{DebuggerDisplay}")] +public class RuneCell : IEquatable { /// - /// Represents a single row/column within the . Includes the glyph and the foreground/background colors. + /// The glyph to draw. /// - [DebuggerDisplay ("{DebuggerDisplay}")] - public class RuneCell : IEquatable { - /// - /// The glyph to draw. - /// - [JsonConverter (typeof (RuneJsonConverter))] - public Rune Rune { get; set; } + [JsonConverter (typeof (RuneJsonConverter))] + public Rune Rune { get; set; } - /// - /// The color sets to draw the glyph with. - /// - [JsonConverter (typeof (ColorSchemeJsonConverter))] - public ColorScheme? ColorScheme { get; set; } - - /// Indicates whether the current object is equal to another object of the same type. - /// An object to compare with this object. - /// - /// if the current object is equal to the parameter; - /// otherwise, . - public bool Equals (RuneCell? other) - { - return other != null && - Rune.Equals (other.Rune) && - ColorScheme == other.ColorScheme; - } + /// + /// The color sets to draw the glyph with. + /// + [JsonConverter (typeof (ColorSchemeJsonConverter))] + public ColorScheme? ColorScheme { get; set; } + + /// Indicates whether the current object is equal to another object of the same type. + /// An object to compare with this object. + /// + /// if the current object is equal to the parameter; + /// otherwise, . + public bool Equals (RuneCell? other) => other != null && + Rune.Equals (other.Rune) && + ColorScheme == other.ColorScheme; + + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString () + { + string colorSchemeStr = ColorSchemeDebuggerDisplay (); + return DebuggerDisplay; + } - /// Returns a string that represents the current object. - /// A string that represents the current object. - public override string ToString () - { - string colorSchemeStr = ColorSchemeDebuggerDisplay (); - return DebuggerDisplay; + string ColorSchemeDebuggerDisplay () + { + string colorSchemeStr = "null"; + if (ColorScheme != null) { + colorSchemeStr = $"Normal: {ColorScheme.Normal.Foreground},{ColorScheme.Normal.Background}; " + + $"Focus: {ColorScheme.Focus.Foreground},{ColorScheme.Focus.Background}; " + + $"HotNormal: {ColorScheme.HotNormal.Foreground},{ColorScheme.HotNormal.Background}; " + + $"HotFocus: {ColorScheme.HotFocus.Foreground},{ColorScheme.HotFocus.Background}; " + + $"Disabled: {ColorScheme.Disabled.Foreground},{ColorScheme.Disabled.Background}"; } - private string ColorSchemeDebuggerDisplay () - { - var colorSchemeStr = "null"; - if (ColorScheme != null) { - colorSchemeStr = $"Normal: {ColorScheme.Normal.Foreground},{ColorScheme.Normal.Background}; " + - $"Focus: {ColorScheme.Focus.Foreground},{ColorScheme.Focus.Background}; " + - $"HotNormal: {ColorScheme.HotNormal.Foreground},{ColorScheme.HotNormal.Background}; " + - $"HotFocus: {ColorScheme.HotFocus.Foreground},{ColorScheme.HotFocus.Background}; " + - $"Disabled: {ColorScheme.Disabled.Foreground},{ColorScheme.Disabled.Background}"; - } - - return colorSchemeStr; - } + return colorSchemeStr; + } - private string DebuggerDisplay { - get { - string colorSchemeStr = ColorSchemeDebuggerDisplay (); - return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}"; - } + string DebuggerDisplay { + get { + string colorSchemeStr = ColorSchemeDebuggerDisplay (); + return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}"; } } +} - class TextModel { - List> _lines = new List> (); - - public event EventHandler? LinesLoaded; - - public bool LoadFile (string file) - { - FilePath = file ?? throw new ArgumentNullException (nameof (file)); +class TextModel { + List> _lines = new (); - using (var stream = File.OpenRead (file)) { - LoadStream (stream); - return true; - } - } + public event EventHandler? LinesLoaded; - public bool CloseFile () - { - if (FilePath == null) - throw new ArgumentNullException (nameof (FilePath)); + public bool LoadFile (string file) + { + FilePath = file ?? throw new ArgumentNullException (nameof (file)); - FilePath = null; - _lines = new List> (); + using (var stream = File.OpenRead (file)) { + LoadStream (stream); return true; } + } - // Turns the string into cells, this does not split the - // contents on a newline if it is present. - internal static List StringToRuneCells (string str, ColorScheme? colorScheme = null) - { - List cells = new List (); - foreach (var rune in str.ToRunes ()) { - cells.Add (new RuneCell { Rune = rune, ColorScheme = colorScheme }); - } - return cells; + public bool CloseFile () + { + if (FilePath == null) { + throw new ArgumentNullException (nameof (FilePath)); } - internal static List ToRuneCells (IEnumerable runes, ColorScheme? colorScheme = null) - { - List cells = new List (); - foreach (var rune in runes) { - cells.Add (new RuneCell { Rune = rune, ColorScheme = colorScheme }); - } - return cells; + FilePath = null; + _lines = new List> (); + return true; + } + + // Turns the string into cells, this does not split the + // contents on a newline if it is present. + internal static List StringToRuneCells (string str, ColorScheme? colorScheme = null) + { + var cells = new List (); + foreach (var rune in str.ToRunes ()) { + cells.Add (new RuneCell { Rune = rune, ColorScheme = colorScheme }); } + return cells; + } - private static List> ToRuneCells (List cells) - { - return SplitNewLines (cells); + internal static List ToRuneCells (IEnumerable runes, ColorScheme? colorScheme = null) + { + var cells = new List (); + foreach (var rune in runes) { + cells.Add (new RuneCell { Rune = rune, ColorScheme = colorScheme }); } + return cells; + } - // Splits a string into a List that contains a List for each line - public static List> StringToLinesOfRuneCells (string content, ColorScheme? colorScheme = null) - { - var cells = content.EnumerateRunes ().Select (x => new RuneCell () { Rune = x, ColorScheme = colorScheme }).ToList (); + static List> ToRuneCells (List cells) => SplitNewLines (cells); - return SplitNewLines (cells); - } + // Splits a string into a List that contains a List for each line + public static List> StringToLinesOfRuneCells (string content, ColorScheme? colorScheme = null) + { + var cells = content.EnumerateRunes ().Select (x => new RuneCell () { Rune = x, ColorScheme = colorScheme }).ToList (); - private static List> SplitNewLines (List cells) - { - var lines = new List> (); - int start = 0, i = 0; - var hasCR = false; - // ASCII code 13 = Carriage Return. - // ASCII code 10 = Line Feed. - for (; i < cells.Count; i++) { - if (cells [i].Rune.Value == 13) { - hasCR = true; - continue; - } - if (cells [i].Rune.Value == 10) { - if (i - start > 0) - lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start)); - else - lines.Add (StringToRuneCells (string.Empty)); - start = i + 1; - hasCR = false; + return SplitNewLines (cells); + } + + static List> SplitNewLines (List cells) + { + var lines = new List> (); + int start = 0, i = 0; + bool hasCR = false; + // ASCII code 13 = Carriage Return. + // ASCII code 10 = Line Feed. + for (; i < cells.Count; i++) { + if (cells [i].Rune.Value == 13) { + hasCR = true; + continue; + } + if (cells [i].Rune.Value == 10) { + if (i - start > 0) { + lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start)); + } else { + lines.Add (StringToRuneCells (string.Empty)); } + start = i + 1; + hasCR = false; } - if (i - start >= 0) - lines.Add (cells.GetRange (start, i - start)); - return lines; } + if (i - start >= 0) { + lines.Add (cells.GetRange (start, i - start)); + } + return lines; + } - void Append (List line) - { - var str = StringExtensions.ToString (line.ToArray ()); - _lines.Add (StringToRuneCells (str)); + void Append (List line) + { + string str = StringExtensions.ToString (line.ToArray ()); + _lines.Add (StringToRuneCells (str)); + } + + public void LoadStream (Stream input) + { + if (input == null) { + throw new ArgumentNullException (nameof (input)); } - public void LoadStream (Stream input) - { - if (input == null) - throw new ArgumentNullException (nameof (input)); - - _lines = new List> (); - var buff = new BufferedStream (input); - int v; - var line = new List (); - var wasNewLine = false; - while ((v = buff.ReadByte ()) != -1) { - if (v == 13) { - continue; - } - if (v == 10) { - Append (line); - line.Clear (); - wasNewLine = true; - continue; - } - line.Add ((byte)v); - wasNewLine = false; + _lines = new List> (); + var buff = new BufferedStream (input); + int v; + var line = new List (); + bool wasNewLine = false; + while ((v = buff.ReadByte ()) != -1) { + if (v == 13) { + continue; } - if (line.Count > 0 || wasNewLine) + if (v == 10) { Append (line); - buff.Dispose (); - - OnLinesLoaded (); + line.Clear (); + wasNewLine = true; + continue; + } + line.Add ((byte)v); + wasNewLine = false; + } + if (line.Count > 0 || wasNewLine) { + Append (line); } + buff.Dispose (); - public void LoadString (string content) - { - _lines = StringToLinesOfRuneCells (content); + OnLinesLoaded (); + } - OnLinesLoaded (); - } + public void LoadString (string content) + { + _lines = StringToLinesOfRuneCells (content); - public void LoadRuneCells (List cells, ColorScheme? colorScheme) - { - _lines = ToRuneCells (cells); - SetColorSchemes (colorScheme); - OnLinesLoaded (); - } + OnLinesLoaded (); + } - public void LoadListRuneCells (List> cellsList, ColorScheme? colorScheme) - { - _lines = cellsList; - SetColorSchemes (colorScheme); - OnLinesLoaded (); - } + public void LoadRuneCells (List cells, ColorScheme? colorScheme) + { + _lines = ToRuneCells (cells); + SetColorSchemes (colorScheme); + OnLinesLoaded (); + } - private void SetColorSchemes (ColorScheme? colorScheme) - { - foreach (var line in _lines) { - foreach (var cell in line) { - if (cell.ColorScheme == null) { - cell.ColorScheme = colorScheme; - } + public void LoadListRuneCells (List> cellsList, ColorScheme? colorScheme) + { + _lines = cellsList; + SetColorSchemes (colorScheme); + OnLinesLoaded (); + } + + void SetColorSchemes (ColorScheme? colorScheme) + { + foreach (var line in _lines) { + foreach (var cell in line) { + if (cell.ColorScheme == null) { + cell.ColorScheme = colorScheme; } } } + } - void OnLinesLoaded () - { - LinesLoaded?.Invoke (this, EventArgs.Empty); - } + void OnLinesLoaded () => LinesLoaded?.Invoke (this, EventArgs.Empty); - public override string ToString () - { - var sb = new StringBuilder (); - for (int i = 0; i < _lines.Count; i++) { - sb.Append (ToString (_lines [i])); - if ((i + 1) < _lines.Count) { - sb.AppendLine (); - } + public override string ToString () + { + var sb = new StringBuilder (); + for (int i = 0; i < _lines.Count; i++) { + sb.Append (ToString (_lines [i])); + if (i + 1 < _lines.Count) { + sb.AppendLine (); } - return sb.ToString (); } + return sb.ToString (); + } - public string? FilePath { get; set; } + public string? FilePath { get; set; } - /// - /// The number of text lines in the model - /// - public int Count => _lines.Count; + /// + /// The number of text lines in the model + /// + public int Count => _lines.Count; - /// - /// Returns the specified line as a List of Rune - /// - /// The line. - /// Line number to retrieve. - public List GetLine (int line) - { - if (_lines.Count > 0) { - if (line < Count) { - return _lines [line]; - } else { - return _lines [Count - 1]; - } + /// + /// Returns the specified line as a List of Rune + /// + /// The line. + /// Line number to retrieve. + public List GetLine (int line) + { + if (_lines.Count > 0) { + if (line < Count) { + return _lines [line]; } else { - _lines.Add (new List ()); - return _lines [0]; + return _lines [Count - 1]; } + } else { + _lines.Add (new List ()); + return _lines [0]; } + } - public List> GetAllLines () => _lines; + public List> GetAllLines () => _lines; - /// - /// Adds a line to the model at the specified position. - /// - /// Line number where the line will be inserted. - /// The line of text and color, as a List of RuneCell. - public void AddLine (int pos, List cells) - { - _lines.Insert (pos, cells); - } + /// + /// Adds a line to the model at the specified position. + /// + /// Line number where the line will be inserted. + /// The line of text and color, as a List of RuneCell. + public void AddLine (int pos, List cells) => _lines.Insert (pos, cells); - /// - /// Removes the line at the specified position - /// - /// Position. - public void RemoveLine (int pos) - { - if (_lines.Count > 0) { - if (_lines.Count == 1 && _lines [0].Count == 0) { - return; - } - _lines.RemoveAt (pos); + /// + /// Removes the line at the specified position + /// + /// Position. + public void RemoveLine (int pos) + { + if (_lines.Count > 0) { + if (_lines.Count == 1 && _lines [0].Count == 0) { + return; } + _lines.RemoveAt (pos); } + } - public void ReplaceLine (int pos, List runes) - { - if (_lines.Count > 0 && pos < _lines.Count) { - _lines [pos] = new List (runes); - } else if (_lines.Count == 0 || (_lines.Count > 0 && pos >= _lines.Count)) { - _lines.Add (runes); - } + public void ReplaceLine (int pos, List runes) + { + if (_lines.Count > 0 && pos < _lines.Count) { + _lines [pos] = new List (runes); + } else if (_lines.Count == 0 || _lines.Count > 0 && pos >= _lines.Count) { + _lines.Add (runes); } + } - /// - /// Returns the maximum line length of the visible lines. - /// - /// The first line. - /// The last line. - /// The tab width. - public int GetMaxVisibleLine (int first, int last, int tabWidth) - { - int maxLength = 0; - last = last < _lines.Count ? last : _lines.Count; - for (int i = first; i < last; i++) { - var line = GetLine (i); - var tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0); - var l = line.Count + tabSum; - if (l > maxLength) { - maxLength = l; - } - } + /// + /// Returns the maximum line length of the visible lines. + /// + /// The first line. + /// The last line. + /// The tab width. + public int GetMaxVisibleLine (int first, int last, int tabWidth) + { + int maxLength = 0; + last = last < _lines.Count ? last : _lines.Count; + for (int i = first; i < last; i++) { + var line = GetLine (i); + int tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0); + int l = line.Count + tabSum; + if (l > maxLength) { + maxLength = l; + } + } + + return maxLength; + } - return maxLength; + internal static bool SetCol (ref int col, int width, int cols) + { + if (col + cols <= width) { + col += cols; + return true; } - internal static bool SetCol (ref int col, int width, int cols) - { - if (col + cols <= width) { - col += cols; - return true; - } + return false; + } - return false; + internal static int GetColFromX (List t, int start, int x, int tabWidth = 0) + { + var runes = new List (); + foreach (var cell in t) { + runes.Add (cell.Rune); } + return GetColFromX (runes, start, x, tabWidth); + } - internal static int GetColFromX (List t, int start, int x, int tabWidth = 0) - { - var runes = new List (); - foreach (var cell in t) { - runes.Add (cell.Rune); + internal static int GetColFromX (List t, int start, int x, int tabWidth = 0) + { + if (x < 0) { + return x; + } + int size = start; + int pX = x + start; + for (int i = start; i < t.Count; i++) { + var r = t [i]; + size += r.GetColumns (); + if (r.Value == '\t') { + size += tabWidth + 1; + } + if (i == pX || size > pX) { + return i - start; } - return GetColFromX (runes, start, x, tabWidth); } + return t.Count - start; + } - internal static int GetColFromX (List t, int start, int x, int tabWidth = 0) - { - if (x < 0) { - return x; - } - int size = start; - var pX = x + start; - for (int i = start; i < t.Count; i++) { - var r = t [i]; - size += r.GetColumns (); - if (r.Value == '\t') { - size += tabWidth + 1; - } - if (i == pX || (size > pX)) { - return i - start; - } - } - return t.Count - start; + internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, + bool checkNextRune = true, int tabWidth = 0) + { + var runes = new List (); + foreach (var cell in t) { + runes.Add (cell.Rune); } + return DisplaySize (runes, start, end, checkNextRune, tabWidth); + } - internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, - bool checkNextRune = true, int tabWidth = 0) - { - List runes = new List (); - foreach (var cell in t) { - runes.Add (cell.Rune); + // Returns the size and length in a range of the string. + internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, + bool checkNextRune = true, int tabWidth = 0) + { + if (t == null || t.Count == 0) { + return (0, 0); + } + int size = 0; + int len = 0; + int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; + int i = start == -1 ? 0 : start; + for (; i < tcount; i++) { + var rune = t [i]; + size += rune.GetColumns (); + len += rune.GetEncodingLength (Encoding.Unicode); + if (rune.Value == '\t') { + size += tabWidth + 1; + len += tabWidth - 1; + } + if (checkNextRune && i == tcount - 1 && t.Count > tcount + && IsWideRune (t [i + 1], tabWidth, out int s, out int l)) { + size += s; + len += l; } - return DisplaySize (runes, start, end, checkNextRune, tabWidth); } - // Returns the size and length in a range of the string. - internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, - bool checkNextRune = true, int tabWidth = 0) + bool IsWideRune (Rune r, int tWidth, out int s, out int l) { - if (t == null || t.Count == 0) { - return (0, 0); - } - int size = 0; - int len = 0; - int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; - int i = start == -1 ? 0 : start; - for (; i < tcount; i++) { - var rune = t [i]; - size += rune.GetColumns (); - len += rune.GetEncodingLength (Encoding.Unicode); - if (rune.Value == '\t') { - size += tabWidth + 1; - len += tabWidth - 1; - } - if (checkNextRune && i == tcount - 1 && t.Count > tcount - && IsWideRune (t [i + 1], tabWidth, out int s, out int l)) { - size += s; - len += l; - } + s = r.GetColumns (); + l = r.GetEncodingLength (); + if (r.Value == '\t') { + s += tWidth + 1; + l += tWidth - 1; } - bool IsWideRune (Rune r, int tWidth, out int s, out int l) - { - s = r.GetColumns (); - l = r.GetEncodingLength (); - if (r.Value == '\t') { - s += tWidth + 1; - l += tWidth - 1; - } + return s > 1; + } - return s > 1; - } + return (size, len); + } - return (size, len); + internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0) + { + var runes = new List (); + foreach (var cell in t) { + runes.Add (cell.Rune); } + return CalculateLeftColumn (runes, start, end, width, tabWidth); + } - internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0) - { - List runes = new List (); - foreach (var cell in t) { - runes.Add (cell.Rune); - } - return CalculateLeftColumn (runes, start, end, width, tabWidth); + // Returns the left column in a range of the string. + internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0) + { + if (t == null || t.Count == 0) { + return 0; } + int size = 0; + int tcount = end > t.Count - 1 ? t.Count - 1 : end; + int col = 0; - // Returns the left column in a range of the string. - internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0) - { - if (t == null || t.Count == 0) { - return 0; + for (int i = tcount; i >= 0; i--) { + var rune = t [i]; + size += rune.GetColumns (); + if (rune.Value == '\t') { + size += tabWidth + 1; } - int size = 0; - int tcount = end > t.Count - 1 ? t.Count - 1 : end; - int col = 0; - - for (int i = tcount; i >= 0; i--) { - var rune = t [i]; - size += rune.GetColumns (); - if (rune.Value == '\t') { - size += tabWidth + 1; - } - if (size > width) { - if (col + width == end) { - col++; - } - break; - } else if ((end < t.Count && col > 0 && start < end && col == start) || (end - col == width - 1)) { - break; + if (size > width) { + if (col + width == end) { + col++; } - col = i; + break; + } else if (end < t.Count && col > 0 && start < end && col == start || end - col == width - 1) { + break; } - - return col; + col = i; } - (Point startPointToFind, Point currentPointToFind, bool found) _toFind; + return col; + } - internal (Point current, bool found) FindNextText (string text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) - { - if (text == null || _lines.Count == 0) { - gaveFullTurn = false; - return (Point.Empty, false); - } + (Point startPointToFind, Point currentPointToFind, bool found) _toFind; - if (_toFind.found) { - _toFind.currentPointToFind.X++; - } - var foundPos = GetFoundNextTextPoint (text, _lines.Count, matchCase, matchWholeWord, _toFind.currentPointToFind); - if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind) { - foundPos = GetFoundNextTextPoint (text, _toFind.startPointToFind.Y + 1, matchCase, matchWholeWord, Point.Empty); - } - gaveFullTurn = ApplyToFind (foundPos); + internal (Point current, bool found) FindNextText (string text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) + { + if (text == null || _lines.Count == 0) { + gaveFullTurn = false; + return (Point.Empty, false); + } - return foundPos; + if (_toFind.found) { + _toFind.currentPointToFind.X++; } + var foundPos = GetFoundNextTextPoint (text, _lines.Count, matchCase, matchWholeWord, _toFind.currentPointToFind); + if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind) { + foundPos = GetFoundNextTextPoint (text, _toFind.startPointToFind.Y + 1, matchCase, matchWholeWord, Point.Empty); + } + gaveFullTurn = ApplyToFind (foundPos); - internal (Point current, bool found) FindPreviousText (string text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) - { - if (text == null || _lines.Count == 0) { - gaveFullTurn = false; - return (Point.Empty, false); - } + return foundPos; + } - if (_toFind.found) { - _toFind.currentPointToFind.X++; - } - var linesCount = _toFind.currentPointToFind.IsEmpty ? _lines.Count - 1 : _toFind.currentPointToFind.Y; - var foundPos = GetFoundPreviousTextPoint (text, linesCount, matchCase, matchWholeWord, _toFind.currentPointToFind); - if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind) { - foundPos = GetFoundPreviousTextPoint (text, _lines.Count - 1, matchCase, matchWholeWord, - new Point (_lines [_lines.Count - 1].Count, _lines.Count)); - } - gaveFullTurn = ApplyToFind (foundPos); + internal (Point current, bool found) FindPreviousText (string text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) + { + if (text == null || _lines.Count == 0) { + gaveFullTurn = false; + return (Point.Empty, false); + } - return foundPos; + if (_toFind.found) { + _toFind.currentPointToFind.X++; } + int linesCount = _toFind.currentPointToFind.IsEmpty ? _lines.Count - 1 : _toFind.currentPointToFind.Y; + var foundPos = GetFoundPreviousTextPoint (text, linesCount, matchCase, matchWholeWord, _toFind.currentPointToFind); + if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind) { + foundPos = GetFoundPreviousTextPoint (text, _lines.Count - 1, matchCase, matchWholeWord, + new Point (_lines [_lines.Count - 1].Count, _lines.Count)); + } + gaveFullTurn = ApplyToFind (foundPos); - internal (Point current, bool found) ReplaceAllText (string text, bool matchCase = false, bool matchWholeWord = false, string? textToReplace = null) - { - bool found = false; - Point pos = Point.Empty; - - for (int i = 0; i < _lines.Count; i++) { - var x = _lines [i]; - var txt = GetText (x); - var matchText = !matchCase ? text.ToUpper () : text; - var col = txt.IndexOf (matchText); - while (col > -1) { - if (matchWholeWord && !MatchWholeWord (txt, matchText, col)) { - if (col + 1 > txt.Length) { - break; - } - col = txt.IndexOf (matchText, col + 1); - continue; - } - if (col > -1) { - if (!found) { - found = true; - } - _lines [i] = ToRuneCellList (ReplaceText (x, textToReplace!, matchText, col)); - x = _lines [i]; - txt = GetText (x); - pos = new Point (col, i); - col += (textToReplace!.Length - matchText.Length); - } - if (col < 0 || col + 1 > txt.Length) { + return foundPos; + } + + internal (Point current, bool found) ReplaceAllText (string text, bool matchCase = false, bool matchWholeWord = false, string? textToReplace = null) + { + bool found = false; + var pos = Point.Empty; + + for (int i = 0; i < _lines.Count; i++) { + var x = _lines [i]; + string txt = GetText (x); + string matchText = !matchCase ? text.ToUpper () : text; + int col = txt.IndexOf (matchText); + while (col > -1) { + if (matchWholeWord && !MatchWholeWord (txt, matchText, col)) { + if (col + 1 > txt.Length) { break; } col = txt.IndexOf (matchText, col + 1); + continue; } - } - - string GetText (List x) - { - var txt = ToString (x); - if (!matchCase) { - txt = txt.ToUpper (); - } - return txt; - } - - return (pos, found); - } - - string ReplaceText (List source, string textToReplace, string matchText, int col) - { - var origTxt = ToString (source); - (int _, int len) = TextModel.DisplaySize (source, 0, col, false); - (int _, int len2) = TextModel.DisplaySize (source, col, col + matchText.Length, false); - (int _, int len3) = TextModel.DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false); - - return origTxt [..len] + - textToReplace + - origTxt.Substring (len + len2, len3); - } - - bool ApplyToFind ((Point current, bool found) foundPos) - { - bool gaveFullTurn = false; - if (foundPos.found) { - _toFind.currentPointToFind = foundPos.current; - if (_toFind.found && _toFind.currentPointToFind == _toFind.startPointToFind) { - gaveFullTurn = true; + if (col > -1) { + if (!found) { + found = true; + } + _lines [i] = ToRuneCellList (ReplaceText (x, textToReplace!, matchText, col)); + x = _lines [i]; + txt = GetText (x); + pos = new Point (col, i); + col += textToReplace!.Length - matchText.Length; } - if (!_toFind.found) { - _toFind.startPointToFind = _toFind.currentPointToFind = foundPos.current; - _toFind.found = foundPos.found; + if (col < 0 || col + 1 > txt.Length) { + break; } + col = txt.IndexOf (matchText, col + 1); } - - return gaveFullTurn; } - (Point current, bool found) GetFoundNextTextPoint (string text, int linesCount, bool matchCase, bool matchWholeWord, Point start) + string GetText (List x) { - for (int i = start.Y; i < linesCount; i++) { - var x = _lines [i]; - var txt = ToString (x); - if (!matchCase) { - txt = txt.ToUpper (); - } - var matchText = !matchCase ? text.ToUpper () : text; - var col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length)); - if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { - continue; - } - if (col > -1 && ((i == start.Y && col >= start.X) - || i > start.Y) - && txt.Contains (matchText)) { - return (new Point (col, i), true); - } else if (col == -1 && start.X > 0) { - start.X = 0; - } + string txt = ToString (x); + if (!matchCase) { + txt = txt.ToUpper (); } - - return (Point.Empty, false); + return txt; } - (Point current, bool found) GetFoundPreviousTextPoint (string text, int linesCount, bool matchCase, bool matchWholeWord, Point start) - { - for (int i = linesCount; i >= 0; i--) { - var x = _lines [i]; - var txt = ToString (x); - if (!matchCase) { - txt = txt.ToUpper (); - } - if (start.Y != i) { - start.X = Math.Max (x.Count - 1, 0); - } - var matchText = !matchCase ? text.ToUpper () : text; - var col = txt.LastIndexOf (matchText, _toFind.found ? start.X - 1 : start.X); - if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { - continue; - } - if (col > -1 && ((i <= linesCount && col <= start.X) - || i < start.Y) - && txt.Contains (matchText)) { - return (new Point (col, i), true); - } - } - - return (Point.Empty, false); - } + return (pos, found); + } - bool MatchWholeWord (string source, string matchText, int index = 0) - { - if (string.IsNullOrEmpty (source) || string.IsNullOrEmpty (matchText)) { - return false; - } + string ReplaceText (List source, string textToReplace, string matchText, int col) + { + string origTxt = ToString (source); + (int _, int len) = DisplaySize (source, 0, col, false); + (int _, int len2) = DisplaySize (source, col, col + matchText.Length, false); + (int _, int len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false); - var txt = matchText.Trim (); - var start = index > 0 ? index - 1 : 0; - var end = index + txt.Length; + return origTxt [..len] + + textToReplace + + origTxt.Substring (len + len2, len3); + } - if ((start == 0 || Rune.IsWhiteSpace ((Rune)source [start])) - && (end == source.Length || Rune.IsWhiteSpace ((Rune)source [end]))) { - return true; + bool ApplyToFind ((Point current, bool found) foundPos) + { + bool gaveFullTurn = false; + if (foundPos.found) { + _toFind.currentPointToFind = foundPos.current; + if (_toFind.found && _toFind.currentPointToFind == _toFind.startPointToFind) { + gaveFullTurn = true; + } + if (!_toFind.found) { + _toFind.startPointToFind = _toFind.currentPointToFind = foundPos.current; + _toFind.found = foundPos.found; } - - return false; } - /// - /// Redefine column and line tracking. - /// - /// Contains the column and line. - internal void ResetContinuousFind (Point point) - { - _toFind.startPointToFind = _toFind.currentPointToFind = point; - _toFind.found = false; - } + return gaveFullTurn; + } - RuneCell RuneAt (int col, int row) - { - var line = GetLine (row); - if (line.Count > 0) { - return line [col > line.Count - 1 ? line.Count - 1 : col]; - } else { - return default!; + (Point current, bool found) GetFoundNextTextPoint (string text, int linesCount, bool matchCase, bool matchWholeWord, Point start) + { + for (int i = start.Y; i < linesCount; i++) { + var x = _lines [i]; + string txt = ToString (x); + if (!matchCase) { + txt = txt.ToUpper (); } - } - - bool MoveNext (ref int col, ref int row, out Rune rune) - { - var line = GetLine (row); - if (col + 1 < line.Count) { - col++; - rune = line [col].Rune; - if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune) - && !Rune.IsWhiteSpace (line [col - 1].Rune)) { - col++; - } - return true; - } else if (col + 1 == line.Count) { - col++; + string matchText = !matchCase ? text.ToUpper () : text; + int col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length)); + if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { + continue; } - while (row + 1 < Count) { - col = 0; - row++; - line = GetLine (row); - if (line.Count > 0) { - rune = line [0].Rune; - return true; - } + if (col > -1 && (i == start.Y && col >= start.X + || i > start.Y) + && txt.Contains (matchText)) { + return (new Point (col, i), true); + } else if (col == -1 && start.X > 0) { + start.X = 0; } - rune = default; - return false; } - bool MovePrev (ref int col, ref int row, out Rune rune) - { - var line = GetLine (row); + return (Point.Empty, false); + } - if (col > 0) { - col--; - rune = line [col].Rune; - return true; + (Point current, bool found) GetFoundPreviousTextPoint (string text, int linesCount, bool matchCase, bool matchWholeWord, Point start) + { + for (int i = linesCount; i >= 0; i--) { + var x = _lines [i]; + string txt = ToString (x); + if (!matchCase) { + txt = txt.ToUpper (); } - if (row == 0) { - rune = default; - return false; + if (start.Y != i) { + start.X = Math.Max (x.Count - 1, 0); } - while (row > 0) { - row--; - line = GetLine (row); - col = line.Count - 1; - if (col >= 0) { - rune = line [col].Rune; - return true; - } + string matchText = !matchCase ? text.ToUpper () : text; + int col = txt.LastIndexOf (matchText, _toFind.found ? start.X - 1 : start.X); + if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { + continue; + } + if (col > -1 && (i <= linesCount && col <= start.X + || i < start.Y) + && txt.Contains (matchText)) { + return (new Point (col, i), true); } - rune = default; - return false; } - enum RuneType { - IsSymbol, - IsWhiteSpace, - IsLetterOrDigit, - IsPunctuation, - IsUnknow + return (Point.Empty, false); + } + + bool MatchWholeWord (string source, string matchText, int index = 0) + { + if (string.IsNullOrEmpty (source) || string.IsNullOrEmpty (matchText)) { + return false; } - RuneType GetRuneType (Rune rune) - { - if (Rune.IsSymbol (rune)) { - return RuneType.IsSymbol; - } else if (Rune.IsWhiteSpace (rune)) { - return RuneType.IsWhiteSpace; - } else if (Rune.IsLetterOrDigit (rune)) { - return RuneType.IsLetterOrDigit; - } else if (Rune.IsPunctuation (rune)) { - return RuneType.IsPunctuation; + string txt = matchText.Trim (); + int start = index > 0 ? index - 1 : 0; + int end = index + txt.Length; + + if ((start == 0 || Rune.IsWhiteSpace ((Rune)source [start])) + && (end == source.Length || Rune.IsWhiteSpace ((Rune)source [end]))) { + return true; + } + + return false; + } + + /// + /// Redefine column and line tracking. + /// + /// Contains the column and line. + internal void ResetContinuousFind (Point point) + { + _toFind.startPointToFind = _toFind.currentPointToFind = point; + _toFind.found = false; + } + + RuneCell RuneAt (int col, int row) + { + var line = GetLine (row); + if (line.Count > 0) { + return line [col > line.Count - 1 ? line.Count - 1 : col]; + } else { + return default!; + } + } + + bool MoveNext (ref int col, ref int row, out Rune rune) + { + var line = GetLine (row); + if (col + 1 < line.Count) { + col++; + rune = line [col].Rune; + if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune) + && !Rune.IsWhiteSpace (line [col - 1].Rune)) { + col++; + } + return true; + } else if (col + 1 == line.Count) { + col++; + } + while (row + 1 < Count) { + col = 0; + row++; + line = GetLine (row); + if (line.Count > 0) { + rune = line [0].Rune; + return true; } - return RuneType.IsUnknow; } + rune = default; + return false; + } - bool IsSameRuneType (Rune newRune, RuneType runeType) - { - var rt = GetRuneType (newRune); - return rt == runeType; + bool MovePrev (ref int col, ref int row, out Rune rune) + { + var line = GetLine (row); + + if (col > 0) { + col--; + rune = line [col].Rune; + return true; + } + if (row == 0) { + rune = default; + return false; + } + while (row > 0) { + row--; + line = GetLine (row); + col = line.Count - 1; + if (col >= 0) { + rune = line [col].Rune; + return true; + } } + rune = default; + return false; + } - public (int col, int row)? WordForward (int fromCol, int fromRow) - { - if (fromRow == _lines.Count - 1 && fromCol == GetLine (_lines.Count - 1).Count) - return null; + enum RuneType { + IsSymbol, + IsWhiteSpace, + IsLetterOrDigit, + IsPunctuation, + IsUnknow + } - var col = fromCol; - var row = fromRow; - try { - var rune = RuneAt (col, row).Rune; - var runeType = GetRuneType (rune); - int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; - - void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) - { - if (Rune.IsWhiteSpace (nRune)) { - while (MoveNext (ref nCol, ref nRow, out nRune)) { - if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)) { - lastValidCol = nCol; - return; - } - } - if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))) { - if (lastValidCol > -1) { - nCol = lastValidCol; - } + RuneType GetRuneType (Rune rune) + { + if (Rune.IsSymbol (rune)) { + return RuneType.IsSymbol; + } else if (Rune.IsWhiteSpace (rune)) { + return RuneType.IsWhiteSpace; + } else if (Rune.IsLetterOrDigit (rune)) { + return RuneType.IsLetterOrDigit; + } else if (Rune.IsPunctuation (rune)) { + return RuneType.IsPunctuation; + } + return RuneType.IsUnknow; + } + + bool IsSameRuneType (Rune newRune, RuneType runeType) + { + var rt = GetRuneType (newRune); + return rt == runeType; + } + + public (int col, int row)? WordForward (int fromCol, int fromRow) + { + if (fromRow == _lines.Count - 1 && fromCol == GetLine (_lines.Count - 1).Count) { + return null; + } + + int col = fromCol; + int row = fromRow; + try { + var rune = RuneAt (col, row).Rune; + var runeType = GetRuneType (rune); + int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; + + void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) + { + if (Rune.IsWhiteSpace (nRune)) { + while (MoveNext (ref nCol, ref nRow, out nRune)) { + if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)) { + lastValidCol = nCol; return; } - while (MoveNext (ref nCol, ref nRow, out nRune)) { - if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune)) - break; - if (nRow != fromRow) { - break; - } - lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; - } + } + if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))) { if (lastValidCol > -1) { nCol = lastValidCol; - nRow = fromRow; } - } else { - if (!MoveNext (ref nCol, ref nRow, out nRune)) { - return; - } - if (!IsSameRuneType (nRune, runeType) && !Rune.IsWhiteSpace (nRune)) { - return; + return; + } + while (MoveNext (ref nCol, ref nRow, out nRune)) { + if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune)) { + break; } - var line = GetLine (nRow); - if (nCol == line.Count && nRow == fromRow && (Rune.IsLetterOrDigit (line [0].Rune) || Rune.IsPunctuation (line [0].Rune) || Rune.IsSymbol (line [0].Rune))) { - return; + if (nRow != fromRow) { + break; } lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; - if (fromRow != nRow) { - nCol = 0; - return; - } - ProcMoveNext (ref nCol, ref nRow, nRune); } + if (lastValidCol > -1) { + nCol = lastValidCol; + nRow = fromRow; + } + } else { + if (!MoveNext (ref nCol, ref nRow, out nRune)) { + return; + } + if (!IsSameRuneType (nRune, runeType) && !Rune.IsWhiteSpace (nRune)) { + return; + } + var line = GetLine (nRow); + if (nCol == line.Count && nRow == fromRow && (Rune.IsLetterOrDigit (line [0].Rune) || Rune.IsPunctuation (line [0].Rune) || Rune.IsSymbol (line [0].Rune))) { + return; + } + lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; + if (fromRow != nRow) { + nCol = 0; + return; + } + ProcMoveNext (ref nCol, ref nRow, nRune); } + } - ProcMoveNext (ref col, ref row, rune); + ProcMoveNext (ref col, ref row, rune); - if (fromCol != col || fromRow != row) - return (col, row); - return null; - } catch (Exception) { - return null; + if (fromCol != col || fromRow != row) { + return (col, row); } + return null; + } catch (Exception) { + return null; } + } - public (int col, int row)? WordBackward (int fromCol, int fromRow) - { - if (fromRow == 0 && fromCol == 0) - return null; + public (int col, int row)? WordBackward (int fromCol, int fromRow) + { + if (fromRow == 0 && fromCol == 0) { + return null; + } - var col = Math.Max (fromCol - 1, 0); - var row = fromRow; - try { - RuneCell cell = RuneAt (col, row); - Rune rune; - if (cell != null) { - rune = cell.Rune; + int col = Math.Max (fromCol - 1, 0); + int row = fromRow; + try { + var cell = RuneAt (col, row); + Rune rune; + if (cell != null) { + rune = cell.Rune; + } else { + if (col > 0) { + return (col, row); + } else if (col == 0 && row > 0) { + row--; + var line = GetLine (row); + return (line.Count, row); } else { - if (col > 0) { - return (col, row); - } else if (col == 0 && row > 0) { - row--; - var line = GetLine (row); - return (line.Count, row); - } else { - return null; - } + return null; } - var runeType = GetRuneType (rune); - int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; - - void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) - { - if (Rune.IsWhiteSpace (nRune)) { - while (MovePrev (ref nCol, ref nRow, out nRune)) { - if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)) { - lastValidCol = nCol; - if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknow) { - runeType = GetRuneType (nRune); - } - break; - } - } - if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))) { - if (lastValidCol > -1) { - nCol = lastValidCol; - } - return; - } - while (MovePrev (ref nCol, ref nRow, out nRune)) { - if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune)) - break; - if (nRow != fromRow) { - break; + } + var runeType = GetRuneType (rune); + int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; + + void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) + { + if (Rune.IsWhiteSpace (nRune)) { + while (MovePrev (ref nCol, ref nRow, out nRune)) { + if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)) { + lastValidCol = nCol; + if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknow) { + runeType = GetRuneType (nRune); } - lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; + break; } + } + if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))) { if (lastValidCol > -1) { nCol = lastValidCol; - nRow = fromRow; } - } else { - if (!MovePrev (ref nCol, ref nRow, out nRune)) { - return; + return; + } + while (MovePrev (ref nCol, ref nRow, out nRune)) { + if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune)) { + break; } - - var line = GetLine (nRow); - if (nCol == 0 && nRow == fromRow && (Rune.IsLetterOrDigit (line [0].Rune) || Rune.IsPunctuation (line [0].Rune) || Rune.IsSymbol (line [0].Rune))) { - return; + if (nRow != fromRow) { + break; } lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; - if (lastValidCol > -1 && Rune.IsWhiteSpace (nRune)) { - nCol = lastValidCol; - return; - } - if (fromRow != nRow) { - nCol = line.Count; - return; - } - ProcMovePrev (ref nCol, ref nRow, nRune); } - } - - ProcMovePrev (ref col, ref row, rune); + if (lastValidCol > -1) { + nCol = lastValidCol; + nRow = fromRow; + } + } else { + if (!MovePrev (ref nCol, ref nRow, out nRune)) { + return; + } - if (fromCol != col || fromRow != row) - return (col, row); - return null; - } catch (Exception) { - return null; + var line = GetLine (nRow); + if (nCol == 0 && nRow == fromRow && (Rune.IsLetterOrDigit (line [0].Rune) || Rune.IsPunctuation (line [0].Rune) || Rune.IsSymbol (line [0].Rune))) { + return; + } + lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; + if (lastValidCol > -1 && Rune.IsWhiteSpace (nRune)) { + nCol = lastValidCol; + return; + } + if (fromRow != nRow) { + nCol = line.Count; + return; + } + ProcMovePrev (ref nCol, ref nRow, nRune); + } } - } - /// - /// Converts the string into a . - /// - /// The string to convert. - /// The to use. - /// - public static List ToRuneCellList (string str, ColorScheme? colorScheme = null) - { - var cells = new List (); - foreach (var rune in str.EnumerateRunes ()) { - cells.Add (new RuneCell { Rune = rune, ColorScheme = colorScheme }); + ProcMovePrev (ref col, ref row, rune); + + if (fromCol != col || fromRow != row) { + return (col, row); } - return cells; + return null; + } catch (Exception) { + return null; } + } - /// - /// Converts a generic collection into a string. - /// - /// The enumerable cell to convert. - /// - public static string ToString (IEnumerable cells) - { - var str = string.Empty; + /// + /// Converts the string into a . + /// + /// The string to convert. + /// The to use. + /// + public static List ToRuneCellList (string str, ColorScheme? colorScheme = null) + { + var cells = new List (); + foreach (var rune in str.EnumerateRunes ()) { + cells.Add (new RuneCell { Rune = rune, ColorScheme = colorScheme }); + } + return cells; + } - foreach (var cell in cells) { - str += cell.Rune.ToString (); - } + /// + /// Converts a generic collection into a string. + /// + /// The enumerable cell to convert. + /// + public static string ToString (IEnumerable cells) + { + string str = string.Empty; - return str; + foreach (var cell in cells) { + str += cell.Rune.ToString (); } - } - partial class HistoryText { - public enum LineStatus { - Original, - Replaced, - Removed, - Added - } + return str; + } +} - List _historyTextItems = new List (); - int _idxHistoryText = -1; - string? _originalText; +partial class HistoryText { + public enum LineStatus { + Original, + Replaced, + Removed, + Added + } - public bool IsFromHistory { get; private set; } + List _historyTextItems = new (); + int _idxHistoryText = -1; + string? _originalText; - public bool HasHistoryChanges => _idxHistoryText > -1; + public bool IsFromHistory { get; private set; } - public event EventHandler? ChangeText; + public bool HasHistoryChanges => _idxHistoryText > -1; - public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original) - { - if (lineStatus == LineStatus.Original && _historyTextItems.Count > 0 - && _historyTextItems.Last ().LineStatus == LineStatus.Original) { - return; - } - if (lineStatus == LineStatus.Replaced && _historyTextItems.Count > 0 - && _historyTextItems.Last ().LineStatus == LineStatus.Replaced) { - return; - } + public event EventHandler? ChangeText; - if (_historyTextItems.Count == 0 && lineStatus != LineStatus.Original) - throw new ArgumentException ("The first item must be the original."); + public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original) + { + if (lineStatus == LineStatus.Original && _historyTextItems.Count > 0 + && _historyTextItems.Last ().LineStatus == LineStatus.Original) { + return; + } + if (lineStatus == LineStatus.Replaced && _historyTextItems.Count > 0 + && _historyTextItems.Last ().LineStatus == LineStatus.Replaced) { + return; + } - if (_idxHistoryText >= 0 && _idxHistoryText + 1 < _historyTextItems.Count) - _historyTextItems.RemoveRange (_idxHistoryText + 1, _historyTextItems.Count - _idxHistoryText - 1); + if (_historyTextItems.Count == 0 && lineStatus != LineStatus.Original) { + throw new ArgumentException ("The first item must be the original."); + } - _historyTextItems.Add (new HistoryTextItem (lines, curPos, lineStatus)); - _idxHistoryText++; + if (_idxHistoryText >= 0 && _idxHistoryText + 1 < _historyTextItems.Count) { + _historyTextItems.RemoveRange (_idxHistoryText + 1, _historyTextItems.Count - _idxHistoryText - 1); } - public void ReplaceLast (List> lines, Point curPos, LineStatus lineStatus) - { - var found = _historyTextItems.FindLast (x => x.LineStatus == lineStatus); - if (found != null) { - found.Lines = lines; - found.CursorPosition = curPos; - } + _historyTextItems.Add (new HistoryTextItem (lines, curPos, lineStatus)); + _idxHistoryText++; + } + + public void ReplaceLast (List> lines, Point curPos, LineStatus lineStatus) + { + var found = _historyTextItems.FindLast (x => x.LineStatus == lineStatus); + if (found != null) { + found.Lines = lines; + found.CursorPosition = curPos; } + } - public void Undo () - { - if (_historyTextItems?.Count > 0 && _idxHistoryText > 0) { - IsFromHistory = true; + public void Undo () + { + if (_historyTextItems?.Count > 0 && _idxHistoryText > 0) { + IsFromHistory = true; - _idxHistoryText--; + _idxHistoryText--; - var historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]) { - IsUndoing = true - }; + var historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]) { + IsUndoing = true + }; - ProcessChanges (ref historyTextItem); + ProcessChanges (ref historyTextItem); - IsFromHistory = false; - } + IsFromHistory = false; } + } - public void Redo () - { - if (_historyTextItems?.Count > 0 && _idxHistoryText < _historyTextItems.Count - 1) { - IsFromHistory = true; + public void Redo () + { + if (_historyTextItems?.Count > 0 && _idxHistoryText < _historyTextItems.Count - 1) { + IsFromHistory = true; - _idxHistoryText++; + _idxHistoryText++; - var historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]) { - IsUndoing = false - }; + var historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]) { + IsUndoing = false + }; - ProcessChanges (ref historyTextItem); + ProcessChanges (ref historyTextItem); - IsFromHistory = false; - } + IsFromHistory = false; } + } - void ProcessChanges (ref HistoryTextItem historyTextItem) - { - if (historyTextItem.IsUndoing) { - if (_idxHistoryText - 1 > -1 && ((_historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Added) - || _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed - || (historyTextItem.LineStatus == LineStatus.Replaced && - _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original))) { + void ProcessChanges (ref HistoryTextItem historyTextItem) + { + if (historyTextItem.IsUndoing) { + if (_idxHistoryText - 1 > -1 && (_historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Added + || _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed + || historyTextItem.LineStatus == LineStatus.Replaced && + _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original)) { - _idxHistoryText--; + _idxHistoryText--; - while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added - && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { + while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added + && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { - _idxHistoryText--; - } - historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]); - historyTextItem.IsUndoing = true; - historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; + _idxHistoryText--; } + historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]); + historyTextItem.IsUndoing = true; + historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; + } - if (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Added) { - historyTextItem.RemovedOnAdded = new HistoryTextItem (_historyTextItems [_idxHistoryText + 1]); - } + if (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Added) { + historyTextItem.RemovedOnAdded = new HistoryTextItem (_historyTextItems [_idxHistoryText + 1]); + } - if ((historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original) - || (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original) - || (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed)) { + if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original + || historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original + || historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { - if (!historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText - 1].Lines [0]) - && historyTextItem.CursorPosition == _historyTextItems [_idxHistoryText - 1].CursorPosition) { - historyTextItem.Lines [0] = new List (_historyTextItems [_idxHistoryText - 1].Lines [0]); - } - if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { - historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText - 2].CursorPosition; - } else { - historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText - 1].CursorPosition; - } + if (!historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText - 1].Lines [0]) + && historyTextItem.CursorPosition == _historyTextItems [_idxHistoryText - 1].CursorPosition) { + historyTextItem.Lines [0] = new List (_historyTextItems [_idxHistoryText - 1].Lines [0]); + } + if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { + historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText - 2].CursorPosition; } else { - historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; + historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText - 1].CursorPosition; } + } else { + historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; + } - OnChangeText (historyTextItem); - while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed - || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { - - _idxHistoryText--; - } - } else if (!historyTextItem.IsUndoing) { - if (_idxHistoryText + 1 < _historyTextItems.Count && (historyTextItem.LineStatus == LineStatus.Original - || _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Added - || _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Removed)) { - - _idxHistoryText++; - historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]); - historyTextItem.IsUndoing = false; - historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; - } + OnChangeText (historyTextItem); + while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed + || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { - if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { - historyTextItem.RemovedOnAdded = new HistoryTextItem (_historyTextItems [_idxHistoryText - 1]); - } + _idxHistoryText--; + } + } else if (!historyTextItem.IsUndoing) { + if (_idxHistoryText + 1 < _historyTextItems.Count && (historyTextItem.LineStatus == LineStatus.Original + || _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Added + || _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Removed)) { - if ((historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced) - || (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Original) - || (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced)) { + _idxHistoryText++; + historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]); + historyTextItem.IsUndoing = false; + historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; + } - if (historyTextItem.LineStatus == LineStatus.Removed - && !historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText + 1].Lines [0])) { - historyTextItem.Lines [0] = new List (_historyTextItems [_idxHistoryText + 1].Lines [0]); - } - historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText + 1].CursorPosition; - } else { - historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; - } + if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { + historyTextItem.RemovedOnAdded = new HistoryTextItem (_historyTextItems [_idxHistoryText - 1]); + } - OnChangeText (historyTextItem); - while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed - || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { + if (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced + || historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Original + || historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced) { - _idxHistoryText++; + if (historyTextItem.LineStatus == LineStatus.Removed + && !historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText + 1].Lines [0])) { + historyTextItem.Lines [0] = new List (_historyTextItems [_idxHistoryText + 1].Lines [0]); } + historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText + 1].CursorPosition; + } else { + historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition; } - } - - void OnChangeText (HistoryTextItem? lines) - { - ChangeText?.Invoke (this, lines!); - } - public void Clear (string text) - { - _historyTextItems.Clear (); - _idxHistoryText = -1; - _originalText = text; - OnChangeText (null); - } + OnChangeText (historyTextItem); + while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed + || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { - public bool IsDirty (string text) - { - return _originalText != text; + _idxHistoryText++; + } } } - class WordWrapManager { - class WrappedLine { - public int ModelLine; - public int Row; - public int RowIndex; - public int ColWidth; - } + void OnChangeText (HistoryTextItem? lines) => ChangeText?.Invoke (this, lines!); - List _wrappedModelLines = new List (); - int _frameWidth; - bool _isWrapModelRefreshing; + public void Clear (string text) + { + _historyTextItems.Clear (); + _idxHistoryText = -1; + _originalText = text; + OnChangeText (null); + } - public TextModel Model { get; private set; } + public bool IsDirty (string text) => _originalText != text; +} - public WordWrapManager (TextModel model) - { - Model = model; - } +class WordWrapManager { + class WrappedLine { + public int ModelLine; + public int Row; + public int RowIndex; + public int ColWidth; + } - public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol, - int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0, bool preserveTrailingSpaces = true) - { - _frameWidth = width; - - var modelRow = _isWrapModelRefreshing ? row : GetModelLineFromWrappedLines (row); - var modelCol = _isWrapModelRefreshing ? col : GetModelColFromWrappedLines (row, col); - var modelStartRow = _isWrapModelRefreshing ? startRow : GetModelLineFromWrappedLines (startRow); - var modelStartCol = _isWrapModelRefreshing ? startCol : GetModelColFromWrappedLines (startRow, startCol); - var wrappedModel = new TextModel (); - int lines = 0; - nRow = 0; - nCol = 0; - nStartRow = 0; - nStartCol = 0; - bool isRowAndColSetted = row == 0 && col == 0; - bool isStartRowAndColSetted = startRow == 0 && startCol == 0; - List wModelLines = new List (); - - for (int i = 0; i < Model.Count; i++) { - var line = Model.GetLine (i); - var wrappedLines = ToListRune ( - TextFormatter.Format (TextModel.ToString (line), width, TextAlignment.Left, true, preserveTrailingSpaces, tabWidth)); - int sumColWidth = 0; - for (int j = 0; j < wrappedLines.Count; j++) { - var wrapLine = wrappedLines [j]; - if (!isRowAndColSetted && modelRow == i) { - if (nCol + wrapLine.Count <= modelCol) { - nCol += wrapLine.Count; - nRow = lines; - if (nCol == modelCol) { - nCol = wrapLine.Count; - isRowAndColSetted = true; - } else if (j == wrappedLines.Count - 1) { - nCol = wrapLine.Count - j + modelCol - nCol; - isRowAndColSetted = true; - } - } else { - var offset = nCol + wrapLine.Count - modelCol; - nCol = wrapLine.Count - offset; - nRow = lines; + List _wrappedModelLines = new (); + int _frameWidth; + bool _isWrapModelRefreshing; + + public TextModel Model { get; private set; } + + public WordWrapManager (TextModel model) => Model = model; + + public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol, + int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0, bool preserveTrailingSpaces = true) + { + _frameWidth = width; + + int modelRow = _isWrapModelRefreshing ? row : GetModelLineFromWrappedLines (row); + int modelCol = _isWrapModelRefreshing ? col : GetModelColFromWrappedLines (row, col); + int modelStartRow = _isWrapModelRefreshing ? startRow : GetModelLineFromWrappedLines (startRow); + int modelStartCol = _isWrapModelRefreshing ? startCol : GetModelColFromWrappedLines (startRow, startCol); + var wrappedModel = new TextModel (); + int lines = 0; + nRow = 0; + nCol = 0; + nStartRow = 0; + nStartCol = 0; + bool isRowAndColSetted = row == 0 && col == 0; + bool isStartRowAndColSetted = startRow == 0 && startCol == 0; + var wModelLines = new List (); + + for (int i = 0; i < Model.Count; i++) { + var line = Model.GetLine (i); + var wrappedLines = ToListRune ( + TextFormatter.Format (TextModel.ToString (line), width, TextAlignment.Left, true, preserveTrailingSpaces, tabWidth)); + int sumColWidth = 0; + for (int j = 0; j < wrappedLines.Count; j++) { + var wrapLine = wrappedLines [j]; + if (!isRowAndColSetted && modelRow == i) { + if (nCol + wrapLine.Count <= modelCol) { + nCol += wrapLine.Count; + nRow = lines; + if (nCol == modelCol) { + nCol = wrapLine.Count; + isRowAndColSetted = true; + } else if (j == wrappedLines.Count - 1) { + nCol = wrapLine.Count - j + modelCol - nCol; isRowAndColSetted = true; } + } else { + int offset = nCol + wrapLine.Count - modelCol; + nCol = wrapLine.Count - offset; + nRow = lines; + isRowAndColSetted = true; } - if (!isStartRowAndColSetted && modelStartRow == i) { - if (nStartCol + wrapLine.Count <= modelStartCol) { - nStartCol += wrapLine.Count; - nStartRow = lines; - if (nStartCol == modelStartCol) { - nStartCol = wrapLine.Count; - isStartRowAndColSetted = true; - } else if (j == wrappedLines.Count - 1) { - nStartCol = wrapLine.Count - j + modelStartCol - nStartCol; - isStartRowAndColSetted = true; - } - } else { - var offset = nStartCol + wrapLine.Count - modelStartCol; - nStartCol = wrapLine.Count - offset; - nStartRow = lines; + } + if (!isStartRowAndColSetted && modelStartRow == i) { + if (nStartCol + wrapLine.Count <= modelStartCol) { + nStartCol += wrapLine.Count; + nStartRow = lines; + if (nStartCol == modelStartCol) { + nStartCol = wrapLine.Count; + isStartRowAndColSetted = true; + } else if (j == wrappedLines.Count - 1) { + nStartCol = wrapLine.Count - j + modelStartCol - nStartCol; isStartRowAndColSetted = true; } + } else { + int offset = nStartCol + wrapLine.Count - modelStartCol; + nStartCol = wrapLine.Count - offset; + nStartRow = lines; + isStartRowAndColSetted = true; } - for (int k = j; k < wrapLine.Count; k++) { - wrapLine [k].ColorScheme = line [k].ColorScheme; - } - wrappedModel.AddLine (lines, wrapLine); - sumColWidth += wrapLine.Count; - var wrappedLine = new WrappedLine () { - ModelLine = i, - Row = lines, - RowIndex = j, - ColWidth = wrapLine.Count, - }; - wModelLines.Add (wrappedLine); - lines++; } + for (int k = j; k < wrapLine.Count; k++) { + wrapLine [k].ColorScheme = line [k].ColorScheme; + } + wrappedModel.AddLine (lines, wrapLine); + sumColWidth += wrapLine.Count; + var wrappedLine = new WrappedLine () { + ModelLine = i, + Row = lines, + RowIndex = j, + ColWidth = wrapLine.Count + }; + wModelLines.Add (wrappedLine); + lines++; } - _wrappedModelLines = wModelLines; + } + _wrappedModelLines = wModelLines; + + return wrappedModel; + } + + public List> ToListRune (List textList) + { + var runesList = new List> (); - return wrappedModel; + foreach (string text in textList) { + runesList.Add (TextModel.ToRuneCellList (text)); } - public List> ToListRune (List textList) - { - var runesList = new List> (); + return runesList; + } - foreach (var text in textList) { - runesList.Add (TextModel.ToRuneCellList (text)); - } + public int GetModelLineFromWrappedLines (int line) => _wrappedModelLines.Count > 0 + ? _wrappedModelLines [Math.Min (line, _wrappedModelLines.Count - 1)].ModelLine + : 0; - return runesList; + public int GetModelColFromWrappedLines (int line, int col) + { + if (_wrappedModelLines?.Count == 0) { + return 0; } - public int GetModelLineFromWrappedLines (int line) => _wrappedModelLines.Count > 0 - ? _wrappedModelLines [Math.Min (line, _wrappedModelLines.Count - 1)].ModelLine - : 0; + int modelLine = GetModelLineFromWrappedLines (line); + int firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); + int modelCol = 0; - public int GetModelColFromWrappedLines (int line, int col) - { - if (_wrappedModelLines?.Count == 0) { - return 0; + for (int i = firstLine; i <= Math.Min (line, _wrappedModelLines!.Count - 1); i++) { + var wLine = _wrappedModelLines [i]; + + if (i < line) { + modelCol += wLine.ColWidth; + } else { + modelCol += col; } + } + + return modelCol; + } - var modelLine = GetModelLineFromWrappedLines (line); - var firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); - int modelCol = 0; + List GetCurrentLine (int row) => Model.GetLine (row); + + public void AddLine (int row, int col) + { + int modelRow = GetModelLineFromWrappedLines (row); + int modelCol = GetModelColFromWrappedLines (row, col); + var line = GetCurrentLine (modelRow); + int restCount = line.Count - modelCol; + var rest = line.GetRange (modelCol, restCount); + line.RemoveRange (modelCol, restCount); + Model.AddLine (modelRow + 1, rest); + _isWrapModelRefreshing = true; + WrapModel (_frameWidth, out _, out _, out _, out _, modelRow + 1, 0); + _isWrapModelRefreshing = false; + } - for (int i = firstLine; i <= Math.Min (line, _wrappedModelLines!.Count - 1); i++) { - var wLine = _wrappedModelLines [i]; + public bool Insert (int row, int col, RuneCell cell) + { + var line = GetCurrentLine (GetModelLineFromWrappedLines (row)); + line.Insert (GetModelColFromWrappedLines (row, col), cell); + if (line.Count > _frameWidth) { + return true; + } else { + return false; + } + } - if (i < line) { - modelCol += wLine.ColWidth; - } else { - modelCol += col; - } - } + public bool RemoveAt (int row, int col) + { + int modelRow = GetModelLineFromWrappedLines (row); + var line = GetCurrentLine (modelRow); + int modelCol = GetModelColFromWrappedLines (row, col); - return modelCol; + if (modelCol > line.Count) { + Model.RemoveLine (modelRow); + RemoveAt (row, 0); + return false; + } + if (modelCol < line.Count) { + line.RemoveAt (modelCol); + } + if (line.Count > _frameWidth || row + 1 < _wrappedModelLines.Count + && _wrappedModelLines [row + 1].ModelLine == modelRow) { + return true; } - List GetCurrentLine (int row) => Model.GetLine (row); + return false; + } - public void AddLine (int row, int col) - { - var modelRow = GetModelLineFromWrappedLines (row); - var modelCol = GetModelColFromWrappedLines (row, col); - var line = GetCurrentLine (modelRow); - var restCount = line.Count - modelCol; - var rest = line.GetRange (modelCol, restCount); - line.RemoveRange (modelCol, restCount); - Model.AddLine (modelRow + 1, rest); - _isWrapModelRefreshing = true; - WrapModel (_frameWidth, out _, out _, out _, out _, modelRow + 1, 0); - _isWrapModelRefreshing = false; - } - - public bool Insert (int row, int col, RuneCell cell) - { - var line = GetCurrentLine (GetModelLineFromWrappedLines (row)); - line.Insert (GetModelColFromWrappedLines (row, col), cell); - if (line.Count > _frameWidth) { + public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = true) + { + lineRemoved = false; + int modelRow = GetModelLineFromWrappedLines (row); + var line = GetCurrentLine (modelRow); + int modelCol = GetModelColFromWrappedLines (row, col); + + if (modelCol == 0 && line.Count == 0) { + Model.RemoveLine (modelRow); + return false; + } else if (modelCol < line.Count) { + if (forward) { + line.RemoveAt (modelCol); + return true; + } else if (modelCol - 1 > -1) { + line.RemoveAt (modelCol - 1); return true; - } else { - return false; } } - - public bool RemoveAt (int row, int col) - { - var modelRow = GetModelLineFromWrappedLines (row); - var line = GetCurrentLine (modelRow); - var modelCol = GetModelColFromWrappedLines (row, col); - - if (modelCol > line.Count) { - Model.RemoveLine (modelRow); - RemoveAt (row, 0); + lineRemoved = true; + if (forward) { + if (modelRow + 1 == Model.Count) { return false; } - if (modelCol < line.Count) - line.RemoveAt (modelCol); - if (line.Count > _frameWidth || (row + 1 < _wrappedModelLines.Count - && _wrappedModelLines [row + 1].ModelLine == modelRow)) { + + var nextLine = Model.GetLine (modelRow + 1); + line.AddRange (nextLine); + Model.RemoveLine (modelRow + 1); + if (line.Count > _frameWidth) { return true; } + } else { + if (modelRow == 0) { + return false; + } - return false; - } - - public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = true) - { - lineRemoved = false; - var modelRow = GetModelLineFromWrappedLines (row); - var line = GetCurrentLine (modelRow); - var modelCol = GetModelColFromWrappedLines (row, col); - - if (modelCol == 0 && line.Count == 0) { - Model.RemoveLine (modelRow); - return false; - } else if (modelCol < line.Count) { - if (forward) { - line.RemoveAt (modelCol); - return true; - } else if (modelCol - 1 > -1) { - line.RemoveAt (modelCol - 1); - return true; - } + var prevLine = Model.GetLine (modelRow - 1); + prevLine.AddRange (line); + Model.RemoveLine (modelRow); + if (prevLine.Count > _frameWidth) { + return true; } - lineRemoved = true; - if (forward) { - if (modelRow + 1 == Model.Count) { - return false; - } + } - var nextLine = Model.GetLine (modelRow + 1); - line.AddRange (nextLine); - Model.RemoveLine (modelRow + 1); - if (line.Count > _frameWidth) { - return true; - } - } else { - if (modelRow == 0) { - return false; - } + return false; + } - var prevLine = Model.GetLine (modelRow - 1); - prevLine.AddRange (line); - Model.RemoveLine (modelRow); - if (prevLine.Count > _frameWidth) { - return true; - } - } + public bool RemoveRange (int row, int index, int count) + { + int modelRow = GetModelLineFromWrappedLines (row); + var line = GetCurrentLine (modelRow); + int modelCol = GetModelColFromWrappedLines (row, index); + try { + line.RemoveRange (modelCol, count); + } catch (Exception) { return false; } - public bool RemoveRange (int row, int index, int count) - { - var modelRow = GetModelLineFromWrappedLines (row); - var line = GetCurrentLine (modelRow); - var modelCol = GetModelColFromWrappedLines (row, index); - - try { - line.RemoveRange (modelCol, count); - } catch (Exception) { - return false; - } + return true; + } - return true; - } + public void UpdateModel (TextModel model, out int nRow, out int nCol, out int nStartRow, out int nStartCol, + int row, int col, int startRow, int startCol, bool preserveTrailingSpaces) + { + _isWrapModelRefreshing = true; + Model = model; + WrapModel (_frameWidth, out nRow, out nCol, out nStartRow, out nStartCol, row, col, startRow, startCol, 0, preserveTrailingSpaces); + _isWrapModelRefreshing = false; + } - public void UpdateModel (TextModel model, out int nRow, out int nCol, out int nStartRow, out int nStartCol, - int row, int col, int startRow, int startCol, bool preserveTrailingSpaces) - { - _isWrapModelRefreshing = true; - Model = model; - WrapModel (_frameWidth, out nRow, out nCol, out nStartRow, out nStartCol, row, col, startRow, startCol, tabWidth: 0, preserveTrailingSpaces); - _isWrapModelRefreshing = false; + public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManager) + { + if (_wrappedModelLines?.Count == 0) { + return 0; } - public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManager) - { - if (_wrappedModelLines?.Count == 0) - return 0; - - var wModelLines = wrapManager._wrappedModelLines; - var modelLine = GetModelLineFromWrappedLines (line); - var firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); - int modelCol = 0; - int colWidthOffset = 0; - int i = firstLine; + var wModelLines = wrapManager._wrappedModelLines; + int modelLine = GetModelLineFromWrappedLines (line); + int firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); + int modelCol = 0; + int colWidthOffset = 0; + int i = firstLine; - while (modelCol < col) { - var wLine = _wrappedModelLines! [i]; - var wLineToCompare = wModelLines [i]; - - if (wLine.ModelLine != modelLine || wLineToCompare.ModelLine != modelLine) - break; + while (modelCol < col) { + var wLine = _wrappedModelLines! [i]; + var wLineToCompare = wModelLines [i]; - modelCol += Math.Max (wLine.ColWidth, wLineToCompare.ColWidth); - colWidthOffset += wLine.ColWidth - wLineToCompare.ColWidth; - if (modelCol > col) { - modelCol += col - modelCol; - } - i++; + if (wLine.ModelLine != modelLine || wLineToCompare.ModelLine != modelLine) { + break; } - return modelCol - colWidthOffset; + modelCol += Math.Max (wLine.ColWidth, wLineToCompare.ColWidth); + colWidthOffset += wLine.ColWidth - wLineToCompare.ColWidth; + if (modelCol > col) { + modelCol += col - modelCol; + } + i++; } + + return modelCol - colWidthOffset; } +} + +/// +/// Multi-line text editing . +/// +/// +/// +/// provides a multi-line text editor. Users interact +/// with it with the standard Windows, Mac, and Linux (Emacs) commands. +/// +/// +/// +/// Shortcut +/// Action performed +/// +/// +/// Left cursor, Control-b +/// +/// Moves the editing point left. +/// +/// +/// +/// Right cursor, Control-f +/// +/// Moves the editing point right. +/// +/// +/// +/// Alt-b +/// +/// Moves one word back. +/// +/// +/// +/// Alt-f +/// +/// Moves one word forward. +/// +/// +/// +/// Up cursor, Control-p +/// +/// Moves the editing point one line up. +/// +/// +/// +/// Down cursor, Control-n +/// +/// Moves the editing point one line down +/// +/// +/// +/// Home key, Control-a +/// +/// Moves the cursor to the beginning of the line. +/// +/// +/// +/// End key, Control-e +/// +/// Moves the cursor to the end of the line. +/// +/// +/// +/// Control-Home +/// +/// Scrolls to the first line and moves the cursor there. +/// +/// +/// +/// Control-End +/// +/// Scrolls to the last line and moves the cursor there. +/// +/// +/// +/// Delete, Control-d +/// +/// Deletes the character in front of the cursor. +/// +/// +/// +/// Backspace +/// +/// Deletes the character behind the cursor. +/// +/// +/// +/// Control-k +/// +/// Deletes the text until the end of the line and replaces the kill buffer +/// with the deleted text. You can paste this text in a different place by +/// using Control-y. +/// +/// +/// +/// Control-y +/// +/// Pastes the content of the kill ring into the current position. +/// +/// +/// +/// Alt-d +/// +/// Deletes the word above the cursor and adds it to the kill ring. You +/// can paste the contents of the kill ring with Control-y. +/// +/// +/// +/// Control-q +/// +/// Quotes the next input character, to prevent the normal processing of +/// key handling to take place. +/// +/// +/// +/// +public class TextView : View { + TextModel _model = new (); + int _topRow; + int _leftColumn; + int _currentRow; + int _currentColumn; + int _selectionStartColumn, _selectionStartRow; + bool _selecting; + bool _wordWrap; + WordWrapManager? _wrapManager; + bool _continuousFind; + int _bottomOffset, _rightOffset; + int _tabWidth = 4; + bool _allowsTab = true; + bool _allowsReturn = true; + bool _multiline = true; + HistoryText _historyText = new (); + CultureInfo? _currentCulture; /// - /// Multi-line text editing . + /// Raised when the property of the changes. /// /// - /// - /// provides a multi-line text editor. Users interact - /// with it with the standard Windows, Mac, and Linux (Emacs) commands. - /// - /// - /// - /// Shortcut - /// Action performed - /// - /// - /// Left cursor, Control-b - /// - /// Moves the editing point left. - /// - /// - /// - /// Right cursor, Control-f - /// - /// Moves the editing point right. - /// - /// - /// - /// Alt-b - /// - /// Moves one word back. - /// - /// - /// - /// Alt-f - /// - /// Moves one word forward. - /// - /// - /// - /// Up cursor, Control-p - /// - /// Moves the editing point one line up. - /// - /// - /// - /// Down cursor, Control-n - /// - /// Moves the editing point one line down - /// - /// - /// - /// Home key, Control-a - /// - /// Moves the cursor to the beginning of the line. - /// - /// - /// - /// End key, Control-e - /// - /// Moves the cursor to the end of the line. - /// - /// - /// - /// Control-Home - /// - /// Scrolls to the first line and moves the cursor there. - /// - /// - /// - /// Control-End - /// - /// Scrolls to the last line and moves the cursor there. - /// - /// - /// - /// Delete, Control-d - /// - /// Deletes the character in front of the cursor. - /// - /// - /// - /// Backspace - /// - /// Deletes the character behind the cursor. - /// - /// - /// - /// Control-k - /// - /// Deletes the text until the end of the line and replaces the kill buffer - /// with the deleted text. You can paste this text in a different place by - /// using Control-y. - /// - /// - /// - /// Control-y - /// - /// Pastes the content of the kill ring into the current position. - /// - /// - /// - /// Alt-d - /// - /// Deletes the word above the cursor and adds it to the kill ring. You - /// can paste the contents of the kill ring with Control-y. - /// - /// - /// - /// Control-q - /// - /// Quotes the next input character, to prevent the normal processing of - /// key handling to take place. - /// - /// - /// + /// The property of only changes when it is explicitly + /// set, not as the user types. To be notified as the user changes the contents of the TextView + /// see . /// - public class TextView : View { - TextModel _model = new TextModel (); - int _topRow; - int _leftColumn; - int _currentRow; - int _currentColumn; - int _selectionStartColumn, _selectionStartRow; - bool _selecting; - bool _wordWrap; - WordWrapManager? _wrapManager; - bool _continuousFind; - int _bottomOffset, _rightOffset; - int _tabWidth = 4; - bool _allowsTab = true; - bool _allowsReturn = true; - bool _multiline = true; - HistoryText _historyText = new HistoryText (); - CultureInfo? _currentCulture; - - /// - /// Raised when the property of the changes. - /// - /// - /// The property of only changes when it is explicitly - /// set, not as the user types. To be notified as the user changes the contents of the TextView - /// see . - /// - public event EventHandler? TextChanged; + public event EventHandler? TextChanged; - /// - /// Raised when the contents of the are changed. - /// - /// - /// Unlike the event, this event is raised whenever the user types or - /// otherwise changes the contents of the . - /// - public event EventHandler? ContentsChanged; + /// + /// Raised when the contents of the are changed. + /// + /// + /// Unlike the event, this event is raised whenever the user types or + /// otherwise changes the contents of the . + /// + public event EventHandler? ContentsChanged; - /// - /// Invoked with the unwrapped . - /// - public event EventHandler? UnwrappedCursorPosition; + /// + /// Invoked with the unwrapped . + /// + public event EventHandler? UnwrappedCursorPosition; - /// - /// Invoked when the normal color is drawn. - /// - public event EventHandler? DrawNormalColor; + /// + /// Invoked when the normal color is drawn. + /// + public event EventHandler? DrawNormalColor; - /// - /// Invoked when the selection color is drawn. - /// - public event EventHandler? DrawSelectionColor; + /// + /// Invoked when the selection color is drawn. + /// + public event EventHandler? DrawSelectionColor; - /// - /// Invoked when the ready only color is drawn. - /// - public event EventHandler? DrawReadOnlyColor; + /// + /// Invoked when the ready only color is drawn. + /// + public event EventHandler? DrawReadOnlyColor; - /// - /// Invoked when the used color is drawn. The Used Color is used to indicate - /// if the was pressed and enabled. - /// - public event EventHandler? DrawUsedColor; + /// + /// Invoked when the used color is drawn. The Used Color is used to indicate + /// if the was pressed and enabled. + /// + public event EventHandler? DrawUsedColor; - /// - /// Provides autocomplete context menu based on suggestions at the current cursor - /// position. Configure to enable this feature - /// - public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete (); + /// + /// Provides autocomplete context menu based on suggestions at the current cursor + /// position. Configure to enable this feature + /// + public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete (); - /// - /// Initializes a on the specified area, with absolute position and size. - /// - /// - /// - public TextView (Rect frame) : base (frame) - { - SetInitialProperties (); - } + /// + /// Initializes a on the specified area, with absolute position and size. + /// + /// + /// + public TextView (Rect frame) : base (frame) => SetInitialProperties (); - /// - /// Initializes a on the specified area, - /// with dimensions controlled with the X, Y, Width and Height properties. - /// - public TextView () : base () - { - SetInitialProperties (); - } + /// + /// Initializes a on the specified area, + /// with dimensions controlled with the X, Y, Width and Height properties. + /// + public TextView () : base () => SetInitialProperties (); - void SetInitialProperties () - { - CanFocus = true; - Used = true; + void SetInitialProperties () + { + CanFocus = true; + Used = true; - _model.LinesLoaded += Model_LinesLoaded!; - _historyText.ChangeText += HistoryText_ChangeText!; + _model.LinesLoaded += Model_LinesLoaded!; + _historyText.ChangeText += HistoryText_ChangeText!; - Initialized += TextView_Initialized!; + Initialized += TextView_Initialized!; // Things this view knows how to do AddCommand (Command.PageDown, () => { ProcessPageDown (); return true; }); @@ -1672,1413 +1662,1388 @@ void SetInitialProperties () return true; }); - // Default keybindings for this view - KeyBindings.Add (KeyCode.PageDown, Command.PageDown); - KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown); + // Default keybindings for this view + KeyBindings.Add (KeyCode.PageDown, Command.PageDown); + KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown); - KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend); + KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend); - KeyBindings.Add (KeyCode.PageUp, Command.PageUp); - KeyBindings.Add (((int)'V' + KeyCode.AltMask), Command.PageUp); + KeyBindings.Add (KeyCode.PageUp, Command.PageUp); + KeyBindings.Add ((int)'V' + KeyCode.AltMask, Command.PageUp); - KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend); + KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend); - KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown); - KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); + KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown); + KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); - KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend); + KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend); - KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp); - KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); + KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp); + KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); - KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend); + KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend); - KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right); - KeyBindings.Add (KeyCode.CursorRight, Command.Right); + KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right); + KeyBindings.Add (KeyCode.CursorRight, Command.Right); - KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend); + KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend); - KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left); - KeyBindings.Add (KeyCode.CursorLeft, Command.Left); + KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left); + KeyBindings.Add (KeyCode.CursorLeft, Command.Left); - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend); + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend); - KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft); - KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); + KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); - KeyBindings.Add (KeyCode.Home, Command.StartOfLine); - KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine); + KeyBindings.Add (KeyCode.Home, Command.StartOfLine); + KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine); - KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend); + KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend); - KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight); - KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); - KeyBindings.Add (KeyCode.End, Command.EndOfLine); - KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine); + KeyBindings.Add (KeyCode.End, Command.EndOfLine); + KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine); - KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend); + KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend); - KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end - KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end + KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end + KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end - KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start - KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start + KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start + KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start - KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank - KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend); + KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank + KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend); - KeyBindings.Add (((int)'C' + KeyCode.AltMask), Command.Copy); - KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); + KeyBindings.Add ((int)'C' + KeyCode.AltMask, Command.Copy); + KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); - KeyBindings.Add (((int)'W' + KeyCode.AltMask), Command.Cut); - KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut); - KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); + KeyBindings.Add ((int)'W' + KeyCode.AltMask, Command.Cut); + KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut); + KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft); - KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft); + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft); + KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft); - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend); + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend); - KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight); - KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight); + KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight); + KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight); - KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend); - KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards - KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards + KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend); + KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards + KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards - // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept). - KeyBindings.Add (KeyCode.Enter, Command.NewLine); - KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd); - KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend); - KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome); - KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend); - KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll); - KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite); - KeyBindings.Add (KeyCode.Tab, Command.Tab); - KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab); + // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept). + KeyBindings.Add (KeyCode.Enter, Command.NewLine); + KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd); + KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend); + KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome); + KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend); + KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll); + KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite); + KeyBindings.Add (KeyCode.Tab, Command.Tab); + KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab); - KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView); - KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView); + KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView); + KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView); - KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView); - KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView); + KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView); + KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView); - KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo); - KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo); + KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo); + KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo); - KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll); - KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll); + KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll); + KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll); - _currentCulture = Thread.CurrentThread.CurrentUICulture; + _currentCulture = Thread.CurrentThread.CurrentUICulture; - ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () }; - ContextMenu.KeyChanged += ContextMenu_KeyChanged!; + ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () }; + ContextMenu.KeyChanged += ContextMenu_KeyChanged!; KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu); } - private MenuBarItem BuildContextMenuBarItem () - { - return new MenuBarItem (new MenuItem [] { - new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)), - new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)), - new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)), - new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)), - new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)), - new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)), - new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)), - }); - } + MenuBarItem BuildContextMenuBarItem () => new (new MenuItem [] { + new (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)), + new (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)), + new (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)), + new (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)), + new (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)), + new (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)), + new (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)) + }); - private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) - { - KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - } + void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - private void Model_LinesLoaded (object sender, EventArgs e) - { - // This call is not needed. Model_LinesLoaded gets invoked when - // model.LoadString (value) is called. LoadString is called from one place - // (Text.set) and historyText.Clear() is called immediately after. - // If this call happens, HistoryText_ChangeText will get called multiple times - // when Text is set, which is wrong. - //historyText.Clear (Text); + void Model_LinesLoaded (object sender, EventArgs e) + { + // This call is not needed. Model_LinesLoaded gets invoked when + // model.LoadString (value) is called. LoadString is called from one place + // (Text.set) and historyText.Clear() is called immediately after. + // If this call happens, HistoryText_ChangeText will get called multiple times + // when Text is set, which is wrong. + //historyText.Clear (Text); - if (!_multiline && !IsInitialized) { - _currentColumn = Text.GetRuneCount (); - _leftColumn = _currentColumn > Frame.Width + 1 ? _currentColumn - Frame.Width + 1 : 0; - } + if (!_multiline && !IsInitialized) { + _currentColumn = Text.GetRuneCount (); + _leftColumn = _currentColumn > Frame.Width + 1 ? _currentColumn - Frame.Width + 1 : 0; } + } - private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj) - { - SetWrapModel (); + void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj) + { + SetWrapModel (); - if (obj != null) { - var startLine = obj.CursorPosition.Y; + if (obj != null) { + int startLine = obj.CursorPosition.Y; - if (obj.RemovedOnAdded != null) { - int offset; - if (obj.IsUndoing) { - offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1); + if (obj.RemovedOnAdded != null) { + int offset; + if (obj.IsUndoing) { + offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1); + } else { + offset = obj.RemovedOnAdded.Lines.Count - 1; + } + for (int i = 0; i < offset; i++) { + if (Lines > obj.RemovedOnAdded.CursorPosition.Y) { + _model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y); } else { - offset = obj.RemovedOnAdded.Lines.Count - 1; - } - for (int i = 0; i < offset; i++) { - if (Lines > obj.RemovedOnAdded.CursorPosition.Y) { - _model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y); - } else { - break; - } + break; } } + } - for (int i = 0; i < obj.Lines.Count; i++) { - if (i == 0) { - _model.ReplaceLine (startLine, obj.Lines [i]); - } else if ((obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed) - || !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) { - _model.AddLine (startLine, obj.Lines [i]); - } else if (Lines > obj.CursorPosition.Y + 1) { - _model.RemoveLine (obj.CursorPosition.Y + 1); - } - startLine++; + for (int i = 0; i < obj.Lines.Count; i++) { + if (i == 0) { + _model.ReplaceLine (startLine, obj.Lines [i]); + } else if (obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed + || !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) { + _model.AddLine (startLine, obj.Lines [i]); + } else if (Lines > obj.CursorPosition.Y + 1) { + _model.RemoveLine (obj.CursorPosition.Y + 1); } - - CursorPosition = obj.FinalCursorPosition; + startLine++; } - UpdateWrapModel (); - - Adjust (); - OnContentsChanged (); + CursorPosition = obj.FinalCursorPosition; } - void TextView_Initialized (object sender, EventArgs e) - { - Autocomplete.HostControl = this; - if (Application.Top != null) { - Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged!; - Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged!; - } - OnContentsChanged (); - } + UpdateWrapModel (); - void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) - { - KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - } + Adjust (); + OnContentsChanged (); + } - void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) - { - KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); + void TextView_Initialized (object sender, EventArgs e) + { + Autocomplete.HostControl = this; + if (Application.Top != null) { + Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged!; + Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged!; } + OnContentsChanged (); + } - /// - /// Tracks whether the text view should be considered "used", that is, that the user has moved in the entry, - /// so new input should be appended at the cursor position, rather than clearing the entry - /// - public bool Used { get; set; } + void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - void ResetPosition () - { - _topRow = _leftColumn = _currentRow = _currentColumn = 0; - StopSelecting (); - ResetCursorVisibility (); - } + void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - /// - /// Sets or gets the text in the . - /// - /// - /// The event is fired whenever this property is set. Note, however, - /// that Text is not set by as the user types. - /// - public override string Text { - get { - if (_wordWrap) { - return _wrapManager!.Model.ToString (); - } else { - return _model.ToString (); - } - } + /// + /// Tracks whether the text view should be considered "used", that is, that the user has moved in the entry, + /// so new input should be appended at the cursor position, rather than clearing the entry + /// + public bool Used { get; set; } - set { - ResetPosition (); - _model.LoadString (value); - if (_wordWrap) { - _wrapManager = new WordWrapManager (_model); - _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); - } - TextChanged?.Invoke (this, EventArgs.Empty); - SetNeedsDisplay (); + void ResetPosition () + { + _topRow = _leftColumn = _currentRow = _currentColumn = 0; + StopSelecting (); + ResetCursorVisibility (); + } - _historyText.Clear (Text); + /// + /// Sets or gets the text in the . + /// + /// + /// The event is fired whenever this property is set. Note, however, + /// that Text is not set by as the user types. + /// + public override string Text { + get { + if (_wordWrap) { + return _wrapManager!.Model.ToString (); + } else { + return _model.ToString (); } } - /// - public override Rect Frame { - get => base.Frame; - set { - base.Frame = value; - if (IsInitialized) { - WrapTextModel (); - Adjust (); - } + set { + ResetPosition (); + _model.LoadString (value); + if (_wordWrap) { + _wrapManager = new WordWrapManager (_model); + _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); } + TextChanged?.Invoke (this, EventArgs.Empty); + SetNeedsDisplay (); + + _historyText.Clear (Text); } + } - void WrapTextModel () - { - if (_wordWrap && _wrapManager != null) { - _model = _wrapManager.WrapModel (_frameWidth, - out int nRow, out int nCol, - out int nStartRow, out int nStartCol, - _currentRow, _currentColumn, - _selectionStartRow, _selectionStartColumn, - _tabWidth, preserveTrailingSpaces: true); - _currentRow = nRow; - _currentColumn = nCol; - _selectionStartRow = nStartRow; - _selectionStartColumn = nStartCol; - SetNeedsDisplay (); + /// + public override Rect Frame { + get => base.Frame; + set { + base.Frame = value; + if (IsInitialized) { + WrapTextModel (); + Adjust (); } } + } - int _frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0); + void WrapTextModel () + { + if (_wordWrap && _wrapManager != null) { + _model = _wrapManager.WrapModel (_frameWidth, + out int nRow, out int nCol, + out int nStartRow, out int nStartCol, + _currentRow, _currentColumn, + _selectionStartRow, _selectionStartColumn, + _tabWidth, true); + _currentRow = nRow; + _currentColumn = nCol; + _selectionStartRow = nStartRow; + _selectionStartColumn = nStartCol; + SetNeedsDisplay (); + } + } - /// - /// Gets or sets the top row. - /// - public int TopRow { get => _topRow; set => _topRow = Math.Max (Math.Min (value, Lines - 1), 0); } + int _frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0); - /// - /// Gets or sets the left column. - /// - public int LeftColumn { - get => _leftColumn; - set { - if (value > 0 && _wordWrap) - return; - _leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); + /// + /// Gets or sets the top row. + /// + public int TopRow { get => _topRow; set => _topRow = Math.Max (Math.Min (value, Lines - 1), 0); } + + /// + /// Gets or sets the left column. + /// + public int LeftColumn { + get => _leftColumn; + set { + if (value > 0 && _wordWrap) { + return; } + _leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); } + } - /// - /// Gets the maximum visible length line. - /// - public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height, TabWidth); + /// + /// Gets the maximum visible length line. + /// + public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height, TabWidth); - /// - /// Gets the number of lines. - /// - public int Lines => _model.Count; + /// + /// Gets the number of lines. + /// + public int Lines => _model.Count; - /// - /// Sets or gets the current cursor position. - /// - public Point CursorPosition { - get => new Point (_currentColumn, _currentRow); - set { - var line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0)); - _currentColumn = value.X < 0 ? 0 : value.X > line.Count ? line.Count : value.X; - _currentRow = value.Y < 0 ? 0 : value.Y > _model.Count - 1 - ? Math.Max (_model.Count - 1, 0) : value.Y; - SetNeedsDisplay (); - Adjust (); - } + /// + /// Sets or gets the current cursor position. + /// + public Point CursorPosition { + get => new (_currentColumn, _currentRow); + set { + var line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0)); + _currentColumn = value.X < 0 ? 0 : value.X > line.Count ? line.Count : value.X; + _currentRow = value.Y < 0 ? 0 : value.Y > _model.Count - 1 + ? Math.Max (_model.Count - 1, 0) : value.Y; + SetNeedsDisplay (); + Adjust (); } + } - /// - /// Start column position of the selected text. - /// - public int SelectionStartColumn { - get => _selectionStartColumn; - set { - var line = _model.GetLine (_selectionStartRow); - _selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value; - _selecting = true; - SetNeedsDisplay (); - Adjust (); - } + /// + /// Start column position of the selected text. + /// + public int SelectionStartColumn { + get => _selectionStartColumn; + set { + var line = _model.GetLine (_selectionStartRow); + _selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value; + _selecting = true; + SetNeedsDisplay (); + Adjust (); } + } - /// - /// Start row position of the selected text. - /// - public int SelectionStartRow { - get => _selectionStartRow; - set { - _selectionStartRow = value < 0 ? 0 : value > _model.Count - 1 - ? Math.Max (_model.Count - 1, 0) : value; - _selecting = true; - SetNeedsDisplay (); - Adjust (); - } + /// + /// Start row position of the selected text. + /// + public int SelectionStartRow { + get => _selectionStartRow; + set { + _selectionStartRow = value < 0 ? 0 : value > _model.Count - 1 + ? Math.Max (_model.Count - 1, 0) : value; + _selecting = true; + SetNeedsDisplay (); + Adjust (); } + } - /// - /// The selected text. - /// - public string SelectedText { - get { - if (!_selecting || (_model.Count == 1 && _model.GetLine (0).Count == 0)) { - return string.Empty; - } - - return GetSelectedRegion (); + /// + /// The selected text. + /// + public string SelectedText { + get { + if (!_selecting || _model.Count == 1 && _model.GetLine (0).Count == 0) { + return string.Empty; } + + return GetSelectedRegion (); } + } - /// - /// Length of the selected text. - /// - public int SelectedLength => GetSelectedLength (); + /// + /// Length of the selected text. + /// + public int SelectedLength => GetSelectedLength (); - /// - /// Get or sets the selecting. - /// - public bool Selecting { - get => _selecting; - set => _selecting = value; - } - /// - /// Allows word wrap the to fit the available container width. - /// - public bool WordWrap { - get => _wordWrap; - set { - if (value == _wordWrap) { - return; - } - if (value && !_multiline) { - return; - } - _wordWrap = value; - ResetPosition (); - if (_wordWrap) { - _wrapManager = new WordWrapManager (_model); - _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); - } else if (!_wordWrap && _wrapManager != null) { - _model = _wrapManager.Model; - } - SetNeedsDisplay (); + /// + /// Get or sets the selecting. + /// + public bool Selecting { + get => _selecting; + set => _selecting = value; + } + + /// + /// Allows word wrap the to fit the available container width. + /// + public bool WordWrap { + get => _wordWrap; + set { + if (value == _wordWrap) { + return; + } + if (value && !_multiline) { + return; } + _wordWrap = value; + ResetPosition (); + if (_wordWrap) { + _wrapManager = new WordWrapManager (_model); + _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); + } else if (!_wordWrap && _wrapManager != null) { + _model = _wrapManager.Model; + } + SetNeedsDisplay (); } + } - /// - /// The bottom offset needed to use a horizontal scrollbar or for another reason. - /// This is only needed with the keyboard navigation. - /// - public int BottomOffset { - get => _bottomOffset; - set { - if (_currentRow == Lines - 1 && _bottomOffset > 0 && value == 0) { - _topRow = Math.Max (_topRow - _bottomOffset, 0); - } - _bottomOffset = value; - Adjust (); + /// + /// The bottom offset needed to use a horizontal scrollbar or for another reason. + /// This is only needed with the keyboard navigation. + /// + public int BottomOffset { + get => _bottomOffset; + set { + if (_currentRow == Lines - 1 && _bottomOffset > 0 && value == 0) { + _topRow = Math.Max (_topRow - _bottomOffset, 0); } + _bottomOffset = value; + Adjust (); } + } - /// - /// The right offset needed to use a vertical scrollbar or for another reason. - /// This is only needed with the keyboard navigation. - /// - public int RightOffset { - get => _rightOffset; - set { - if (!_wordWrap && _currentColumn == GetCurrentLine ().Count && _rightOffset > 0 && value == 0) { - _leftColumn = Math.Max (_leftColumn - _rightOffset, 0); - } - _rightOffset = value; - Adjust (); + /// + /// The right offset needed to use a vertical scrollbar or for another reason. + /// This is only needed with the keyboard navigation. + /// + public int RightOffset { + get => _rightOffset; + set { + if (!_wordWrap && _currentColumn == GetCurrentLine ().Count && _rightOffset > 0 && value == 0) { + _leftColumn = Math.Max (_leftColumn - _rightOffset, 0); } + _rightOffset = value; + Adjust (); } + } - /// - /// Gets or sets a value indicating whether pressing ENTER in a - /// creates a new line of text in the view or activates the default button for the Toplevel. - /// - public bool AllowsReturn { - get => _allowsReturn; - set { - _allowsReturn = value; - if (_allowsReturn && !_multiline) { - Multiline = true; - } - if (!_allowsReturn && _multiline) { - Multiline = false; - AllowsTab = false; - } - SetNeedsDisplay (); + /// + /// Gets or sets a value indicating whether pressing ENTER in a + /// creates a new line of text in the view or activates the default button for the Toplevel. + /// + public bool AllowsReturn { + get => _allowsReturn; + set { + _allowsReturn = value; + if (_allowsReturn && !_multiline) { + Multiline = true; + } + if (!_allowsReturn && _multiline) { + Multiline = false; + AllowsTab = false; } + SetNeedsDisplay (); } + } - /// - /// Gets or sets whether the inserts a tab character into the text or ignores - /// tab input. If set to `false` and the user presses the tab key (or shift-tab) the focus will move to the - /// next view (or previous with shift-tab). The default is `true`; if the user presses the tab key, a tab - /// character will be inserted into the text. - /// - public bool AllowsTab { - get => _allowsTab; - set { - _allowsTab = value; - if (_allowsTab && _tabWidth == 0) { - _tabWidth = 4; - } - if (_allowsTab && !_multiline) { - Multiline = true; - } - if (!_allowsTab && _tabWidth > 0) { - _tabWidth = 0; - } - SetNeedsDisplay (); + /// + /// Gets or sets whether the inserts a tab character into the text or ignores + /// tab input. If set to `false` and the user presses the tab key (or shift-tab) the focus will move to the + /// next view (or previous with shift-tab). The default is `true`; if the user presses the tab key, a tab + /// character will be inserted into the text. + /// + public bool AllowsTab { + get => _allowsTab; + set { + _allowsTab = value; + if (_allowsTab && _tabWidth == 0) { + _tabWidth = 4; } + if (_allowsTab && !_multiline) { + Multiline = true; + } + if (!_allowsTab && _tabWidth > 0) { + _tabWidth = 0; + } + SetNeedsDisplay (); } + } - /// - /// Gets or sets a value indicating the number of whitespace when pressing the TAB key. - /// - public int TabWidth { - get => _tabWidth; - set { - _tabWidth = Math.Max (value, 0); - if (_tabWidth > 0 && !AllowsTab) { - AllowsTab = true; - } - SetNeedsDisplay (); + /// + /// Gets or sets a value indicating the number of whitespace when pressing the TAB key. + /// + public int TabWidth { + get => _tabWidth; + set { + _tabWidth = Math.Max (value, 0); + if (_tabWidth > 0 && !AllowsTab) { + AllowsTab = true; } + SetNeedsDisplay (); } + } - Dim? savedHeight = null; + Dim? savedHeight = null; - /// - /// Gets or sets a value indicating whether this is a multiline text view. - /// - public bool Multiline { - get => _multiline; - set { - _multiline = value; - if (_multiline && !_allowsTab) { - AllowsTab = true; + /// + /// Gets or sets a value indicating whether this is a multiline text view. + /// + public bool Multiline { + get => _multiline; + set { + _multiline = value; + if (_multiline && !_allowsTab) { + AllowsTab = true; + } + if (_multiline && !_allowsReturn) { + AllowsReturn = true; + } + + if (!_multiline) { + AllowsReturn = false; + AllowsTab = false; + WordWrap = false; + _currentColumn = 0; + _currentRow = 0; + savedHeight = Height; + var prevLayoutStyle = LayoutStyle; + if (LayoutStyle == LayoutStyle.Computed) { + LayoutStyle = LayoutStyle.Absolute; } - if (_multiline && !_allowsReturn) { - AllowsReturn = true; + Height = 1; + LayoutStyle = prevLayoutStyle; + if (!IsInitialized) { + _model.LoadString (Text); } - - if (!_multiline) { - AllowsReturn = false; - AllowsTab = false; - WordWrap = false; - _currentColumn = 0; - _currentRow = 0; - savedHeight = Height; - var prevLayoutStyle = LayoutStyle; - if (LayoutStyle == LayoutStyle.Computed) { - LayoutStyle = LayoutStyle.Absolute; - } - Height = 1; - LayoutStyle = prevLayoutStyle; - if (!IsInitialized) { - _model.LoadString (Text); - } - SetNeedsDisplay (); - } else if (_multiline && savedHeight != null) { - var lyout = LayoutStyle; - if (LayoutStyle == LayoutStyle.Computed) { - LayoutStyle = LayoutStyle.Absolute; - } - Height = savedHeight; - LayoutStyle = lyout; - SetNeedsDisplay (); + SetNeedsDisplay (); + } else if (_multiline && savedHeight != null) { + var lyout = LayoutStyle; + if (LayoutStyle == LayoutStyle.Computed) { + LayoutStyle = LayoutStyle.Absolute; } + Height = savedHeight; + LayoutStyle = lyout; + SetNeedsDisplay (); } } + } - /// - /// Indicates whatever the text was changed or not. - /// if the text was changed otherwise. - /// - public bool IsDirty { - get { - return _historyText.IsDirty (Text); - } - set { - _historyText.Clear (Text); - } - } - - /// - /// Indicates whatever the text has history changes or not. - /// if the text has history changes otherwise. - /// - public bool HasHistoryChanges => _historyText.HasHistoryChanges; + /// + /// Indicates whatever the text was changed or not. + /// if the text was changed otherwise. + /// + public bool IsDirty { + get => _historyText.IsDirty (Text); + set => _historyText.Clear (Text); + } - /// - /// Get the for this view. - /// - public ContextMenu? ContextMenu { get; private set; } + /// + /// Indicates whatever the text has history changes or not. + /// if the text has history changes otherwise. + /// + public bool HasHistoryChanges => _historyText.HasHistoryChanges; - /// - /// If and the current is null - /// will inherit from the previous, otherwise if (default) do nothing. - /// If the text is load with this - /// property is automatically sets to . - /// - public bool InheritsPreviousColorScheme { get; set; } + /// + /// Get the for this view. + /// + public ContextMenu? ContextMenu { get; private set; } - int GetSelectedLength () - { - return SelectedText.Length; - } + /// + /// If and the current is null + /// will inherit from the previous, otherwise if (default) do nothing. + /// If the text is load with this + /// property is automatically sets to . + /// + public bool InheritsPreviousColorScheme { get; set; } - CursorVisibility _savedCursorVisibility; + int GetSelectedLength () => SelectedText.Length; - void SaveCursorVisibility () - { - if (_desiredCursorVisibility != CursorVisibility.Invisible) { - if (_savedCursorVisibility == 0) { - _savedCursorVisibility = _desiredCursorVisibility; - } - DesiredCursorVisibility = CursorVisibility.Invisible; - } - } + CursorVisibility _savedCursorVisibility; - void ResetCursorVisibility () - { - if (_savedCursorVisibility != 0) { - DesiredCursorVisibility = _savedCursorVisibility; - _savedCursorVisibility = 0; + void SaveCursorVisibility () + { + if (_desiredCursorVisibility != CursorVisibility.Invisible) { + if (_savedCursorVisibility == 0) { + _savedCursorVisibility = _desiredCursorVisibility; } + DesiredCursorVisibility = CursorVisibility.Invisible; } + } - /// - /// Loads the contents of the file into the . - /// - /// true, if file was loaded, false otherwise. - /// Path to the file to load. - public bool Load (string path) - { - SetWrapModel (); - bool res; - try { - SetWrapModel (); - res = _model.LoadFile (path); - _historyText.Clear (Text); - ResetPosition (); - } catch (Exception) { - throw; - } finally { - UpdateWrapModel (); - SetNeedsDisplay (); - Adjust (); - } - UpdateWrapModel (); - return res; + void ResetCursorVisibility () + { + if (_savedCursorVisibility != 0) { + DesiredCursorVisibility = _savedCursorVisibility; + _savedCursorVisibility = 0; } + } - /// - /// Loads the contents of the stream into the . - /// - /// true, if stream was loaded, false otherwise. - /// Stream to load the contents from. - public void Load (Stream stream) - { + /// + /// Loads the contents of the file into the . + /// + /// true, if file was loaded, false otherwise. + /// Path to the file to load. + public bool Load (string path) + { + SetWrapModel (); + bool res; + try { SetWrapModel (); - _model.LoadStream (stream); + res = _model.LoadFile (path); _historyText.Clear (Text); ResetPosition (); - SetNeedsDisplay (); + } catch (Exception) { + throw; + } finally { UpdateWrapModel (); - } - - /// - /// Loads the contents of the list into the . - /// - /// Rune cells list to load the contents from. - public void Load (List cells) - { - SetWrapModel (); - _model.LoadRuneCells (cells, ColorScheme); - _historyText.Clear (Text); - ResetPosition (); SetNeedsDisplay (); - UpdateWrapModel (); - InheritsPreviousColorScheme = true; + Adjust (); } + UpdateWrapModel (); + return res; + } - /// - /// Loads the contents of the list of list into the . - /// - /// List of rune cells list to load the contents from. - public void Load (List> cellsList) - { - SetWrapModel (); - InheritsPreviousColorScheme = true; - _model.LoadListRuneCells (cellsList, ColorScheme); - _historyText.Clear (Text); - ResetPosition (); - SetNeedsDisplay (); - UpdateWrapModel (); - } + /// + /// Loads the contents of the stream into the . + /// + /// true, if stream was loaded, false otherwise. + /// Stream to load the contents from. + public void Load (Stream stream) + { + SetWrapModel (); + _model.LoadStream (stream); + _historyText.Clear (Text); + ResetPosition (); + SetNeedsDisplay (); + UpdateWrapModel (); + } - /// - /// Closes the contents of the stream into the . - /// - /// true, if stream was closed, false otherwise. - public bool CloseFile () - { - SetWrapModel (); - var res = _model.CloseFile (); - ResetPosition (); - SetNeedsDisplay (); - UpdateWrapModel (); - return res; - } + /// + /// Loads the contents of the list into the . + /// + /// Rune cells list to load the contents from. + public void Load (List cells) + { + SetWrapModel (); + _model.LoadRuneCells (cells, ColorScheme); + _historyText.Clear (Text); + ResetPosition (); + SetNeedsDisplay (); + UpdateWrapModel (); + InheritsPreviousColorScheme = true; + } - /// - /// Gets the current cursor row. - /// - public int CurrentRow => _currentRow; + /// + /// Loads the contents of the list of list into the . + /// + /// List of rune cells list to load the contents from. + public void Load (List> cellsList) + { + SetWrapModel (); + InheritsPreviousColorScheme = true; + _model.LoadListRuneCells (cellsList, ColorScheme); + _historyText.Clear (Text); + ResetPosition (); + SetNeedsDisplay (); + UpdateWrapModel (); + } - /// - /// Gets the cursor column. - /// - /// The cursor column. - public int CurrentColumn => _currentColumn; + /// + /// Closes the contents of the stream into the . + /// + /// true, if stream was closed, false otherwise. + public bool CloseFile () + { + SetWrapModel (); + bool res = _model.CloseFile (); + ResetPosition (); + SetNeedsDisplay (); + UpdateWrapModel (); + return res; + } - /// - /// Positions the cursor on the current row and column - /// - public override void PositionCursor () - { - ProcessAutocomplete (); + /// + /// Gets the current cursor row. + /// + public int CurrentRow => _currentRow; - if (!CanFocus || !Enabled || Application.Driver == null) { - return; - } + /// + /// Gets the cursor column. + /// + /// The cursor column. + public int CurrentColumn => _currentColumn; - if (_selecting) { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height); - //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height); - //SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow)); - SetNeedsDisplay (); - } - var line = _model.GetLine (_currentRow); - var col = 0; - if (line.Count > 0) { - for (int idx = _leftColumn; idx < line.Count; idx++) { - if (idx >= _currentColumn) - break; - var cols = line [idx].Rune.GetColumns (); - if (line [idx].Rune.Value == '\t') { - cols += TabWidth + 1; - } - if (!TextModel.SetCol (ref col, Frame.Width, cols)) { - col = _currentColumn; - break; - } + /// + /// Positions the cursor on the current row and column + /// + public override void PositionCursor () + { + ProcessAutocomplete (); + + if (!CanFocus || !Enabled || Application.Driver == null) { + return; + } + + if (_selecting) { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height); + //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height); + //SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow)); + SetNeedsDisplay (); + } + var line = _model.GetLine (_currentRow); + int col = 0; + if (line.Count > 0) { + for (int idx = _leftColumn; idx < line.Count; idx++) { + if (idx >= _currentColumn) { + break; + } + int cols = line [idx].Rune.GetColumns (); + if (line [idx].Rune.Value == '\t') { + cols += TabWidth + 1; + } + if (!TextModel.SetCol (ref col, Frame.Width, cols)) { + col = _currentColumn; + break; } } - var posX = _currentColumn - _leftColumn; - var posY = _currentRow - _topRow; - if (posX > -1 && col >= posX && posX < Frame.Width - RightOffset - && _topRow <= _currentRow && posY < Frame.Height - BottomOffset) { - ResetCursorVisibility (); - Move (col, _currentRow - _topRow); - } else { - SaveCursorVisibility (); - } } + int posX = _currentColumn - _leftColumn; + int posY = _currentRow - _topRow; + if (posX > -1 && col >= posX && posX < Frame.Width - RightOffset + && _topRow <= _currentRow && posY < Frame.Height - BottomOffset) { + ResetCursorVisibility (); + Move (col, _currentRow - _topRow); + } else { + SaveCursorVisibility (); + } + } - void ClearRegion (int left, int top, int right, int bottom) - { - for (int row = top; row < bottom; row++) { - Move (left, row); - for (int col = left; col < right; col++) - AddRune (col, row, (Rune)' '); + void ClearRegion (int left, int top, int right, int bottom) + { + for (int row = top; row < bottom; row++) { + Move (left, row); + for (int col = left; col < right; col++) { + AddRune (col, row, (Rune)' '); } } + } - /// - public override Attribute GetNormalColor () - { - ColorScheme cs = ColorScheme; - if (ColorScheme == null) { - cs = new ColorScheme (); - } - return Enabled ? cs.Focus : cs.Disabled; + /// + public override Attribute GetNormalColor () + { + var cs = ColorScheme; + if (ColorScheme == null) { + cs = new ColorScheme (); } + return Enabled ? cs.Focus : cs.Disabled; + } - /// - /// Sets the driver to the default color for the control where no text is being rendered. Defaults to . - /// - protected virtual void SetNormalColor () - { + /// + /// Sets the driver to the default color for the control where no text is being rendered. Defaults to . + /// + protected virtual void SetNormalColor () => Driver.SetAttribute (GetNormalColor ()); + + /// + /// Sets the to an appropriate color for rendering the given of the + /// current . Override to provide custom coloring by calling + /// Defaults to . + /// + /// The line. + /// The col index. + /// The row index. + protected virtual void OnDrawNormalColor (List line, int idxCol, int idxRow) + { + var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); + var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + DrawNormalColor?.Invoke (this, ev); + + if (line [idxCol].ColorScheme != null) { + var colorScheme = line [idxCol].ColorScheme; + Driver.SetAttribute (Enabled ? colorScheme!.Focus : colorScheme!.Disabled); + } else { Driver.SetAttribute (GetNormalColor ()); } + } - /// - /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling - /// Defaults to . - /// - /// The line. - /// The col index. - /// The row index. - protected virtual void OnDrawNormalColor (List line, int idxCol, int idxRow) - { - var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); - DrawNormalColor?.Invoke (this, ev); - - if (line [idxCol].ColorScheme != null) { - var colorScheme = line [idxCol].ColorScheme; - Driver.SetAttribute (Enabled ? colorScheme!.Focus : colorScheme!.Disabled); - } else { - Driver.SetAttribute (GetNormalColor ()); - } + /// + /// Sets the to an appropriate color for rendering the given of the + /// current . Override to provide custom coloring by calling + /// Defaults to . + /// + /// The line. + /// The col index. + /// /// The row index. + protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow) + { + var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); + var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + DrawSelectionColor?.Invoke (this, ev); + + if (line [idxCol].ColorScheme != null) { + var colorScheme = line [idxCol].ColorScheme; + Driver.SetAttribute (new Attribute (colorScheme!.Focus.Background, colorScheme.Focus.Foreground)); + } else { + Driver.SetAttribute (new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground)); } + } - /// - /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling - /// Defaults to . - /// - /// The line. - /// The col index. - /// /// The row index. - protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow) - { - var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); - DrawSelectionColor?.Invoke (this, ev); + /// + /// Sets the to an appropriate color for rendering the given of the + /// current . Override to provide custom coloring by calling + /// Defaults to . + /// + /// The line. + /// The col index. + /// /// The row index. + protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow) + { + var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); + var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + DrawReadOnlyColor?.Invoke (this, ev); + + var colorScheme = line [idxCol].ColorScheme != null ? line [idxCol].ColorScheme : ColorScheme; + Attribute attribute; + if (colorScheme!.Disabled.Foreground == colorScheme.Focus.Background) { + attribute = new Attribute (colorScheme.Focus.Foreground, colorScheme.Focus.Background); + } else { + attribute = new Attribute (colorScheme.Disabled.Foreground, colorScheme.Focus.Background); + } + Driver.SetAttribute (attribute); + } - if (line [idxCol].ColorScheme != null) { - var colorScheme = line [idxCol].ColorScheme; - Driver.SetAttribute (new Attribute (colorScheme!.Focus.Background, colorScheme.Focus.Foreground)); - } else { - Driver.SetAttribute (new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground)); - } + /// + /// Sets the to an appropriate color for rendering the given of the + /// current . Override to provide custom coloring by calling + /// Defaults to . + /// + /// The line. + /// The col index. + /// /// The row index. + protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow) + { + var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); + var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + DrawUsedColor?.Invoke (this, ev); + + if (line [idxCol].ColorScheme != null) { + var colorScheme = line [idxCol].ColorScheme; + SetValidUsedColor (colorScheme!); + } else { + SetValidUsedColor (ColorScheme); } + } - /// - /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling - /// Defaults to . - /// - /// The line. - /// The col index. - /// /// The row index. - protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow) - { - var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); - DrawReadOnlyColor?.Invoke (this, ev); - - var colorScheme = line [idxCol].ColorScheme != null ? line [idxCol].ColorScheme : ColorScheme; - Attribute attribute; - if (colorScheme!.Disabled.Foreground == colorScheme.Focus.Background) { - attribute = new Attribute (colorScheme.Focus.Foreground, colorScheme.Focus.Background); - } else { - attribute = new Attribute (colorScheme.Disabled.Foreground, colorScheme.Focus.Background); + static void SetValidUsedColor (ColorScheme colorScheme) => + // BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now + //if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) { + Driver.SetAttribute (new Attribute (colorScheme.Focus.Background, colorScheme.Focus.Foreground)); + + //} else { + //Driver.SetAttribute (new Attribute (colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background, colorScheme.Focus.Foreground)); + //} + bool _isReadOnly = false; + + /// + /// Gets or sets whether the is in read-only mode or not + /// + /// Boolean value(Default false) + public bool ReadOnly { + get => _isReadOnly; + set { + if (value != _isReadOnly) { + _isReadOnly = value; + + SetNeedsDisplay (); + Adjust (); } - Driver.SetAttribute (attribute); } + } - /// - /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling - /// Defaults to . - /// - /// The line. - /// The col index. - /// /// The row index. - protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow) - { - var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); - DrawUsedColor?.Invoke (this, ev); + CursorVisibility _desiredCursorVisibility = CursorVisibility.Default; - if (line [idxCol].ColorScheme != null) { - var colorScheme = line [idxCol].ColorScheme; - SetValidUsedColor (colorScheme!); - } else { - SetValidUsedColor (ColorScheme); + /// + /// Get / Set the wished cursor when the field is focused + /// + public CursorVisibility DesiredCursorVisibility { + get => _desiredCursorVisibility; + set { + if (HasFocus) { + Application.Driver.SetCursorVisibility (value); } - } - private static void SetValidUsedColor (ColorScheme colorScheme) - { - // BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now - //if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) { - Driver.SetAttribute (new Attribute (colorScheme.Focus.Background, colorScheme.Focus.Foreground)); - //} else { - //Driver.SetAttribute (new Attribute (colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background, colorScheme.Focus.Foreground)); - //} + _desiredCursorVisibility = value; + SetNeedsDisplay (); } + } - bool _isReadOnly = false; + /// + public override bool OnEnter (View view) + { + //TODO: Improve it by handling read only mode of the text field + Application.Driver.SetCursorVisibility (DesiredCursorVisibility); - /// - /// Gets or sets whether the is in read-only mode or not - /// - /// Boolean value(Default false) - public bool ReadOnly { - get => _isReadOnly; - set { - if (value != _isReadOnly) { - _isReadOnly = value; + return base.OnEnter (view); + } - SetNeedsDisplay (); - Adjust (); - } - } + /// + public override bool OnLeave (View view) + { + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { + Application.UngrabMouse (); } - CursorVisibility _desiredCursorVisibility = CursorVisibility.Default; - - /// - /// Get / Set the wished cursor when the field is focused - /// - public CursorVisibility DesiredCursorVisibility { - get => _desiredCursorVisibility; - set { - if (HasFocus) { - Application.Driver.SetCursorVisibility (value); - } + return base.OnLeave (view); + } - _desiredCursorVisibility = value; - SetNeedsDisplay (); - } + // Returns an encoded region start..end (top 32 bits are the row, low32 the column) + void GetEncodedRegionBounds (out long start, out long end, + int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null) + { + long selection; + long point; + if (startRow == null || startCol == null || cRow == null || cCol == null) { + selection = (long)(uint)_selectionStartRow << 32 | (uint)_selectionStartColumn; + point = (long)(uint)_currentRow << 32 | (uint)_currentColumn; + } else { + selection = (long)(uint)startRow << 32 | (uint)startCol; + point = (long)(uint)cRow << 32 | (uint)cCol; + } + if (selection > point) { + start = point; + end = selection; + } else { + start = selection; + end = point; } + } - /// - public override bool OnEnter (View view) - { - //TODO: Improve it by handling read only mode of the text field - Application.Driver.SetCursorVisibility (DesiredCursorVisibility); + bool PointInSelection (int col, int row) + { + long start, end; + GetEncodedRegionBounds (out start, out end); + long q = (long)(uint)row << 32 | (uint)col; + return q >= start && q <= end - 1; + } - return base.OnEnter (view); + // + // Returns a string with the text in the selected + // region. + // + string GetRegion (int? sRow = null, int? sCol = null, int? cRow = null, int? cCol = null, TextModel? model = null) + { + long start, end; + GetEncodedRegionBounds (out start, out end, sRow, sCol, cRow, cCol); + if (start == end) { + return string.Empty; } + int startRow = (int)(start >> 32); + int maxrow = (int)(end >> 32); + int startCol = (int)(start & 0xffffffff); + int endCol = (int)(end & 0xffffffff); + var line = model == null ? _model.GetLine (startRow) : model.GetLine (startRow); - /// - public override bool OnLeave (View view) - { - if (Application.MouseGrabView != null && Application.MouseGrabView == this) { - Application.UngrabMouse (); - } + if (startRow == maxrow) { + return StringFromRunes (line.GetRange (startCol, endCol - startCol)); + } + + string res = StringFromRunes (line.GetRange (startCol, line.Count - startCol)); - return base.OnLeave (view); + for (int row = startRow + 1; row < maxrow; row++) { + res = res + Environment.NewLine + StringFromRunes (model == null + ? _model.GetLine (row) : model.GetLine (row)); } + line = model == null ? _model.GetLine (maxrow) : model.GetLine (maxrow); + res = res + Environment.NewLine + StringFromRunes (line.GetRange (0, endCol)); + return res; + } - // Returns an encoded region start..end (top 32 bits are the row, low32 the column) - void GetEncodedRegionBounds (out long start, out long end, - int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null) - { - long selection; - long point; - if (startRow == null || startCol == null || cRow == null || cCol == null) { - selection = ((long)(uint)_selectionStartRow << 32) | (uint)_selectionStartColumn; - point = ((long)(uint)_currentRow << 32) | (uint)_currentColumn; - } else { - selection = ((long)(uint)startRow << 32) | (uint)startCol; - point = ((long)(uint)cRow << 32) | (uint)cCol; - } - if (selection > point) { - start = point; - end = selection; - } else { - start = selection; - end = point; - } - } + // + // Clears the contents of the selected region + // + void ClearRegion () + { + SetWrapModel (); - bool PointInSelection (int col, int row) - { - long start, end; - GetEncodedRegionBounds (out start, out end); - var q = ((long)(uint)row << 32) | (uint)col; - return q >= start && q <= end - 1; - } + long start, end; + long currentEncoded = (long)(uint)_currentRow << 32 | (uint)_currentColumn; + GetEncodedRegionBounds (out start, out end); + int startRow = (int)(start >> 32); + int maxrow = (int)(end >> 32); + int startCol = (int)(start & 0xffffffff); + int endCol = (int)(end & 0xffffffff); + var line = _model.GetLine (startRow); - // - // Returns a string with the text in the selected - // region. - // - string GetRegion (int? sRow = null, int? sCol = null, int? cRow = null, int? cCol = null, TextModel? model = null) - { - long start, end; - GetEncodedRegionBounds (out start, out end, sRow, sCol, cRow, cCol); - if (start == end) { - return string.Empty; - } - int startRow = (int)(start >> 32); - var maxrow = ((int)(end >> 32)); - int startCol = (int)(start & 0xffffffff); - var endCol = (int)(end & 0xffffffff); - var line = model == null ? this._model.GetLine (startRow) : model.GetLine (startRow); + _historyText.Add (new List> () { new (line) }, new Point (startCol, startRow)); - if (startRow == maxrow) - return StringFromRunes (line.GetRange (startCol, endCol - startCol)); + var removedLines = new List> (); - string res = StringFromRunes (line.GetRange (startCol, line.Count - startCol)); + if (startRow == maxrow) { + removedLines.Add (new List (line)); - for (int row = startRow + 1; row < maxrow; row++) { - res = res + Environment.NewLine + StringFromRunes (model == null - ? this._model.GetLine (row) : model.GetLine (row)); + line.RemoveRange (startCol, endCol - startCol); + _currentColumn = startCol; + if (_wordWrap) { + SetNeedsDisplay (); + } else { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //SetNeedsDisplay (new Rect (0, startRow - topRow, Frame.Width, startRow - topRow + 1)); + SetNeedsDisplay (); } - line = model == null ? this._model.GetLine (maxrow) : model.GetLine (maxrow); - res = res + Environment.NewLine + StringFromRunes (line.GetRange (0, endCol)); - return res; - } - - // - // Clears the contents of the selected region - // - void ClearRegion () - { - SetWrapModel (); - long start, end; - long currentEncoded = ((long)(uint)_currentRow << 32) | (uint)_currentColumn; - GetEncodedRegionBounds (out start, out end); - int startRow = (int)(start >> 32); - var maxrow = ((int)(end >> 32)); - int startCol = (int)(start & 0xffffffff); - var endCol = (int)(end & 0xffffffff); - var line = _model.GetLine (startRow); + _historyText.Add (new List> (removedLines), CursorPosition, HistoryText.LineStatus.Removed); - _historyText.Add (new List> () { new List (line) }, new Point (startCol, startRow)); + UpdateWrapModel (); - List> removedLines = new List> (); + return; + } - if (startRow == maxrow) { - removedLines.Add (new List (line)); + removedLines.Add (new List (line)); - line.RemoveRange (startCol, endCol - startCol); - _currentColumn = startCol; - if (_wordWrap) { - SetNeedsDisplay (); - } else { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //SetNeedsDisplay (new Rect (0, startRow - topRow, Frame.Width, startRow - topRow + 1)); - SetNeedsDisplay (); - } + line.RemoveRange (startCol, line.Count - startCol); + var line2 = _model.GetLine (maxrow); + line.AddRange (line2.Skip (endCol)); + for (int row = startRow + 1; row <= maxrow; row++) { - _historyText.Add (new List> (removedLines), CursorPosition, HistoryText.LineStatus.Removed); + removedLines.Add (new List (_model.GetLine (startRow + 1))); - UpdateWrapModel (); + _model.RemoveLine (startRow + 1); + } + if (currentEncoded == end) { + _currentRow -= maxrow - startRow; + } + _currentColumn = startCol; - return; - } + _historyText.Add (new List> (removedLines), CursorPosition, + HistoryText.LineStatus.Removed); - removedLines.Add (new List (line)); + UpdateWrapModel (); - line.RemoveRange (startCol, line.Count - startCol); - var line2 = _model.GetLine (maxrow); - line.AddRange (line2.Skip (endCol)); - for (int row = startRow + 1; row <= maxrow; row++) { + SetNeedsDisplay (); + } - removedLines.Add (new List (_model.GetLine (startRow + 1))); + /// + /// Select all text. + /// + public void SelectAll () + { + if (_model.Count == 0) { + return; + } + + StartSelecting (); + _selectionStartColumn = 0; + _selectionStartRow = 0; + _currentColumn = _model.GetLine (_model.Count - 1).Count; + _currentRow = _model.Count - 1; + SetNeedsDisplay (); + } - _model.RemoveLine (startRow + 1); - } - if (currentEncoded == end) { - _currentRow -= maxrow - (startRow); - } - _currentColumn = startCol; + /// + /// Find the next text based on the match case with the option to replace it. + /// + /// The text to find. + /// trueIf all the text was forward searched.falseotherwise. + /// The match case setting. + /// The match whole word setting. + /// The text to replace. + /// trueIf is replacing.falseotherwise. + /// trueIf the text was found.falseotherwise. + public bool FindNextText (string textToFind, out bool gaveFullTurn, bool matchCase = false, + bool matchWholeWord = false, string? textToReplace = null, bool replace = false) + { + if (_model.Count == 0) { + gaveFullTurn = false; + return false; + } - _historyText.Add (new List> (removedLines), CursorPosition, - HistoryText.LineStatus.Removed); + SetWrapModel (); + ResetContinuousFind (); + var foundPos = _model.FindNextText (textToFind, out gaveFullTurn, matchCase, matchWholeWord); - UpdateWrapModel (); + return SetFoundText (textToFind, foundPos, textToReplace, replace); + } - SetNeedsDisplay (); + /// + /// Find the previous text based on the match case with the option to replace it. + /// + /// The text to find. + /// trueIf all the text was backward searched.falseotherwise. + /// The match case setting. + /// The match whole word setting. + /// The text to replace. + /// trueIf the text was found.falseotherwise. + /// trueIf the text was found.falseotherwise. + public bool FindPreviousText (string textToFind, out bool gaveFullTurn, bool matchCase = false, + bool matchWholeWord = false, string? textToReplace = null, bool replace = false) + { + if (_model.Count == 0) { + gaveFullTurn = false; + return false; } - /// - /// Select all text. - /// - public void SelectAll () - { - if (_model.Count == 0) { - return; - } - - StartSelecting (); - _selectionStartColumn = 0; - _selectionStartRow = 0; - _currentColumn = _model.GetLine (_model.Count - 1).Count; - _currentRow = _model.Count - 1; - SetNeedsDisplay (); - } + SetWrapModel (); + ResetContinuousFind (); + var foundPos = _model.FindPreviousText (textToFind, out gaveFullTurn, matchCase, matchWholeWord); - /// - /// Find the next text based on the match case with the option to replace it. - /// - /// The text to find. - /// trueIf all the text was forward searched.falseotherwise. - /// The match case setting. - /// The match whole word setting. - /// The text to replace. - /// trueIf is replacing.falseotherwise. - /// trueIf the text was found.falseotherwise. - public bool FindNextText (string textToFind, out bool gaveFullTurn, bool matchCase = false, - bool matchWholeWord = false, string? textToReplace = null, bool replace = false) - { - if (_model.Count == 0) { - gaveFullTurn = false; - return false; - } + return SetFoundText (textToFind, foundPos, textToReplace, replace); + } - SetWrapModel (); - ResetContinuousFind (); - var foundPos = _model.FindNextText (textToFind, out gaveFullTurn, matchCase, matchWholeWord); + /// + /// Reset the flag to stop continuous find. + /// + public void FindTextChanged () => _continuousFind = false; - return SetFoundText (textToFind, foundPos, textToReplace, replace); + /// + /// Replaces all the text based on the match case. + /// + /// The text to find. + /// The match case setting. + /// The match whole word setting. + /// The text to replace. + /// trueIf the text was found.falseotherwise. + public bool ReplaceAllText (string textToFind, bool matchCase = false, bool matchWholeWord = false, + string? textToReplace = null) + { + if (_isReadOnly || _model.Count == 0) { + return false; } - /// - /// Find the previous text based on the match case with the option to replace it. - /// - /// The text to find. - /// trueIf all the text was backward searched.falseotherwise. - /// The match case setting. - /// The match whole word setting. - /// The text to replace. - /// trueIf the text was found.falseotherwise. - /// trueIf the text was found.falseotherwise. - public bool FindPreviousText (string textToFind, out bool gaveFullTurn, bool matchCase = false, - bool matchWholeWord = false, string? textToReplace = null, bool replace = false) - { - if (_model.Count == 0) { - gaveFullTurn = false; - return false; - } + SetWrapModel (); + ResetContinuousFind (); + var foundPos = _model.ReplaceAllText (textToFind, matchCase, matchWholeWord, textToReplace); - SetWrapModel (); - ResetContinuousFind (); - var foundPos = _model.FindPreviousText (textToFind, out gaveFullTurn, matchCase, matchWholeWord); + return SetFoundText (textToFind, foundPos, textToReplace, false, true); + } - return SetFoundText (textToFind, foundPos, textToReplace, replace); + bool SetFoundText (string text, (Point current, bool found) foundPos, + string? textToReplace = null, bool replace = false, bool replaceAll = false) + { + if (foundPos.found) { + StartSelecting (); + _selectionStartColumn = foundPos.current.X; + _selectionStartRow = foundPos.current.Y; + if (!replaceAll) { + _currentColumn = _selectionStartColumn + text.GetRuneCount (); + } else { + _currentColumn = _selectionStartColumn + textToReplace!.GetRuneCount (); + } + _currentRow = foundPos.current.Y; + if (!_isReadOnly && replace) { + Adjust (); + ClearSelectedRegion (); + InsertAllText (textToReplace!); + StartSelecting (); + _selectionStartColumn = _currentColumn - textToReplace!.GetRuneCount (); + } else { + UpdateWrapModel (); + SetNeedsDisplay (); + Adjust (); + } + _continuousFind = true; + return foundPos.found; } + UpdateWrapModel (); + _continuousFind = false; - /// - /// Reset the flag to stop continuous find. - /// - public void FindTextChanged () - { - _continuousFind = false; - } + return foundPos.found; + } - /// - /// Replaces all the text based on the match case. - /// - /// The text to find. - /// The match case setting. - /// The match whole word setting. - /// The text to replace. - /// trueIf the text was found.falseotherwise. - public bool ReplaceAllText (string textToFind, bool matchCase = false, bool matchWholeWord = false, - string? textToReplace = null) - { - if (_isReadOnly || _model.Count == 0) { - return false; - } + void ResetContinuousFind () + { + if (!_continuousFind) { + int col = _selecting ? _selectionStartColumn : _currentColumn; + int row = _selecting ? _selectionStartRow : _currentRow; + _model.ResetContinuousFind (new Point (col, row)); + } + } - SetWrapModel (); - ResetContinuousFind (); - var foundPos = _model.ReplaceAllText (textToFind, matchCase, matchWholeWord, textToReplace); + string? _currentCaller; - return SetFoundText (textToFind, foundPos, textToReplace, false, true); + /// + /// Restore from original model. + /// + void SetWrapModel ([CallerMemberName] string? caller = null) + { + if (_currentCaller != null) { + return; } - bool SetFoundText (string text, (Point current, bool found) foundPos, - string? textToReplace = null, bool replace = false, bool replaceAll = false) - { - if (foundPos.found) { - StartSelecting (); - _selectionStartColumn = foundPos.current.X; - _selectionStartRow = foundPos.current.Y; - if (!replaceAll) { - _currentColumn = _selectionStartColumn + text.GetRuneCount (); - } else { - _currentColumn = _selectionStartColumn + textToReplace!.GetRuneCount (); - } - _currentRow = foundPos.current.Y; - if (!_isReadOnly && replace) { - Adjust (); - ClearSelectedRegion (); - InsertAllText (textToReplace!); - StartSelecting (); - _selectionStartColumn = _currentColumn - textToReplace!.GetRuneCount (); - } else { - UpdateWrapModel (); - SetNeedsDisplay (); - Adjust (); - } - _continuousFind = true; - return foundPos.found; - } - UpdateWrapModel (); - _continuousFind = false; + if (_wordWrap) { + _currentCaller = caller; - return foundPos.found; + _currentColumn = _wrapManager!.GetModelColFromWrappedLines (_currentRow, _currentColumn); + _currentRow = _wrapManager.GetModelLineFromWrappedLines (_currentRow); + _selectionStartColumn = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn); + _selectionStartRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow); + _model = _wrapManager.Model; } + } - void ResetContinuousFind () - { - if (!_continuousFind) { - var col = _selecting ? _selectionStartColumn : _currentColumn; - var row = _selecting ? _selectionStartRow : _currentRow; - _model.ResetContinuousFind (new Point (col, row)); - } + /// + /// Update the original model. + /// + void UpdateWrapModel ([CallerMemberName] string? caller = null) + { + if (_currentCaller != null && _currentCaller != caller) { + return; } - string? _currentCaller; - - /// - /// Restore from original model. - /// - void SetWrapModel ([CallerMemberName] string? caller = null) - { - if (_currentCaller != null) - return; + if (_wordWrap) { + _currentCaller = null; - if (_wordWrap) { - _currentCaller = caller; + _wrapManager!.UpdateModel (_model, out int nRow, out int nCol, + out int nStartRow, out int nStartCol, + _currentRow, _currentColumn, + _selectionStartRow, _selectionStartColumn, true); + _currentRow = nRow; + _currentColumn = nCol; + _selectionStartRow = nStartRow; + _selectionStartColumn = nStartCol; + _wrapNeeded = true; - _currentColumn = _wrapManager!.GetModelColFromWrappedLines (_currentRow, _currentColumn); - _currentRow = _wrapManager.GetModelLineFromWrappedLines (_currentRow); - _selectionStartColumn = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn); - _selectionStartRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow); - _model = _wrapManager.Model; - } + SetNeedsDisplay (); } + if (_currentCaller != null) { + throw new InvalidOperationException ($"WordWrap settings was changed after the {_currentCaller} call."); + } + } - /// - /// Update the original model. - /// - void UpdateWrapModel ([CallerMemberName] string? caller = null) - { - if (_currentCaller != null && _currentCaller != caller) - return; - - if (_wordWrap) { - _currentCaller = null; - - _wrapManager!.UpdateModel (_model, out int nRow, out int nCol, - out int nStartRow, out int nStartCol, - _currentRow, _currentColumn, - _selectionStartRow, _selectionStartColumn, preserveTrailingSpaces: true); - _currentRow = nRow; - _currentColumn = nCol; - _selectionStartRow = nStartRow; - _selectionStartColumn = nStartCol; - _wrapNeeded = true; + /// + /// Invoke the event with the unwrapped . + /// + public virtual void OnUnwrappedCursorPosition (int? cRow = null, int? cCol = null) + { + int? row = cRow == null ? _currentRow : cRow; + int? col = cCol == null ? _currentColumn : cCol; + if (cRow == null && cCol == null && _wordWrap) { + row = _wrapManager!.GetModelLineFromWrappedLines (_currentRow); + col = _wrapManager.GetModelColFromWrappedLines (_currentRow, _currentColumn); + } + UnwrappedCursorPosition?.Invoke (this, new PointEventArgs (new Point ((int)col, (int)row))); + } - SetNeedsDisplay (); - } - if (_currentCaller != null) - throw new InvalidOperationException ($"WordWrap settings was changed after the {_currentCaller} call."); - } + string GetSelectedRegion () + { + int cRow = _currentRow; + int cCol = _currentColumn; + int startRow = _selectionStartRow; + int startCol = _selectionStartColumn; + var model = _model; + if (_wordWrap) { + cRow = _wrapManager!.GetModelLineFromWrappedLines (_currentRow); + cCol = _wrapManager.GetModelColFromWrappedLines (_currentRow, _currentColumn); + startRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow); + startCol = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn); + model = _wrapManager.Model; + } + OnUnwrappedCursorPosition (cRow, cCol); + return GetRegion (startRow, startCol, cRow, cCol, model); + } - /// - /// Invoke the event with the unwrapped . - /// - public virtual void OnUnwrappedCursorPosition (int? cRow = null, int? cCol = null) - { - var row = cRow == null ? _currentRow : cRow; - var col = cCol == null ? _currentColumn : cCol; - if (cRow == null && cCol == null && _wordWrap) { - row = _wrapManager!.GetModelLineFromWrappedLines (_currentRow); - col = _wrapManager.GetModelColFromWrappedLines (_currentRow, _currentColumn); - } - UnwrappedCursorPosition?.Invoke (this, new PointEventArgs (new Point ((int)col, (int)row))); - } + bool _isDrawing = false; - string GetSelectedRegion () - { - var cRow = _currentRow; - var cCol = _currentColumn; - var startRow = _selectionStartRow; - var startCol = _selectionStartColumn; - var model = this._model; - if (_wordWrap) { - cRow = _wrapManager!.GetModelLineFromWrappedLines (_currentRow); - cCol = _wrapManager.GetModelColFromWrappedLines (_currentRow, _currentColumn); - startRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow); - startCol = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn); - model = _wrapManager.Model; - } - OnUnwrappedCursorPosition (cRow, cCol); - return GetRegion (startRow, startCol, cRow, cCol, model); - } + /// + public override void OnDrawContent (Rect contentArea) + { + _isDrawing = true; - bool _isDrawing = false; + SetNormalColor (); - /// - public override void OnDrawContent (Rect contentArea) - { - _isDrawing = true; + var offB = OffSetBackground (); + int right = Frame.Width + offB.width + RightOffset; + int bottom = Frame.Height + offB.height + BottomOffset; + int row = 0; + for (int idxRow = _topRow; idxRow < _model.Count; idxRow++) { + var line = _model.GetLine (idxRow); + int lineRuneCount = line.Count; + int col = 0; - SetNormalColor (); + Move (0, row); + for (int idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++) { + var rune = idxCol >= lineRuneCount ? (Rune)' ' : line [idxCol].Rune; + int cols = rune.GetColumns (); + if (idxCol < line.Count && _selecting && PointInSelection (idxCol, idxRow)) { + OnDrawSelectionColor (line, idxCol, idxRow); + } else if (idxCol == _currentColumn && idxRow == _currentRow && !_selecting && !Used + && HasFocus && idxCol < lineRuneCount) { + OnDrawUsedColor (line, idxCol, idxRow); + } else if (ReadOnly) { + OnDrawReadOnlyColor (line, idxCol, idxRow); + } else { + OnDrawNormalColor (line, idxCol, idxRow); + } - var offB = OffSetBackground (); - int right = Frame.Width + offB.width + RightOffset; - int bottom = Frame.Height + offB.height + BottomOffset; - var row = 0; - for (int idxRow = _topRow; idxRow < _model.Count; idxRow++) { - var line = _model.GetLine (idxRow); - int lineRuneCount = line.Count; - var col = 0; - - Move (0, row); - for (int idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++) { - var rune = idxCol >= lineRuneCount ? (Rune)' ' : line [idxCol].Rune; - var cols = rune.GetColumns (); - if (idxCol < line.Count && _selecting && PointInSelection (idxCol, idxRow)) { - OnDrawSelectionColor (line, idxCol, idxRow); - } else if (idxCol == _currentColumn && idxRow == _currentRow && !_selecting && !Used - && HasFocus && idxCol < lineRuneCount) { - OnDrawUsedColor (line, idxCol, idxRow); - } else if (ReadOnly) { - OnDrawReadOnlyColor (line, idxCol, idxRow); - } else { - OnDrawNormalColor (line, idxCol, idxRow); + if (rune.Value == '\t') { + cols += TabWidth + 1; + if (col + cols > right) { + cols = right - col; } - - if (rune.Value == '\t') { - cols += TabWidth + 1; - if (col + cols > right) { - cols = right - col; - } - for (int i = 0; i < cols; i++) { - if (col + i < right) { - AddRune (col + i, row, (Rune)' '); - } + for (int i = 0; i < cols; i++) { + if (col + i < right) { + AddRune (col + i, row, (Rune)' '); } - } else { - AddRune (col, row, rune); - } - if (!TextModel.SetCol (ref col, contentArea.Right, cols)) { - break; - } - if (idxCol + 1 < lineRuneCount && col + line [idxCol + 1].Rune.GetColumns () > right) { - break; } + } else { + AddRune (col, row, rune); + } + if (!TextModel.SetCol (ref col, contentArea.Right, cols)) { + break; } - if (col < right) { - SetNormalColor (); - ClearRegion (col, row, right, row + 1); + if (idxCol + 1 < lineRuneCount && col + line [idxCol + 1].Rune.GetColumns () > right) { + break; } - row++; } - if (row < bottom) { + if (col < right) { SetNormalColor (); - ClearRegion (contentArea.Left, row, right, bottom); + ClearRegion (col, row, right, row + 1); } - - PositionCursor (); - - _isDrawing = false; + row++; } - - private (int Row, int Col) GetUnwrappedPosition (int line, int col) - { - if (WordWrap) { - return new (_wrapManager!.GetModelLineFromWrappedLines (line), _wrapManager.GetModelColFromWrappedLines (line, col)); - } - return new (line, col); + if (row < bottom) { + SetNormalColor (); + ClearRegion (contentArea.Left, row, right, bottom); } - private void ProcessAutocomplete () - { - if (_isDrawing) { - return; - } - if (_clickWithSelecting) { - _clickWithSelecting = false; - return; - } - if (SelectedLength > 0) - return; - - // draw autocomplete - GenerateSuggestions (); + PositionCursor (); - var renderAt = new Point ( - Autocomplete.Context.CursorPosition, - Autocomplete.PopupInsideContainer - ? (CursorPosition.Y + 1) - TopRow - : 0); + _isDrawing = false; + } - Autocomplete.RenderOverlay (renderAt); + (int Row, int Col) GetUnwrappedPosition (int line, int col) + { + if (WordWrap) { + return new ValueTuple (_wrapManager!.GetModelLineFromWrappedLines (line), _wrapManager.GetModelColFromWrappedLines (line, col)); } + return new ValueTuple (line, col); + } - private void GenerateSuggestions () - { - var currentLine = this.GetCurrentLine (); - var cursorPosition = Math.Min (this.CurrentColumn, currentLine.Count); - Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition, - Autocomplete.Context != null ? Autocomplete.Context.Canceled : false); - Autocomplete.GenerateSuggestions ( - Autocomplete.Context); + void ProcessAutocomplete () + { + if (_isDrawing) { + return; } - - /// - public override bool CanFocus { - get => base.CanFocus; - set { base.CanFocus = value; } + if (_clickWithSelecting) { + _clickWithSelecting = false; + return; } - - void SetClipboard (string text) - { - if (text != null) { - Clipboard.Contents = text; - } + if (SelectedLength > 0) { + return; } - void AppendClipboard (string text) - { - Clipboard.Contents += text; - } + // draw autocomplete + GenerateSuggestions (); - /// - /// Inserts the given text at the current cursor position - /// exactly as if the user had just typed it - /// - /// Text to add - public void InsertText (string toAdd) - { - foreach (var ch in toAdd) { + var renderAt = new Point ( + Autocomplete.Context.CursorPosition, + Autocomplete.PopupInsideContainer + ? CursorPosition.Y + 1 - TopRow + : 0); - KeyCode key; + Autocomplete.RenderOverlay (renderAt); + } - try { - key = (KeyCode)ch; - } catch (Exception) { + void GenerateSuggestions () + { + var currentLine = this.GetCurrentLine (); + var cursorPosition = Math.Min (CurrentColumn, currentLine.Count); + Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition, + Autocomplete.Context != null ? Autocomplete.Context.Canceled : false); + Autocomplete.GenerateSuggestions ( + Autocomplete.Context); + } - throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key"); - } + /// + public override bool CanFocus { + get => base.CanFocus; + set => base.CanFocus = value; + } + + void SetClipboard (string text) + { + if (text != null) { + Clipboard.Contents = text; + } + } + + void AppendClipboard (string text) => Clipboard.Contents += text; - InsertText (new Key () { KeyCode = key }); + /// + /// Inserts the given text at the current cursor position + /// exactly as if the user had just typed it + /// + /// Text to add + public void InsertText (string toAdd) + { + foreach (char ch in toAdd) { + KeyCode key; + try { + key = (KeyCode)ch; + } catch (Exception) { + throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key"); } + InsertText (new Key (key)); + if (NeedsDisplay) { Adjust (); } else { PositionCursor (); } } + } - void Insert (RuneCell cell) + void Insert (RuneCell cell) { var line = GetCurrentLine (); if (Used) { @@ -3089,7 +3054,7 @@ void Insert (RuneCell cell) } line.Insert (Math.Min (_currentColumn, line.Count), cell); } - var prow = _currentRow - _topRow; + int prow = _currentRow - _topRow; if (!_wrapNeeded) { // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. //SetNeedsDisplay (new Rect (0, prow, Math.Max (Frame.Width, 0), Math.Max (prow + 1, 0))); @@ -3099,13 +3064,14 @@ void Insert (RuneCell cell) string StringFromRunes (List cells) { - if (cells == null) + if (cells == null) { throw new ArgumentNullException (nameof (cells)); + } int size = 0; foreach (var cell in cells) { size += cell.Rune.GetEncodingLength (); } - var encoded = new byte [size]; + byte [] encoded = new byte [size]; int offset = 0; foreach (var cell in cells) { offset += cell.Rune.Encode (encoded, offset); @@ -3150,15 +3116,15 @@ void InsertAllText (string text) var line = GetCurrentLine (); - _historyText.Add (new List> () { new List (line) }, CursorPosition); + _historyText.Add (new List> () { new (line) }, CursorPosition); // Optimize single line if (lines.Count == 1) { line.InsertRange (_currentColumn, lines [0]); _currentColumn += lines [0].Count; - _historyText.Add (new List> () { new List (line) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.Add (new List> () { new (line) }, CursorPosition, + HistoryText.LineStatus.Replaced); if (!_wordWrap && _currentColumn - _leftColumn > Frame.Width) { _leftColumn = Math.Max (_currentColumn - Frame.Width + 1, 0); @@ -3183,7 +3149,7 @@ void InsertAllText (string text) if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection) { // Keep a copy of the rest of the line - var restCount = line.Count - _currentColumn; + int restCount = line.Count - _currentColumn; rest = line.GetRange (_currentColumn, restCount); line.RemoveRange (_currentColumn, restCount); } @@ -3192,7 +3158,7 @@ void InsertAllText (string text) line.InsertRange (_currentColumn, lines [0]); //model.AddLine (currentRow, lines [0]); - var addedLines = new List> () { new List (line) }; + var addedLines = new List> () { new (line) }; for (int i = 1; i < lines.Count; i++) { _model.AddLine (_currentRow + i, lines [i]); @@ -3215,8 +3181,8 @@ void InsertAllText (string text) _currentColumn = rest != null ? lastp : lines [lines.Count - 1].Count; Adjust (); - _historyText.Add (new List> () { new List (line) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.Add (new List> () { new (line) }, CursorPosition, + HistoryText.LineStatus.Replaced); UpdateWrapModel (); OnContentsChanged (); @@ -3230,12 +3196,13 @@ void TrackColumn () { // Now track the column var line = GetCurrentLine (); - if (line.Count < _columnTrack) + if (line.Count < _columnTrack) { _currentColumn = line.Count; - else if (_columnTrack != -1) + } else if (_columnTrack != -1) { _currentColumn = _columnTrack; - else if (_currentColumn > line.Count) + } else if (_currentColumn > line.Count) { _currentColumn = line.Count; + } Adjust (); } @@ -3250,12 +3217,12 @@ void Adjust () _leftColumn = _currentColumn; need = true; } else if (!_wordWrap && (_currentColumn - _leftColumn + RightOffset > Frame.Width + offB.width - || dSize.size + RightOffset >= Frame.Width + offB.width)) { + || dSize.size + RightOffset >= Frame.Width + offB.width)) { _leftColumn = TextModel.CalculateLeftColumn (line, _leftColumn, _currentColumn, - Frame.Width + offB.width - RightOffset, TabWidth); + Frame.Width + offB.width - RightOffset, TabWidth); need = true; - } else if ((_wordWrap && _leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width - && tSize.size + RightOffset < Frame.Width + offB.width)) { + } else if (_wordWrap && _leftColumn > 0 || dSize.size + RightOffset < Frame.Width + offB.width + && tSize.size + RightOffset < Frame.Width + offB.width) { if (_leftColumn > 0) { _leftColumn = 0; need = true; @@ -3285,1680 +3252,1679 @@ void Adjust () OnUnwrappedCursorPosition (); } - /// - /// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises - /// the event. - /// - public virtual void OnContentsChanged () - { - ContentsChanged?.Invoke (this, new ContentsChangedEventArgs (CurrentRow, CurrentColumn)); + /// + /// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises + /// the event. + /// + public virtual void OnContentsChanged () + { + ContentsChanged?.Invoke (this, new ContentsChangedEventArgs (CurrentRow, CurrentColumn)); + + ProcessInheritsPreviousColorScheme (CurrentRow, CurrentColumn); + ProcessAutocomplete (); + } - ProcessInheritsPreviousColorScheme (CurrentRow, CurrentColumn); - ProcessAutocomplete (); + // If InheritsPreviousColorScheme is enabled this method will check if the rune cell on + // the row and col location and around has a not null color scheme. If it's null will set it with + // the very most previous valid color scheme. + void ProcessInheritsPreviousColorScheme (int row, int col) + { + if (!InheritsPreviousColorScheme || Lines == 1 && GetLine (Lines).Count == 0) { + return; } - // If InheritsPreviousColorScheme is enabled this method will check if the rune cell on - // the row and col location and around has a not null color scheme. If it's null will set it with - // the very most previous valid color scheme. - private void ProcessInheritsPreviousColorScheme (int row, int col) - { - if (!InheritsPreviousColorScheme || (Lines == 1 && GetLine (Lines).Count == 0)) { + var line = GetLine (row); + var lineToSet = line; + while (line.Count == 0) { + if (row == 0 && line.Count == 0) { return; } + row--; + line = GetLine (row); + lineToSet = line; + } + int colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0); + var cell = line [colWithColor]; + int colWithoutColor = Math.Max (col - 1, 0); - var line = GetLine (row); - var lineToSet = line; - while (line.Count == 0) { - if (row == 0 && line.Count == 0) { - return; - } - row--; - line = GetLine (row); - lineToSet = line; - } - var colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0); - var cell = line [colWithColor]; - var colWithoutColor = Math.Max (col - 1, 0); - - if (cell.ColorScheme != null && colWithColor == 0 && lineToSet [colWithoutColor].ColorScheme != null) { - for (int r = row - 1; r > -1; r--) { - var l = GetLine (r); - for (int c = l.Count - 1; c > -1; c--) { - if (l [c].ColorScheme == null) { - l [c].ColorScheme = cell.ColorScheme; - } else { - return; - } + if (cell.ColorScheme != null && colWithColor == 0 && lineToSet [colWithoutColor].ColorScheme != null) { + for (int r = row - 1; r > -1; r--) { + var l = GetLine (r); + for (int c = l.Count - 1; c > -1; c--) { + if (l [c].ColorScheme == null) { + l [c].ColorScheme = cell.ColorScheme; + } else { + return; } } - return; - } else if (cell.ColorScheme == null) { - for (int r = row; r > -1; r--) { - var l = GetLine (r); - colWithColor = l.FindLastIndex (colWithColor > -1 ? colWithColor : l.Count - 1, rc => rc.ColorScheme != null); - if (colWithColor > -1 && l [colWithColor].ColorScheme != null) { - cell = l [colWithColor]; - break; - } + } + return; + } else if (cell.ColorScheme == null) { + for (int r = row; r > -1; r--) { + var l = GetLine (r); + colWithColor = l.FindLastIndex (colWithColor > -1 ? colWithColor : l.Count - 1, rc => rc.ColorScheme != null); + if (colWithColor > -1 && l [colWithColor].ColorScheme != null) { + cell = l [colWithColor]; + break; } - } else { - var cRow = row; - while (cell.ColorScheme == null) { - if ((colWithColor == 0 || cell.ColorScheme == null) && cRow > 0) { - line = GetLine (--cRow); - colWithColor = line.Count - 1; - cell = line [colWithColor]; - } else if (cRow == 0 && colWithColor < line.Count) { - cell = line [colWithColor + 1]; - } + } + } else { + int cRow = row; + while (cell.ColorScheme == null) { + if ((colWithColor == 0 || cell.ColorScheme == null) && cRow > 0) { + line = GetLine (--cRow); + colWithColor = line.Count - 1; + cell = line [colWithColor]; + } else if (cRow == 0 && colWithColor < line.Count) { + cell = line [colWithColor + 1]; } } - if (cell.ColorScheme != null && colWithColor > -1 && colWithoutColor < lineToSet.Count && lineToSet [colWithoutColor].ColorScheme == null) { - while (lineToSet [colWithoutColor].ColorScheme == null) { - lineToSet [colWithoutColor].ColorScheme = cell.ColorScheme; - colWithoutColor--; - if (colWithoutColor == -1 && row > 0) { - lineToSet = GetLine (--row); - colWithoutColor = lineToSet.Count - 1; - } + } + if (cell.ColorScheme != null && colWithColor > -1 && colWithoutColor < lineToSet.Count && lineToSet [colWithoutColor].ColorScheme == null) { + while (lineToSet [colWithoutColor].ColorScheme == null) { + lineToSet [colWithoutColor].ColorScheme = cell.ColorScheme; + colWithoutColor--; + if (colWithoutColor == -1 && row > 0) { + lineToSet = GetLine (--row); + colWithoutColor = lineToSet.Count - 1; } } } + } - (int width, int height) OffSetBackground () - { - int w = 0; - int h = 0; - if (SuperView?.Frame.Right - Frame.Right < 0) { - w = SuperView!.Frame.Right - Frame.Right - 1; - } - if (SuperView?.Frame.Bottom - Frame.Bottom < 0) { - h = SuperView!.Frame.Bottom - Frame.Bottom - 1; - } - return (w, h); + (int width, int height) OffSetBackground () + { + int w = 0; + int h = 0; + if (SuperView?.Frame.Right - Frame.Right < 0) { + w = SuperView!.Frame.Right - Frame.Right - 1; + } + if (SuperView?.Frame.Bottom - Frame.Bottom < 0) { + h = SuperView!.Frame.Bottom - Frame.Bottom - 1; } + return (w, h); + } - /// - /// Will scroll the to display the specified row at the top if is true or - /// will scroll the to display the specified column at the left if is false. - /// - /// Row that should be displayed at the top or Column that should be displayed at the left, - /// if the value is negative it will be reset to zero - /// If true (default) the is a row, column otherwise. - public void ScrollTo (int idx, bool isRow = true) - { - if (idx < 0) { - idx = 0; - } - if (isRow) { - _topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0); - } else if (!_wordWrap) { - var maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height + RightOffset, TabWidth); - _leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0); - } - SetNeedsDisplay (); - } + /// + /// Will scroll the to display the specified row at the top if is true or + /// will scroll the to display the specified column at the left if is false. + /// + /// Row that should be displayed at the top or Column that should be displayed at the left, + /// if the value is negative it will be reset to zero + /// If true (default) the is a row, column otherwise. + public void ScrollTo (int idx, bool isRow = true) + { + if (idx < 0) { + idx = 0; + } + if (isRow) { + _topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0); + } else if (!_wordWrap) { + int maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height + RightOffset, TabWidth); + _leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0); + } + SetNeedsDisplay (); + } - bool _lastWasKill; - bool _wrapNeeded; - bool _shiftSelecting; + bool _lastWasKill; + bool _wrapNeeded; + bool _shiftSelecting; - /// - public override bool? OnInvokingKeyBindings (Key a) - { - // Give autocomplete first opportunity to respond to key presses - if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) { - return true; - } - return base.OnInvokingKeyBindings (a); + /// + public override bool? OnInvokingKeyBindings (Key a) + { + // Give autocomplete first opportunity to respond to key presses + if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) { + return true; } + return base.OnInvokingKeyBindings (a); + } - /// - public override bool OnProcessKeyDown (Key a) - { - if (!CanFocus) { - return true; - } + /// + public override bool OnProcessKeyDown (Key a) + { + if (!CanFocus) { + return true; + } - ResetColumnTrack (); + ResetColumnTrack (); - // Ignore control characters and other special keys - if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) { - return false; - } + // Ignore control characters and other special keys + if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) { + return false; + } - InsertText (a); - DoNeededAction (); + InsertText (a); + DoNeededAction (); - return true; + return true; + } + + /// + /// Redoes the latest changes. + /// + public void Redo () + { + if (ReadOnly) { + return; } - /// - /// Redoes the latest changes. - /// - public void Redo () - { - if (ReadOnly) { - return; - } + _historyText.Redo (); + } - _historyText.Redo (); + /// + /// Undoes the latest changes. + /// + public void Undo () + { + if (ReadOnly) { + return; } - /// - /// Undoes the latest changes. - /// - public void Undo () - { - if (ReadOnly) { - return; - } + _historyText.Undo (); + } - _historyText.Undo (); - } + bool ProcessMovePreviousView () + { + ResetColumnTrack (); + return MovePreviousView (); + } - bool ProcessMovePreviousView () - { - ResetColumnTrack (); - return MovePreviousView (); - } + bool ProcessMoveNextView () + { + ResetColumnTrack (); + return MoveNextView (); + } - bool ProcessMoveNextView () - { - ResetColumnTrack (); - return MoveNextView (); - } + void ProcessSetOverwrite () + { + ResetColumnTrack (); + SetOverwrite (!Used); + } - void ProcessSetOverwrite () - { - ResetColumnTrack (); - SetOverwrite (!Used); - } + void ProcessSelectAll () + { + ResetColumnTrack (); + SelectAll (); + } - void ProcessSelectAll () - { - ResetColumnTrack (); - SelectAll (); - } + void MoveTopHomeExtend () + { + ResetColumnTrack (); + StartSelecting (); + MoveHome (); + } - void MoveTopHomeExtend () - { - ResetColumnTrack (); - StartSelecting (); - MoveHome (); + void MoveTopHome () + { + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveHome (); + } - void MoveTopHome () - { - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveHome (); - } + void MoveBottomEndExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveEnd (); + } - void MoveBottomEndExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveEnd (); + void MoveBottomEnd () + { + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveEnd (); + } - void MoveBottomEnd () - { - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveEnd (); - } + void ProcessKillWordBackward () + { + ResetColumnTrack (); + KillWordBackward (); + } - void ProcessKillWordBackward () - { - ResetColumnTrack (); - KillWordBackward (); - } + void ProcessKillWordForward () + { + ResetColumnTrack (); + KillWordForward (); + } - void ProcessKillWordForward () - { - ResetColumnTrack (); - KillWordForward (); - } + void ProcessMoveWordForwardExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveWordForward (); + } - void ProcessMoveWordForwardExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveWordForward (); + void ProcessMoveWordForward () + { + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveWordForward (); + } - void ProcessMoveWordForward () - { - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveWordForward (); - } + void ProcessMoveWordBackwardExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveWordBackward (); + } - void ProcessMoveWordBackwardExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveWordBackward (); + void ProcessMoveWordBackward () + { + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveWordBackward (); + } - void ProcessMoveWordBackward () - { - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveWordBackward (); - } + void ProcessCut () + { + ResetColumnTrack (); + Cut (); + } - void ProcessCut () - { - ResetColumnTrack (); - Cut (); - } + void ProcessCopy () + { + ResetColumnTrack (); + Copy (); + } - void ProcessCopy () - { - ResetColumnTrack (); - Copy (); - } + void ToggleSelecting () + { + ResetColumnTrack (); + _selecting = !_selecting; + _selectionStartColumn = _currentColumn; + _selectionStartRow = _currentRow; + } - void ToggleSelecting () - { - ResetColumnTrack (); - _selecting = !_selecting; - _selectionStartColumn = _currentColumn; - _selectionStartRow = _currentRow; + void ProcessPaste () + { + ResetColumnTrack (); + if (_isReadOnly) { + return; } + Paste (); + } - void ProcessPaste () - { - ResetColumnTrack (); - if (_isReadOnly) - return; - Paste (); - } + void ProcessMoveEndOfLineExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveEndOfLine (); + } - void ProcessMoveEndOfLineExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveEndOfLine (); + void ProcessMoveEndOfLine () + { + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveEndOfLine (); + } - void ProcessMoveEndOfLine () - { - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveEndOfLine (); - } + void ProcessDeleteCharRight () + { + ResetColumnTrack (); + DeleteCharRight (); + } - void ProcessDeleteCharRight () - { - ResetColumnTrack (); - DeleteCharRight (); - } + void ProcessMoveStartOfLineExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveStartOfLine (); + } - void ProcessMoveStartOfLineExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveStartOfLine (); + void ProcessMoveStartOfLine () + { + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveStartOfLine (); + } - void ProcessMoveStartOfLine () - { - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveStartOfLine (); - } + void ProcessDeleteCharLeft () + { + ResetColumnTrack (); + DeleteCharLeft (); + } - void ProcessDeleteCharLeft () - { - ResetColumnTrack (); - DeleteCharLeft (); + void ProcessMoveLeftExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveLeft (); + } + + bool ProcessMoveLeft () + { + // if the user presses Left (without any control keys) and they are at the start of the text + if (_currentColumn == 0 && _currentRow == 0) { + // do not respond (this lets the key press fall through to navigation system - which usually changes focus backward) + return false; } - void ProcessMoveLeftExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveLeft (); + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveLeft (); + return true; + } - bool ProcessMoveLeft () - { - // if the user presses Left (without any control keys) and they are at the start of the text - if (_currentColumn == 0 && _currentRow == 0) { - // do not respond (this lets the key press fall through to navigation system - which usually changes focus backward) - return false; - } + void ProcessMoveRightExtend () + { + ResetAllTrack (); + StartSelecting (); + MoveRight (); + } - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveLeft (); - return true; - } + bool ProcessMoveRight () + { + // if the user presses Right (without any control keys) + // determine where the last cursor position in the text is + int lastRow = _model.Count - 1; + int lastCol = _model.GetLine (lastRow).Count; - void ProcessMoveRightExtend () - { - ResetAllTrack (); - StartSelecting (); - MoveRight (); + // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward) + if (_currentColumn == lastCol && _currentRow == lastRow) { + return false; } - bool ProcessMoveRight () - { - // if the user presses Right (without any control keys) - // determine where the last cursor position in the text is - var lastRow = _model.Count - 1; - var lastCol = _model.GetLine (lastRow).Count; + ResetAllTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); + } + MoveRight (); + return true; + } - // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward) - if (_currentColumn == lastCol && _currentRow == lastRow) { - return false; - } + void ProcessMoveUpExtend () + { + ResetColumnTrack (); + StartSelecting (); + MoveUp (); + } - ResetAllTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveRight (); - return true; + void ProcessMoveUp () + { + ResetContinuousFindTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveUp (); + } - void ProcessMoveUpExtend () - { - ResetColumnTrack (); - StartSelecting (); - MoveUp (); - } + void ProcessMoveDownExtend () + { + ResetColumnTrack (); + StartSelecting (); + MoveDown (); + } - void ProcessMoveUp () - { - ResetContinuousFindTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveUp (); + void ProcessMoveDown () + { + ResetContinuousFindTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MoveDown (); + } - void ProcessMoveDownExtend () - { - ResetColumnTrack (); - StartSelecting (); - MoveDown (); + void ProcessPageUpExtend () + { + ResetColumnTrack (); + StartSelecting (); + MovePageUp (); + } + + void ProcessPageUp () + { + ResetColumnTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MovePageUp (); + } - void ProcessMoveDown () - { - ResetContinuousFindTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MoveDown (); + void ProcessPageDownExtend () + { + ResetColumnTrack (); + StartSelecting (); + MovePageDown (); + } + + void ProcessPageDown () + { + ResetColumnTrack (); + if (_shiftSelecting && _selecting) { + StopSelecting (); } + MovePageDown (); + } - void ProcessPageUpExtend () - { - ResetColumnTrack (); - StartSelecting (); - MovePageUp (); + bool MovePreviousView () + { + if (Application.OverlappedTop != null) { + return SuperView?.FocusPrev () == true; } - void ProcessPageUp () - { - ResetColumnTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); - } - MovePageUp (); + return false; + } + + bool MoveNextView () + { + if (Application.OverlappedTop != null) { + return SuperView?.FocusNext () == true; } - void ProcessPageDownExtend () - { - ResetColumnTrack (); - StartSelecting (); - MovePageDown (); + return false; + } + + bool ProcessBackTab () + { + ResetColumnTrack (); + + if (!AllowsTab || _isReadOnly) { + return ProcessMovePreviousView (); } + if (_currentColumn > 0) { + SetWrapModel (); - void ProcessPageDown () - { - ResetColumnTrack (); - if (_shiftSelecting && _selecting) { - StopSelecting (); + var currentLine = GetCurrentLine (); + if (currentLine.Count > 0 && currentLine [_currentColumn - 1].Rune.Value == '\t') { + + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + + currentLine.RemoveAt (_currentColumn - 1); + _currentColumn--; + + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); } - MovePageDown (); + + UpdateWrapModel (); } + DoNeededAction (); + return true; + } - bool MovePreviousView () - { - if (Application.OverlappedTop != null) { - return SuperView?.FocusPrev () == true; - } + bool ProcessTab () + { + ResetColumnTrack (); - return false; + if (!AllowsTab || _isReadOnly) { + return ProcessMoveNextView (); } + InsertText (new Key ((KeyCode)'\t')); + DoNeededAction (); + return true; + } - bool MoveNextView () - { - if (Application.OverlappedTop != null) { - return SuperView?.FocusNext () == true; - } + void SetOverwrite (bool overwrite) + { + Used = overwrite; + SetNeedsDisplay (); + DoNeededAction (); + } + + bool ProcessReturn () + { + ResetColumnTrack (); + if (!AllowsReturn || _isReadOnly) { return false; } - bool ProcessBackTab () - { - ResetColumnTrack (); + SetWrapModel (); - if (!AllowsTab || _isReadOnly) { - return ProcessMovePreviousView (); - } - if (_currentColumn > 0) { - SetWrapModel (); + var currentLine = GetCurrentLine (); - var currentLine = GetCurrentLine (); - if (currentLine.Count > 0 && currentLine [_currentColumn - 1].Rune.Value == '\t') { + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + if (_selecting) { + ClearSelectedRegion (); + currentLine = GetCurrentLine (); + } + int restCount = currentLine.Count - _currentColumn; + var rest = currentLine.GetRange (_currentColumn, restCount); + currentLine.RemoveRange (_currentColumn, restCount); - currentLine.RemoveAt (_currentColumn - 1); - _currentColumn--; + var addedLines = new List> () { new List (currentLine) }; - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); - } + _model.AddLine (_currentRow + 1, rest); - UpdateWrapModel (); - } - DoNeededAction (); - return true; + addedLines.Add (new List (_model.GetLine (_currentRow + 1))); + + _historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added); + + _currentRow++; + + bool fullNeedsDisplay = false; + if (_currentRow >= _topRow + Frame.Height) { + _topRow++; + fullNeedsDisplay = true; } + _currentColumn = 0; - bool ProcessTab () - { - ResetColumnTrack (); + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - if (!AllowsTab || _isReadOnly) { - return ProcessMoveNextView (); - } - InsertText (new Key ((KeyCode)'\t')); - DoNeededAction (); - return true; + if (!_wordWrap && _currentColumn < _leftColumn) { + fullNeedsDisplay = true; + _leftColumn = 0; } - void SetOverwrite (bool overwrite) - { - Used = overwrite; + if (fullNeedsDisplay) { + SetNeedsDisplay (); + } else { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height)); SetNeedsDisplay (); - DoNeededAction (); } - bool ProcessReturn () - { - ResetColumnTrack (); - - if (!AllowsReturn || _isReadOnly) { - return false; - } - - SetWrapModel (); + UpdateWrapModel (); - var currentLine = GetCurrentLine (); + DoNeededAction (); + OnContentsChanged (); + return true; + } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + void KillWordBackward () + { + if (_isReadOnly) { + return; + } - if (_selecting) { - ClearSelectedRegion (); - currentLine = GetCurrentLine (); - } - var restCount = currentLine.Count - _currentColumn; - var rest = currentLine.GetRange (_currentColumn, restCount); - currentLine.RemoveRange (_currentColumn, restCount); + SetWrapModel (); - var addedLines = new List> () { new List (currentLine) }; + var currentLine = GetCurrentLine (); - _model.AddLine (_currentRow + 1, rest); + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); - addedLines.Add (new List (_model.GetLine (_currentRow + 1))); + if (_currentColumn == 0) { + DeleteTextBackwards (); - _historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added); + _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - _currentRow++; + UpdateWrapModel (); - bool fullNeedsDisplay = false; - if (_currentRow >= _topRow + Frame.Height) { - _topRow++; - fullNeedsDisplay = true; + return; + } + var newPos = _model.WordBackward (_currentColumn, _currentRow); + if (newPos.HasValue && _currentRow == newPos.Value.row) { + int restCount = _currentColumn - newPos.Value.col; + currentLine.RemoveRange (newPos.Value.col, restCount); + if (_wordWrap) { + _wrapNeeded = true; } - _currentColumn = 0; - - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); - - if (!_wordWrap && _currentColumn < _leftColumn) { - fullNeedsDisplay = true; - _leftColumn = 0; - } - - if (fullNeedsDisplay) { - SetNeedsDisplay (); - } else { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height)); - SetNeedsDisplay (); + _currentColumn = newPos.Value.col; + } else if (newPos.HasValue) { + int restCount = currentLine.Count - _currentColumn; + currentLine.RemoveRange (_currentColumn, restCount); + if (_wordWrap) { + _wrapNeeded = true; } - - UpdateWrapModel (); - - DoNeededAction (); - OnContentsChanged (); - return true; + _currentColumn = newPos.Value.col; + _currentRow = newPos.Value.row; } - void KillWordBackward () - { - if (_isReadOnly) - return; + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - SetWrapModel (); + UpdateWrapModel (); - var currentLine = GetCurrentLine (); + DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoNeededAction (); + } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); + void KillWordForward () + { + if (_isReadOnly) { + return; + } - if (_currentColumn == 0) { - DeleteTextBackwards (); + SetWrapModel (); - _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + var currentLine = GetCurrentLine (); - UpdateWrapModel (); + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); - return; - } - var newPos = _model.WordBackward (_currentColumn, _currentRow); - if (newPos.HasValue && _currentRow == newPos.Value.row) { - var restCount = _currentColumn - newPos.Value.col; - currentLine.RemoveRange (newPos.Value.col, restCount); - if (_wordWrap) { - _wrapNeeded = true; - } - _currentColumn = newPos.Value.col; - } else if (newPos.HasValue) { - var restCount = currentLine.Count - _currentColumn; - currentLine.RemoveRange (_currentColumn, restCount); - if (_wordWrap) { - _wrapNeeded = true; - } - _currentColumn = newPos.Value.col; - _currentRow = newPos.Value.row; - } + if (currentLine.Count == 0 || _currentColumn == currentLine.Count) { + DeleteTextForwards (); - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); - DoNeededAction (); + return; + } + var newPos = _model.WordForward (_currentColumn, _currentRow); + int restCount = 0; + if (newPos.HasValue && _currentRow == newPos.Value.row) { + restCount = newPos.Value.col - _currentColumn; + currentLine.RemoveRange (_currentColumn, restCount); + } else if (newPos.HasValue) { + restCount = currentLine.Count - _currentColumn; + currentLine.RemoveRange (_currentColumn, restCount); + } + if (_wordWrap) { + _wrapNeeded = true; } - void KillWordForward () - { - if (_isReadOnly) - return; - - SetWrapModel (); - - var currentLine = GetCurrentLine (); - - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); - - if (currentLine.Count == 0 || _currentColumn == currentLine.Count) { - DeleteTextForwards (); - - _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); - - UpdateWrapModel (); - - return; - } - var newPos = _model.WordForward (_currentColumn, _currentRow); - var restCount = 0; - if (newPos.HasValue && _currentRow == newPos.Value.row) { - restCount = newPos.Value.col - _currentColumn; - currentLine.RemoveRange (_currentColumn, restCount); - } else if (newPos.HasValue) { - restCount = currentLine.Count - _currentColumn; - currentLine.RemoveRange (_currentColumn, restCount); - } - if (_wordWrap) { - _wrapNeeded = true; - } + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + UpdateWrapModel (); - UpdateWrapModel (); + DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoNeededAction (); + } - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); - DoNeededAction (); + void MoveWordForward () + { + var newPos = _model.WordForward (_currentColumn, _currentRow); + if (newPos.HasValue) { + _currentColumn = newPos.Value.col; + _currentRow = newPos.Value.row; } + Adjust (); + DoNeededAction (); + } - void MoveWordForward () - { - var newPos = _model.WordForward (_currentColumn, _currentRow); - if (newPos.HasValue) { - _currentColumn = newPos.Value.col; - _currentRow = newPos.Value.row; - } - Adjust (); - DoNeededAction (); + void MoveWordBackward () + { + var newPos = _model.WordBackward (_currentColumn, _currentRow); + if (newPos.HasValue) { + _currentColumn = newPos.Value.col; + _currentRow = newPos.Value.row; } + Adjust (); + DoNeededAction (); + } - void MoveWordBackward () - { - var newPos = _model.WordBackward (_currentColumn, _currentRow); - if (newPos.HasValue) { - _currentColumn = newPos.Value.col; - _currentRow = newPos.Value.row; - } - Adjust (); - DoNeededAction (); + void KillToStartOfLine () + { + if (_isReadOnly) { + return; + } + if (_model.Count == 1 && GetCurrentLine ().Count == 0) { + // Prevents from adding line feeds if there is no more lines. + return; } - void KillToStartOfLine () - { - if (_isReadOnly) - return; - if (_model.Count == 1 && GetCurrentLine ().Count == 0) { - // Prevents from adding line feeds if there is no more lines. - return; - } - - SetWrapModel (); + SetWrapModel (); - var currentLine = GetCurrentLine (); - var setLastWasKill = true; - if (currentLine.Count > 0 && _currentColumn == 0) { - UpdateWrapModel (); + var currentLine = GetCurrentLine (); + bool setLastWasKill = true; + if (currentLine.Count > 0 && _currentColumn == 0) { + UpdateWrapModel (); - DeleteTextBackwards (); - return; - } + DeleteTextBackwards (); + return; + } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - if (currentLine.Count == 0) { - if (_currentRow > 0) { - _model.RemoveLine (_currentRow); + if (currentLine.Count == 0) { + if (_currentRow > 0) { + _model.RemoveLine (_currentRow); - if (_model.Count > 0 || _lastWasKill) { - var val = Environment.NewLine; - if (_lastWasKill) { - AppendClipboard (val); - } else { - SetClipboard (val); - } - } - if (_model.Count == 0) { - // Prevents from adding line feeds if there is no more lines. - setLastWasKill = false; + if (_model.Count > 0 || _lastWasKill) { + string val = Environment.NewLine; + if (_lastWasKill) { + AppendClipboard (val); + } else { + SetClipboard (val); } + } + if (_model.Count == 0) { + // Prevents from adding line feeds if there is no more lines. + setLastWasKill = false; + } - _currentRow--; - currentLine = _model.GetLine (_currentRow); + _currentRow--; + currentLine = _model.GetLine (_currentRow); - var removedLine = new List> () { new List (currentLine) }; + var removedLine = new List> () { new List (currentLine) }; - removedLine.Add (new List ()); + removedLine.Add (new List ()); - _historyText.Add (new List> (removedLine), CursorPosition, HistoryText.LineStatus.Removed); + _historyText.Add (new List> (removedLine), CursorPosition, HistoryText.LineStatus.Removed); - _currentColumn = currentLine.Count; - } + _currentColumn = currentLine.Count; + } + } else { + int restCount = _currentColumn; + var rest = currentLine.GetRange (0, restCount); + string val = string.Empty; + val += StringFromRunes (rest); + if (_lastWasKill) { + AppendClipboard (val); } else { - var restCount = _currentColumn; - var rest = currentLine.GetRange (0, restCount); - var val = string.Empty; - val += StringFromRunes (rest); - if (_lastWasKill) { - AppendClipboard (val); - } else { - SetClipboard (val); - } - currentLine.RemoveRange (0, restCount); - _currentColumn = 0; + SetClipboard (val); } + currentLine.RemoveRange (0, restCount); + _currentColumn = 0; + } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - UpdateWrapModel (); + UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); - _lastWasKill = setLastWasKill; - DoNeededAction (); - } + _lastWasKill = setLastWasKill; + DoNeededAction (); + } - void KillToEndOfLine () - { - if (_isReadOnly) - return; - if (_model.Count == 1 && GetCurrentLine ().Count == 0) { - // Prevents from adding line feeds if there is no more lines. - return; - } + void KillToEndOfLine () + { + if (_isReadOnly) { + return; + } + if (_model.Count == 1 && GetCurrentLine ().Count == 0) { + // Prevents from adding line feeds if there is no more lines. + return; + } - SetWrapModel (); + SetWrapModel (); - var currentLine = GetCurrentLine (); - var setLastWasKill = true; - if (currentLine.Count > 0 && _currentColumn == currentLine.Count) { - UpdateWrapModel (); + var currentLine = GetCurrentLine (); + bool setLastWasKill = true; + if (currentLine.Count > 0 && _currentColumn == currentLine.Count) { + UpdateWrapModel (); - DeleteTextForwards (); - return; - } + DeleteTextForwards (); + return; + } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - if (currentLine.Count == 0) { - if (_currentRow < _model.Count - 1) { - var removedLines = new List> () { new List (currentLine) }; + if (currentLine.Count == 0) { + if (_currentRow < _model.Count - 1) { + var removedLines = new List> () { new List (currentLine) }; - _model.RemoveLine (_currentRow); + _model.RemoveLine (_currentRow); - removedLines.Add (new List (GetCurrentLine ())); + removedLines.Add (new List (GetCurrentLine ())); - _historyText.Add (new List> (removedLines), CursorPosition, - HistoryText.LineStatus.Removed); - } - if (_model.Count > 0 || _lastWasKill) { - var val = Environment.NewLine; - if (_lastWasKill) { - AppendClipboard (val); - } else { - SetClipboard (val); - } - } - if (_model.Count == 0) { - // Prevents from adding line feeds if there is no more lines. - setLastWasKill = false; - } - } else { - var restCount = currentLine.Count - _currentColumn; - var rest = currentLine.GetRange (_currentColumn, restCount); - var val = string.Empty; - val += StringFromRunes (rest); + _historyText.Add (new List> (removedLines), CursorPosition, + HistoryText.LineStatus.Removed); + } + if (_model.Count > 0 || _lastWasKill) { + string val = Environment.NewLine; if (_lastWasKill) { AppendClipboard (val); } else { SetClipboard (val); } - currentLine.RemoveRange (_currentColumn, restCount); } + if (_model.Count == 0) { + // Prevents from adding line feeds if there is no more lines. + setLastWasKill = false; + } + } else { + int restCount = currentLine.Count - _currentColumn; + var rest = currentLine.GetRange (_currentColumn, restCount); + string val = string.Empty; + val += StringFromRunes (rest); + if (_lastWasKill) { + AppendClipboard (val); + } else { + SetClipboard (val); + } + currentLine.RemoveRange (_currentColumn, restCount); + } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - UpdateWrapModel (); + UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); - _lastWasKill = setLastWasKill; - DoNeededAction (); - } + _lastWasKill = setLastWasKill; + DoNeededAction (); + } - void MoveEndOfLine () - { - var currentLine = GetCurrentLine (); - _currentColumn = currentLine.Count; - Adjust (); - DoNeededAction (); - } + void MoveEndOfLine () + { + var currentLine = GetCurrentLine (); + _currentColumn = currentLine.Count; + Adjust (); + DoNeededAction (); + } - void MoveStartOfLine () - { - if (_leftColumn > 0) { - SetNeedsDisplay (); - } - _currentColumn = 0; - _leftColumn = 0; - Adjust (); - DoNeededAction (); + void MoveStartOfLine () + { + if (_leftColumn > 0) { + SetNeedsDisplay (); } + _currentColumn = 0; + _leftColumn = 0; + Adjust (); + DoNeededAction (); + } - /// - /// Deletes all the selected or a single character at right from the position of the cursor. - /// - public void DeleteCharRight () - { - if (_isReadOnly) - return; - - SetWrapModel (); - - if (_selecting) { - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Original); - - ClearSelectedRegion (); + /// + /// Deletes all the selected or a single character at right from the position of the cursor. + /// + public void DeleteCharRight () + { + if (_isReadOnly) { + return; + } - var currentLine = GetCurrentLine (); + SetWrapModel (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, - HistoryText.LineStatus.Replaced); + if (_selecting) { + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Original); - UpdateWrapModel (); - OnContentsChanged (); + ClearSelectedRegion (); - return; - } - if (DeleteTextForwards ()) { - UpdateWrapModel (); - OnContentsChanged (); + var currentLine = GetCurrentLine (); - return; - } + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + HistoryText.LineStatus.Replaced); UpdateWrapModel (); - - DoNeededAction (); OnContentsChanged (); + + return; } + if (DeleteTextForwards ()) { + UpdateWrapModel (); + OnContentsChanged (); - /// - /// Deletes all the selected or a single character at left from the position of the cursor. - /// - public void DeleteCharLeft () - { - if (_isReadOnly) - return; + return; + } - SetWrapModel (); + UpdateWrapModel (); - if (_selecting) { - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Original); + DoNeededAction (); + OnContentsChanged (); + } - ClearSelectedRegion (); + /// + /// Deletes all the selected or a single character at left from the position of the cursor. + /// + public void DeleteCharLeft () + { + if (_isReadOnly) { + return; + } - var currentLine = GetCurrentLine (); + SetWrapModel (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, - HistoryText.LineStatus.Replaced); + if (_selecting) { + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Original); - UpdateWrapModel (); - OnContentsChanged (); + ClearSelectedRegion (); - return; - } - if (DeleteTextBackwards ()) { - UpdateWrapModel (); - OnContentsChanged (); + var currentLine = GetCurrentLine (); - return; - } + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + HistoryText.LineStatus.Replaced); UpdateWrapModel (); - - DoNeededAction (); OnContentsChanged (); - } - void MoveLeft () - { - if (_currentColumn > 0) { - _currentColumn--; - } else { - if (_currentRow > 0) { - _currentRow--; - if (_currentRow < _topRow) { - _topRow--; - SetNeedsDisplay (); - } - var currentLine = GetCurrentLine (); - _currentColumn = currentLine.Count; - } - } - Adjust (); - DoNeededAction (); + return; } + if (DeleteTextBackwards ()) { + UpdateWrapModel (); + OnContentsChanged (); - void MoveRight () - { - var currentLine = GetCurrentLine (); - if (_currentColumn < currentLine.Count) { - _currentColumn++; - } else { - if (_currentRow + 1 < _model.Count) { - _currentRow++; - _currentColumn = 0; - if (_currentRow >= _topRow + Frame.Height) { - _topRow++; - SetNeedsDisplay (); - } - } - } - Adjust (); - DoNeededAction (); + return; } - void MovePageUp () - { - int nPageUpShift = Frame.Height - 1; + UpdateWrapModel (); + + DoNeededAction (); + OnContentsChanged (); + } + + void MoveLeft () + { + if (_currentColumn > 0) { + _currentColumn--; + } else { if (_currentRow > 0) { - if (_columnTrack == -1) - _columnTrack = _currentColumn; - _currentRow = _currentRow - nPageUpShift < 0 ? 0 : _currentRow - nPageUpShift; + _currentRow--; if (_currentRow < _topRow) { - _topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift; + _topRow--; SetNeedsDisplay (); } - TrackColumn (); - PositionCursor (); + var currentLine = GetCurrentLine (); + _currentColumn = currentLine.Count; } - DoNeededAction (); } + Adjust (); + DoNeededAction (); + } - void MovePageDown () - { - int nPageDnShift = Frame.Height - 1; - if (_currentRow >= 0 && _currentRow < _model.Count) { - if (_columnTrack == -1) - _columnTrack = _currentColumn; - _currentRow = (_currentRow + nPageDnShift) > _model.Count - ? _model.Count > 0 ? _model.Count - 1 : 0 - : _currentRow + nPageDnShift; - if (_topRow < _currentRow - nPageDnShift) { - _topRow = _currentRow >= _model.Count ? _currentRow - nPageDnShift : _topRow + nPageDnShift; + void MoveRight () + { + var currentLine = GetCurrentLine (); + if (_currentColumn < currentLine.Count) { + _currentColumn++; + } else { + if (_currentRow + 1 < _model.Count) { + _currentRow++; + _currentColumn = 0; + if (_currentRow >= _topRow + Frame.Height) { + _topRow++; SetNeedsDisplay (); } - TrackColumn (); - PositionCursor (); } - DoNeededAction (); - } - - void ResetContinuousFindTrack () - { - // Handle some state here - whether the last command was a kill - // operation and the column tracking (up/down) - _lastWasKill = false; - _continuousFind = false; - } - - void ResetColumnTrack () - { - // Handle some state here - whether the last command was a kill - // operation and the column tracking (up/down) - _lastWasKill = false; - _columnTrack = -1; - } - - void ResetAllTrack () - { - // Handle some state here - whether the last command was a kill - // operation and the column tracking (up/down) - _lastWasKill = false; - _columnTrack = -1; - _continuousFind = false; } + Adjust (); + DoNeededAction (); + } - bool InsertText (Key a, ColorScheme? colorScheme = null) - { - //So that special keys like tab can be processed - if (_isReadOnly) { - return true; + void MovePageUp () + { + int nPageUpShift = Frame.Height - 1; + if (_currentRow > 0) { + if (_columnTrack == -1) { + _columnTrack = _currentColumn; } + _currentRow = _currentRow - nPageUpShift < 0 ? 0 : _currentRow - nPageUpShift; + if (_currentRow < _topRow) { + _topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift; + SetNeedsDisplay (); + } + TrackColumn (); + PositionCursor (); + } + DoNeededAction (); + } - SetWrapModel (); - - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); - - if (_selecting) { - ClearSelectedRegion (); + void MovePageDown () + { + int nPageDnShift = Frame.Height - 1; + if (_currentRow >= 0 && _currentRow < _model.Count) { + if (_columnTrack == -1) { + _columnTrack = _currentColumn; } - if (a.KeyCode == KeyCode.Enter) { - _model.AddLine (_currentRow + 1, new List ()); - _currentRow++; - _currentColumn = 0; - } else if ((uint)a.KeyCode == '\r') { - _currentColumn = 0; - } else { - if (Used) { - Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme }); - _currentColumn++; - if (_currentColumn >= _leftColumn + Frame.Width) { - _leftColumn++; - SetNeedsDisplay (); - } - } else { - Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme }); - _currentColumn++; - } + _currentRow = _currentRow + nPageDnShift > _model.Count + ? _model.Count > 0 ? _model.Count - 1 : 0 + : _currentRow + nPageDnShift; + if (_topRow < _currentRow - nPageDnShift) { + _topRow = _currentRow >= _model.Count ? _currentRow - nPageDnShift : _topRow + nPageDnShift; + SetNeedsDisplay (); } + TrackColumn (); + PositionCursor (); + } + DoNeededAction (); + } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + void ResetContinuousFindTrack () + { + // Handle some state here - whether the last command was a kill + // operation and the column tracking (up/down) + _lastWasKill = false; + _continuousFind = false; + } - UpdateWrapModel (); - OnContentsChanged (); + void ResetColumnTrack () + { + // Handle some state here - whether the last command was a kill + // operation and the column tracking (up/down) + _lastWasKill = false; + _columnTrack = -1; + } + + void ResetAllTrack () + { + // Handle some state here - whether the last command was a kill + // operation and the column tracking (up/down) + _lastWasKill = false; + _columnTrack = -1; + _continuousFind = false; + } + bool InsertText (Key a, ColorScheme? colorScheme = null) + { + //So that special keys like tab can be processed + if (_isReadOnly) { return true; } - void ShowContextMenu () - { - if (_currentCulture != Thread.CurrentThread.CurrentUICulture) { + SetWrapModel (); - _currentCulture = Thread.CurrentThread.CurrentUICulture; + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); - ContextMenu!.MenuItems = BuildContextMenuBarItem (); + if (_selecting) { + ClearSelectedRegion (); + } + if ((uint)a.KeyCode == '\n') { + _model.AddLine (_currentRow + 1, new List ()); + _currentRow++; + _currentColumn = 0; + } else if ((uint)a.KeyCode == '\r') { + _currentColumn = 0; + } else { + if (Used) { + Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme }); + _currentColumn++; + if (_currentColumn >= _leftColumn + Frame.Width) { + _leftColumn++; + SetNeedsDisplay (); + } + } else { + Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme }); + _currentColumn++; } - ContextMenu!.Show (); } - /// - /// Deletes all text. - /// - public void DeleteAll () - { - if (Lines == 0) { - return; - } + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - _selectionStartColumn = 0; - _selectionStartRow = 0; - MoveBottomEndExtend (); - DeleteCharLeft (); - SetNeedsDisplay (); - } + UpdateWrapModel (); + OnContentsChanged (); - /// - public override bool OnKeyUp (Key a) - { - switch (a.KeyCode) { - case KeyCode.Space | KeyCode.CtrlMask: - return true; - } + return true; + } - return base.OnKeyUp (a); + void ShowContextMenu () + { + if (_currentCulture != Thread.CurrentThread.CurrentUICulture) { + + _currentCulture = Thread.CurrentThread.CurrentUICulture; + + ContextMenu!.MenuItems = BuildContextMenuBarItem (); } + ContextMenu!.Show (); + } - void DoNeededAction () - { - if (NeedsDisplay) { - Adjust (); - } else { - PositionCursor (); - } + /// + /// Deletes all text. + /// + public void DeleteAll () + { + if (Lines == 0) { + return; } - bool DeleteTextForwards () - { - SetWrapModel (); + _selectionStartColumn = 0; + _selectionStartRow = 0; + MoveBottomEndExtend (); + DeleteCharLeft (); + SetNeedsDisplay (); + } - var currentLine = GetCurrentLine (); - if (_currentColumn == currentLine.Count) { - if (_currentRow + 1 == _model.Count) { - UpdateWrapModel (); + /// + public override bool OnKeyUp (Key a) + { + switch (a.KeyCode) { + case KeyCode.Space | KeyCode.CtrlMask: + return true; + } - return true; - } + return base.OnKeyUp (a); + } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + void DoNeededAction () + { + if (NeedsDisplay) { + Adjust (); + } else { + PositionCursor (); + } + } - var removedLines = new List> () { new List (currentLine) }; + bool DeleteTextForwards () + { + SetWrapModel (); - var nextLine = _model.GetLine (_currentRow + 1); + var currentLine = GetCurrentLine (); + if (_currentColumn == currentLine.Count) { + if (_currentRow + 1 == _model.Count) { + UpdateWrapModel (); - removedLines.Add (new List (nextLine)); + return true; + } - _historyText.Add (removedLines, CursorPosition, HistoryText.LineStatus.Removed); + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - currentLine.AddRange (nextLine); - _model.RemoveLine (_currentRow + 1); + var removedLines = new List> () { new List (currentLine) }; - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, - HistoryText.LineStatus.Replaced); + var nextLine = _model.GetLine (_currentRow + 1); - if (_wordWrap) { - _wrapNeeded = true; - } - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, _currentRow - _topRow + 1)); - } else { - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + removedLines.Add (new List (nextLine)); - currentLine.RemoveAt (_currentColumn); + _historyText.Add (removedLines, CursorPosition, HistoryText.LineStatus.Removed); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, - HistoryText.LineStatus.Replaced); + currentLine.AddRange (nextLine); + _model.RemoveLine (_currentRow + 1); - if (_wordWrap) { - _wrapNeeded = true; - } + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + HistoryText.LineStatus.Replaced); - DoSetNeedsDisplay (new Rect (_currentColumn - _leftColumn, _currentRow - _topRow, Frame.Width, _currentRow - _topRow + 1)); + if (_wordWrap) { + _wrapNeeded = true; } + DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, _currentRow - _topRow + 1)); + } else { + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - UpdateWrapModel (); + currentLine.RemoveAt (_currentColumn); - return false; - } + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + HistoryText.LineStatus.Replaced); - private void DoSetNeedsDisplay (Rect rect) - { - if (_wrapNeeded) { - SetNeedsDisplay (); - } else { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //SetNeedsDisplay (rect); - SetNeedsDisplay (); + if (_wordWrap) { + _wrapNeeded = true; } + + DoSetNeedsDisplay (new Rect (_currentColumn - _leftColumn, _currentRow - _topRow, Frame.Width, _currentRow - _topRow + 1)); } - bool DeleteTextBackwards () - { - SetWrapModel (); + UpdateWrapModel (); - if (_currentColumn > 0) { - // Delete backwards - var currentLine = GetCurrentLine (); + return false; + } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + void DoSetNeedsDisplay (Rect rect) + { + if (_wrapNeeded) { + SetNeedsDisplay (); + } else { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //SetNeedsDisplay (rect); + SetNeedsDisplay (); + } + } - currentLine.RemoveAt (_currentColumn - 1); - if (_wordWrap) { - _wrapNeeded = true; - } - _currentColumn--; + bool DeleteTextBackwards () + { + SetWrapModel (); + + if (_currentColumn > 0) { + // Delete backwards + var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - if (_currentColumn < _leftColumn) { - _leftColumn--; - SetNeedsDisplay (); - } else { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width)); - SetNeedsDisplay (); - } - } else { - // Merges the current line with the previous one. - if (_currentRow == 0) - return true; - var prowIdx = _currentRow - 1; - var prevRow = _model.GetLine (prowIdx); + currentLine.RemoveAt (_currentColumn - 1); + if (_wordWrap) { + _wrapNeeded = true; + } + _currentColumn--; - _historyText.Add (new List> () { new List (prevRow) }, CursorPosition); + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + HistoryText.LineStatus.Replaced); - List> removedLines = new List> () { new List (prevRow) }; + if (_currentColumn < _leftColumn) { + _leftColumn--; + SetNeedsDisplay (); + } else { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width)); + SetNeedsDisplay (); + } + } else { + // Merges the current line with the previous one. + if (_currentRow == 0) { + return true; + } + int prowIdx = _currentRow - 1; + var prevRow = _model.GetLine (prowIdx); - removedLines.Add (new List (GetCurrentLine ())); + _historyText.Add (new List> () { new (prevRow) }, CursorPosition); - _historyText.Add (removedLines, new Point (_currentColumn, prowIdx), - HistoryText.LineStatus.Removed); + List> removedLines = new () { new List (prevRow) }; - var prevCount = prevRow.Count; - _model.GetLine (prowIdx).AddRange (GetCurrentLine ()); - _model.RemoveLine (_currentRow); - if (_wordWrap) { - _wrapNeeded = true; - } - _currentRow--; + removedLines.Add (new List (GetCurrentLine ())); - _historyText.Add (new List> () { GetCurrentLine () }, new Point (_currentColumn, prowIdx), - HistoryText.LineStatus.Replaced); + _historyText.Add (removedLines, new Point (_currentColumn, prowIdx), + HistoryText.LineStatus.Removed); - _currentColumn = prevCount; - SetNeedsDisplay (); + int prevCount = prevRow.Count; + _model.GetLine (prowIdx).AddRange (GetCurrentLine ()); + _model.RemoveLine (_currentRow); + if (_wordWrap) { + _wrapNeeded = true; } + _currentRow--; - UpdateWrapModel (); + _historyText.Add (new List> () { GetCurrentLine () }, new Point (_currentColumn, prowIdx), + HistoryText.LineStatus.Replaced); - return false; + _currentColumn = prevCount; + SetNeedsDisplay (); } - bool _copyWithoutSelection; + UpdateWrapModel (); - /// - /// Copy the selected text to the clipboard contents. - /// - public void Copy () - { - SetWrapModel (); - if (_selecting) { - SetClipboard (GetRegion ()); - _copyWithoutSelection = false; - } else { - var currentLine = GetCurrentLine (); - SetClipboard (TextModel.ToString (currentLine)); - _copyWithoutSelection = true; - } - UpdateWrapModel (); - DoNeededAction (); - } + return false; + } - /// - /// Cut the selected text to the clipboard contents. - /// - public void Cut () - { - SetWrapModel (); - SetClipboard (GetRegion ()); - if (!_isReadOnly) { - ClearRegion (); + bool _copyWithoutSelection; - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); - } - UpdateWrapModel (); - _selecting = false; - DoNeededAction (); - OnContentsChanged (); + /// + /// Copy the selected text to the clipboard contents. + /// + public void Copy () + { + SetWrapModel (); + if (_selecting) { + SetClipboard (GetRegion ()); + _copyWithoutSelection = false; + } else { + var currentLine = GetCurrentLine (); + SetClipboard (TextModel.ToString (currentLine)); + _copyWithoutSelection = true; } + UpdateWrapModel (); + DoNeededAction (); + } - /// - /// Paste the clipboard contents into the current selected position. - /// - public void Paste () - { - if (_isReadOnly) { - return; - } + /// + /// Cut the selected text to the clipboard contents. + /// + public void Cut () + { + SetWrapModel (); + SetClipboard (GetRegion ()); + if (!_isReadOnly) { + ClearRegion (); - SetWrapModel (); - var contents = Clipboard.Contents; - if (_copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0) { - var runeList = contents == null ? new List () : TextModel.ToRuneCellList (contents); - var currentLine = GetCurrentLine (); + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); + } + UpdateWrapModel (); + _selecting = false; + DoNeededAction (); + OnContentsChanged (); + } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + /// + /// Paste the clipboard contents into the current selected position. + /// + public void Paste () + { + if (_isReadOnly) { + return; + } - var addedLine = new List> { - new List (currentLine), - runeList - }; + SetWrapModel (); + string? contents = Clipboard.Contents; + if (_copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0) { + var runeList = contents == null ? new List () : TextModel.ToRuneCellList (contents); + var currentLine = GetCurrentLine (); - _historyText.Add (new List> (addedLine), CursorPosition, HistoryText.LineStatus.Added); + _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - _model.AddLine (_currentRow, runeList); - _currentRow++; + var addedLine = new List> { + new List (currentLine), + runeList + }; - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Replaced); + _historyText.Add (new List> (addedLine), CursorPosition, HistoryText.LineStatus.Added); - SetNeedsDisplay (); - OnContentsChanged (); - } else { - if (_selecting) { - ClearRegion (); - } - _copyWithoutSelection = false; - InsertAllText (contents); + _model.AddLine (_currentRow, runeList); + _currentRow++; - if (_selecting) { - _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Original); - } + _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Replaced); - SetNeedsDisplay (); + SetNeedsDisplay (); + OnContentsChanged (); + } else { + if (_selecting) { + ClearRegion (); } - UpdateWrapModel (); - _selecting = false; - DoNeededAction (); - } + _copyWithoutSelection = false; + InsertAllText (contents); - void StartSelecting () - { - if (_shiftSelecting && _selecting) { - return; + if (_selecting) { + _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + HistoryText.LineStatus.Original); } - _shiftSelecting = true; - _selecting = true; - _selectionStartColumn = _currentColumn; - _selectionStartRow = _currentRow; - } - void StopSelecting () - { - _shiftSelecting = false; - _selecting = false; - _isButtonShift = false; + SetNeedsDisplay (); } + UpdateWrapModel (); + _selecting = false; + DoNeededAction (); + } - void ClearSelectedRegion () - { - SetWrapModel (); - if (!_isReadOnly) { - ClearRegion (); - } - UpdateWrapModel (); - _selecting = false; - DoNeededAction (); + void StartSelecting () + { + if (_shiftSelecting && _selecting) { + return; } + _shiftSelecting = true; + _selecting = true; + _selectionStartColumn = _currentColumn; + _selectionStartRow = _currentRow; + } - void MoveUp () - { - if (_currentRow > 0) { - if (_columnTrack == -1) { - _columnTrack = _currentColumn; - } - _currentRow--; - if (_currentRow < _topRow) { - _topRow--; - SetNeedsDisplay (); - } - TrackColumn (); - PositionCursor (); - } - DoNeededAction (); - } + void StopSelecting () + { + _shiftSelecting = false; + _selecting = false; + _isButtonShift = false; + } - void MoveDown () - { - if (_currentRow + 1 < _model.Count) { - if (_columnTrack == -1) { - _columnTrack = _currentColumn; - } - _currentRow++; - if (_currentRow + BottomOffset >= _topRow + Frame.Height) { - _topRow++; - SetNeedsDisplay (); - } - TrackColumn (); - PositionCursor (); - } else if (_currentRow > Frame.Height) { - Adjust (); - } - DoNeededAction (); + void ClearSelectedRegion () + { + SetWrapModel (); + if (!_isReadOnly) { + ClearRegion (); } + UpdateWrapModel (); + _selecting = false; + DoNeededAction (); + } - IEnumerable<(int col, int row, RuneCell rune)> ForwardIterator (int col, int row) - { - if (col < 0 || row < 0) - yield break; - if (row >= _model.Count) - yield break; - var line = GetCurrentLine (); - if (col >= line.Count) - yield break; - - while (row < _model.Count) { - for (int c = col; c < line.Count; c++) { - yield return (c, row, line [c]); - } - col = 0; - row++; - line = GetCurrentLine (); + void MoveUp () + { + if (_currentRow > 0) { + if (_columnTrack == -1) { + _columnTrack = _currentColumn; + } + _currentRow--; + if (_currentRow < _topRow) { + _topRow--; + SetNeedsDisplay (); } - } - - /// - /// Will scroll the to the last line and position the cursor there. - /// - public void MoveEnd () - { - _currentRow = _model.Count - 1; - var line = GetCurrentLine (); - _currentColumn = line.Count; TrackColumn (); PositionCursor (); } + DoNeededAction (); + } - /// - /// Will scroll the to the first line and position the cursor there. - /// - public void MoveHome () - { - _currentRow = 0; - _topRow = 0; - _currentColumn = 0; - _leftColumn = 0; + void MoveDown () + { + if (_currentRow + 1 < _model.Count) { + if (_columnTrack == -1) { + _columnTrack = _currentColumn; + } + _currentRow++; + if (_currentRow + BottomOffset >= _topRow + Frame.Height) { + _topRow++; + SetNeedsDisplay (); + } TrackColumn (); PositionCursor (); - SetNeedsDisplay (); + } else if (_currentRow > Frame.Height) { + Adjust (); } + DoNeededAction (); + } - bool _isButtonShift; - bool _clickWithSelecting; + IEnumerable<(int col, int row, RuneCell rune)> ForwardIterator (int col, int row) + { + if (col < 0 || row < 0) { + yield break; + } + if (row >= _model.Count) { + yield break; + } + var line = GetCurrentLine (); + if (col >= line.Count) { + yield break; + } - /// - public override bool MouseEvent (MouseEvent ev) - { - if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && !ev.Flags.HasFlag (MouseFlags.Button1Pressed) - && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) - && !ev.Flags.HasFlag (MouseFlags.Button1Released) - && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift) - && !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp) - && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) - && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift) - && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) - && !ev.Flags.HasFlag (ContextMenu!.MouseFlags)) { - return false; + while (row < _model.Count) { + for (int c = col; c < line.Count; c++) { + yield return (c, row, line [c]); } + col = 0; + row++; + line = GetCurrentLine (); + } + } - if (!CanFocus) { - return true; - } + /// + /// Will scroll the to the last line and position the cursor there. + /// + public void MoveEnd () + { + _currentRow = _model.Count - 1; + var line = GetCurrentLine (); + _currentColumn = line.Count; + TrackColumn (); + PositionCursor (); + } - if (!HasFocus) { - SetFocus (); - } + /// + /// Will scroll the to the first line and position the cursor there. + /// + public void MoveHome () + { + _currentRow = 0; + _topRow = 0; + _currentColumn = 0; + _leftColumn = 0; + TrackColumn (); + PositionCursor (); + SetNeedsDisplay (); + } - _continuousFind = false; + bool _isButtonShift; + bool _clickWithSelecting; + + /// + public override bool MouseEvent (MouseEvent ev) + { + if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && !ev.Flags.HasFlag (MouseFlags.Button1Pressed) + && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) + && !ev.Flags.HasFlag (MouseFlags.Button1Released) + && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift) + && !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp) + && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) + && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift) + && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) + && !ev.Flags.HasFlag (ContextMenu!.MouseFlags)) { + return false; + } - // Give autocomplete first opportunity to respond to mouse clicks - if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) { - return true; - } + if (!CanFocus) { + return true; + } - if (ev.Flags == MouseFlags.Button1Clicked) { - if (_shiftSelecting && !_isButtonShift) { - StopSelecting (); - } - ProcessMouseClick (ev, out _); - if (Used) { - PositionCursor (); - } else { - SetNeedsDisplay (); - } - _lastWasKill = false; - _columnTrack = _currentColumn; - } else if (ev.Flags == MouseFlags.WheeledDown) { - _lastWasKill = false; - _columnTrack = _currentColumn; - ScrollTo (_topRow + 1); - } else if (ev.Flags == MouseFlags.WheeledUp) { - _lastWasKill = false; - _columnTrack = _currentColumn; - ScrollTo (_topRow - 1); - } else if (ev.Flags == MouseFlags.WheeledRight) { - _lastWasKill = false; - _columnTrack = _currentColumn; - ScrollTo (_leftColumn + 1, false); - } else if (ev.Flags == MouseFlags.WheeledLeft) { - _lastWasKill = false; - _columnTrack = _currentColumn; - ScrollTo (_leftColumn - 1, false); - } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - ProcessMouseClick (ev, out List line); - PositionCursor (); - if (_model.Count > 0 && _shiftSelecting && _selecting) { - if (_currentRow - _topRow + BottomOffset >= Frame.Height - 1 - && _model.Count + BottomOffset > _topRow + _currentRow) { - ScrollTo (_topRow + Frame.Height); - } else if (_topRow > 0 && _currentRow <= _topRow) { - ScrollTo (_topRow - Frame.Height); - } else if (ev.Y >= Frame.Height) { - ScrollTo (_model.Count + BottomOffset); - } else if (ev.Y < 0 && _topRow > 0) { - ScrollTo (0); - } - if (_currentColumn - _leftColumn + RightOffset >= Frame.Width - 1 - && line.Count + RightOffset > _leftColumn + _currentColumn) { - ScrollTo (_leftColumn + Frame.Width, false); - } else if (_leftColumn > 0 && _currentColumn <= _leftColumn) { - ScrollTo (_leftColumn - Frame.Width, false); - } else if (ev.X >= Frame.Width) { - ScrollTo (line.Count + RightOffset, false); - } else if (ev.X < 0 && _leftColumn > 0) { - ScrollTo (0, false); - } - } - _lastWasKill = false; - _columnTrack = _currentColumn; - } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)) { - if (!_shiftSelecting) { - _isButtonShift = true; - StartSelecting (); - } - ProcessMouseClick (ev, out _); - PositionCursor (); - _lastWasKill = false; - _columnTrack = _currentColumn; - } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) { - if (_shiftSelecting) { - _clickWithSelecting = true; - StopSelecting (); - } - ProcessMouseClick (ev, out _); - PositionCursor (); - if (!_selecting) { - StartSelecting (); - } - _lastWasKill = false; - _columnTrack = _currentColumn; - if (Application.MouseGrabView == null) { - Application.GrabMouse (this); - } - } else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) { - Application.UngrabMouse (); - } else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) { - if (ev.Flags.HasFlag (MouseFlags.ButtonShift)) { - if (!_selecting) { - StartSelecting (); - } - } else if (_selecting) { - StopSelecting (); - } - ProcessMouseClick (ev, out List line); - (int col, int row)? newPos; - if (_currentColumn == line.Count || (_currentColumn > 0 && (line [_currentColumn - 1].Rune.Value != ' ' - || line [_currentColumn].Rune.Value == ' '))) { - - newPos = _model.WordBackward (_currentColumn, _currentRow); - if (newPos.HasValue) { - _currentColumn = _currentRow == newPos.Value.row ? newPos.Value.col : 0; - } - } - if (!_selecting) { - StartSelecting (); - } - newPos = _model.WordForward (_currentColumn, _currentRow); - if (newPos != null && newPos.HasValue) { - _currentColumn = _currentRow == newPos.Value.row ? newPos.Value.col : line.Count; - } + if (!HasFocus) { + SetFocus (); + } + + _continuousFind = false; + + // Give autocomplete first opportunity to respond to mouse clicks + if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) { + return true; + } + + if (ev.Flags == MouseFlags.Button1Clicked) { + if (_shiftSelecting && !_isButtonShift) { + StopSelecting (); + } + ProcessMouseClick (ev, out _); + if (Used) { PositionCursor (); - _lastWasKill = false; - _columnTrack = _currentColumn; - } else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)) { - if (_selecting) { - StopSelecting (); + } else { + SetNeedsDisplay (); + } + _lastWasKill = false; + _columnTrack = _currentColumn; + } else if (ev.Flags == MouseFlags.WheeledDown) { + _lastWasKill = false; + _columnTrack = _currentColumn; + ScrollTo (_topRow + 1); + } else if (ev.Flags == MouseFlags.WheeledUp) { + _lastWasKill = false; + _columnTrack = _currentColumn; + ScrollTo (_topRow - 1); + } else if (ev.Flags == MouseFlags.WheeledRight) { + _lastWasKill = false; + _columnTrack = _currentColumn; + ScrollTo (_leftColumn + 1, false); + } else if (ev.Flags == MouseFlags.WheeledLeft) { + _lastWasKill = false; + _columnTrack = _currentColumn; + ScrollTo (_leftColumn - 1, false); + } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + ProcessMouseClick (ev, out var line); + PositionCursor (); + if (_model.Count > 0 && _shiftSelecting && _selecting) { + if (_currentRow - _topRow + BottomOffset >= Frame.Height - 1 + && _model.Count + BottomOffset > _topRow + _currentRow) { + ScrollTo (_topRow + Frame.Height); + } else if (_topRow > 0 && _currentRow <= _topRow) { + ScrollTo (_topRow - Frame.Height); + } else if (ev.Y >= Frame.Height) { + ScrollTo (_model.Count + BottomOffset); + } else if (ev.Y < 0 && _topRow > 0) { + ScrollTo (0); + } + if (_currentColumn - _leftColumn + RightOffset >= Frame.Width - 1 + && line.Count + RightOffset > _leftColumn + _currentColumn) { + ScrollTo (_leftColumn + Frame.Width, false); + } else if (_leftColumn > 0 && _currentColumn <= _leftColumn) { + ScrollTo (_leftColumn - Frame.Width, false); + } else if (ev.X >= Frame.Width) { + ScrollTo (line.Count + RightOffset, false); + } else if (ev.X < 0 && _leftColumn > 0) { + ScrollTo (0, false); } - ProcessMouseClick (ev, out List line); - _currentColumn = 0; + } + _lastWasKill = false; + _columnTrack = _currentColumn; + } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)) { + if (!_shiftSelecting) { + _isButtonShift = true; + StartSelecting (); + } + ProcessMouseClick (ev, out _); + PositionCursor (); + _lastWasKill = false; + _columnTrack = _currentColumn; + } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) { + if (_shiftSelecting) { + _clickWithSelecting = true; + StopSelecting (); + } + ProcessMouseClick (ev, out _); + PositionCursor (); + if (!_selecting) { + StartSelecting (); + } + _lastWasKill = false; + _columnTrack = _currentColumn; + if (Application.MouseGrabView == null) { + Application.GrabMouse (this); + } + } else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) { + Application.UngrabMouse (); + } else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) { + if (ev.Flags.HasFlag (MouseFlags.ButtonShift)) { if (!_selecting) { StartSelecting (); } - _currentColumn = line.Count; - PositionCursor (); - _lastWasKill = false; - _columnTrack = _currentColumn; - } else if (ev.Flags == ContextMenu!.MouseFlags) { - ContextMenu.Position = new Point (ev.X + 2, ev.Y + 2); - ShowContextMenu (); + } else if (_selecting) { + StopSelecting (); } + ProcessMouseClick (ev, out var line); + (int col, int row)? newPos; + if (_currentColumn == line.Count || _currentColumn > 0 && (line [_currentColumn - 1].Rune.Value != ' ' + || line [_currentColumn].Rune.Value == ' ')) { - return true; - } - - void ProcessMouseClick (MouseEvent ev, out List line) - { - List? r = null; - if (_model.Count > 0) { - var maxCursorPositionableLine = Math.Max ((_model.Count - 1) - _topRow, 0); - if (Math.Max (ev.Y, 0) > maxCursorPositionableLine) { - _currentRow = maxCursorPositionableLine + _topRow; - } else { - _currentRow = Math.Max (ev.Y + _topRow, 0); - } - r = GetCurrentLine (); - var idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.X, 0), TabWidth); - if (idx - _leftColumn >= r.Count + RightOffset) { - _currentColumn = Math.Max (r.Count - _leftColumn + RightOffset, 0); - } else { - _currentColumn = idx + _leftColumn; + newPos = _model.WordBackward (_currentColumn, _currentRow); + if (newPos.HasValue) { + _currentColumn = _currentRow == newPos.Value.row ? newPos.Value.col : 0; } } - - line = r!; + if (!_selecting) { + StartSelecting (); + } + newPos = _model.WordForward (_currentColumn, _currentRow); + if (newPos != null && newPos.HasValue) { + _currentColumn = _currentRow == newPos.Value.row ? newPos.Value.col : line.Count; + } + PositionCursor (); + _lastWasKill = false; + _columnTrack = _currentColumn; + } else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)) { + if (_selecting) { + StopSelecting (); + } + ProcessMouseClick (ev, out var line); + _currentColumn = 0; + if (!_selecting) { + StartSelecting (); + } + _currentColumn = line.Count; + PositionCursor (); + _lastWasKill = false; + _columnTrack = _currentColumn; + } else if (ev.Flags == ContextMenu!.MouseFlags) { + ContextMenu.Position = new Point (ev.X + 2, ev.Y + 2); + ShowContextMenu (); } - /// - /// Allows clearing the items updating the original text. - /// - public void ClearHistoryChanges () - { - _historyText?.Clear (Text); - } + return true; } - /// - /// Renders an overlay on another view at a given point that allows selecting - /// from a range of 'autocomplete' options. - /// An implementation on a TextView. - /// - public class TextViewAutocomplete : PopupAutocomplete { - - /// - protected override void DeleteTextBackwards () - { - ((TextView)HostControl).DeleteCharLeft (); - } - - /// - protected override void InsertText (string accepted) - { - ((TextView)HostControl).InsertText (accepted); + void ProcessMouseClick (MouseEvent ev, out List line) + { + List? r = null; + if (_model.Count > 0) { + int maxCursorPositionableLine = Math.Max (_model.Count - 1 - _topRow, 0); + if (Math.Max (ev.Y, 0) > maxCursorPositionableLine) { + _currentRow = maxCursorPositionableLine + _topRow; + } else { + _currentRow = Math.Max (ev.Y + _topRow, 0); + } + r = GetCurrentLine (); + int idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.X, 0), TabWidth); + if (idx - _leftColumn >= r.Count + RightOffset) { + _currentColumn = Math.Max (r.Count - _leftColumn + RightOffset, 0); + } else { + _currentColumn = idx + _leftColumn; + } } - /// - protected override void SetCursorPosition (int column) - { - ((TextView)HostControl).CursorPosition = new Point (column, ((TextView)HostControl).CurrentRow); - } + line = r!; } + + /// + /// Allows clearing the items updating the original text. + /// + public void ClearHistoryChanges () => _historyText?.Clear (Text); } + +/// +/// Renders an overlay on another view at a given point that allows selecting +/// from a range of 'autocomplete' options. +/// An implementation on a TextView. +/// +public class TextViewAutocomplete : PopupAutocomplete { + /// + protected override void DeleteTextBackwards () => ((TextView)HostControl).DeleteCharLeft (); + + /// + protected override void InsertText (string accepted) => ((TextView)HostControl).InsertText (accepted); + + /// + protected override void SetCursorPosition (int column) => ((TextView)HostControl).CursorPosition = new Point (column, ((TextView)HostControl).CurrentRow); +} \ No newline at end of file diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index 173edd2ffb..ee45f1ad1e 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -87,7 +87,7 @@ void Initialize (TimeSpan time, bool isShort = false) AddCommand (Command.Right, () => MoveRight ()); // Default keybindings for this view - KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft); diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index 483309edac..4ab1790695 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -466,9 +466,9 @@ void SetupScrollBar () } - void TableViewKeyPress (object sender, Key e) - { - if (e.KeyCode == KeyCode.DeleteChar) { + private void TableViewKeyPress (object sender, Key e) + { + if (e.KeyCode == KeyCode.Delete) { if (tableView.FullRowSelect) { // Delete button deletes all rows when in full row mode diff --git a/UICatalog/Scenarios/InteractiveTree.cs b/UICatalog/Scenarios/InteractiveTree.cs index 540a0e70ae..2dc931806c 100644 --- a/UICatalog/Scenarios/InteractiveTree.cs +++ b/UICatalog/Scenarios/InteractiveTree.cs @@ -45,7 +45,7 @@ public override void Setup () private void TreeView_KeyPress (object sender, Key obj) { - if (obj.KeyCode == KeyCode.DeleteChar) { + if (obj.KeyCode == KeyCode.Delete) { var toDelete = treeView.SelectedObject; diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 628d6d8d03..96b27d7f31 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -154,7 +154,7 @@ private void SetupScrollBar () private void TableViewKeyPress (object sender, Key e) { - if (e.KeyCode == KeyCode.DeleteChar) { + if (e.KeyCode == KeyCode.Delete) { // set all selected cells to null foreach (var pt in listColView.GetAllSelectedCells ()) { diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 27869da737..7d0ccc9ece 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -391,7 +391,7 @@ private void TableViewKeyPress (object sender, Key e) return; } - if (e.KeyCode == KeyCode.DeleteChar) { + if (e.KeyCode == KeyCode.Delete) { if (tableView.FullRowSelect) { // Delete button deletes all rows when in full row mode diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index 229d580249..36e79900b8 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -5,12 +5,12 @@ using Terminal.Gui; using Terminal.Gui.ConsoleDrivers; -namespace UICatalog.Scenarios; +namespace UICatalog.Scenarios; [ScenarioMetadata ("VkeyPacketSimulator", "Simulates the Virtual Key Packet")] [ScenarioCategory ("Mouse and Keyboard")] public class VkeyPacketSimulator : Scenario { - List _keyboardStrokes = new (); + List _keyboardStrokes = new (); bool _outputStarted = false; bool _wasUnknown = false; static ManualResetEventSlim _stopOutput = new (false); @@ -94,54 +94,55 @@ public override void Setup () // Detect unknown keys and reject them. tvOutput.KeyDown += (s, e) => { - //System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.Key}"); - //e.Handled = true; - if (e == Key.Empty) { + //System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.KeyCode}"); + if (e.NoAlt.NoCtrl.NoShift == KeyCode.Null) { _wasUnknown = true; + e.Handled = true; + return; } - }; - tvOutput.KeyUp += (s, e) => { //System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}"); - if (_outputStarted && _keyboardStrokes.Count > 0) { - //// TODO: Tig: I don't understand what this is trying to do - //if (!tvOutput.ProcessKeyDown (e)) { - // Application.Invoke (() => { - // MessageBox.Query ("Keys", $"'{KeyEventArgs.ToString (e.ConsoleDriverKey, MenuBar.ShortcutDelimiter)}' pressed!", "Ok"); - // }); - //} - e.Handled = true; - _stopOutput.Set (); + if (_outputStarted) { + // If the key wasn't handled by the TextView will popup a Dialog with the keys pressed. + var handled = tvOutput.OnInvokingKeyBindings (e); + if (handled == null || handled == false) { + if (!tvOutput.OnProcessKeyDown (e)) { + Application.Invoke (() => MessageBox.Query ("Keys", $"'{Key.ToString (e.KeyCode, MenuBar.ShortcutDelimiter)}' pressed!", "Ok")); + } + } } - //System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}"); + e.Handled = true; + _stopOutput.Set (); }; Win.Add (tvOutput); - Key unknownChar = null; + tvInput.KeyDown += (s, e) => { + //System.Diagnostics.Debug.WriteLine ($"Input - KeyDown: {e.KeyCode.Key}"); + if (e.KeyCode == Key.Empty) { + _wasUnknown = true; + e.Handled = true; + } else { + _wasUnknown = false; + } + }; - tvInput.KeyUp += (s, e) => { - //System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.Key}"); - //var ke = e; + tvInput.InvokingKeyBindings += (s, e) => { + var ev = e; + //System.Diagnostics.Debug.WriteLine ($"Input - KeyPress: {ev}"); + //System.Diagnostics.Debug.WriteLine ($"Input - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}"); - if (e == Key.Empty) { + if (!e.IsValid) { _wasUnknown = true; e.Handled = true; return; } - if (_wasUnknown && _keyboardStrokes.Count == 1) { - _wasUnknown = false; - } else if (_wasUnknown && char.IsLetter ((char)e.KeyCode)) { - _wasUnknown = false; - } - if (_keyboardStrokes.Count == 0) { - AddKeyboardStrokes (e); - } else { - _keyboardStrokes.Insert (0, 0); - } - if (_wasUnknown && (int)e.KeyCode - (int)(e.KeyCode & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask)) != 0) { - unknownChar = e; - } + + _keyboardStrokes.Add (e.KeyCode); + }; + + tvInput.KeyUp += (s, e) => { + //System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.Key}"); e.Handled = true; if (!_wasUnknown && _keyboardStrokes.Count > 0) { _outputStarted = true; @@ -152,38 +153,31 @@ public override void Setup () Task.Run (() => { while (_outputStarted) { try { - var mod = new ConsoleModifiers (); - if (e.KeyCode.HasFlag (KeyCode.ShiftMask)) { - mod |= ConsoleModifiers.Shift; - } - if (e.KeyCode.HasFlag (KeyCode.AltMask)) { - mod |= ConsoleModifiers.Alt; - } - if (e.KeyCode.HasFlag (KeyCode.CtrlMask)) { - mod |= ConsoleModifiers.Control; - } - for (int i = 0; i < _keyboardStrokes.Count; i++) { - var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey ((uint)_keyboardStrokes [i], mod, out _); - Application.Driver.SendKeys (consoleKeyInfo.KeyChar, ConsoleKey.Packet, mod.HasFlag (ConsoleModifiers.Shift), - mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control)); + while (_keyboardStrokes.Count > 0) { + if (_keyboardStrokes [0] == KeyCode.Null) { + continue; + } + var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (_keyboardStrokes [0]); + var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo); + Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift), + consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)); + + _stopOutput.Wait (); + _stopOutput.Reset (); + _keyboardStrokes.RemoveAt (0); + Application.Invoke (() => { + tvOutput.ReadOnly = true; + tvInput.SetFocus (); + }); } - //} + _outputStarted = false; + } catch (Exception) { Application.Invoke (() => { MessageBox.ErrorQuery ("Error", "Couldn't send the keystrokes!", "Ok"); Application.RequestStop (); }); } - _stopOutput.Wait (); - _stopOutput.Reset (); - _keyboardStrokes.RemoveAt (0); - if (_keyboardStrokes.Count == 0) { - _outputStarted = false; - Application.Invoke (() => { - tvOutput.ReadOnly = true; - tvInput.SetFocus (); - }); - } } //System.Diagnostics.Debug.WriteLine ($"_outputStarted: {_outputStarted}"); }); @@ -214,12 +208,4 @@ void Win_LayoutComplete (object sender, LayoutEventArgs obj) Win.LayoutComplete += Win_LayoutComplete; } - - void AddKeyboardStrokes (Key e) - { - int keyChar = (int)e.KeyCode; - int mK = (int)(e.KeyCode & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask)); - keyChar &= ~mK; - _keyboardStrokes.Add (keyChar); - } -} \ No newline at end of file +} diff --git a/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs b/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs index 26cb60ec5a..d05e7d7b6e 100644 --- a/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs @@ -1,46 +1,57 @@ using System; -using System.Collections; using System.Collections.Generic; using Xunit; +using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; namespace Terminal.Gui.ConsoleDrivers; public class ConsoleKeyMappingTests { + +#if ENABLE_VK_PACKET_NON_WINDOWS + // This test (and the GetConsoleKeyInfoFromKeyCode API) are bogus. They make no sense outside of + // the context of Windows and knowing they keyboard layout. They should be removed. [Theory] - [InlineData ((KeyCode)'a' | KeyCode.ShiftMask, ConsoleKey.A, KeyCode.A, 'A')] - [InlineData ((KeyCode)'A', ConsoleKey.A, (KeyCode)'a', 'a')] - [InlineData ((KeyCode)'à' | KeyCode.ShiftMask, ConsoleKey.A, (KeyCode)'À', 'À')] - [InlineData ((KeyCode)'À', ConsoleKey.A, (KeyCode)'à', 'à')] - [InlineData ((KeyCode)'ü' | KeyCode.ShiftMask, ConsoleKey.U, (KeyCode)'Ü', 'Ü')] - [InlineData ((KeyCode)'Ü', ConsoleKey.U, (KeyCode)'ü', 'ü')] - [InlineData ((KeyCode)'ý' | KeyCode.ShiftMask, ConsoleKey.Y, (KeyCode)'Ý', 'Ý')] - [InlineData ((KeyCode)'Ý', ConsoleKey.Y, (KeyCode)'ý', 'ý')] + [InlineData (KeyCode.A | KeyCode.ShiftMask, ConsoleKey.A, KeyCode.A, 'A')] + [InlineData ((KeyCode)'a', ConsoleKey.A, (KeyCode)'a', 'a')] + [InlineData ((KeyCode)'À' | KeyCode.ShiftMask, ConsoleKey.A, (KeyCode)'À', 'À')] + [InlineData ((KeyCode)'à', ConsoleKey.A, (KeyCode)'à', 'à')] + [InlineData ((KeyCode)'Ü' | KeyCode.ShiftMask, ConsoleKey.U, (KeyCode)'Ü', 'Ü')] + [InlineData ((KeyCode)'ü', ConsoleKey.U, (KeyCode)'ü', 'ü')] + [InlineData ((KeyCode)'Ý' | KeyCode.ShiftMask, ConsoleKey.Y, (KeyCode)'Ý', 'Ý')] + [InlineData ((KeyCode)'ý', ConsoleKey.Y, (KeyCode)'ý', 'ý')] [InlineData ((KeyCode)'!' | KeyCode.ShiftMask, ConsoleKey.D1, (KeyCode)'!', '!')] + [InlineData (KeyCode.D1 | KeyCode.ShiftMask, ConsoleKey.D1, (KeyCode)'!', '!')] [InlineData (KeyCode.D1, ConsoleKey.D1, KeyCode.D1, '1')] - [InlineData ((KeyCode)'/' | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')] + [InlineData ((KeyCode)'/' | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')] // BUGBUG: This is incorrect for ENG keyboards. Shift-7 should be &. + [InlineData (KeyCode.D7 | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')] [InlineData (KeyCode.D7, ConsoleKey.D7, KeyCode.D7, '7')] + [InlineData ((KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, ConsoleKey.D7, (KeyCode)'{', '{')] + [InlineData ((KeyCode)'?' | KeyCode.ShiftMask, ConsoleKey.Oem4, (KeyCode)'?', '?')] + [InlineData ((KeyCode)'\'', ConsoleKey.Oem4, (KeyCode)'\'', '\'')] [InlineData (KeyCode.PageDown | KeyCode.ShiftMask, ConsoleKey.PageDown, KeyCode.Null, '\0')] [InlineData (KeyCode.PageDown, ConsoleKey.PageDown, KeyCode.Null, '\0')] - - public void TestIfEqual (KeyCode key, ConsoleKey expectedConsoleKey, KeyCode expectedKey, char expectedChar) + [InlineData ((KeyCode)'q', ConsoleKey.Q, (KeyCode)'q', 'q')] + [InlineData (KeyCode.F2, ConsoleKey.F2, KeyCode.Null, '\0')] + [InlineData ((KeyCode)'英', ConsoleKey.None, (KeyCode)'英', '英')] + public void GetConsoleKeyInfoFromKeyCode_Tests (KeyCode keyCode, ConsoleKey expectedConsoleKey, KeyCode expectedKeyCode, char expectedKeyChar) { - var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (key); + var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (keyCode); Assert.Equal (consoleKeyInfo.Key, expectedConsoleKey); - Assert.Equal ((char)expectedKey, expectedChar); - Assert.Equal (consoleKeyInfo.KeyChar, expectedChar); + Assert.Equal ((char)expectedKeyCode, expectedKeyChar); + Assert.Equal (consoleKeyInfo.KeyChar, expectedKeyChar); } static object packetLock = new object (); /// /// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'. - /// These are indicated with the wVirtualKeyCode of 231. When we see this code + /// These are indicated with the wVirtualKeyCode of 231 (VK_PACKET). When we see this code /// then we need to look to the unicode character (UnicodeChar) instead of the key /// when telling the rest of the framework what button was pressed. For full details /// see: https://github.com/gui-cs/Terminal.Gui/issues/2008 /// [Theory] [AutoInitShutdown] - [ClassData (typeof (PacketTest))] + [MemberData (nameof (VKPacket))] public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, KeyCode expectedRemapping, uint expectedVirtualKey, uint expectedScanCode) { @@ -48,20 +59,17 @@ public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool cont Application._forceFakeConsole = true; Application.Init (); - var modifiers = new ConsoleModifiers (); - if (shift) { - modifiers |= ConsoleModifiers.Shift; - } - if (alt) { - modifiers |= ConsoleModifiers.Alt; - } - if (control) { - modifiers |= ConsoleModifiers.Control; - } - ConsoleKeyInfo consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode); + ConsoleKeyInfo originalConsoleKeyInfo = new ConsoleKeyInfo ((char)unicodeCharacter, (ConsoleKey)initialVirtualKey, shift, alt, control); + var encodedChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (originalConsoleKeyInfo); + ConsoleKeyInfo packetConsoleKeyInfo = new ConsoleKeyInfo (encodedChar, ConsoleKey.Packet, shift, alt, control); + ConsoleKeyInfo consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (packetConsoleKeyInfo); - Assert.Equal ((uint)consoleKeyInfo.Key, initialVirtualKey); + Assert.Equal (originalConsoleKeyInfo, consoleKeyInfo); + var modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control); + var scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo); + + Assert.Equal ((uint)consoleKeyInfo.Key, initialVirtualKey); if (scanCode > 0 && consoleKeyInfo.KeyChar == 0) { Assert.Equal (0, (double)consoleKeyInfo.KeyChar); @@ -85,7 +93,8 @@ public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool cont Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { - Application.Driver.SendKeys (consoleKeyInfo.KeyChar, ConsoleKey.Packet, shift, alt, control); + var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo); + Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control); } }; Application.Run (); @@ -93,126 +102,384 @@ public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool cont } } - public class PacketTest : IEnumerable, IEnumerable { - public IEnumerator GetEnumerator () - { - lock (packetLock) { - // unicodeCharacter, shift, alt, control, initialVirtualKey, initialScanCode, expectedRemapping, expectedVirtualKey, expectedScanCode - yield return new object [] { 'a', false, false, false, 'A', 30, KeyCode.A, 'A', 30 }; - yield return new object [] { 'A', true, false, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask, 'A', 30 }; - yield return new object [] { 'A', true, true, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, 'A', 30 }; - yield return new object [] { 'A', true, true, true, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'A', 30 }; - yield return new object [] { 'z', false, false, false, 'Z', 44, KeyCode.Z, 'Z', 44 }; - yield return new object [] { 'Z', true, false, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask, 'Z', 44 }; - yield return new object [] { 'Z', true, true, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask, 'Z', 44 }; - yield return new object [] { 'Z', true, true, true, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'Z', 44 }; - yield return new object [] { '英', false, false, false, '\0', 0, (KeyCode)'英', '\0', 0 }; - yield return new object [] { '英', true, false, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask, '\0', 0 }; - yield return new object [] { '英', true, true, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask, '\0', 0 }; - yield return new object [] { '英', true, true, true, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0', 0 }; - yield return new object [] { '+', false, false, false, 187, 26, (KeyCode)'+', 187, 26 }; - yield return new object [] { '*', true, false, false, 187, 26, (KeyCode)'*' | KeyCode.ShiftMask, 187, 26 }; - yield return new object [] { '+', true, true, false, 187, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask, 187, 26 }; - yield return new object [] { '+', true, true, true, 187, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 187, 26 }; - yield return new object [] { '1', false, false, false, '1', 2, KeyCode.D1, '1', 2 }; - yield return new object [] { '!', true, false, false, '1', 2, (KeyCode)'!' | KeyCode.ShiftMask, '1', 2 }; - yield return new object [] { '1', true, true, false, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask, '1', 2 }; - yield return new object [] { '1', true, true, true, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 }; - yield return new object [] { '1', false, true, true, '1', 2, KeyCode.D1 | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 }; - yield return new object [] { '2', false, false, false, '2', 3, KeyCode.D2, '2', 3 }; - yield return new object [] { '"', true, false, false, '2', 3, (KeyCode)'"' | KeyCode.ShiftMask, '2', 3 }; - yield return new object [] { '2', true, true, false, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask, '2', 3 }; - yield return new object [] { '2', true, true, true, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 }; - yield return new object [] { '@', false, true, true, '2', 3, (KeyCode)'@' | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 }; - yield return new object [] { '3', false, false, false, '3', 4, KeyCode.D3, '3', 4 }; - yield return new object [] { '#', true, false, false, '3', 4, (KeyCode)'#' | KeyCode.ShiftMask, '3', 4 }; - yield return new object [] { '3', true, true, false, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask, '3', 4 }; - yield return new object [] { '3', true, true, true, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 }; - yield return new object [] { '£', false, true, true, '3', 4, (KeyCode)'£' | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 }; - yield return new object [] { '4', false, false, false, '4', 5, KeyCode.D4, '4', 5 }; - yield return new object [] { '$', true, false, false, '4', 5, (KeyCode)'$' | KeyCode.ShiftMask, '4', 5 }; - yield return new object [] { '4', true, true, false, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask, '4', 5 }; - yield return new object [] { '4', true, true, true, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 }; - yield return new object [] { '§', false, true, true, '4', 5, (KeyCode)'§' | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 }; - yield return new object [] { '5', false, false, false, '5', 6, KeyCode.D5, '5', 6 }; - yield return new object [] { '%', true, false, false, '5', 6, (KeyCode)'%' | KeyCode.ShiftMask, '5', 6 }; - yield return new object [] { '5', true, true, false, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask, '5', 6 }; - yield return new object [] { '5', true, true, true, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 }; - yield return new object [] { '€', false, true, true, '5', 6, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 }; - yield return new object [] { '6', false, false, false, '6', 7, KeyCode.D6, '6', 7 }; - yield return new object [] { '&', true, false, false, '6', 7, (KeyCode)'&' | KeyCode.ShiftMask, '6', 7 }; - yield return new object [] { '6', true, true, false, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask, '6', 7 }; - yield return new object [] { '6', true, true, true, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 }; - yield return new object [] { '6', false, true, true, '6', 7, KeyCode.D6 | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 }; - yield return new object [] { '7', false, false, false, '7', 8, KeyCode.D7, '7', 8 }; - yield return new object [] { '/', true, false, false, '7', 8, (KeyCode)'/' | KeyCode.ShiftMask, '7', 8 }; - yield return new object [] { '7', true, true, false, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask, '7', 8 }; - yield return new object [] { '7', true, true, true, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 }; - yield return new object [] { '{', false, true, true, '7', 8, (KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 }; - yield return new object [] { '8', false, false, false, '8', 9, KeyCode.D8, '8', 9 }; - yield return new object [] { '(', true, false, false, '8', 9, (KeyCode)'(' | KeyCode.ShiftMask, '8', 9 }; - yield return new object [] { '8', true, true, false, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask, '8', 9 }; - yield return new object [] { '8', true, true, true, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 }; - yield return new object [] { '[', false, true, true, '8', 9, (KeyCode)'[' | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 }; - yield return new object [] { '9', false, false, false, '9', 10, KeyCode.D9, '9', 10 }; - yield return new object [] { ')', true, false, false, '9', 10, (KeyCode)')' | KeyCode.ShiftMask, '9', 10 }; - yield return new object [] { '9', true, true, false, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask, '9', 10 }; - yield return new object [] { '9', true, true, true, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 }; - yield return new object [] { ']', false, true, true, '9', 10, (KeyCode)']' | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 }; - yield return new object [] { '0', false, false, false, '0', 11, KeyCode.D0, '0', 11 }; - yield return new object [] { '=', true, false, false, '0', 11, (KeyCode)'=' | KeyCode.ShiftMask, '0', 11 }; - yield return new object [] { '0', true, true, false, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask, '0', 11 }; - yield return new object [] { '0', true, true, true, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 }; - yield return new object [] { '}', false, true, true, '0', 11, (KeyCode)'}' | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 }; - yield return new object [] { '\'', false, false, false, 219, 12, (KeyCode)'\'', 219, 12 }; - yield return new object [] { '?', true, false, false, 219, 12, (KeyCode)'?' | KeyCode.ShiftMask, 219, 12 }; - yield return new object [] { '\'', true, true, false, 219, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask, 219, 12 }; - yield return new object [] { '\'', true, true, true, 219, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 219, 12 }; - yield return new object [] { '«', false, false, false, 221, 13, (KeyCode)'«', 221, 13 }; - yield return new object [] { '»', true, false, false, 221, 13, (KeyCode)'»' | KeyCode.ShiftMask, 221, 13 }; - yield return new object [] { '«', true, true, false, 221, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask, 221, 13 }; - yield return new object [] { '«', true, true, true, 221, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 221, 13 }; - yield return new object [] { 'á', false, false, false, 'A', 30, (KeyCode)'á', 'A', 30 }; - yield return new object [] { 'Á', true, false, false, 'A', 30, (KeyCode)'Á' | KeyCode.ShiftMask, 'A', 30 }; - yield return new object [] { 'à', false, false, false, 'A', 30, (KeyCode)'à', 'A', 30 }; - yield return new object [] { 'À', true, false, false, 'A', 30, (KeyCode)'À' | KeyCode.ShiftMask, 'A', 30 }; - yield return new object [] { 'é', false, false, false, 'E', 18, (KeyCode)'é', 'E', 18 }; - yield return new object [] { 'É', true, false, false, 'E', 18, (KeyCode)'É' | KeyCode.ShiftMask, 'E', 18 }; - yield return new object [] { 'è', false, false, false, 'E', 18, (KeyCode)'è', 'E', 18 }; - yield return new object [] { 'È', true, false, false, 'E', 18, (KeyCode)'È' | KeyCode.ShiftMask, 'E', 18 }; - yield return new object [] { 'í', false, false, false, 'I', 23, (KeyCode)'í', 'I', 23 }; - yield return new object [] { 'Í', true, false, false, 'I', 23, (KeyCode)'Í' | KeyCode.ShiftMask, 'I', 23 }; - yield return new object [] { 'ì', false, false, false, 'I', 23, (KeyCode)'ì', 'I', 23 }; - yield return new object [] { 'Ì', true, false, false, 'I', 23, (KeyCode)'Ì' | KeyCode.ShiftMask, 'I', 23 }; - yield return new object [] { 'ó', false, false, false, 'O', 24, (KeyCode)'ó', 'O', 24 }; - yield return new object [] { 'Ó', true, false, false, 'O', 24, (KeyCode)'Ó' | KeyCode.ShiftMask, 'O', 24 }; - yield return new object [] { 'ò', false, false, false, 'O', 24, (KeyCode)'ò', 'O', 24 }; - yield return new object [] { 'Ò', true, false, false, 'O', 24, (KeyCode)'Ò' | KeyCode.ShiftMask, 'O', 24 }; - yield return new object [] { 'ú', false, false, false, 'U', 22, (KeyCode)'ú', 'U', 22 }; - yield return new object [] { 'Ú', true, false, false, 'U', 22, (KeyCode)'Ú' | KeyCode.ShiftMask, 'U', 22 }; - yield return new object [] { 'ù', false, false, false, 'U', 22, (KeyCode)'ù', 'U', 22 }; - yield return new object [] { 'Ù', true, false, false, 'U', 22, (KeyCode)'Ù' | KeyCode.ShiftMask, 'U', 22 }; - yield return new object [] { 'ö', false, false, false, 'O', 24, (KeyCode)'ö', 'O', 24 }; - yield return new object [] { 'Ö', true, false, false, 'O', 24, (KeyCode)'Ö' | KeyCode.ShiftMask, 'O', 24 }; - yield return new object [] { '<', false, false, false, 226, 86, (KeyCode)'<', 226, 86 }; - yield return new object [] { '>', true, false, false, 226, 86, (KeyCode)'>' | KeyCode.ShiftMask, 226, 86 }; - yield return new object [] { '<', true, true, false, 226, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask, 226, 86 }; - yield return new object [] { '<', true, true, true, 226, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 226, 86 }; - yield return new object [] { 'ç', false, false, false, 192, 39, (KeyCode)'ç', 192, 39 }; - yield return new object [] { 'Ç', true, false, false, 192, 39, (KeyCode)'Ç' | KeyCode.ShiftMask, 192, 39 }; - yield return new object [] { 'ç', true, true, false, 192, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask, 192, 39 }; - yield return new object [] { 'ç', true, true, true, 192, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 192, 39 }; - yield return new object [] { '¨', false, true, true, 187, 26, (KeyCode)'¨' | KeyCode.AltMask | KeyCode.CtrlMask, 187, 26 }; - yield return new object [] { KeyCode.PageUp, false, false, false, 33, 73, KeyCode.Null, 33, 73 }; - yield return new object [] { KeyCode.PageUp, true, false, false, 33, 73, KeyCode.Null | KeyCode.ShiftMask, 33, 73 }; - yield return new object [] { KeyCode.PageUp, true, true, false, 33, 73, KeyCode.Null | KeyCode.ShiftMask | KeyCode.AltMask, 33, 73 }; - yield return new object [] { KeyCode.PageUp, true, true, true, 33, 73, KeyCode.Null | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 33, 73 }; - } + public static IEnumerable VKPacket () + { + lock (packetLock) { + // unicodeCharacter, shift, alt, control, initialVirtualKey, initialScanCode, expectedRemapping, expectedVirtualKey, expectedScanCode + yield return new object [] { 'a', false, false, false, 'A', 30, KeyCode.A, 'A', 30 }; + yield return new object [] { 'A', true, false, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask, 'A', 30 }; + yield return new object [] { 'A', true, true, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, 'A', 30 }; + yield return new object [] { 'A', true, true, true, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'A', 30 }; + yield return new object [] { 'z', false, false, false, 'Z', 44, KeyCode.Z, 'Z', 44 }; + yield return new object [] { 'Z', true, false, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask, 'Z', 44 }; + yield return new object [] { 'Z', true, true, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask, 'Z', 44 }; + yield return new object [] { 'Z', true, true, true, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'Z', 44 }; + yield return new object [] { '英', false, false, false, '\0', 0, (KeyCode)'英', '\0', 0 }; + yield return new object [] { '英', true, false, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask, '\0', 0 }; + yield return new object [] { '英', true, true, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask, '\0', 0 }; + yield return new object [] { '英', true, true, true, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0', 0 }; + yield return new object [] { '+', false, false, false, VK.OEM_PLUS, 26, (KeyCode)'+', VK.OEM_PLUS, 26 }; + yield return new object [] { '*', true, false, false, VK.OEM_PLUS, 26, (KeyCode)'*' | KeyCode.ShiftMask, VK.OEM_PLUS, 26 }; + yield return new object [] { '+', true, true, false, VK.OEM_PLUS, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_PLUS, 26 }; + yield return new object [] { '+', true, true, true, VK.OEM_PLUS, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_PLUS, 26 }; + yield return new object [] { '1', false, false, false, '1', 2, KeyCode.D1, '1', 2 }; + yield return new object [] { '!', true, false, false, '1', 2, (KeyCode)'!' | KeyCode.ShiftMask, '1', 2 }; + yield return new object [] { '1', true, true, false, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask, '1', 2 }; + yield return new object [] { '1', true, true, true, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 }; + yield return new object [] { '1', false, true, true, '1', 2, KeyCode.D1 | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 }; + yield return new object [] { '2', false, false, false, '2', 3, KeyCode.D2, '2', 3 }; + yield return new object [] { '"', true, false, false, '2', 3, (KeyCode)'"' | KeyCode.ShiftMask, '2', 3 }; + yield return new object [] { '2', true, true, false, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask, '2', 3 }; + yield return new object [] { '2', true, true, true, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 }; + yield return new object [] { '@', false, true, true, '2', 3, (KeyCode)'@' | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 }; + yield return new object [] { '3', false, false, false, '3', 4, KeyCode.D3, '3', 4 }; + yield return new object [] { '#', true, false, false, '3', 4, (KeyCode)'#' | KeyCode.ShiftMask, '3', 4 }; + yield return new object [] { '3', true, true, false, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask, '3', 4 }; + yield return new object [] { '3', true, true, true, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 }; + yield return new object [] { '£', false, true, true, '3', 4, (KeyCode)'£' | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 }; + yield return new object [] { '4', false, false, false, '4', 5, KeyCode.D4, '4', 5 }; + yield return new object [] { '$', true, false, false, '4', 5, (KeyCode)'$' | KeyCode.ShiftMask, '4', 5 }; + yield return new object [] { '4', true, true, false, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask, '4', 5 }; + yield return new object [] { '4', true, true, true, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 }; + yield return new object [] { '§', false, true, true, '4', 5, (KeyCode)'§' | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 }; + yield return new object [] { '5', false, false, false, '5', 6, KeyCode.D5, '5', 6 }; + yield return new object [] { '%', true, false, false, '5', 6, (KeyCode)'%' | KeyCode.ShiftMask, '5', 6 }; + yield return new object [] { '5', true, true, false, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask, '5', 6 }; + yield return new object [] { '5', true, true, true, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 }; + yield return new object [] { '€', false, true, true, '5', 6, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 }; + yield return new object [] { '6', false, false, false, '6', 7, KeyCode.D6, '6', 7 }; + yield return new object [] { '&', true, false, false, '6', 7, (KeyCode)'&' | KeyCode.ShiftMask, '6', 7 }; + yield return new object [] { '6', true, true, false, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask, '6', 7 }; + yield return new object [] { '6', true, true, true, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 }; + yield return new object [] { '6', false, true, true, '6', 7, KeyCode.D6 | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 }; + yield return new object [] { '7', false, false, false, '7', 8, KeyCode.D7, '7', 8 }; + yield return new object [] { '/', true, false, false, '7', 8, (KeyCode)'/' | KeyCode.ShiftMask, '7', 8 }; // BUGBUG: This is not true for ENG keyboards. Shift-7 is &. + yield return new object [] { '7', true, true, false, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask, '7', 8 }; + yield return new object [] { '7', true, true, true, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 }; + yield return new object [] { '{', false, true, true, '7', 8, (KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 }; + yield return new object [] { '8', false, false, false, '8', 9, KeyCode.D8, '8', 9 }; + yield return new object [] { '(', true, false, false, '8', 9, (KeyCode)'(' | KeyCode.ShiftMask, '8', 9 }; + yield return new object [] { '8', true, true, false, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask, '8', 9 }; + yield return new object [] { '8', true, true, true, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 }; + yield return new object [] { '[', false, true, true, '8', 9, (KeyCode)'[' | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 }; + yield return new object [] { '9', false, false, false, '9', 10, KeyCode.D9, '9', 10 }; + yield return new object [] { ')', true, false, false, '9', 10, (KeyCode)')' | KeyCode.ShiftMask, '9', 10 }; + yield return new object [] { '9', true, true, false, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask, '9', 10 }; + yield return new object [] { '9', true, true, true, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 }; + yield return new object [] { ']', false, true, true, '9', 10, (KeyCode)']' | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 }; + yield return new object [] { '0', false, false, false, '0', 11, KeyCode.D0, '0', 11 }; + yield return new object [] { '=', true, false, false, '0', 11, (KeyCode)'=' | KeyCode.ShiftMask, '0', 11 }; + yield return new object [] { '0', true, true, false, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask, '0', 11 }; + yield return new object [] { '0', true, true, true, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 }; + yield return new object [] { '}', false, true, true, '0', 11, (KeyCode)'}' | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 }; + yield return new object [] { '\'', false, false, false, VK.OEM_4, 12, (KeyCode)'\'', VK.OEM_4, 12 }; + yield return new object [] { '?', true, false, false, VK.OEM_4, 12, (KeyCode)'?' | KeyCode.ShiftMask, VK.OEM_4, 12 }; + yield return new object [] { '\'', true, true, false, VK.OEM_4, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_4, 12 }; + yield return new object [] { '\'', true, true, true, VK.OEM_4, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_4, 12 }; + yield return new object [] { '«', false, false, false, VK.OEM_6, 13, (KeyCode)'«', VK.OEM_6, 13 }; + yield return new object [] { '»', true, false, false, VK.OEM_6, 13, (KeyCode)'»' | KeyCode.ShiftMask, VK.OEM_6, 13 }; + yield return new object [] { '«', true, true, false, VK.OEM_6, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_6, 13 }; + yield return new object [] { '«', true, true, true, VK.OEM_6, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_6, 13 }; + yield return new object [] { 'á', false, false, false, 'A', 30, (KeyCode)'á', 'A', 30 }; + yield return new object [] { 'Á', true, false, false, 'A', 30, (KeyCode)'Á' | KeyCode.ShiftMask, 'A', 30 }; + yield return new object [] { 'à', false, false, false, 'A', 30, (KeyCode)'à', 'A', 30 }; + yield return new object [] { 'À', true, false, false, 'A', 30, (KeyCode)'À' | KeyCode.ShiftMask, 'A', 30 }; + yield return new object [] { 'é', false, false, false, 'E', 18, (KeyCode)'é', 'E', 18 }; + yield return new object [] { 'É', true, false, false, 'E', 18, (KeyCode)'É' | KeyCode.ShiftMask, 'E', 18 }; + yield return new object [] { 'è', false, false, false, 'E', 18, (KeyCode)'è', 'E', 18 }; + yield return new object [] { 'È', true, false, false, 'E', 18, (KeyCode)'È' | KeyCode.ShiftMask, 'E', 18 }; + yield return new object [] { 'í', false, false, false, 'I', 23, (KeyCode)'í', 'I', 23 }; + yield return new object [] { 'Í', true, false, false, 'I', 23, (KeyCode)'Í' | KeyCode.ShiftMask, 'I', 23 }; + yield return new object [] { 'ì', false, false, false, 'I', 23, (KeyCode)'ì', 'I', 23 }; + yield return new object [] { 'Ì', true, false, false, 'I', 23, (KeyCode)'Ì' | KeyCode.ShiftMask, 'I', 23 }; + yield return new object [] { 'ó', false, false, false, 'O', 24, (KeyCode)'ó', 'O', 24 }; + yield return new object [] { 'Ó', true, false, false, 'O', 24, (KeyCode)'Ó' | KeyCode.ShiftMask, 'O', 24 }; + yield return new object [] { 'ò', false, false, false, 'O', 24, (KeyCode)'ò', 'O', 24 }; + yield return new object [] { 'Ò', true, false, false, 'O', 24, (KeyCode)'Ò' | KeyCode.ShiftMask, 'O', 24 }; + yield return new object [] { 'ú', false, false, false, 'U', 22, (KeyCode)'ú', 'U', 22 }; + yield return new object [] { 'Ú', true, false, false, 'U', 22, (KeyCode)'Ú' | KeyCode.ShiftMask, 'U', 22 }; + yield return new object [] { 'ù', false, false, false, 'U', 22, (KeyCode)'ù', 'U', 22 }; + yield return new object [] { 'Ù', true, false, false, 'U', 22, (KeyCode)'Ù' | KeyCode.ShiftMask, 'U', 22 }; + yield return new object [] { 'ö', false, false, false, 'O', 24, (KeyCode)'ö', 'O', 24 }; + yield return new object [] { 'Ö', true, false, false, 'O', 24, (KeyCode)'Ö' | KeyCode.ShiftMask, 'O', 24 }; + yield return new object [] { '<', false, false, false, VK.OEM_102, 86, (KeyCode)'<', VK.OEM_102, 86 }; + yield return new object [] { '>', true, false, false, VK.OEM_102, 86, (KeyCode)'>' | KeyCode.ShiftMask, VK.OEM_102, 86 }; + yield return new object [] { '<', true, true, false, VK.OEM_102, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_102, 86 }; + yield return new object [] { '<', true, true, true, VK.OEM_102, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_102, 86 }; + yield return new object [] { 'ç', false, false, false, VK.OEM_3, 39, (KeyCode)'ç', VK.OEM_3, 39 }; + yield return new object [] { 'Ç', true, false, false, VK.OEM_3, 39, (KeyCode)'Ç' | KeyCode.ShiftMask, VK.OEM_3, 39 }; + yield return new object [] { 'ç', true, true, false, VK.OEM_3, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_3, 39 }; + yield return new object [] { 'ç', true, true, true, VK.OEM_3, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_3, 39 }; + yield return new object [] { '¨', false, true, true, VK.OEM_PLUS, 26, (KeyCode)'¨' | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_PLUS, 26 }; + yield return new object [] { '\0', false, false, false, VK.PRIOR, 73, KeyCode.PageUp, VK.PRIOR, 73 }; + yield return new object [] { '\0', true, false, false, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask, VK.PRIOR, 73 }; + yield return new object [] { '\0', true, true, false, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask, VK.PRIOR, 73 }; + yield return new object [] { '\0', true, true, true, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.PRIOR, 73 }; + yield return new object [] { '~', false, false, false, VK.SPACE, 57, (KeyCode)'~', VK.SPACE, 57 }; + yield return new object [] { '^', false, false, false, VK.SPACE, 57, (KeyCode)'^', VK.SPACE, 57 }; } + } + + [Theory] + [InlineData ('a', ConsoleKey.A, 'a', ConsoleKey.A)] + [InlineData ('A', ConsoleKey.A, 'A', ConsoleKey.A)] + [InlineData ('á', ConsoleKey.A, 'á', ConsoleKey.A)] + [InlineData ('Á', ConsoleKey.A, 'Á', ConsoleKey.A)] + [InlineData ('à', ConsoleKey.A, 'à', ConsoleKey.A)] + [InlineData ('À', ConsoleKey.A, 'À', ConsoleKey.A)] + [InlineData ('5', ConsoleKey.D5, '5', ConsoleKey.D5)] + [InlineData ('%', ConsoleKey.D5, '%', ConsoleKey.D5)] + [InlineData ('€', ConsoleKey.D5, '€', ConsoleKey.D5)] + [InlineData ('?', ConsoleKey.Oem4, '?', ConsoleKey.Oem4)] + [InlineData ('\'', ConsoleKey.Oem4, '\'', ConsoleKey.Oem4)] + [InlineData ('q', ConsoleKey.Q, 'q', ConsoleKey.Q)] + [InlineData ('\0', ConsoleKey.F2, '\0', ConsoleKey.F2)] + [InlineData ('英', ConsoleKey.None, '英', ConsoleKey.None)] + [InlineData ('´', ConsoleKey.None, '´', ConsoleKey.Oem1)] + [InlineData ('`', ConsoleKey.None, '`', ConsoleKey.Oem1)] + //[InlineData ('~', ConsoleKey.None, '~', ConsoleKey.Oem2)] + //[InlineData ('^', ConsoleKey.None, '^', ConsoleKey.Oem2)] // BUGBUG: '^' is Shift-6 on ENG keyboard,not Oem2. + // For the US standard keyboard, Oem2 is the /? key + public void EncodeKeyCharForVKPacket_DecodeVKPacketToKConsoleKeyInfo (char keyChar, ConsoleKey consoleKey, char expectedChar, ConsoleKey expectedConsoleKey) + { + var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, false, false, false); + var encodedKeyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo); + var encodedConsoleKeyInfo = new ConsoleKeyInfo (encodedKeyChar, ConsoleKey.Packet, false, false, false); + var decodedConsoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (encodedConsoleKeyInfo); + Assert.Equal (consoleKeyInfo.Key, consoleKey); + Assert.Equal (expectedConsoleKey, decodedConsoleKeyInfo.Key); + Assert.Equal (expectedChar, decodedConsoleKeyInfo.KeyChar); + } - IEnumerator IEnumerable.GetEnumerator () - { - return GetEnumerator (); + + [Theory] + [InlineData ((KeyCode)'a', false, ConsoleKey.A, 'a')] + [InlineData (KeyCode.A | KeyCode.ShiftMask, false, ConsoleKey.A, 'A')] + [InlineData ((KeyCode)'á', false, ConsoleKey.A, 'á')] + [InlineData ((KeyCode)'Á' | KeyCode.ShiftMask, false, ConsoleKey.A, 'Á')] + [InlineData ((KeyCode)'à', false, ConsoleKey.A, 'à')] + [InlineData ((KeyCode)'À' | KeyCode.ShiftMask, false, ConsoleKey.A, 'À')] + [InlineData (KeyCode.D5, false, ConsoleKey.D5, '5')] + [InlineData ((KeyCode)'%' | KeyCode.ShiftMask, false, ConsoleKey.D5, '%')] + //[InlineData ((KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, false, ConsoleKey.D5, '€')] // Bogus test. This is not true on ENG keyboard layout. + [InlineData ((KeyCode)'?' | KeyCode.ShiftMask, false, ConsoleKey.Oem4, '?')] + [InlineData ((KeyCode)'\'', false, ConsoleKey.Oem4, '\'')] + [InlineData ((KeyCode)'q', false, ConsoleKey.Q, 'q')] + [InlineData (KeyCode.F2, true, ConsoleKey.F2, 'q')] + [InlineData ((KeyCode)'英', false, ConsoleKey.None, '英')] + [InlineData (KeyCode.Enter, true, ConsoleKey.Enter, '\r')] + public void MapKeyCodeToConsoleKey_GetKeyCharFromUnicodeChar (KeyCode keyCode, bool expectedIsConsoleKey, ConsoleKey expectedConsoleKey, char expectedConsoleKeyChar) + { + var modifiers = ConsoleKeyMapping.MapToConsoleModifiers (keyCode); + var consoleKey = ConsoleKeyMapping.MapKeyCodeToConsoleKey (keyCode, out bool isConsoleKey); + if (isConsoleKey) { + Assert.True (isConsoleKey == expectedIsConsoleKey); + Assert.Equal (expectedConsoleKey , (ConsoleKey)consoleKey); + Assert.Equal (expectedConsoleKeyChar, consoleKey); + } else { + var keyChar = ConsoleKeyMapping.GetKeyCharFromUnicodeChar (consoleKey, modifiers, out consoleKey, out _, isConsoleKey); + Assert.True (isConsoleKey == expectedIsConsoleKey); + Assert.Equal (expectedConsoleKey , (ConsoleKey)consoleKey); + Assert.Equal (expectedConsoleKeyChar, keyChar); } } +#endif + + [Theory] + [InlineData ('a', ConsoleKey.A, false, false, false, (KeyCode)'a')] + [InlineData ('A', ConsoleKey.A, true, false, false, KeyCode.A | KeyCode.ShiftMask)] + [InlineData ('á', ConsoleKey.A, false, false, false, (KeyCode)'á')] + [InlineData ('Á', ConsoleKey.A, true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)] + [InlineData ('à', ConsoleKey.A, false, false, false, (KeyCode)'à')] + [InlineData ('À', ConsoleKey.A, true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)] + [InlineData ('5', ConsoleKey.D5, false, false, false, KeyCode.D5)] + [InlineData ('%', ConsoleKey.D5, true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)] + [InlineData ('€', ConsoleKey.D5, false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)] + [InlineData ('?', ConsoleKey.Oem4, true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)] + [InlineData ('\'', ConsoleKey.Oem4, false, false, false, (KeyCode)'\'')] + [InlineData ('q', ConsoleKey.Q, false, false, false, (KeyCode)'q')] + [InlineData ('\0', ConsoleKey.F2, false, false, false, KeyCode.F2)] + [InlineData ('英', ConsoleKey.None, false, false, false, (KeyCode)'英')] + [InlineData ('\r', ConsoleKey.Enter, false, false, false, KeyCode.Enter)] + public void MapConsoleKeyInfoToKeyCode_Also_Return_Modifiers (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control, KeyCode expectedKeyCode) + { + var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); + var keyCode = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo); + + Assert.Equal (keyCode, expectedKeyCode); + } + + [Theory] + [InlineData ('a', false, false, false, (KeyCode)'a')] + [InlineData ('A', true, false, false, KeyCode.A | KeyCode.ShiftMask)] + [InlineData ('á', false, false, false, (KeyCode)'á')] + [InlineData ('Á', true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)] + [InlineData ('à', false, false, false, (KeyCode)'à')] + [InlineData ('À', true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)] + [InlineData ('5', false, false, false, KeyCode.D5)] + [InlineData ('%', true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)] + [InlineData ('€', false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)] + [InlineData ('?', true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)] + [InlineData ('\'', false, false, false, (KeyCode)'\'')] + [InlineData ('q', false, false, false, (KeyCode)'q')] + [InlineData ((uint)KeyCode.F2, false, false, false, KeyCode.F2)] + [InlineData ('英', false, false, false, (KeyCode)'英')] + [InlineData ('\r', false, false, false, KeyCode.Enter)] + [InlineData ('\n', false, false, false, (KeyCode)'\n')] + public void MapToKeyCodeModifiers_Tests (uint keyChar, bool shift, bool alt, bool control, KeyCode excpectedKeyCode) + { + var modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control); + KeyCode keyCode = (KeyCode)keyChar; + keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode); + + Assert.Equal (keyCode, excpectedKeyCode); + } + + [Theory] + [InlineData ('a', ConsoleKey.A, false, false, false, 30)] + [InlineData ('A', ConsoleKey.A, true, false, false, 30)] + [InlineData ('á', ConsoleKey.A, false, false, false, 30)] + [InlineData ('Á', ConsoleKey.A, true, false, false, 30)] + [InlineData ('à', ConsoleKey.A, false, false, false, 30)] + [InlineData ('À', ConsoleKey.A, true, false, false, 30)] + [InlineData ('0', ConsoleKey.D0, false, false, false, 11)] + [InlineData ('=', ConsoleKey.D0, true, false, false, 11)] + [InlineData ('}', ConsoleKey.D0, false, true, true, 11)] + [InlineData ('1', ConsoleKey.D1, false, false, false, 2)] + [InlineData ('!', ConsoleKey.D1, true, false, false, 2)] + [InlineData ('2', ConsoleKey.D2, false, false, false, 3)] + [InlineData ('"', ConsoleKey.D2, true, false, false, 3)] + [InlineData ('@', ConsoleKey.D2, false, true, true, 3)] + [InlineData ('3', ConsoleKey.D3, false, false, false, 4)] + [InlineData ('#', ConsoleKey.D3, true, false, false, 4)] + [InlineData ('£', ConsoleKey.D3, false, true, true, 4)] + [InlineData ('4', ConsoleKey.D4, false, false, false, 5)] + [InlineData ('$', ConsoleKey.D4, true, false, false, 5)] + [InlineData ('§', ConsoleKey.D4, false, true, true, 5)] + [InlineData ('5', ConsoleKey.D5, false, false, false, 6)] + [InlineData ('%', ConsoleKey.D5, true, false, false, 6)] + [InlineData ('€', ConsoleKey.D5, false, true, true, 6)] + [InlineData ('6', ConsoleKey.D6, false, false, false, 7)] + [InlineData ('&', ConsoleKey.D6, true, false, false, 7)] + [InlineData ('7', ConsoleKey.D7, false, false, false, 8)] + [InlineData ('/', ConsoleKey.D7, true, false, false, 8)] + [InlineData ('{', ConsoleKey.D7, false, true, true, 8)] + [InlineData ('8', ConsoleKey.D8, false, false, false, 9)] + [InlineData ('(', ConsoleKey.D8, true, false, false, 9)] + [InlineData ('[', ConsoleKey.D8, false, true, true, 9)] + [InlineData ('9', ConsoleKey.D9, false, false, false, 10)] + [InlineData (')', ConsoleKey.D9, true, false, false, 10)] + [InlineData (']', ConsoleKey.D9, false, true, true, 10)] + [InlineData ('´', ConsoleKey.Oem1, false, false, false, 27)] + [InlineData ('`', ConsoleKey.Oem1, true, false, false, 27)] + [InlineData ('~', ConsoleKey.Oem2, false, false, false, 43)] + [InlineData ('^', ConsoleKey.Oem2, true, false, false, 43)] + [InlineData ('ç', ConsoleKey.Oem3, false, false, false, 39)] + [InlineData ('Ç', ConsoleKey.Oem3, true, false, false, 39)] + [InlineData ('\'', ConsoleKey.Oem4, false, false, false, 12)] + [InlineData ('?', ConsoleKey.Oem4, true, false, false, 12)] + [InlineData ('\\', ConsoleKey.Oem5, false, true, true, 41)] + [InlineData ('|', ConsoleKey.Oem5, true, false, false, 41)] + [InlineData ('«', ConsoleKey.Oem6, false, true, true, 13)] + [InlineData ('»', ConsoleKey.Oem6, true, false, false, 13)] + [InlineData ('º', ConsoleKey.Oem7, false, true, true, 40)] + [InlineData ('ª', ConsoleKey.Oem7, true, false, false, 40)] + [InlineData ('+', ConsoleKey.OemPlus, false, true, true, 26)] + [InlineData ('*', ConsoleKey.OemPlus, true, false, false, 26)] + [InlineData ('¨', ConsoleKey.OemPlus, false, true, true, 26)] + [InlineData (',', ConsoleKey.OemComma, false, true, true, 51)] + [InlineData (';', ConsoleKey.OemComma, true, false, false, 51)] + [InlineData ('.', ConsoleKey.OemPeriod, false, true, true, 52)] + [InlineData (':', ConsoleKey.OemPeriod, true, false, false, 52)] + [InlineData ('-', ConsoleKey.OemMinus, false, true, true, 53)] + [InlineData ('_', ConsoleKey.OemMinus, true, false, false, 53)] + [InlineData ('q', ConsoleKey.Q, false, false, false, 16)] + [InlineData ('\0', ConsoleKey.F2, false, false, false, 60)] + [InlineData ('英', ConsoleKey.None, false, false, false, 0)] + [InlineData ('英', ConsoleKey.None, true, false, false, 0)] + public void GetScanCodeFromConsoleKeyInfo_Tests (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control, uint expectedScanCode) + { + var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); + var scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo); + + Assert.Equal (scanCode, expectedScanCode); + } + + [Theory] + [InlineData ('a', 'A', KeyCode.A | KeyCode.ShiftMask)] + [InlineData ('z', 'Z', KeyCode.Z | KeyCode.ShiftMask)] + [InlineData ('á', 'Á', (KeyCode)'Á' | KeyCode.ShiftMask)] + [InlineData ('à', 'À', (KeyCode)'À' | KeyCode.ShiftMask)] + [InlineData ('ý', 'Ý', (KeyCode)'Ý' | KeyCode.ShiftMask)] + [InlineData ('1', '!', (KeyCode)'!' | KeyCode.ShiftMask)] + [InlineData ('2', '"', (KeyCode)'"' | KeyCode.ShiftMask)] + [InlineData ('3', '#', (KeyCode)'#' | KeyCode.ShiftMask)] + [InlineData ('4', '$', (KeyCode)'$' | KeyCode.ShiftMask)] + [InlineData ('5', '%', (KeyCode)'%' | KeyCode.ShiftMask)] + [InlineData ('6', '&', (KeyCode)'&' | KeyCode.ShiftMask)] + [InlineData ('7', '/', (KeyCode)'/' | KeyCode.ShiftMask)] + [InlineData ('8', '(', (KeyCode)'(' | KeyCode.ShiftMask)] + [InlineData ('9', ')', (KeyCode)')' | KeyCode.ShiftMask)] + [InlineData ('0', '=', (KeyCode)'=' | KeyCode.ShiftMask)] + [InlineData ('\\', '|', (KeyCode)'|' | KeyCode.ShiftMask)] + [InlineData ('\'', '?', (KeyCode)'?' | KeyCode.ShiftMask)] + [InlineData ('«', '»', (KeyCode)'»' | KeyCode.ShiftMask)] + [InlineData ('+', '*', (KeyCode)'*' | KeyCode.ShiftMask)] + [InlineData ('´', '`', (KeyCode)'`' | KeyCode.ShiftMask)] + [InlineData ('º', 'ª', (KeyCode)'ª' | KeyCode.ShiftMask)] + [InlineData ('~', '^', (KeyCode)'^' | KeyCode.ShiftMask)] + [InlineData ('<', '>', (KeyCode)'>' | KeyCode.ShiftMask)] + [InlineData (',', ';', (KeyCode)';' | KeyCode.ShiftMask)] + [InlineData ('.', ':', (KeyCode)':' | KeyCode.ShiftMask)] + [InlineData ('-', '_', (KeyCode)'_' | KeyCode.ShiftMask)] + public void GetKeyChar_Shifted_Char_From_UnShifted_Char (char unicodeChar, char expectedKeyChar, KeyCode excpectedKeyCode) + { + var modifiers = ConsoleKeyMapping.GetModifiers (true, false, false); + var keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers); + Assert.Equal (keyChar, expectedKeyChar); + + KeyCode keyCode = (KeyCode)keyChar; + keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode); + + Assert.Equal (keyCode, excpectedKeyCode); + } + + [Theory] + [InlineData ('A', 'a', (KeyCode)'a')] + [InlineData ('Z', 'z', (KeyCode)'z')] + [InlineData ('Á', 'á', (KeyCode)'á')] + [InlineData ('À', 'à', (KeyCode)'à')] + [InlineData ('Ý', 'ý', (KeyCode)'ý')] + [InlineData ('!', '1', KeyCode.D1)] + [InlineData ('"', '2', KeyCode.D2)] + [InlineData ('#', '3', KeyCode.D3)] + [InlineData ('$', '4', KeyCode.D4)] + [InlineData ('%', '5', KeyCode.D5)] + [InlineData ('&', '6', KeyCode.D6)] + [InlineData ('/', '7', KeyCode.D7)] + [InlineData ('(', '8', KeyCode.D8)] + [InlineData (')', '9', KeyCode.D9)] + [InlineData ('=', '0', KeyCode.D0)] + [InlineData ('|', '\\', (KeyCode)'\\')] + [InlineData ('?', '\'', (KeyCode)'\'')] + [InlineData ('»', '«', (KeyCode)'«')] + [InlineData ('*', '+', (KeyCode)'+')] + [InlineData ('`', '´', (KeyCode)'´')] + [InlineData ('ª', 'º', (KeyCode)'º')] + [InlineData ('^', '~', (KeyCode)'~')] + [InlineData ('>', '<', (KeyCode)'<')] + [InlineData (';', ',', (KeyCode)',')] + [InlineData (':', '.', (KeyCode)'.')] + [InlineData ('_', '-', (KeyCode)'-')] + public void GetKeyChar_UnShifted_Char_From_Shifted_Char (char unicodeChar, char expectedKeyChar, KeyCode excpectedKeyCode) + { + var modifiers = ConsoleKeyMapping.GetModifiers (false, false, false); + var keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers); + Assert.Equal (keyChar, expectedKeyChar); + + KeyCode keyCode = (KeyCode)keyChar; + keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode); + + Assert.Equal (keyCode, excpectedKeyCode); + } } diff --git a/UnitTests/Input/KeyTests.cs b/UnitTests/Input/KeyTests.cs index 018b69b102..082665b83f 100644 --- a/UnitTests/Input/KeyTests.cs +++ b/UnitTests/Input/KeyTests.cs @@ -35,9 +35,10 @@ public void Constructor_WithKey_ShouldSetCorrectKey (KeyCode key) [InlineData (' ', KeyCode.Space)] [InlineData ('1', KeyCode.D1)] [InlineData ('!', (KeyCode)'!')] - [InlineData ('\n', KeyCode.Enter)] + [InlineData ('\r', KeyCode.Enter)] [InlineData ('\t', KeyCode.Tab)] [InlineData ('\r', (KeyCode)13)] + [InlineData ('\n', (KeyCode)10)] [InlineData ('ó', (KeyCode)'ó')] [InlineData ('Ó', (KeyCode)'Ó')] [InlineData ('❿', (KeyCode)'❿')] @@ -78,6 +79,10 @@ public void Constructor_Char (char ch, KeyCode expectedKeyCode) [InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)] [InlineData ("", KeyCode.Null)] [InlineData (" ", KeyCode.Space)] + [InlineData ("Space", KeyCode.Space)] + [InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)] + [InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)] + [InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)] [InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)] [InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)] [InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)] @@ -87,8 +92,8 @@ public void Constructor_Char (char ch, KeyCode expectedKeyCode) [InlineData ("D0", KeyCode.D0)] [InlineData ("65", KeyCode.A | KeyCode.ShiftMask)] [InlineData ("97", KeyCode.A)] - [InlineData ("Shift", KeyCode.ShiftKey)] - [InlineData ("Ctrl", KeyCode.CtrlKey)] + [InlineData ("Shift", KeyCode.ShiftMask)] + [InlineData ("Ctrl", KeyCode.CtrlMask)] [InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)] [InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)] [InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)] @@ -114,9 +119,10 @@ public void Constructor_String_Invalid_Throws (string keyString) [InlineData (' ', KeyCode.Space)] [InlineData ('1', KeyCode.D1)] [InlineData ('!', (KeyCode)'!')] - [InlineData ('\n', KeyCode.Enter)] + [InlineData ('\r', KeyCode.Enter)] [InlineData ('\t', KeyCode.Tab)] [InlineData ('\r', (KeyCode)13)] + [InlineData ('\n', (KeyCode)10)] [InlineData ('ó', (KeyCode)'ó')] [InlineData ('Ó', (KeyCode)'Ó')] [InlineData ('❿', (KeyCode)'❿')] @@ -157,7 +163,7 @@ public void Cast_Key_ToString () [InlineData (KeyCode.CtrlMask, false)] [InlineData (KeyCode.AltMask, false)] [InlineData (KeyCode.ShiftMask | KeyCode.AltMask, false)] - public void IsValid (Key key, bool expected) => Assert.Equal (expected, key.IsValid); + public void IsValid (KeyCode key, bool expected) => Assert.Equal (expected, ((Key)key).IsValid); [Fact] public void HandledProperty_ShouldBeFalseByDefault () @@ -173,15 +179,15 @@ public void HandledProperty_ShouldBeFalseByDefault () [InlineData (KeyCode.A | KeyCode.ShiftMask, KeyCode.A | KeyCode.ShiftMask)] [InlineData (KeyCode.Z, (KeyCode)'z')] [InlineData (KeyCode.Space, KeyCode.Space)] - public void Cast_KeyCode_To_Key (KeyCode cdk, Key expected) + public void Cast_KeyCode_To_Key (KeyCode cdk, KeyCode expected) { // explicit var key = (Key)cdk; - Assert.Equal (expected.ToString (), key.ToString ()); + Assert.Equal (((Key)expected).ToString (), key.ToString ()); // implicit key = cdk; - Assert.Equal (expected.ToString (), key.ToString ()); + Assert.Equal (((Key)expected).ToString (), key.ToString ()); } [Fact] @@ -256,7 +262,7 @@ public void IsKeyCodeAtoZ (KeyCode key, bool expected) [InlineData (KeyCode.F1, '\0')] [InlineData (KeyCode.ShiftMask | KeyCode.F1, '\0')] [InlineData (KeyCode.CtrlMask | KeyCode.F1, '\0')] - [InlineData (KeyCode.Enter, '\n')] + [InlineData (KeyCode.Enter, '\r')] [InlineData (KeyCode.Tab, '\t')] [InlineData (KeyCode.Esc, 0x1b)] [InlineData (KeyCode.Space, ' ')] @@ -265,10 +271,10 @@ public void IsKeyCodeAtoZ (KeyCode key, bool expected) [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, '\0')] [InlineData (KeyCode.CharMask, '\0')] [InlineData (KeyCode.SpecialMask, '\0')] - public void AsRune_ShouldReturnCorrectIntValue (KeyCode key, Rune expected) + public void AsRune_ShouldReturnCorrectIntValue (KeyCode key, uint expected) { var eventArgs = new Key (key); - Assert.Equal (expected, eventArgs.AsRune); + Assert.Equal ((Rune)expected, eventArgs.AsRune); } [Theory] @@ -316,7 +322,7 @@ public void Standard_Keys_Should_Equal_KeyCode () } // TODO: Create equality operator for KeyCode - //Assert.Equal (KeyCode.Delete, Key.Delete); + //Assert.Equal (KeyCode.DeleteChar, Key.Delete); // Similar tests for IsShift and IsCtrl [Fact] @@ -333,11 +339,11 @@ public void ToString_ShouldReturnReadableString () [Theory] [InlineData ((KeyCode)'☑', "☑")] - //[InlineData ((ConsoleDriverKey)'英', "英")] - //[InlineData ((ConsoleDriverKey)'{', "{")] + [InlineData ((KeyCode)'英', "英")] + [InlineData ((KeyCode)'{', "{")] [InlineData ((KeyCode)'\'', "\'")] [InlineData ((KeyCode)'ó', "ó")] - [InlineData ((KeyCode)'ó' | KeyCode.ShiftMask, "Shift+ó")] // is this right??? + [InlineData ((KeyCode)'Ó' | KeyCode.ShiftMask, "Shift+Ó")] // TODO: This is not correct, it should be Shift+ó or just Ó [InlineData ((KeyCode)'Ó', "Ó")] [InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+ç")] [InlineData ((KeyCode)'a', "a")] // 97 or Key.Space | Key.A @@ -397,19 +403,33 @@ public void ToString_ShouldReturnReadableString () [InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CursorUp, "Alt+Shift+CursorUp")] [InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.CursorUp, "Ctrl+Alt+CursorUp")] [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp, "Ctrl+Alt+Shift+CursorUp")] + [InlineData (KeyCode.Space, "Space")] [InlineData (KeyCode.Null, "Null")] - [InlineData (KeyCode.ShiftMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.CtrlMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.AltMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Null")] - [InlineData (KeyCode.AltKey, "AltKey")] - [InlineData (KeyCode.CtrlKey, "CtrlKey")] - [InlineData (KeyCode.ShiftKey, "ShiftKey")] + [InlineData (KeyCode.ShiftMask | KeyCode.Null, "Shift")] + [InlineData (KeyCode.CtrlMask | KeyCode.Null, "Ctrl")] + [InlineData (KeyCode.AltMask | KeyCode.Null, "Alt")] + [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Ctrl+Shift")] + [InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Alt+Shift")] + [InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Ctrl+Alt")] + [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Ctrl+Alt+Shift")] +#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to + [InlineData (KeyCode.ShiftMask, "Shift")] + [InlineData (KeyCode.CtrlMask, "Ctrl")] + [InlineData (KeyCode.AltMask, "Alt")] + [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, "Ctrl+Shift")] + [InlineData (KeyCode.ShiftMask | KeyCode.AltMask, "Alt+Shift")] + [InlineData (KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt")] + [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask, "Ctrl+Alt+Shift")] +#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to + [InlineData (KeyCode.AltMask, "Alt")] + [InlineData (KeyCode.CtrlMask, "Ctrl")] + [InlineData (KeyCode.ShiftMask, "Shift")] [InlineData (KeyCode.CharMask, "CharMask")] [InlineData (KeyCode.SpecialMask, "Ctrl+Alt+Shift")] + [InlineData ((KeyCode)'+', "+")] + [InlineData ((KeyCode)'+' | KeyCode.ShiftMask, "Shift++")] + [InlineData ((KeyCode)'+' | KeyCode.CtrlMask, "Ctrl++")] + [InlineData ((KeyCode)'+' | KeyCode.ShiftMask | KeyCode.CtrlMask, "Ctrl+Shift++")] public void ToString_ShouldReturnFormattedString (KeyCode key, string expected) => Assert.Equal (expected, Key.ToString (key)); // TryParse @@ -436,6 +456,10 @@ public void ToString_ShouldReturnReadableString () [InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)] [InlineData ("", KeyCode.Null)] [InlineData (" ", KeyCode.Space)] + [InlineData ("Space", KeyCode.Space)] + [InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)] + [InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)] + [InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)] [InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)] [InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)] [InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)] @@ -445,17 +469,17 @@ public void ToString_ShouldReturnReadableString () [InlineData ("D0", KeyCode.D0)] [InlineData ("65", KeyCode.A | KeyCode.ShiftMask)] [InlineData ("97", KeyCode.A)] - [InlineData ("Shift", KeyCode.ShiftKey)] - [InlineData ("Ctrl", KeyCode.CtrlKey)] + [InlineData ("Shift", KeyCode.ShiftMask)] + [InlineData ("Ctrl", KeyCode.CtrlMask)] [InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)] [InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)] [InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)] [InlineData ("Alt-A-Ctrl", KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)] - public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, Key expected) + public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, KeyCode expected) { - Key key; - Assert.True (Key.TryParse (keyString, out key)); - Assert.Equal (((Key)expected).ToString (), key.ToString ()); + Assert.True (Key.TryParse (keyString, out Key key)); + Key expectedKey = (Key)expected; + Assert.Equal (expectedKey.ToString (), key.ToString ()); } [Theory] @@ -471,4 +495,30 @@ public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, Key expect [InlineData ("0x99")] [InlineData ("Ctrl-Ctrl")] public void TryParse_ShouldReturnFalse_On_InvalidKey (string keyString) => Assert.False (Key.TryParse (keyString, out var _)); + + [Theory] + [InlineData (KeyCode.ShiftMask, true, false, false)] + [InlineData (KeyCode.ShiftMask | KeyCode.AltMask, true, true, false)] + [InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, true, true, true)] + [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, true, false, true)] + [InlineData (KeyCode.AltMask, false, true, false)] + [InlineData (KeyCode.AltMask | KeyCode.CtrlMask, false, true, true)] + [InlineData (KeyCode.CtrlMask, false, false, true)] + public void IsShift_IsAlt_IsCtrl (KeyCode keyCode, bool isShift, bool isAlt, bool isCtrl) + { + Assert.Equal (((Key)keyCode).IsShift, isShift); + Assert.Equal (((Key)keyCode).IsAlt, isAlt); + Assert.Equal (((Key)keyCode).IsCtrl, isCtrl); + } + + [Theory] + [InlineData (KeyCode.A, KeyCode.A)] + [InlineData (KeyCode.F1, KeyCode.F1)] + public void Casting_Between_Key_And_KeyCode (KeyCode keyCode, KeyCode key) + { + Assert.Equal (keyCode, (Key)key); + Assert.NotEqual (keyCode, ((Key)key).WithShift); + Assert.Equal ((uint)keyCode, (uint)(Key)key); + Assert.NotEqual ((uint)keyCode, (uint)((Key)key).WithShift); + } } \ No newline at end of file diff --git a/UnitTests/Text/CollectionNavigatorTests.cs b/UnitTests/Text/CollectionNavigatorTests.cs index f3f2271bb1..5328c64e6e 100644 --- a/UnitTests/Text/CollectionNavigatorTests.cs +++ b/UnitTests/Text/CollectionNavigatorTests.cs @@ -383,22 +383,21 @@ public void MinimizeMovement_True_ShouldStayOnCurrentIfMultipleMatches () Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", true)); } - - [Fact] - public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys () - { - // test all Keys - foreach (KeyCode key in Enum.GetValues (typeof (KeyCode))) { - var ke = new Key (key); - _output.WriteLine ($"Testing {key}"); - if (key == KeyCode.AltMask || key == KeyCode.CtrlMask || key == KeyCode.SpecialMask) { - Assert.False (CollectionNavigator.IsCompatibleKey (ke)); - } else { - Assert.True (CollectionNavigator.IsCompatibleKey (ke)); - } - } - - // test Capslock, Numlock and Scrolllock - Assert.True (CollectionNavigator.IsCompatibleKey (new (KeyCode.Null))); - } + + [Theory] + [InlineData (KeyCode.A, true)] + [InlineData (KeyCode.Z, true)] + [InlineData (KeyCode.D0, true)] + [InlineData (KeyCode.A | KeyCode.ShiftMask, true)] + [InlineData (KeyCode.Z | KeyCode.ShiftMask, true)] + [InlineData (KeyCode.Space, true)] + + [InlineData (KeyCode.Z | KeyCode.CtrlMask, false)] + [InlineData (KeyCode.Z | KeyCode.AltMask, false)] + [InlineData (KeyCode.F1, false)] + [InlineData (KeyCode.Delete, false)] + [InlineData (KeyCode.Delete, false)] + [InlineData (KeyCode.Esc, false)] + [InlineData (KeyCode.ShiftMask, false)] + public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys (KeyCode keyCode, bool compatible) => Assert.Equal (compatible, CollectionNavigatorBase.IsCompatibleKey (keyCode)); } \ No newline at end of file diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index b6b7969bbd..e310f5d7b6 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -249,7 +249,7 @@ public void FindHotKey_Invalid_ReturnsFalse (string text) [InlineData ("After K_", false, -1, KeyCode.Null, true)] [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)] [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К) - public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false) + public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) { Rune hotKeySpecifier = (Rune)'_'; @@ -261,7 +261,7 @@ public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult } Assert.Equal (expectedResult, result); Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); + Assert.Equal ((Key)expectedKey, hotKey); } [Theory] @@ -277,7 +277,7 @@ public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult [InlineData ("After k_", false, -1, KeyCode.Null, true)] [InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)] [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К) - public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false) + public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) { Rune hotKeySpecifier = (Rune)'_'; @@ -289,7 +289,7 @@ public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult } Assert.Equal (expectedResult, result); Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); + Assert.Equal ((Key)expectedKey, hotKey); } [Theory] @@ -303,7 +303,7 @@ public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult [InlineData ("Last _1", true, 5, (KeyCode)'1', true)] [InlineData ("After 1_", false, -1, KeyCode.Null, true)] [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)] - public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false) + public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) { Rune hotKeySpecifier = (Rune)'_'; @@ -315,7 +315,7 @@ public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int e } Assert.Equal (expectedResult, result); Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); + Assert.Equal ((Key)expectedKey, hotKey); } [Theory] @@ -324,7 +324,7 @@ public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int e [InlineData ("last K", true, 5, (KeyCode)'K')] [InlineData ("multiple K and R", true, 9, (KeyCode)'K')] [InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) - public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey) + public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey) { var supportFirstUpperCase = true; @@ -338,25 +338,25 @@ public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expecte } Assert.Equal (expectedResult, result); Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); + Assert.Equal ((Key)expectedKey, hotKey); } [Theory] - //[InlineData ("_\"k before", false, Key.Null)] // BUGBUG: Not sure why this fails + [InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char [InlineData ("\"_k before", true, KeyCode.K)] [InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')] [InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')] - //[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (Key)'_')] // BUGBUG: Not sure why this fails + [InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second [InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode [InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode [InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к) - public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, Key expected) + public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected) { var hotKeySpecifier = (Rune)'_'; var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey); Assert.Equal (found, result); - Assert.Equal (expected, hotKey); + Assert.Equal ((Key)expected, hotKey); } [Theory] diff --git a/UnitTests/View/HotKeyTests.cs b/UnitTests/View/HotKeyTests.cs index 6c7ed6b469..6f36fff950 100644 --- a/UnitTests/View/HotKeyTests.cs +++ b/UnitTests/View/HotKeyTests.cs @@ -51,37 +51,37 @@ public void Set_Sets_WithValidKey (KeyCode key) [InlineData ((KeyCode)'х')] // Cyrillic x [InlineData ((KeyCode)'你')] // Chinese ni [InlineData ((KeyCode)'ö')] // German o umlaut - public void Set_SetsKeyBindings (Key key) + public void Set_SetsKeyBindings (KeyCode key) { var view = new View (); - view.HotKey = key; + view.HotKey = (Key)key; Assert.Equal (string.Empty, view.Title); - Assert.Equal (key, view.HotKey); + Assert.Equal ((Key)key, view.HotKey); // Verify key bindings were set // As passed - var commands = view.KeyBindings.GetCommands (key); + var commands = view.KeyBindings.GetCommands ((Key)key); Assert.Contains (Command.Accept, commands); - var baseKey = key.NoShift; + var baseKey = ((Key)key).NoShift; // If A...Z, with and without shift if (baseKey.IsKeyCodeAtoZ) { - commands = view.KeyBindings.GetCommands (key.WithShift); + commands = view.KeyBindings.GetCommands (((Key)key).WithShift); Assert.Contains (Command.Accept, commands); - commands = view.KeyBindings.GetCommands (key.NoShift); + commands = view.KeyBindings.GetCommands (((Key)key).NoShift); Assert.Contains (Command.Accept, commands); - commands = view.KeyBindings.GetCommands (key.WithAlt); + commands = view.KeyBindings.GetCommands (((Key)key).WithAlt); Assert.Contains (Command.Accept, commands); - commands = view.KeyBindings.GetCommands (key.NoShift.WithAlt); + commands = view.KeyBindings.GetCommands (((Key)key).NoShift.WithAlt); Assert.Contains (Command.Accept, commands); } else { // Non A..Z keys should not have shift bindings - if (key.IsShift) { - commands = view.KeyBindings.GetCommands (key.NoShift); + if (((Key)key).IsShift) { + commands = view.KeyBindings.GetCommands (((Key)key).NoShift); Assert.Empty (commands); } else { - commands = view.KeyBindings.GetCommands (key.WithShift); + commands = view.KeyBindings.GetCommands (((Key)key).WithShift); Assert.Empty (commands); } } diff --git a/UnitTests/Views/DateFieldTests.cs b/UnitTests/Views/DateFieldTests.cs index 4c906d972c..237e318c56 100644 --- a/UnitTests/Views/DateFieldTests.cs +++ b/UnitTests/Views/DateFieldTests.cs @@ -64,7 +64,7 @@ public void KeyBindings_Command () CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; DateField df = new DateField (DateTime.Parse ("12/12/1971")); df.ReadOnly = true; - Assert.True (df.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal (" 12/12/1971", df.Text); df.ReadOnly = false; Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask))); diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index e5b3fe2cf8..d47ba2bfe8 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -950,10 +950,10 @@ public void KeyBindings_Command () Assert.Equal (15, tf.CursorPosition); Assert.False (tf.ReadOnly); - Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal ("This is a test.", tf.Text); tf.CursorPosition = 0; - Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal ("his is a test.", tf.Text); tf.ReadOnly = true; Assert.True (tf.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask))); @@ -1114,7 +1114,7 @@ public void KeyBindings_Command () Assert.Equal ("is is a t", tf.Text); Assert.Equal (9, tf.CursorPosition); Assert.True (tf.Used); - Assert.True (tf.NewKeyDownEvent (new (KeyCode.InsertChar))); + Assert.True (tf.NewKeyDownEvent (new (KeyCode.Insert))); Assert.Equal ("is is a t", tf.Text); Assert.Equal (9, tf.CursorPosition); Assert.False (tf.Used); @@ -1137,7 +1137,7 @@ public void KeyBindings_Command () Assert.Equal ("is is a", Clipboard.Contents); tf.Text = "TAB to jump between text fields."; Assert.Equal (0, tf.CursorPosition); - Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ("to jump between text fields.", tf.Text); tf.CursorPosition = tf.Text.Length; Assert.True (tf.NewKeyDownEvent (new (KeyCode.Backspace | KeyCode.CtrlMask))); @@ -1212,7 +1212,7 @@ public void DeleteSelectedText_InsertText_DeleteCharLeft_DeleteCharRight_Cut () tf.CursorPosition = 2; Assert.Equal (1, tf.SelectedLength); Assert.Equal ("1", tf.SelectedText); - Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal ("-", newText); Assert.Equal ("-1", oldText); Assert.Equal ("-", tf.Text); diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index a948db6192..290da1061d 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -1007,7 +1007,7 @@ public void Kill_To_End_Delete_Forwards_Copy_To_The_Clipboard_And_Paste () Assert.Equal ("This is the first line.", Clipboard.Contents); break; case 1: - _textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask)); + _textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask)); Assert.Equal (0, _textView.CursorPosition.X); Assert.Equal (0, _textView.CursorPosition.Y); Assert.Equal ("This is the second line.", _textView.Text); @@ -1085,7 +1085,7 @@ public void Kill_Delete_WordForward () bool iterationsFinished = false; while (!iterationsFinished) { - _textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)); + _textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)); switch (iteration) { case 0: Assert.Equal (0, _textView.CursorPosition.X); @@ -1180,7 +1180,7 @@ public void Kill_Delete_WordForward_Multiline () bool iterationsFinished = false; while (!iterationsFinished) { - _textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)); + _textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)); switch (iteration) { case 0: Assert.Equal (0, _textView.CursorPosition.X); @@ -2500,7 +2500,6 @@ public void DesiredCursorVisibility_Vertical_Navigation () [Theory] [TextViewTestsAutoInitShutdown] [InlineData (KeyCode.Delete)] - [InlineData (KeyCode.DeleteChar)] public void WordWrap_Draw_Typed_Keys_After_Text_Is_Deleted (KeyCode del) { Application.Top.Add (_textView); @@ -2744,13 +2743,13 @@ public void KeyBindings_Command () Assert.Equal (0, tv.SelectedLength); Assert.Equal ("", tv.SelectedText); Assert.True (tv.Selecting); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text); Assert.Equal (new Point (0, 0), tv.CursorPosition); Assert.Equal (0, tv.SelectedLength); Assert.Equal ("", tv.SelectedText); Assert.False (tv.Selecting); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal ($"his is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text); Assert.Equal (new Point (0, 0), tv.CursorPosition); Assert.Equal (0, tv.SelectedLength); @@ -2762,7 +2761,7 @@ public void KeyBindings_Command () Assert.True (tv.NewKeyDownEvent (new (KeyCode.End))); Assert.Equal ($"is is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text); Assert.Equal (new Point (21, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Backspace))); Assert.Equal ($"is is the first line{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text); Assert.Equal (new Point (20, 0), tv.CursorPosition); Assert.True (tv.NewKeyDownEvent (new (KeyCode.Backspace))); @@ -2808,7 +2807,7 @@ public void KeyBindings_Command () Assert.False (tv.Selecting); Assert.Equal ("is is the first lin", Clipboard.Contents); tv.CursorPosition = Point.Empty; - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask))); Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text); Assert.Equal (new Point (0, 0), tv.CursorPosition); Assert.Equal (0, tv.SelectedLength); @@ -2964,7 +2963,7 @@ public void KeyBindings_Command () Assert.Equal (0, tv.SelectedLength); Assert.Equal ("", tv.SelectedText); Assert.False (tv.Selecting); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ($"This is the second line.{Environment.NewLine}This is the third line.first", tv.Text); Assert.Equal (new Point (0, 0), tv.CursorPosition); Assert.Equal (0, tv.SelectedLength); @@ -3020,7 +3019,7 @@ public void KeyBindings_Command () Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.SelectedText); Assert.True (tv.Selecting); Assert.True (tv.Used); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.InsertChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Insert))); Assert.False (tv.Used); Assert.True (tv.AllowsTab); Assert.Equal (new Point (18, 2), tv.CursorPosition); @@ -4691,7 +4690,7 @@ public void HistoryText_Undo_Redo_Multi_Line_Selected_DeleteCharRight_All () Assert.False (tv.IsDirty); Assert.False (tv.HasHistoryChanges); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal ("", tv.Text); Assert.Equal ("", tv.SelectedText); Assert.Equal (1, tv.Lines); @@ -5182,28 +5181,28 @@ public void HistoryText_Undo_Redo_KillWordForward () var text = "First line.\nSecond line."; var tv = new TextView () { Text = text }; - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text); Assert.Equal ("", tv.SelectedText); Assert.Equal (2, tv.Lines); Assert.Equal (new Point (0, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text); Assert.Equal (2, tv.Lines); Assert.Equal (new Point (0, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ("Second line.", tv.Text); Assert.Equal (1, tv.Lines); Assert.Equal (new Point (0, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ("line.", tv.Text); Assert.Equal (1, tv.Lines); Assert.Equal (new Point (0, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask))); Assert.Equal ("", tv.Text); Assert.Equal (1, tv.Lines); Assert.Equal (new Point (0, 0), tv.CursorPosition); @@ -6397,7 +6396,7 @@ This is the second line. tv.CursorPosition = new Point (2, 0); Assert.Equal (new Point (2, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); tv.Draw (); Assert.Equal (new Point (2, 0), tv.CursorPosition); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -6407,7 +6406,7 @@ This is the second line. tv.CursorPosition = new Point (22, 0); Assert.Equal (new Point (22, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); tv.Draw (); Assert.Equal (new Point (22, 0), tv.CursorPosition); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -6454,7 +6453,7 @@ This is the second line. tv.CursorPosition = new Point (2, 0); Assert.Equal (new Point (2, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); tv.Draw (); Assert.Equal (new Point (2, 0), tv.CursorPosition); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -6464,7 +6463,7 @@ This is the second line. tv.CursorPosition = new Point (22, 0); Assert.Equal (new Point (22, 0), tv.CursorPosition); - Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete))); tv.Draw (); Assert.Equal (new Point (22, 0), tv.CursorPosition); TestHelpers.AssertDriverContentsWithFrameAre (@" diff --git a/UnitTests/Views/TimeFieldTests.cs b/UnitTests/Views/TimeFieldTests.cs index d32a8762af..1b4c3afd35 100644 --- a/UnitTests/Views/TimeFieldTests.cs +++ b/UnitTests/Views/TimeFieldTests.cs @@ -61,7 +61,7 @@ public void KeyBindings_Command () { TimeField tf = new TimeField (TimeSpan.Parse ("12:12:19")); tf.ReadOnly = true; - Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar))); + Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete))); Assert.Equal (" 12:12:19", tf.Text); tf.ReadOnly = false; Assert.True (tf.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));