From ca3de5d7400078d3c88541726b324acb91719e1f Mon Sep 17 00:00:00 2001 From: Springcomp Date: Sat, 19 Aug 2023 11:54:38 +0200 Subject: [PATCH 1/4] Supports abstract clipboard --- PSReadLine/ViRegister.cs | 87 +++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/PSReadLine/ViRegister.cs b/PSReadLine/ViRegister.cs index 48df9965f..88860e88f 100644 --- a/PSReadLine/ViRegister.cs +++ b/PSReadLine/ViRegister.cs @@ -5,31 +5,81 @@ namespace Microsoft.PowerShell { public partial class PSConsoleReadLine { + /// + /// Represents a clipboard that holds a piece of text. + /// + internal interface IClipboard + { + /// + /// Retrieves the text stored in the clipboard. + /// + string GetText(); + /// + /// Stores some text in the clipboard. + /// + /// + void SetText(string text); + } + + /// + /// Represents an in-memory clipboard. + /// + internal sealed class InMemoryClipboard : IClipboard + { + private string _text; + public string GetText() + => _text ?? ""; + + public void SetText(string text) + => _text = text; + } + + /// + /// Represents a clipboard that does not store any text. + /// + internal sealed class NoOpClipboard : IClipboard + { + public string GetText() => ""; + public void SetText(string text) { } + } + /// /// Represents a named register. /// internal sealed class ViRegister { private readonly PSConsoleReadLine _singleton; - private string _text; + private readonly IClipboard _clipboard; - /// /// Initialize a new instance of the class. /// /// The object. /// Used to hook into the undo / redo subsystem as part of /// pasting the contents of the register into a buffer. /// - public ViRegister(PSConsoleReadLine singleton) + /// The clipboard to store text to and retrieve text from + public ViRegister(PSConsoleReadLine singleton, IClipboard clipboard) { + _clipboard = clipboard; _singleton = singleton; } + /// Initialize a new instance of the class. + /// + /// The object. + /// Used to hook into the undo / redo subsystem as part of + /// pasting the contents of the register into a buffer. + /// + public ViRegister(PSConsoleReadLine singleton) + : this(singleton, new InMemoryClipboard()) + { + } + /// /// Returns whether this register is empty. /// public bool IsEmpty - => String.IsNullOrEmpty(_text); + => String.IsNullOrEmpty(_clipboard.GetText()); /// /// Returns whether this register contains @@ -41,16 +91,14 @@ public bool IsEmpty /// Gets the raw text contained in the register /// public string RawText - => _text; + => _clipboard.GetText(); /// /// Records the entire buffer in the register. /// /// public void Record(StringBuilder buffer) - { - Record(buffer, 0, buffer.Length); - } + => Record(buffer, 0, buffer.Length); /// /// Records a piece of text in the register. @@ -67,7 +115,7 @@ public void Record(StringBuilder buffer, int offset, int count) System.Diagnostics.Debug.Assert(offset + count <= buffer.Length); HasLinewiseText = false; - _text = buffer.ToString(offset, count); + _clipboard.SetText(buffer.ToString(offset, count)); } /// @@ -77,7 +125,7 @@ public void Record(StringBuilder buffer, int offset, int count) public void LinewiseRecord(string text) { HasLinewiseText = true; - _text = text; + _clipboard.SetText(text); } public int PasteAfter(StringBuilder buffer, int position) @@ -87,9 +135,11 @@ public int PasteAfter(StringBuilder buffer, int position) return position; } + var yanked = _clipboard.GetText(); + if (HasLinewiseText) { - var text = _text; + var text = yanked; if (text[0] != '\n') { @@ -99,7 +149,6 @@ public int PasteAfter(StringBuilder buffer, int position) // paste text after the next line var pastePosition = -1; - var newCursorPosition = position; for (var index = position; index < buffer.Length; index++) { @@ -127,8 +176,8 @@ public int PasteAfter(StringBuilder buffer, int position) position += 1; } - InsertAt(buffer, _text, position, position); - position += _text.Length - 1; + InsertAt(buffer, yanked, position, position); + position += yanked.Length - 1; return position; } @@ -136,6 +185,8 @@ public int PasteAfter(StringBuilder buffer, int position) public int PasteBefore(StringBuilder buffer, int position) { + var yanked = _clipboard.GetText(); + if (HasLinewiseText) { // currently, in Vi Edit Mode, the cursor may be positioned @@ -145,7 +196,7 @@ public int PasteBefore(StringBuilder buffer, int position) position = Math.Max(0, Math.Min(position, buffer.Length - 1)); - var text = _text; + var text = yanked; if (text[0] == '\n') { @@ -184,8 +235,8 @@ public int PasteBefore(StringBuilder buffer, int position) } else { - InsertAt(buffer, _text, position, position); - return position + _text.Length - 1; + InsertAt(buffer, yanked, position, position); + return position + yanked.Length - 1; } } @@ -246,7 +297,7 @@ private void RecordPaste(string text, int position, int anchor) #if DEBUG public override string ToString() { - var text = _text.Replace("\n", "\\n"); + var text = _clipboard.GetText().Replace("\n", "\\n"); return (HasLinewiseText ? "line: " : "") + "\"" + text + "\""; } #endif From b0e9ed92ac8df45100bdc780ff731d8fcc4f3622 Mon Sep 17 00:00:00 2001 From: Maxime Labelle Date: Sat, 19 Aug 2023 15:22:17 +0200 Subject: [PATCH 2/4] Supports black hole "_" named register. --- PSReadLine/KeyBindings.vi.cs | 8 +++++ PSReadLine/ReadLine.cs | 9 +++++- PSReadLine/ReadLine.vi.cs | 14 ++++++++- PSReadLine/YankPaste.vi.cs | 58 ++++++++++++++++++++++++++++++++++-- test/YankPasteTest.VI.cs | 13 ++++++++ 5 files changed, 97 insertions(+), 5 deletions(-) diff --git a/PSReadLine/KeyBindings.vi.cs b/PSReadLine/KeyBindings.vi.cs index 9d051ec02..42e8bf949 100644 --- a/PSReadLine/KeyBindings.vi.cs +++ b/PSReadLine/KeyBindings.vi.cs @@ -44,6 +44,7 @@ internal static ConsoleColor AlternateBackground(ConsoleColor bg) private static Dictionary _viChordCTable; private static Dictionary _viChordYTable; private static Dictionary _viChordDGTable; + private static Dictionary _viChordDQuoteTable; private static Dictionary> _viCmdChordTable; private static Dictionary> _viInsChordTable; @@ -216,6 +217,7 @@ private void SetDefaultViBindings() { Keys.Comma, MakeKeyHandler(RepeatLastCharSearchBackwards, "RepeatLastCharSearchBackwards") }, { Keys.AltH, MakeKeyHandler(ShowParameterHelp, "ShowParameterHelp") }, { Keys.F1, MakeKeyHandler(ShowCommandHelp, "ShowCommandHelp") }, + { Keys.DQuote, MakeKeyHandler(ViChord, "ChordFirstKey") }, }; // Some bindings are not available on certain platforms @@ -301,6 +303,11 @@ private void SetDefaultViBindings() { Keys.G, MakeKeyHandler( DeleteRelativeLines, "DeleteRelativeLines") }, }; + _viChordDQuoteTable = new Dictionary + { + { Keys.Underbar, MakeKeyHandler( ViSelectNamedRegister, "ViSelectNamedRegister" ) }, + }; + _viCmdChordTable = new Dictionary>(); _viInsChordTable = new Dictionary>(); @@ -310,6 +317,7 @@ private void SetDefaultViBindings() _viCmdChordTable[Keys.D] = _viChordDTable; _viCmdChordTable[Keys.C] = _viChordCTable; _viCmdChordTable[Keys.Y] = _viChordYTable; + _viCmdChordTable[Keys.DQuote] = _viChordDQuoteTable; _normalCursorSize = _console.CursorSize; if ((_normalCursorSize < 1) || (_normalCursorSize > 100)) diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 3b042a448..49d0303b0 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -651,7 +651,14 @@ void ProcessOneKey(PSKeyInfo key, Dictionary dispatchTabl static PSConsoleReadLine() { _singleton = new PSConsoleReadLine(); - _viRegister = new ViRegister(_singleton); + + _registers = new Dictionary { + [""] = new ViRegister(_singleton, new InMemoryClipboard()), + ["_"] = new ViRegister(_singleton, new NoOpClipboard()), + }; + + _viRegister = _registers[""]; + InitializePropertyInfo(); } diff --git a/PSReadLine/ReadLine.vi.cs b/PSReadLine/ReadLine.vi.cs index c39932c75..522459a68 100644 --- a/PSReadLine/ReadLine.vi.cs +++ b/PSReadLine/ReadLine.vi.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Security.Cryptography.Pkcs; namespace Microsoft.PowerShell { @@ -445,6 +446,7 @@ public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null) _singleton._dispatchTable = _viCmdKeyMap; _singleton._chordDispatchTable = _viCmdChordTable; ViBackwardChar(); + ViSelectNamedRegister(""); _singleton.ViIndicateCommandMode(); } @@ -746,7 +748,7 @@ private static int DeleteLineImpl(int lineIndex, int lineCount) var deleteText = _singleton._buffer.ToString(range.Offset, range.Count); - _viRegister.LinewiseRecord(deleteText); + _singleton.SaveLinesToClipboard(deleteText); var deletePosition = range.Offset; var anchor = _singleton._current; @@ -1150,6 +1152,16 @@ private static void ViDGChord(ConsoleKeyInfo? key = null, object arg = null) ViChordHandler(_viChordDGTable, arg); } + private static void ViDQuoteChord(ConsoleKeyInfo? key = null, object arg = null) + { + if (!key.HasValue) + { + throw new ArgumentNullException(nameof(key)); + } + + ViChordHandler(_viChordDQuoteTable, arg); + } + private static bool IsNumeric(PSKeyInfo key) { return key.KeyChar >= '0' && key.KeyChar <= '9' && !key.Control && !key.Alt; diff --git a/PSReadLine/YankPaste.vi.cs b/PSReadLine/YankPaste.vi.cs index d59fdd5cc..7fc261176 100644 --- a/PSReadLine/YankPaste.vi.cs +++ b/PSReadLine/YankPaste.vi.cs @@ -3,7 +3,7 @@ --********************************************************************/ using System; -using System.Text; +using System.Collections.Generic; namespace Microsoft.PowerShell { @@ -12,7 +12,12 @@ public partial class PSConsoleReadLine // *must* be initialized in the static ctor // because it depends on static member _singleton // being initialized first. - private static readonly ViRegister _viRegister; + private static readonly IDictionary _registers; + + /// + /// The currently selected object. + /// + private static ViRegister _viRegister; /// /// Paste the clipboard after the cursor, moving the cursor to the end of the pasted text. @@ -44,18 +49,21 @@ public static void PasteBefore(ConsoleKeyInfo? key = null, object arg = null) private void PasteAfterImpl() { _current = _viRegister.PasteAfter(_buffer, _current); + ViSelectNamedRegister(""); Render(); } private void PasteBeforeImpl() { _current = _viRegister.PasteBefore(_buffer, _current); + ViSelectNamedRegister(""); Render(); } private void SaveToClipboard(int startIndex, int length) { _viRegister.Record(_buffer, startIndex, length); + ViSelectNamedRegister(""); } /// @@ -67,7 +75,17 @@ private void SaveToClipboard(int startIndex, int length) private void SaveLinesToClipboard(int lineIndex, int lineCount) { var range = _buffer.GetRange(lineIndex, lineCount); - _viRegister.LinewiseRecord(_buffer.ToString(range.Offset, range.Count)); + SaveLinesToClipboard(_buffer.ToString(range.Offset, range.Count)); + } + + /// + /// Save the specified text as a linewise selection. + /// + /// + private void SaveLinesToClipboard(string text) + { + _viRegister.LinewiseRecord(text); + ViSelectNamedRegister(""); } /// @@ -366,5 +384,39 @@ public static void ViYankNextGlob(ConsoleKeyInfo? key = null, object arg = null) } _singleton.SaveToClipboard(_singleton._current, end - _singleton._current); } + + /// + /// Selects one the availabled named register for + /// subsequent yank/paste operations. + /// PSReadLine supports the following named registers: + /// + /// - "_" (underscore): void register + /// + /// In addition, PSReadLine supports an internal in-memory + /// unnamed register which is selected by default for all + /// yank/paste operations unless specifically overridden. + /// + /// + /// + /// + public static void ViSelectNamedRegister(ConsoleKeyInfo? key = null, object arg = null) + { + if (key != null) + { + var name = key.Value.KeyChar.ToString(); + ViSelectNamedRegister(name); + } + } + + private static void ViSelectNamedRegister(string name) + { + if (!_registers.ContainsKey(name)) + { + Ding(); + return; + } + + _viRegister = _registers[name]; + } } } diff --git a/test/YankPasteTest.VI.cs b/test/YankPasteTest.VI.cs index 590f04be1..d155069a3 100644 --- a/test/YankPasteTest.VI.cs +++ b/test/YankPasteTest.VI.cs @@ -584,5 +584,18 @@ public void ViDeleteAndPasteLogicalLines_EmptyBuffer() "2dd", 'P', CheckThat(() => AssertCursorLeftTopIs(0, 0)) )); } + + [SkippableFact] + public void ViYankToBlackHoleRegister() + { + TestSetup(KeyMode.Vi); + + Test("text\n", Keys( + "text", _.Escape, "dd", + "iline", _.Escape, + "\"_dd", CheckThat(() => AssertLineIs("")), + "P", CheckThat(() => AssertLineIs("text\n")) + )); + } } } From be04a0ced0c9f997509c709c2144fedd266bac1e Mon Sep 17 00:00:00 2001 From: Springcomp Date: Sat, 19 Aug 2023 18:01:41 +0200 Subject: [PATCH 3/4] supports system clipboard --- PSReadLine/KeyBindings.vi.cs | 2 ++ PSReadLine/ReadLine.cs | 7 +++++++ PSReadLine/ReadLine.vi.cs | 1 - PSReadLine/ViRegister.cs | 12 ++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/PSReadLine/KeyBindings.vi.cs b/PSReadLine/KeyBindings.vi.cs index 42e8bf949..5860dbc6b 100644 --- a/PSReadLine/KeyBindings.vi.cs +++ b/PSReadLine/KeyBindings.vi.cs @@ -305,6 +305,8 @@ private void SetDefaultViBindings() _viChordDQuoteTable = new Dictionary { + { Keys.DQuote, MakeKeyHandler( ViSelectNamedRegister, "ViSelectNamedRegister" ) }, + { Keys.Plus, MakeKeyHandler( ViSelectNamedRegister, "ViSelectNamedRegister" ) }, { Keys.Underbar, MakeKeyHandler( ViSelectNamedRegister, "ViSelectNamedRegister" ) }, }; diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 49d0303b0..2eefd3785 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -655,8 +655,15 @@ static PSConsoleReadLine() _registers = new Dictionary { [""] = new ViRegister(_singleton, new InMemoryClipboard()), ["_"] = new ViRegister(_singleton, new NoOpClipboard()), + ["\""] = new ViRegister(_singleton, new SystemClipboard()), }; + // '+' and '"' are synonyms + + _registers["+"] = _registers["\""]; + + // default register is the unnamed local register + _viRegister = _registers[""]; InitializePropertyInfo(); diff --git a/PSReadLine/ReadLine.vi.cs b/PSReadLine/ReadLine.vi.cs index 522459a68..5f202c02f 100644 --- a/PSReadLine/ReadLine.vi.cs +++ b/PSReadLine/ReadLine.vi.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Security.Cryptography.Pkcs; namespace Microsoft.PowerShell { diff --git a/PSReadLine/ViRegister.cs b/PSReadLine/ViRegister.cs index 88860e88f..29940aea3 100644 --- a/PSReadLine/ViRegister.cs +++ b/PSReadLine/ViRegister.cs @@ -43,6 +43,18 @@ internal sealed class NoOpClipboard : IClipboard public void SetText(string text) { } } + /// + /// Represents the system clipboard. + /// + internal sealed class SystemClipboard : IClipboard + { + public string GetText() + => Internal.Clipboard.GetText(); + + public void SetText(string text) + => Internal.Clipboard.SetText(text); + } + /// /// Represents a named register. /// From cd23a4b53ad7b3f547ce707efb076e1b5fe20b8b Mon Sep 17 00:00:00 2001 From: Springcomp Date: Sat, 19 Aug 2023 18:54:26 +0200 Subject: [PATCH 4/4] completed doc comment --- PSReadLine/YankPaste.vi.cs | 68 ++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/PSReadLine/YankPaste.vi.cs b/PSReadLine/YankPaste.vi.cs index 7fc261176..d9705e6e4 100644 --- a/PSReadLine/YankPaste.vi.cs +++ b/PSReadLine/YankPaste.vi.cs @@ -3,7 +3,7 @@ --********************************************************************/ using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Microsoft.PowerShell { @@ -14,8 +14,8 @@ public partial class PSConsoleReadLine // being initialized first. private static readonly IDictionary _registers; - /// - /// The currently selected object. + /// + /// The currently selected object. /// private static ViRegister _viRegister; @@ -78,12 +78,12 @@ private void SaveLinesToClipboard(int lineIndex, int lineCount) SaveLinesToClipboard(_buffer.ToString(range.Offset, range.Count)); } - /// - /// Save the specified text as a linewise selection. - /// + /// + /// Save the specified text as a linewise selection. + /// /// - private void SaveLinesToClipboard(string text) - { + private void SaveLinesToClipboard(string text) + { _viRegister.LinewiseRecord(text); ViSelectNamedRegister(""); } @@ -302,7 +302,7 @@ public static void ViYankBeginningOfLine(ConsoleKeyInfo? key = null, object arg var start = GetBeginningOfLinePos(_singleton._current); var length = _singleton._current - start; if (length > 0) - { + { _singleton.SaveToClipboard(start, length); _singleton.MoveCursor(start); } @@ -385,33 +385,35 @@ public static void ViYankNextGlob(ConsoleKeyInfo? key = null, object arg = null) _singleton.SaveToClipboard(_singleton._current, end - _singleton._current); } - /// - /// Selects one the availabled named register for - /// subsequent yank/paste operations. - /// PSReadLine supports the following named registers: - /// - /// - "_" (underscore): void register - /// - /// In addition, PSReadLine supports an internal in-memory - /// unnamed register which is selected by default for all - /// yank/paste operations unless specifically overridden. - /// - /// - /// + /// + /// Selects one the available named register for + /// subsequent yank/paste operations. + /// PSReadLine supports the following named registers: + /// + /// - '_' (underscore): void register + /// - '"' (quote): host system clipboard + /// - '+' (plus): synonym for host system clipboard + /// + /// In addition, PSReadLine supports an internal in-memory + /// unnamed register which is selected by default for all + /// yank/paste operations unless specifically overridden. + /// + /// + /// /// - public static void ViSelectNamedRegister(ConsoleKeyInfo? key = null, object arg = null) - { - if (key != null) - { - var name = key.Value.KeyChar.ToString(); - ViSelectNamedRegister(name); - } + public static void ViSelectNamedRegister(ConsoleKeyInfo? key = null, object arg = null) + { + if (key != null) + { + var name = key.Value.KeyChar.ToString(); + ViSelectNamedRegister(name); + } } - private static void ViSelectNamedRegister(string name) - { - if (!_registers.ContainsKey(name)) - { + private static void ViSelectNamedRegister(string name) + { + if (!_registers.ContainsKey(name)) + { Ding(); return; }