From 9e554f8d5369271e76c3ccba1566451df2c610e7 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Tue, 7 Mar 2017 09:18:39 +0300 Subject: [PATCH 01/33] Added IncrementalMenuComplete option and support --- PSReadLine/Cmdlets.cs | 15 ++++++++++++ PSReadLine/Completion.cs | 39 ++++++++++++++++++++++++++++--- PSReadLine/Options.cs | 4 ++++ PSReadLine/ReadLine.cs | 7 ++++++ UnitTestPSReadLine/OptionsTest.cs | 1 + 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs index 001435ea9..1f256815c 100644 --- a/PSReadLine/Cmdlets.cs +++ b/PSReadLine/Cmdlets.cs @@ -109,6 +109,11 @@ public class PSConsoleReadlineOptions /// public const bool DefaultShowToolTips = false; + /// + /// Enables Incremental MenuComplete style + /// + public const bool DefaultIncrementalMenuComplete = false; + /// /// When ringing the bell, what frequency do we use? /// @@ -144,6 +149,7 @@ public PSConsoleReadlineOptions(string hostName) MaximumKillRingCount = DefaultMaximumKillRingCount; HistorySearchCursorMovesToEnd = DefaultHistorySearchCursorMovesToEnd; ShowToolTips = DefaultShowToolTips; + IncrementalMenuComplete = DefaultIncrementalMenuComplete; DingDuration = DefaultDingDuration; DingTone = DefaultDingTone; BellStyle = DefaultBellStyle; @@ -222,6 +228,7 @@ public PSConsoleReadlineOptions(string hostName) public int MaximumKillRingCount { get; set; } public bool HistorySearchCursorMovesToEnd { get; set; } public bool ShowToolTips { get; set; } + public bool IncrementalMenuComplete { get; set; } public int DingTone { get; set; } public int CompletionQueryItems { get; set; } public string WordDelimiters { get; set; } @@ -493,6 +500,14 @@ public SwitchParameter ShowToolTips } internal SwitchParameter? _showToolTips; + [Parameter(ParameterSetName = "OptionsSet")] + public SwitchParameter IncrementalMenuComplete + { + get { return _incrementalMenuComplete.GetValueOrDefault(); } + set { _incrementalMenuComplete = value; } + } + internal SwitchParameter? _incrementalMenuComplete; + [Parameter(ParameterSetName = "OptionsSet")] public int ExtraPromptLineCount { diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 40a8be45b..16e732319 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -22,6 +22,7 @@ public partial class PSConsoleReadLine private int _tabCommandCount; private CommandCompletion _tabCompletions; private Runspace _runspace; + private int _InvokeMenuCompleteCounter; // Stub helper method so completion can be mocked [ExcludeFromCodeCoverage] @@ -429,8 +430,16 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int selectedItem = 0; bool undo = false; - DoReplacementForCompletion(matches[0], completions); - + if (Options.IncrementalMenuComplete) { + VisualSelectionCommon(() => { + DoReplacementForCompletion(matches[0], completions); + ExchangePointAndMark(); + }); + } + else { + DoReplacementForCompletion(matches[0], completions); + } + // Recompute end of buffer coordinates as the replacement could have // added a line. var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); @@ -484,15 +493,39 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel undo = true; processingKeys = false; } + else if (Options.IncrementalMenuComplete && (nextKey == Keys.Space || nextKey == Keys.Enter)) { + if (nextKey == Keys.Space) + PrependQueuedKeys(nextKey); + else + PrependQueuedKeys(Keys.Backspace); + processingKeys = false; + ExchangePointAndMark(); + SetMark(); + } else { PrependQueuedKeys(nextKey); processingKeys = false; + if (Options.IncrementalMenuComplete) { + //TODO: here we need to request another MenuSelect call. Need more efficient way! + // Dumb variant - add another Keys.CtrlSpace key to key queue + if (nextKey != Keys.Backspace) { + //_queuedKeys.Enqueue(Keys.CtrlSpace); + _InvokeMenuCompleteCounter = 2; + } + } } if (selectedItem != previousItem) { - DoReplacementForCompletion(matches[selectedItem], completions); + if (Options.IncrementalMenuComplete) { + SetMark(); + DoReplacementForCompletion(matches[selectedItem], completions); + ExchangePointAndMark(); + } + else { + DoReplacementForCompletion(matches[selectedItem], completions); + } endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); menuAreaTop = endBufferCoords.Y + 1; diff --git a/PSReadLine/Options.cs b/PSReadLine/Options.cs index 4382f9abe..f45b4791b 100644 --- a/PSReadLine/Options.cs +++ b/PSReadLine/Options.cs @@ -113,6 +113,10 @@ private void SetOptionsInternal(SetPSReadlineOption options) { Options.ShowToolTips = options.ShowToolTips; } + if (options._incrementalMenuComplete.HasValue) + { + Options.IncrementalMenuComplete = options.IncrementalMenuComplete; + } if (options._extraPromptLineCount.HasValue) { Options.ExtraPromptLineCount = options.ExtraPromptLineCount; diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 50333f615..4a2e91608 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -432,6 +432,12 @@ private string InputLoop() { _moveToLineCommandCount = 0; } + if (_InvokeMenuCompleteCounter > 0) { + _InvokeMenuCompleteCounter--; + if (_InvokeMenuCompleteCounter == 0) { + MenuComplete(); + } + } } } @@ -567,6 +573,7 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) _recallHistoryCommandCount = 0; _visualSelectionCommandCount = 0; _hashedHistory = null; + _InvokeMenuCompleteCounter = 0; if (_getNextHistoryIndex > 0) { diff --git a/UnitTestPSReadLine/OptionsTest.cs b/UnitTestPSReadLine/OptionsTest.cs index 9f573f7ba..c8e0dcb5d 100644 --- a/UnitTestPSReadLine/OptionsTest.cs +++ b/UnitTestPSReadLine/OptionsTest.cs @@ -101,6 +101,7 @@ public void TestUselessStuffForBetterCoverage() + options.BellStyle.GetHashCode() + options.ExtraPromptLineCount.GetHashCode() + options.ShowToolTips.GetHashCode() + + options.IncrementalMenuComplete.GetHashCode() + options.CompletionQueryItems.GetHashCode() + options.EmphasisBackgroundColor.GetHashCode() + options.EmphasisForegroundColor.GetHashCode() From d4d4f6705b56740e2f933dc47a070a5a1b21814d Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 10 Mar 2017 13:59:19 +0300 Subject: [PATCH 02/33] Fixed bug when long complete substitution changes to short and stay artifact at the end of buffer --- PSReadLine/Completion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 16e732319..a30f21865 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -452,13 +452,13 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that. _initialY -= (previousMenuTop - menuAreaTop); PlaceCursor(); - previousMenuTop = menuAreaTop; int previousItem = selectedItem; bool processingKeys = true; while (processingKeys) { + previousMenuTop = menuAreaTop; var nextKey = ReadKey(); if (nextKey == Keys.RightArrow) { From 3169977e03fe87d2f9e29aa6fb52c34ede61f8d1 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 10 Mar 2017 14:05:38 +0300 Subject: [PATCH 03/33] Implemented IncrementalMenuComplete using cached Completitions --- PSReadLine/Completion.cs | 8 +++---- PSReadLine/ReadLine.cs | 46 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index a30f21865..e9e390101 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -492,6 +492,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { undo = true; processingKeys = false; + _visualSelectionCommandCount = 0; } else if (Options.IncrementalMenuComplete && (nextKey == Keys.Space || nextKey == Keys.Enter)) { if (nextKey == Keys.Space) @@ -507,12 +508,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel PrependQueuedKeys(nextKey); processingKeys = false; if (Options.IncrementalMenuComplete) { - //TODO: here we need to request another MenuSelect call. Need more efficient way! - // Dumb variant - add another Keys.CtrlSpace key to key queue - if (nextKey != Keys.Backspace) { - //_queuedKeys.Enqueue(Keys.CtrlSpace); + //here we need to request another MenuSelect call. + if (nextKey != Keys.Backspace) _InvokeMenuCompleteCounter = 2; - } } } diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 4a2e91608..d1d9d86b7 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -392,7 +392,7 @@ private string InputLoop() _yankLastArgCommandCount = 0; _yankLastArgState = null; } - if (tabCommandCount == _tabCommandCount) + if (tabCommandCount == _tabCommandCount && _InvokeMenuCompleteCounter == 0) // reset _tabCompletitions only if not awaits next IncrementalMenuComplete call { // Reset tab command count if it didn't change _tabCommandCount = 0; @@ -432,10 +432,48 @@ private string InputLoop() { _moveToLineCommandCount = 0; } - if (_InvokeMenuCompleteCounter > 0) { + if (_InvokeMenuCompleteCounter > 0) + { _InvokeMenuCompleteCounter--; - if (_InvokeMenuCompleteCounter == 0) { - MenuComplete(); + //here we need to filter out completions.CompletionMatches + if (_InvokeMenuCompleteCounter == 0 && _tabCompletions != null && _tabCompletions.CompletionMatches.Count > 0) + { + // set current replacement length + _tabCompletions.ReplacementLength = _current - _tabCompletions.ReplacementIndex; + // useless check ? + if (_buffer.Length <= _tabCompletions.ReplacementIndex + _tabCompletions.ReplacementLength) + { + string textToComplete = _buffer.ToString().Substring(_tabCompletions.ReplacementIndex, _tabCompletions.ReplacementLength); + /* + //var filtered = from item in _tabCompletions.CompletionMatches + // where (GetUnquotedText(item.CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) + // select item; + System.Collections.ObjectModel.Collection filtered = new System.Collections.ObjectModel.Collection(); + foreach (var item in _tabCompletions.CompletionMatches) + { + if (GetUnquotedText(item.CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) + filtered.Add(item); + } + // invalid constructor. wtf ???? + _tabCompletions = new CommandCompletion(filtered, 0, _tabCompletions.ReplacementIndex, _tabCompletions.ReplacementLength); + */ + int index = 0; + while (index < _tabCompletions.CompletionMatches.Count) + { + if (GetUnquotedText(_tabCompletions.CompletionMatches[index].CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) + index++; + else + _tabCompletions.CompletionMatches.RemoveAt(index); + } + _tabCompletions.CurrentMatchIndex = 0; + + if (_tabCompletions.CompletionMatches.Count > 0) + { + PossibleCompletionsImpl(_tabCompletions, true); + if (_InvokeMenuCompleteCounter > 0) + _InvokeMenuCompleteCounter = 1; + } + } } } } From 926bb993954a0fcf682b229af048d0240780a019 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Tue, 21 Mar 2017 13:20:05 +0300 Subject: [PATCH 04/33] IncrementalMenuComplete using cached Completitions, changed filtering method to Add() instead of Remove() --- PSReadLine/Completion.cs | 8 +++++--- PSReadLine/ReadLine.cs | 24 ++++++------------------ 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index e9e390101..e9f50fa6e 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -430,16 +430,18 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int selectedItem = 0; bool undo = false; - if (Options.IncrementalMenuComplete) { + if (Options.IncrementalMenuComplete) + { VisualSelectionCommon(() => { DoReplacementForCompletion(matches[0], completions); ExchangePointAndMark(); }); } - else { + else + { DoReplacementForCompletion(matches[0], completions); } - + // Recompute end of buffer coordinates as the replacement could have // added a line. var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index d1d9d86b7..57013b472 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -444,26 +444,14 @@ private string InputLoop() if (_buffer.Length <= _tabCompletions.ReplacementIndex + _tabCompletions.ReplacementLength) { string textToComplete = _buffer.ToString().Substring(_tabCompletions.ReplacementIndex, _tabCompletions.ReplacementLength); - /* - //var filtered = from item in _tabCompletions.CompletionMatches - // where (GetUnquotedText(item.CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) - // select item; - System.Collections.ObjectModel.Collection filtered = new System.Collections.ObjectModel.Collection(); - foreach (var item in _tabCompletions.CompletionMatches) + + CompletionResult[] tmpMatches = new CompletionResult[_tabCompletions.CompletionMatches.Count]; + _tabCompletions.CompletionMatches.CopyTo(tmpMatches, 0); + _tabCompletions.CompletionMatches.Clear(); + foreach (CompletionResult item in tmpMatches) { if (GetUnquotedText(item.CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) - filtered.Add(item); - } - // invalid constructor. wtf ???? - _tabCompletions = new CommandCompletion(filtered, 0, _tabCompletions.ReplacementIndex, _tabCompletions.ReplacementLength); - */ - int index = 0; - while (index < _tabCompletions.CompletionMatches.Count) - { - if (GetUnquotedText(_tabCompletions.CompletionMatches[index].CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) - index++; - else - _tabCompletions.CompletionMatches.RemoveAt(index); + _tabCompletions.CompletionMatches.Add(item); } _tabCompletions.CurrentMatchIndex = 0; From 7819c4929f9ec7e8e08914b91704aa3a13d1203c Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Wed, 22 Mar 2017 10:43:50 +0300 Subject: [PATCH 05/33] Save/restore usermark while MenuComplete, Key.Backspace in MenuComplete feature --- PSReadLine/Completion.cs | 29 ++++++++++++++++++++++------- PSReadLine/ReadLine.cs | 13 ++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index e9f50fa6e..7c361363e 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -23,6 +23,7 @@ public partial class PSConsoleReadLine private CommandCompletion _tabCompletions; private Runspace _runspace; private int _InvokeMenuCompleteCounter; + private int _InvokeMenuCompleteUserMark; // Stub helper method so completion can be mocked [ExcludeFromCodeCoverage] @@ -430,6 +431,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int selectedItem = 0; bool undo = false; + _InvokeMenuCompleteUserMark = _singleton._mark; + if (Options.IncrementalMenuComplete) { VisualSelectionCommon(() => { @@ -495,24 +498,36 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel undo = true; processingKeys = false; _visualSelectionCommandCount = 0; + _singleton._mark = _InvokeMenuCompleteUserMark; + } + else if (nextKey == Keys.Backspace) + { + // Esc alternative: + // stay string beginning as in current replacement but don't replace to full string + CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); + DoReplacementForCompletion(r, completions); + processingKeys = false; + _visualSelectionCommandCount = 0; + _singleton._mark = _InvokeMenuCompleteUserMark; + Render(); } else if (Options.IncrementalMenuComplete && (nextKey == Keys.Space || nextKey == Keys.Enter)) { + ExchangePointAndMark(); + processingKeys = false; + _singleton._mark = _InvokeMenuCompleteUserMark; + _visualSelectionCommandCount = 0; if (nextKey == Keys.Space) PrependQueuedKeys(nextKey); else - PrependQueuedKeys(Keys.Backspace); - processingKeys = false; - ExchangePointAndMark(); - SetMark(); + Render(); } else { PrependQueuedKeys(nextKey); processingKeys = false; if (Options.IncrementalMenuComplete) { - //here we need to request another MenuSelect call. - if (nextKey != Keys.Backspace) - _InvokeMenuCompleteCounter = 2; + //here we need to request another MenuSelect call. + _InvokeMenuCompleteCounter = 2; } } diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 57013b472..1959de406 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -436,13 +436,16 @@ private string InputLoop() { _InvokeMenuCompleteCounter--; //here we need to filter out completions.CompletionMatches - if (_InvokeMenuCompleteCounter == 0 && _tabCompletions != null && _tabCompletions.CompletionMatches.Count > 0) + if (_InvokeMenuCompleteCounter == 0) { - // set current replacement length - _tabCompletions.ReplacementLength = _current - _tabCompletions.ReplacementIndex; - // useless check ? - if (_buffer.Length <= _tabCompletions.ReplacementIndex + _tabCompletions.ReplacementLength) + _singleton._mark = _InvokeMenuCompleteUserMark; + if (_tabCompletions != null && _tabCompletions.CompletionMatches.Count > 0) { + // set current replacement length + _tabCompletions.ReplacementLength = _current - _tabCompletions.ReplacementIndex; + // useless check ? it never be true? + if (_buffer.Length < _tabCompletions.ReplacementIndex + _tabCompletions.ReplacementLength) + _tabCompletions.ReplacementLength = _buffer.Length - _tabCompletions.ReplacementIndex; string textToComplete = _buffer.ToString().Substring(_tabCompletions.ReplacementIndex, _tabCompletions.ReplacementLength); CompletionResult[] tmpMatches = new CompletionResult[_tabCompletions.CompletionMatches.Count]; From cba5d6bdb8ec772327f4e697410b1e5f62787728 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Wed, 22 Mar 2017 10:55:56 +0300 Subject: [PATCH 06/33] Removed IncrementalMenuComplete Option --- PSReadLine/Cmdlets.cs | 15 --------------- PSReadLine/Completion.cs | 32 +++++++++---------------------- PSReadLine/Options.cs | 4 ---- UnitTestPSReadLine/OptionsTest.cs | 1 - 4 files changed, 9 insertions(+), 43 deletions(-) diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs index 1f256815c..001435ea9 100644 --- a/PSReadLine/Cmdlets.cs +++ b/PSReadLine/Cmdlets.cs @@ -109,11 +109,6 @@ public class PSConsoleReadlineOptions /// public const bool DefaultShowToolTips = false; - /// - /// Enables Incremental MenuComplete style - /// - public const bool DefaultIncrementalMenuComplete = false; - /// /// When ringing the bell, what frequency do we use? /// @@ -149,7 +144,6 @@ public PSConsoleReadlineOptions(string hostName) MaximumKillRingCount = DefaultMaximumKillRingCount; HistorySearchCursorMovesToEnd = DefaultHistorySearchCursorMovesToEnd; ShowToolTips = DefaultShowToolTips; - IncrementalMenuComplete = DefaultIncrementalMenuComplete; DingDuration = DefaultDingDuration; DingTone = DefaultDingTone; BellStyle = DefaultBellStyle; @@ -228,7 +222,6 @@ public PSConsoleReadlineOptions(string hostName) public int MaximumKillRingCount { get; set; } public bool HistorySearchCursorMovesToEnd { get; set; } public bool ShowToolTips { get; set; } - public bool IncrementalMenuComplete { get; set; } public int DingTone { get; set; } public int CompletionQueryItems { get; set; } public string WordDelimiters { get; set; } @@ -500,14 +493,6 @@ public SwitchParameter ShowToolTips } internal SwitchParameter? _showToolTips; - [Parameter(ParameterSetName = "OptionsSet")] - public SwitchParameter IncrementalMenuComplete - { - get { return _incrementalMenuComplete.GetValueOrDefault(); } - set { _incrementalMenuComplete = value; } - } - internal SwitchParameter? _incrementalMenuComplete; - [Parameter(ParameterSetName = "OptionsSet")] public int ExtraPromptLineCount { diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 7c361363e..c439b2436 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -433,17 +433,10 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _InvokeMenuCompleteUserMark = _singleton._mark; - if (Options.IncrementalMenuComplete) - { - VisualSelectionCommon(() => { - DoReplacementForCompletion(matches[0], completions); - ExchangePointAndMark(); - }); - } - else - { + VisualSelectionCommon(() => { DoReplacementForCompletion(matches[0], completions); - } + ExchangePointAndMark(); + }); // Recompute end of buffer coordinates as the replacement could have // added a line. @@ -511,7 +504,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _singleton._mark = _InvokeMenuCompleteUserMark; Render(); } - else if (Options.IncrementalMenuComplete && (nextKey == Keys.Space || nextKey == Keys.Enter)) { + else if (nextKey == Keys.Space || nextKey == Keys.Enter) { ExchangePointAndMark(); processingKeys = false; _singleton._mark = _InvokeMenuCompleteUserMark; @@ -525,22 +518,15 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { PrependQueuedKeys(nextKey); processingKeys = false; - if (Options.IncrementalMenuComplete) { - //here we need to request another MenuSelect call. - _InvokeMenuCompleteCounter = 2; - } + //here we need to request another MenuSelect call. + _InvokeMenuCompleteCounter = 2; } if (selectedItem != previousItem) { - if (Options.IncrementalMenuComplete) { - SetMark(); - DoReplacementForCompletion(matches[selectedItem], completions); - ExchangePointAndMark(); - } - else { - DoReplacementForCompletion(matches[selectedItem], completions); - } + SetMark(); + DoReplacementForCompletion(matches[selectedItem], completions); + ExchangePointAndMark(); endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); menuAreaTop = endBufferCoords.Y + 1; diff --git a/PSReadLine/Options.cs b/PSReadLine/Options.cs index f45b4791b..4382f9abe 100644 --- a/PSReadLine/Options.cs +++ b/PSReadLine/Options.cs @@ -113,10 +113,6 @@ private void SetOptionsInternal(SetPSReadlineOption options) { Options.ShowToolTips = options.ShowToolTips; } - if (options._incrementalMenuComplete.HasValue) - { - Options.IncrementalMenuComplete = options.IncrementalMenuComplete; - } if (options._extraPromptLineCount.HasValue) { Options.ExtraPromptLineCount = options.ExtraPromptLineCount; diff --git a/UnitTestPSReadLine/OptionsTest.cs b/UnitTestPSReadLine/OptionsTest.cs index c8e0dcb5d..9f573f7ba 100644 --- a/UnitTestPSReadLine/OptionsTest.cs +++ b/UnitTestPSReadLine/OptionsTest.cs @@ -101,7 +101,6 @@ public void TestUselessStuffForBetterCoverage() + options.BellStyle.GetHashCode() + options.ExtraPromptLineCount.GetHashCode() + options.ShowToolTips.GetHashCode() - + options.IncrementalMenuComplete.GetHashCode() + options.CompletionQueryItems.GetHashCode() + options.EmphasisBackgroundColor.GetHashCode() + options.EmphasisForegroundColor.GetHashCode() From efe77b70f4d436ffd45caa566d1ab3f513aa37b4 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Thu, 23 Mar 2017 09:04:26 +0300 Subject: [PATCH 07/33] fixed bug with suggestions when something already selected --- PSReadLine/Completion.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index c439b2436..29a6ec8df 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -433,10 +433,11 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _InvokeMenuCompleteUserMark = _singleton._mark; - VisualSelectionCommon(() => { - DoReplacementForCompletion(matches[0], completions); - ExchangePointAndMark(); - }); + SetMark(); + _visualSelectionCommandCount += 1; + DoReplacementForCompletion(matches[0], completions); + ExchangePointAndMark(); + Render(); // Recompute end of buffer coordinates as the replacement could have // added a line. From bdfc4d341bf5171dbab549b068867e1361fd0e9b Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 24 Mar 2017 17:50:56 +0300 Subject: [PATCH 08/33] IncrementalMenuComplete inside of PossibleCompletionsImpl() only --- PSReadLine/Completion.cs | 153 +++++++++++++++++++++++++++------------ PSReadLine/ReadLine.cs | 38 +--------- 2 files changed, 106 insertions(+), 85 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 29a6ec8df..6f1e480f3 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -22,8 +22,6 @@ public partial class PSConsoleReadLine private int _tabCommandCount; private CommandCompletion _tabCompletions; private Runspace _runspace; - private int _InvokeMenuCompleteCounter; - private int _InvokeMenuCompleteUserMark; // Stub helper method so completion can be mocked [ExcludeFromCodeCoverage] @@ -323,37 +321,19 @@ private static string HandleNewlinesForPossibleCompletions(string s) return s; } - private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSelect) + private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.Collection matches, IConsole console, bool showToolTips, out int MenuColumnWidth, out int DisplayRows) { - if (completions == null || completions.CompletionMatches.Count == 0) - { - Ding(); - return; - } - - if (completions.CompletionMatches.Count >= _options.CompletionQueryItems) - { - if (!PromptYesOrNo(string.Format(CultureInfo.CurrentCulture, PSReadLineResources.DisplayAllPossibilities, completions.CompletionMatches.Count))) - { - return; - } - } - - var matches = completions.CompletionMatches; var minColWidth = matches.Max(c => c.ListItemText.Length); minColWidth += 2; - var menuColumnWidth = minColWidth; - - int displayRows; - var bufferWidth = _console.BufferWidth; + var bufferWidth = console.BufferWidth; ConsoleBufferBuilder cb; - if (Options.ShowToolTips) + if (showToolTips) { const string seperator = "- "; var maxTooltipWidth = bufferWidth - minColWidth - seperator.Length; - displayRows = matches.Count; - cb = new ConsoleBufferBuilder(displayRows * bufferWidth, _console); + DisplayRows = matches.Count; + cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); for (int index = 0; index < matches.Count; index++) { var match = matches[index]; @@ -378,19 +358,19 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel cb.Append(' ', spacesNeeded); } } - menuColumnWidth = bufferWidth; + MenuColumnWidth = bufferWidth; } else { var screenColumns = bufferWidth; var displayColumns = Math.Max(1, screenColumns / minColWidth); - displayRows = (completions.CompletionMatches.Count + displayColumns - 1) / displayColumns; - cb = new ConsoleBufferBuilder(displayRows * bufferWidth, _console); - for (var row = 0; row < displayRows; row++) + DisplayRows = (matches.Count + displayColumns - 1) / displayColumns; + cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); + for (var row = 0; row < DisplayRows; row++) { for (var col = 0; col < displayColumns; col++) { - var index = row + (displayRows * col); + var index = row + (DisplayRows * col); if (index >= matches.Count) break; var match = matches[index]; @@ -406,9 +386,33 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel cb.Append(' ', spacesNeeded); } } + MenuColumnWidth = minColWidth; + } + + return cb.ToArray(); + } + + private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSelect) + { + if (completions == null || completions.CompletionMatches.Count == 0) + { + Ding(); + return; + } + + if (completions.CompletionMatches.Count >= _options.CompletionQueryItems) + { + if (!PromptYesOrNo(string.Format(CultureInfo.CurrentCulture, PSReadLineResources.DisplayAllPossibilities, completions.CompletionMatches.Count))) + { + return; + } } - var menuBuffer = cb.ToArray(); + var matches = completions.CompletionMatches; + + int menuColumnWidth; + int displayRows; + var menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); if (menuSelect) { @@ -431,13 +435,11 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int selectedItem = 0; bool undo = false; - _InvokeMenuCompleteUserMark = _singleton._mark; - - SetMark(); + int savedUserMark = _singleton._mark; _visualSelectionCommandCount += 1; + + string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, _current - completions.ReplacementIndex); DoReplacementForCompletion(matches[0], completions); - ExchangePointAndMark(); - Render(); // Recompute end of buffer coordinates as the replacement could have // added a line. @@ -458,8 +460,22 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel while (processingKeys) { previousMenuTop = menuAreaTop; + SetMark(); // set mark to cursor + int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + // set cursor to the end of UserCompletion but in real completion (because of .\ and so on) + _current = completions.ReplacementIndex + curPos + userCompletionText.Length; + Render(); + var nextKey = ReadKey(); - if (nextKey == Keys.RightArrow) + if (nextKey == Keys.Home) + { + selectedItem = 0; + } + else if (nextKey == Keys.End) + { + selectedItem = matches.Count - 1; + } + else if (nextKey == Keys.RightArrow) { selectedItem = Math.Min(selectedItem + displayRows, matches.Count - 1); } @@ -475,6 +491,14 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { selectedItem = Math.Max(selectedItem - 1, 0); } + else if (nextKey == Keys.PageDown) + { + selectedItem = Math.Min(selectedItem + displayRows - (selectedItem % displayRows) - 1, matches.Count - 1); + } + else if (nextKey == Keys.PageUp) + { + selectedItem = Math.Max(selectedItem - (selectedItem % displayRows), 0); + } else if (nextKey == Keys.Tab) { selectedItem = (selectedItem + 1) % matches.Count; @@ -492,7 +516,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel undo = true; processingKeys = false; _visualSelectionCommandCount = 0; - _singleton._mark = _InvokeMenuCompleteUserMark; + _singleton._mark = savedUserMark; } else if (nextKey == Keys.Backspace) { @@ -502,32 +526,65 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel DoReplacementForCompletion(r, completions); processingKeys = false; _visualSelectionCommandCount = 0; - _singleton._mark = _InvokeMenuCompleteUserMark; + _singleton._mark = savedUserMark; Render(); } - else if (nextKey == Keys.Space || nextKey == Keys.Enter) { + else if (nextKey == Keys.Space || nextKey == Keys.Enter) + { ExchangePointAndMark(); processingKeys = false; - _singleton._mark = _InvokeMenuCompleteUserMark; + _singleton._mark = savedUserMark; _visualSelectionCommandCount = 0; - if (nextKey == Keys.Space) + if (nextKey == Keys.Space) { + int cursorAdjustment = 0; + if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) + userCompletionText = GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment); + _current -= cursorAdjustment; PrependQueuedKeys(nextKey); + } else Render(); } + else if (nextKey.KeyChar != 0) + { + userCompletionText += nextKey.KeyChar; + string replacementText = matches[selectedItem].CompletionText; + // filter out matches and redraw menu + WriteBlankLines(displayRows, menuAreaTop); + matches = new System.Collections.ObjectModel.Collection(); + + foreach (CompletionResult item in completions.CompletionMatches) + { + if (item.ListItemText.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) || + GetUnquotedText(item.CompletionText, false).StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) + ) + matches.Add(item); + } + if (matches.Count > 0) + { + menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); + previousItem = selectedItem = 0; + InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); + _console.WriteBufferLines(menuBuffer, ref menuAreaTop); + DoReplacementForCompletion(matches[0], completions); + } + else + { + CompletionResult r = new CompletionResult(replacementText.Substring(0, _current - completions.ReplacementIndex)+nextKey.KeyChar); + DoReplacementForCompletion(r, completions); + _singleton._mark = savedUserMark; + _visualSelectionCommandCount = 0; + processingKeys = false; + } + } else { - PrependQueuedKeys(nextKey); - processingKeys = false; - //here we need to request another MenuSelect call. - _InvokeMenuCompleteCounter = 2; + Ding(); } if (selectedItem != previousItem) { - SetMark(); DoReplacementForCompletion(matches[selectedItem], completions); - ExchangePointAndMark(); endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); menuAreaTop = endBufferCoords.Y + 1; diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index 1959de406..50333f615 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -392,7 +392,7 @@ private string InputLoop() _yankLastArgCommandCount = 0; _yankLastArgState = null; } - if (tabCommandCount == _tabCommandCount && _InvokeMenuCompleteCounter == 0) // reset _tabCompletitions only if not awaits next IncrementalMenuComplete call + if (tabCommandCount == _tabCommandCount) { // Reset tab command count if it didn't change _tabCommandCount = 0; @@ -432,41 +432,6 @@ private string InputLoop() { _moveToLineCommandCount = 0; } - if (_InvokeMenuCompleteCounter > 0) - { - _InvokeMenuCompleteCounter--; - //here we need to filter out completions.CompletionMatches - if (_InvokeMenuCompleteCounter == 0) - { - _singleton._mark = _InvokeMenuCompleteUserMark; - if (_tabCompletions != null && _tabCompletions.CompletionMatches.Count > 0) - { - // set current replacement length - _tabCompletions.ReplacementLength = _current - _tabCompletions.ReplacementIndex; - // useless check ? it never be true? - if (_buffer.Length < _tabCompletions.ReplacementIndex + _tabCompletions.ReplacementLength) - _tabCompletions.ReplacementLength = _buffer.Length - _tabCompletions.ReplacementIndex; - string textToComplete = _buffer.ToString().Substring(_tabCompletions.ReplacementIndex, _tabCompletions.ReplacementLength); - - CompletionResult[] tmpMatches = new CompletionResult[_tabCompletions.CompletionMatches.Count]; - _tabCompletions.CompletionMatches.CopyTo(tmpMatches, 0); - _tabCompletions.CompletionMatches.Clear(); - foreach (CompletionResult item in tmpMatches) - { - if (GetUnquotedText(item.CompletionText, true).StartsWith(textToComplete, StringComparison.InvariantCultureIgnoreCase)) - _tabCompletions.CompletionMatches.Add(item); - } - _tabCompletions.CurrentMatchIndex = 0; - - if (_tabCompletions.CompletionMatches.Count > 0) - { - PossibleCompletionsImpl(_tabCompletions, true); - if (_InvokeMenuCompleteCounter > 0) - _InvokeMenuCompleteCounter = 1; - } - } - } - } } } @@ -602,7 +567,6 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) _recallHistoryCommandCount = 0; _visualSelectionCommandCount = 0; _hashedHistory = null; - _InvokeMenuCompleteCounter = 0; if (_getNextHistoryIndex > 0) { From 61837844a4e201f6b21fe9d3b43c81ce8ae723e3 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 24 Mar 2017 21:28:23 +0300 Subject: [PATCH 09/33] Backspace/Delete key behaviour more natural, symbols that do matches==null are forbidden --- PSReadLine/Completion.cs | 66 +++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 6f1e480f3..fa00d934b 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -439,6 +439,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _visualSelectionCommandCount += 1; string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, _current - completions.ReplacementIndex); + int userInitialCompletionLength = userCompletionText.Length; DoReplacementForCompletion(matches[0], completions); // Recompute end of buffer coordinates as the replacement could have @@ -518,7 +519,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _visualSelectionCommandCount = 0; _singleton._mark = savedUserMark; } - else if (nextKey == Keys.Backspace) + else if (nextKey == Keys.Delete) { // Esc alternative: // stay string beginning as in current replacement but don't replace to full string @@ -545,36 +546,53 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel else Render(); } - else if (nextKey.KeyChar != 0) + else if (nextKey == Keys.Backspace || nextKey.KeyChar != 0) { - userCompletionText += nextKey.KeyChar; string replacementText = matches[selectedItem].CompletionText; - // filter out matches and redraw menu - WriteBlankLines(displayRows, menuAreaTop); - matches = new System.Collections.ObjectModel.Collection(); - foreach (CompletionResult item in completions.CompletionMatches) + if (userInitialCompletionLength == userCompletionText.Length && nextKey == Keys.Backspace) { - if (item.ListItemText.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) || - GetUnquotedText(item.CompletionText, false).StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) - ) - matches.Add(item); - } - if (matches.Count > 0) - { - menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); - previousItem = selectedItem = 0; - InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); - _console.WriteBufferLines(menuBuffer, ref menuAreaTop); - DoReplacementForCompletion(matches[0], completions); + _current = _mark; + Ding(); } else { - CompletionResult r = new CompletionResult(replacementText.Substring(0, _current - completions.ReplacementIndex)+nextKey.KeyChar); - DoReplacementForCompletion(r, completions); - _singleton._mark = savedUserMark; - _visualSelectionCommandCount = 0; - processingKeys = false; + if (nextKey == Keys.Backspace) + { + userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); + } + else + { + userCompletionText += nextKey.KeyChar; + } + + // filter out matches and redraw menu + + var tmpMatches = new System.Collections.ObjectModel.Collection(); + + foreach (CompletionResult item in completions.CompletionMatches) + { + if (item.ListItemText.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) || + GetUnquotedText(item.CompletionText, false).StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) + ) + tmpMatches.Add(item); + } + if (tmpMatches.Count > 0) + { + WriteBlankLines(displayRows, menuAreaTop); + matches = tmpMatches; + previousItem = selectedItem = 0; + menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); + InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); + _console.WriteBufferLines(menuBuffer, ref menuAreaTop); + DoReplacementForCompletion(matches[0], completions); + } + else + { + _current = _mark; + Ding(); + userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); + } } } else From 0545ae6002867c9b3a0b71bc926603803f24a5cf Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 24 Mar 2017 22:12:58 +0300 Subject: [PATCH 10/33] Small fix --- PSReadLine/Completion.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index fa00d934b..e96e43008 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -597,6 +597,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else { + _current = _mark; Ding(); } From f82ac36199c0b4f192ff5dd2921fa80654120108 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Mon, 27 Mar 2017 11:49:30 +0300 Subject: [PATCH 11/33] * Fixed bug in ExchangePointAndMark() + Optimized Render() calls * Fixed Keys.Delete behaviour on empty completition --- PSReadLine/Completion.cs | 77 ++++++++++++++++++++-------------------- PSReadLine/KillYank.cs | 2 +- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index e96e43008..626aa0f4a 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -448,24 +448,36 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel var menuAreaTop = endBufferCoords.Y + 1; var previousMenuTop = menuAreaTop; - InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows); - _console.WriteBufferLines(menuBuffer, ref menuAreaTop); - // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that. _initialY -= (previousMenuTop - menuAreaTop); - PlaceCursor(); - int previousItem = selectedItem; + int previousItem = -1; bool processingKeys = true; while (processingKeys) { + if (selectedItem != previousItem) + { + int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + // set mark to the end of UserCompletion but in real completion (because of .\ and so on) + _mark = completions.ReplacementIndex + curPos + userCompletionText.Length; + DoReplacementForCompletion(matches[selectedItem], completions); + ExchangePointAndMark(); + + endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); + menuAreaTop = endBufferCoords.Y + 1; + if (previousItem != -1) + InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); + InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows); + _console.WriteBufferLines(menuBuffer, ref menuAreaTop); + previousItem = selectedItem; + + if (previousMenuTop > menuAreaTop) + { + WriteBlankLines(previousMenuTop - menuAreaTop, menuAreaTop + displayRows); + } + } previousMenuTop = menuAreaTop; - SetMark(); // set mark to cursor - int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); - // set cursor to the end of UserCompletion but in real completion (because of .\ and so on) - _current = completions.ReplacementIndex + curPos + userCompletionText.Length; - Render(); var nextKey = ReadKey(); if (nextKey == Keys.Home) @@ -523,8 +535,15 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { // Esc alternative: // stay string beginning as in current replacement but don't replace to full string - CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); - DoReplacementForCompletion(r, completions); + if (_current == completions.ReplacementIndex) + { + undo = true; + } + else + { + CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); + DoReplacementForCompletion(r, completions); + } processingKeys = false; _visualSelectionCommandCount = 0; _singleton._mark = savedUserMark; @@ -534,8 +553,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { ExchangePointAndMark(); processingKeys = false; - _singleton._mark = savedUserMark; _visualSelectionCommandCount = 0; + _singleton._mark = savedUserMark; if (nextKey == Keys.Space) { int cursorAdjustment = 0; if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) @@ -552,7 +571,6 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (userInitialCompletionLength == userCompletionText.Length && nextKey == Keys.Backspace) { - _current = _mark; Ding(); } else @@ -569,11 +587,13 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // filter out matches and redraw menu var tmpMatches = new System.Collections.ObjectModel.Collection(); - foreach (CompletionResult item in completions.CompletionMatches) { + string unQuoted = GetUnquotedText(item.CompletionText, false); + if (item.CompletionText.StartsWith("& ")) // mask special cases + unQuoted = GetUnquotedText(item.CompletionText.Substring(2), false); if (item.ListItemText.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) || - GetUnquotedText(item.CompletionText, false).StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) + unQuoted.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) ) tmpMatches.Add(item); } @@ -581,15 +601,12 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { WriteBlankLines(displayRows, menuAreaTop); matches = tmpMatches; - previousItem = selectedItem = 0; + previousItem = -1; + selectedItem = 0; menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); - InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); - _console.WriteBufferLines(menuBuffer, ref menuAreaTop); - DoReplacementForCompletion(matches[0], completions); } else { - _current = _mark; Ding(); userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); } @@ -597,27 +614,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else { - _current = _mark; Ding(); } - if (selectedItem != previousItem) - { - DoReplacementForCompletion(matches[selectedItem], completions); - - endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); - menuAreaTop = endBufferCoords.Y + 1; - - InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); - InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows); - _console.WriteBufferLines(menuBuffer, ref menuAreaTop); - previousItem = selectedItem; - - if (previousMenuTop > menuAreaTop) - { - WriteBlankLines(previousMenuTop - menuAreaTop, menuAreaTop + displayRows); - } - } } WriteBlankLines(displayRows, menuAreaTop); diff --git a/PSReadLine/KillYank.cs b/PSReadLine/KillYank.cs index 9c73b425d..4a1fc8a32 100644 --- a/PSReadLine/KillYank.cs +++ b/PSReadLine/KillYank.cs @@ -45,7 +45,7 @@ public static void ExchangePointAndMark(ConsoleKeyInfo? key = null, object arg = { var tmp = _singleton._mark; _singleton._mark = _singleton._current; - _singleton._current = tmp; + _singleton._current = Math.Min(tmp, _singleton._buffer.Length); _singleton.PlaceCursor(); } From 68de75e2df18b4f3d19470fa224cb28c9f7a2c97 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 7 Apr 2017 23:21:58 +0300 Subject: [PATCH 12/33] Crash with exotic like 'Set-Content -pad:\11' fixed --- PSReadLine/Completion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 626aa0f4a..c7717fc80 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -438,7 +438,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int savedUserMark = _singleton._mark; _visualSelectionCommandCount += 1; - string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, _current - completions.ReplacementIndex); + string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, Math.Max(_current - completions.ReplacementIndex,0)); int userInitialCompletionLength = userCompletionText.Length; DoReplacementForCompletion(matches[0], completions); From 084edfd35132b1e53b760e4280bbadb90649a1c2 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 14 Apr 2017 14:23:44 +0300 Subject: [PATCH 13/33] Added Completion exiting on period, double backspace --- PSReadLine/Completion.cs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index c7717fc80..03f9ef48b 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -440,6 +440,10 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, Math.Max(_current - completions.ReplacementIndex,0)); int userInitialCompletionLength = userCompletionText.Length; + // Period added to 'done competion' keys if Result type allow dots after it but does not allow inside + bool doneOnPeriod = matches[0].ResultType == CompletionResultType.Namespace || + matches[0].ResultType == CompletionResultType.Property || + matches[0].ResultType == CompletionResultType.Variable; DoReplacementForCompletion(matches[0], completions); // Recompute end of buffer coordinates as the replacement could have @@ -454,8 +458,10 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int previousItem = -1; bool processingKeys = true; + int backspaceCounter = 0; while (processingKeys) { + int _backspaceCounter = backspaceCounter; if (selectedItem != previousItem) { int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); @@ -534,7 +540,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel else if (nextKey == Keys.Delete) { // Esc alternative: - // stay string beginning as in current replacement but don't replace to full string + // leave string beginning as in current replacement but don't replace to full completion if (_current == completions.ReplacementIndex) { undo = true; @@ -549,13 +555,13 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _singleton._mark = savedUserMark; Render(); } - else if (nextKey == Keys.Space || nextKey == Keys.Enter) + else if (nextKey == Keys.Space || nextKey == Keys.Enter || (doneOnPeriod && nextKey == Keys.Period)) { ExchangePointAndMark(); processingKeys = false; _visualSelectionCommandCount = 0; _singleton._mark = savedUserMark; - if (nextKey == Keys.Space) { + if (nextKey == Keys.Space || nextKey == Keys.Period) { int cursorAdjustment = 0; if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) userCompletionText = GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment); @@ -571,7 +577,16 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (userInitialCompletionLength == userCompletionText.Length && nextKey == Keys.Backspace) { - Ding(); + if (backspaceCounter == 0) + { + Ding(); + backspaceCounter++; + } + else + { + // Double Backspace, exit on next cycle + PrependQueuedKeys(Keys.Delete); + } } else { @@ -616,7 +631,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { Ding(); } - + if (backspaceCounter == _backspaceCounter) + backspaceCounter = 0; } WriteBlankLines(displayRows, menuAreaTop); From fcc608b9ce6bcb6831d79970af0c7320de0359ca Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Mon, 17 Apr 2017 11:25:50 +0300 Subject: [PATCH 14/33] Added UnambiguousPrefix on Tab, fixed quoting --- PSReadLine/Completion.cs | 132 +++++++++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 41 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 03f9ef48b..4f119c2ce 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -63,7 +63,8 @@ private static bool IsQuoted(string s) { if (s.Length >= 2) { - var first = s[0]; + //consider possible '& ' prefix + var first = (s.Length > 4 && s.StartsWith("& ")) ? s[2] : s[0]; var last = s[s.Length - 1]; return ((IsSingleQuote(first) && IsSingleQuote(last)) @@ -77,7 +78,9 @@ private static string GetUnquotedText(string s, bool consistentQuoting) { if (!consistentQuoting && IsQuoted(s)) { - s = s.Substring(1, s.Length - 2); + //consider possible '& ' prefix + int startindex = (s.StartsWith("& ")) ? 3 : 1; + s = s.Substring(startindex, s.Length - startindex - 1); } return s; } @@ -104,6 +107,49 @@ public static void MenuComplete(ConsoleKeyInfo? key = null, object arg = null) _singleton.CompleteImpl(key, arg, true); } + private bool IsConsistentQuoting(System.Collections.ObjectModel.Collection matches) + { + int quotedCompletions = matches.Count(match => IsQuoted(match.CompletionText)); + return + quotedCompletions == 0 || + (quotedCompletions == matches.Count && + quotedCompletions == matches.Count( + m => m.CompletionText[0] == matches[0].CompletionText[0])); + } + + private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection matches, out bool ambiguous) + { + // Find the longest unambiguous prefix. This might be the empty + // string, in which case we don't want to remove any of the users input, + // instead we'll immediately show possible completions. + // For the purposes of unambiguous prefix, we'll ignore quotes if + // some completions aren't quoted. + ambiguous = false; + var firstResult = matches[0]; + bool consistentQuoting = IsConsistentQuoting(matches); + + var replacementText = GetUnquotedText(firstResult.CompletionText, consistentQuoting); + foreach (var match in matches.Skip(1)) + { + var matchText = GetUnquotedText(match.CompletionText, consistentQuoting); + for (int i = 0; i < replacementText.Length; i++) + { + if (i == matchText.Length + || char.ToLowerInvariant(replacementText[i]) != char.ToLowerInvariant(matchText[i])) + { + ambiguous = true; + replacementText = replacementText.Substring(0, i); + break; + } + } + if (replacementText.Length == 0) + { + break; + } + } + return replacementText; + } + private void CompleteImpl(ConsoleKeyInfo? key, object arg, bool menuSelect) { var completions = GetCompletions(); @@ -140,39 +186,8 @@ private void CompleteImpl(ConsoleKeyInfo? key, object arg, bool menuSelect) return; } - // Find the longest unambiguous prefix. This might be the empty - // string, in which case we don't want to remove any of the users input, - // instead we'll immediately show possible completions. - // For the purposes of unambiguous prefix, we'll ignore quotes if - // some completions aren't quoted. - var firstResult = completions.CompletionMatches[0]; - int quotedCompletions = completions.CompletionMatches.Count(match => IsQuoted(match.CompletionText)); - bool consistentQuoting = - quotedCompletions == 0 || - (quotedCompletions == completions.CompletionMatches.Count && - quotedCompletions == completions.CompletionMatches.Count( - m => m.CompletionText[0] == firstResult.CompletionText[0])); - - bool ambiguous = false; - var replacementText = GetUnquotedText(firstResult.CompletionText, consistentQuoting); - foreach (var match in completions.CompletionMatches.Skip(1)) - { - var matchText = GetUnquotedText(match.CompletionText, consistentQuoting); - for (int i = 0; i < replacementText.Length; i++) - { - if (i == matchText.Length - || char.ToLowerInvariant(replacementText[i]) != char.ToLowerInvariant(matchText[i])) - { - ambiguous = true; - replacementText = replacementText.Substring(0, i); - break; - } - } - if (replacementText.Length == 0) - { - break; - } - } + bool ambiguous; + var replacementText = GetUnambiguousPrefix(completions.CompletionMatches, out ambiguous); if (replacementText.Length > 0) { @@ -439,6 +454,15 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _visualSelectionCommandCount += 1; string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, Math.Max(_current - completions.ReplacementIndex,0)); + // remove possible first quote + if (userCompletionText.Length > 0 && + ( IsSingleQuote(userCompletionText[0]) + || IsDoubleQuote(userCompletionText[0]) + ) + ) + { + userCompletionText = userCompletionText.Substring(1); + } int userInitialCompletionLength = userCompletionText.Length; // Period added to 'done competion' keys if Result type allow dots after it but does not allow inside bool doneOnPeriod = matches[0].ResultType == CompletionResultType.Namespace || @@ -465,6 +489,12 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (selectedItem != previousItem) { int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + if (userCompletionText.Length == 0 && + ( IsSingleQuote(matches[selectedItem].CompletionText[0]) + || IsDoubleQuote(matches[selectedItem].CompletionText[0]) + ) + ) + curPos++; // set mark to the end of UserCompletion but in real completion (because of .\ and so on) _mark = completions.ReplacementIndex + curPos + userCompletionText.Length; DoReplacementForCompletion(matches[selectedItem], completions); @@ -520,7 +550,20 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else if (nextKey == Keys.Tab) { - selectedItem = (selectedItem + 1) % matches.Count; + bool ambiguous; + var replacementText = GetUnambiguousPrefix(matches, out ambiguous); + int p = replacementText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + if (replacementText.Length > 0 && p >= 0 && replacementText.Length > (p + userCompletionText.Length)) + { + userCompletionText = replacementText.Substring(p); + _current = completions.ReplacementIndex + p + userCompletionText.Length; + Render(); + Ding(); + } + else + { + selectedItem = (selectedItem + 1) % matches.Count; + } } else if (nextKey == Keys.ShiftTab) { @@ -600,15 +643,22 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } // filter out matches and redraw menu - var tmpMatches = new System.Collections.ObjectModel.Collection(); + bool consistentQuoting = IsConsistentQuoting(matches); + string completionFilter = userCompletionText; + // add possible first quote to userCompletionText + if (consistentQuoting && + ( IsSingleQuote(matches[0].CompletionText[0]) + || IsDoubleQuote(matches[0].CompletionText[0]) + ) + ) + { + completionFilter = matches[0].CompletionText[0] + completionFilter; + } foreach (CompletionResult item in completions.CompletionMatches) { - string unQuoted = GetUnquotedText(item.CompletionText, false); - if (item.CompletionText.StartsWith("& ")) // mask special cases - unQuoted = GetUnquotedText(item.CompletionText.Substring(2), false); if (item.ListItemText.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) || - unQuoted.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) + GetUnquotedText(item.CompletionText, consistentQuoting).StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) ) tmpMatches.Add(item); } From 4068a853d84a11720a9b1b7c1dc185d0ac805986 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Mon, 24 Apr 2017 12:20:23 +0300 Subject: [PATCH 15/33] UnambiguousPrefix usage extended to first replacement, generalized exiting on additional keys --- PSReadLine/Completion.cs | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 4f119c2ce..8a5b2e8ef 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -23,6 +23,14 @@ public partial class PSConsoleReadLine private CommandCompletion _tabCompletions; private Runspace _runspace; + private Dictionary doneCompletionKeys = new Dictionary() + { + { CompletionResultType.Variable, new ConsoleKeyInfo[] { Keys.Period } }, + { CompletionResultType.Namespace, new ConsoleKeyInfo[] { Keys.Period } }, + { CompletionResultType.Property, new ConsoleKeyInfo[] { Keys.Period } }, + { CompletionResultType.ProviderContainer, new ConsoleKeyInfo[] { Keys.Backslash, Keys.Slash } }, + }; + // Stub helper method so completion can be mocked [ExcludeFromCodeCoverage] CommandCompletion IPSConsoleReadLineMockableMethods.CompleteInput(string input, int cursorIndex, Hashtable options, System.Management.Automation.PowerShell powershell) @@ -453,7 +461,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int savedUserMark = _singleton._mark; _visualSelectionCommandCount += 1; - string userCompletionText = _buffer.ToString().Substring(completions.ReplacementIndex, Math.Max(_current - completions.ReplacementIndex,0)); + bool ambiguous; + var userCompletionText = GetUnambiguousPrefix(matches, out ambiguous); // remove possible first quote if (userCompletionText.Length > 0 && ( IsSingleQuote(userCompletionText[0]) @@ -464,10 +473,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel userCompletionText = userCompletionText.Substring(1); } int userInitialCompletionLength = userCompletionText.Length; - // Period added to 'done competion' keys if Result type allow dots after it but does not allow inside - bool doneOnPeriod = matches[0].ResultType == CompletionResultType.Namespace || - matches[0].ResultType == CompletionResultType.Property || - matches[0].ResultType == CompletionResultType.Variable; + DoReplacementForCompletion(matches[0], completions); // Recompute end of buffer coordinates as the replacement could have @@ -550,13 +556,12 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else if (nextKey == Keys.Tab) { - bool ambiguous; - var replacementText = GetUnambiguousPrefix(matches, out ambiguous); - int p = replacementText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); - if (replacementText.Length > 0 && p >= 0 && replacementText.Length > (p + userCompletionText.Length)) + string unAmbiguousText = GetUnambiguousPrefix(matches, out ambiguous); + int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { - userCompletionText = replacementText.Substring(p); - _current = completions.ReplacementIndex + p + userCompletionText.Length; + userCompletionText = unAmbiguousText.Substring(userComplPos); + _current = completions.ReplacementIndex + userComplPos + userCompletionText.Length; Render(); Ding(); } @@ -598,21 +603,26 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _singleton._mark = savedUserMark; Render(); } - else if (nextKey == Keys.Space || nextKey == Keys.Enter || (doneOnPeriod && nextKey == Keys.Period)) + else if (nextKey == Keys.Space || nextKey == Keys.Enter || + (doneCompletionKeys.ContainsKey(matches[selectedItem].ResultType) && + doneCompletionKeys[matches[selectedItem].ResultType].Contains(nextKey) + )) { ExchangePointAndMark(); processingKeys = false; _visualSelectionCommandCount = 0; _singleton._mark = savedUserMark; - if (nextKey == Keys.Space || nextKey == Keys.Period) { + if (nextKey == Keys.Enter) + { + Render(); + } + else { int cursorAdjustment = 0; if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) userCompletionText = GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment); _current -= cursorAdjustment; PrependQueuedKeys(nextKey); } - else - Render(); } else if (nextKey == Keys.Backspace || nextKey.KeyChar != 0) { From a4105ac3519a0858454085b21ae5d212ee486175 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 28 Apr 2017 11:44:32 +0300 Subject: [PATCH 16/33] Fixed Menu behaviour at the end of buffer, added ListItemText to unAmbiguous search --- PSReadLine/Completion.cs | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 8a5b2e8ef..9cb9f5b5e 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -125,7 +125,7 @@ private bool IsConsistentQuoting(System.Collections.ObjectModel.Collection m.CompletionText[0] == matches[0].CompletionText[0])); } - private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection matches, out bool ambiguous) + private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection matches, bool useCompletionText, out bool ambiguous) { // Find the longest unambiguous prefix. This might be the empty // string, in which case we don't want to remove any of the users input, @@ -136,10 +136,10 @@ private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection 0) { @@ -353,7 +353,7 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C if (showToolTips) { const string seperator = "- "; - var maxTooltipWidth = bufferWidth - minColWidth - seperator.Length; + var maxTooltipWidth = Math.Max(bufferWidth - minColWidth - seperator.Length, 0); DisplayRows = matches.Count; cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); @@ -462,7 +462,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _visualSelectionCommandCount += 1; bool ambiguous; - var userCompletionText = GetUnambiguousPrefix(matches, out ambiguous); + var userCompletionText = GetUnambiguousPrefix(matches, true, out ambiguous); + if (userCompletionText.Length == 0) + userCompletionText = GetUnambiguousPrefix(matches, false, out ambiguous); // remove possible first quote if (userCompletionText.Length > 0 && ( IsSingleQuote(userCompletionText[0]) @@ -475,16 +477,12 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int userInitialCompletionLength = userCompletionText.Length; DoReplacementForCompletion(matches[0], completions); - // Recompute end of buffer coordinates as the replacement could have // added a line. var endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); var menuAreaTop = endBufferCoords.Y + 1; var previousMenuTop = menuAreaTop; - // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that. - _initialY -= (previousMenuTop - menuAreaTop); - int previousItem = -1; bool processingKeys = true; @@ -508,18 +506,28 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); menuAreaTop = endBufferCoords.Y + 1; + if (previousItem != -1) InvertSelectedCompletion(menuBuffer, previousItem, menuColumnWidth, displayRows); InvertSelectedCompletion(menuBuffer, selectedItem, menuColumnWidth, displayRows); + + var blanklinesCount = previousMenuTop - menuAreaTop; + previousMenuTop = menuAreaTop; _console.WriteBufferLines(menuBuffer, ref menuAreaTop); - previousItem = selectedItem; - if (previousMenuTop > menuAreaTop) + // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that. + _initialY -= (previousMenuTop - menuAreaTop); + // return cursor in place if screen was scrolled + if (previousMenuTop != menuAreaTop) + PlaceCursor(); + + if (previousItem != -1 && blanklinesCount > 0) { - WriteBlankLines(previousMenuTop - menuAreaTop, menuAreaTop + displayRows); + WriteBlankLines(blanklinesCount, menuAreaTop + displayRows); } + previousMenuTop = menuAreaTop; + previousItem = selectedItem; } - previousMenuTop = menuAreaTop; var nextKey = ReadKey(); if (nextKey == Keys.Home) @@ -556,7 +564,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else if (nextKey == Keys.Tab) { - string unAmbiguousText = GetUnambiguousPrefix(matches, out ambiguous); + string unAmbiguousText = GetUnambiguousPrefix(matches, true, out ambiguous); + if (unAmbiguousText.Length == 0) + unAmbiguousText = GetUnambiguousPrefix(matches, false, out ambiguous); int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { From 4726d8e508ca0a70a082ba5aa9365a836fb051a3 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Tue, 2 May 2017 13:27:38 +0300 Subject: [PATCH 17/33] Fixed most of the remarks, implemented displayed completion maxLength. cycle exiting still need to rework --- PSReadLine/Completion.cs | 106 +++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 9cb9f5b5e..4fc0fa594 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -29,6 +29,8 @@ public partial class PSConsoleReadLine { CompletionResultType.Namespace, new ConsoleKeyInfo[] { Keys.Period } }, { CompletionResultType.Property, new ConsoleKeyInfo[] { Keys.Period } }, { CompletionResultType.ProviderContainer, new ConsoleKeyInfo[] { Keys.Backslash, Keys.Slash } }, + { CompletionResultType.Method, new ConsoleKeyInfo[] { Keys.LParen } }, + { CompletionResultType.Type, new ConsoleKeyInfo[] { Keys.RBracket } }, }; // Stub helper method so completion can be mocked @@ -195,7 +197,7 @@ private void CompleteImpl(ConsoleKeyInfo? key, object arg, bool menuSelect) } bool ambiguous; - var replacementText = GetUnambiguousPrefix(completions.CompletionMatches, true, out ambiguous); + var replacementText = GetUnambiguousPrefix(completions.CompletionMatches, useCompletionText: true, ambiguous: out ambiguous); if (replacementText.Length > 0) { @@ -343,36 +345,58 @@ private static string HandleNewlinesForPossibleCompletions(string s) } return s; } + private static string ShortenLongCompletions(string s, int maxLength) + { + if (s.Length <= maxLength) return s; + // position of split point where ... inserted + int splitPos = 10; + ///TODO: is it needed ? + // insert '.' + //if (s.Length - maxLength <= 2) + // return s.Substring(0, maxLength - splitPos - 1) + '.' + s.Substring(s.Length - 10, 10); + // insert '...' + return s.Substring(0, maxLength - splitPos - 3) + "..." + s.Substring(s.Length - 10, 10); + } private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.Collection matches, IConsole console, bool showToolTips, out int MenuColumnWidth, out int DisplayRows) { var minColWidth = matches.Max(c => c.ListItemText.Length); minColWidth += 2; var bufferWidth = console.BufferWidth; + var displayColumns = Math.Max(1, bufferWidth / minColWidth); + ConsoleBufferBuilder cb; - if (showToolTips) + if (displayColumns == 1 || showToolTips) { const string seperator = "- "; - var maxTooltipWidth = Math.Max(bufferWidth - minColWidth - seperator.Length, 0); - + var maxTooltipWidth = bufferWidth - minColWidth - seperator.Length; + // switch off tooltips if it too short + if (maxTooltipWidth < 5) + { + minColWidth = bufferWidth; + showToolTips = false; + } DisplayRows = matches.Count; cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); for (int index = 0; index < matches.Count; index++) { var match = matches[index]; - var listItemText = HandleNewlinesForPossibleCompletions(match.ListItemText); + var listItemText = ShortenLongCompletions(HandleNewlinesForPossibleCompletions(match.ListItemText), minColWidth); cb.Append(listItemText); var spacesNeeded = minColWidth - listItemText.Length; if (spacesNeeded > 0) { cb.Append(' ', spacesNeeded); } - cb.Append(seperator); - var toolTip = HandleNewlinesForPossibleCompletions(match.ToolTip); - toolTip = toolTip.Length <= maxTooltipWidth - ? toolTip - : toolTip.Substring(0, maxTooltipWidth); - cb.Append(toolTip); + if (showToolTips) + { + cb.Append(seperator); + var toolTip = HandleNewlinesForPossibleCompletions(match.ToolTip); + toolTip = toolTip.Length <= maxTooltipWidth + ? toolTip + : toolTip.Substring(0, maxTooltipWidth); + cb.Append(toolTip); + } // Make sure we always write out exactly 1 buffer width spacesNeeded = (bufferWidth * (index + 1)) - cb.Length; @@ -385,8 +409,6 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C } else { - var screenColumns = bufferWidth; - var displayColumns = Math.Max(1, screenColumns / minColWidth); DisplayRows = (matches.Count + displayColumns - 1) / displayColumns; cb = new ConsoleBufferBuilder(DisplayRows * bufferWidth, console); for (var row = 0; row < DisplayRows; row++) @@ -415,6 +437,17 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C return cb.ToArray(); } + private bool IsDoneWithCompletions(CompletionResult currentCompletion, ConsoleKeyInfo nextKey) + { + if (nextKey == Keys.Space || nextKey == Keys.Enter) return true; + ConsoleKeyInfo[] doneKeys; + if (doneCompletionKeys.TryGetValue(currentCompletion.ResultType, out doneKeys)) + { + return doneKeys.Contains(nextKey); + } + else return false; + } + private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSelect) { if (completions == null || completions.CompletionMatches.Count == 0) @@ -458,13 +491,13 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int selectedItem = 0; bool undo = false; - int savedUserMark = _singleton._mark; + int savedUserMark = _mark; _visualSelectionCommandCount += 1; bool ambiguous; - var userCompletionText = GetUnambiguousPrefix(matches, true, out ambiguous); + var userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); if (userCompletionText.Length == 0) - userCompletionText = GetUnambiguousPrefix(matches, false, out ambiguous); + userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); // remove possible first quote if (userCompletionText.Length > 0 && ( IsSingleQuote(userCompletionText[0]) @@ -489,7 +522,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int backspaceCounter = 0; while (processingKeys) { - int _backspaceCounter = backspaceCounter; + int cycleBackspaceCounter = backspaceCounter; if (selectedItem != previousItem) { int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); @@ -530,15 +563,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } var nextKey = ReadKey(); - if (nextKey == Keys.Home) - { - selectedItem = 0; - } - else if (nextKey == Keys.End) - { - selectedItem = matches.Count - 1; - } - else if (nextKey == Keys.RightArrow) + if (nextKey == Keys.RightArrow) { selectedItem = Math.Min(selectedItem + displayRows, matches.Count - 1); } @@ -564,9 +589,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else if (nextKey == Keys.Tab) { - string unAmbiguousText = GetUnambiguousPrefix(matches, true, out ambiguous); + string unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); if (unAmbiguousText.Length == 0) - unAmbiguousText = GetUnambiguousPrefix(matches, false, out ambiguous); + unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { @@ -593,7 +618,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel undo = true; processingKeys = false; _visualSelectionCommandCount = 0; - _singleton._mark = savedUserMark; + _mark = savedUserMark; } else if (nextKey == Keys.Delete) { @@ -610,34 +635,37 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } processingKeys = false; _visualSelectionCommandCount = 0; - _singleton._mark = savedUserMark; + _mark = savedUserMark; Render(); } - else if (nextKey == Keys.Space || nextKey == Keys.Enter || - (doneCompletionKeys.ContainsKey(matches[selectedItem].ResultType) && - doneCompletionKeys[matches[selectedItem].ResultType].Contains(nextKey) - )) + else if (IsDoneWithCompletions(matches[selectedItem], nextKey)) { ExchangePointAndMark(); processingKeys = false; _visualSelectionCommandCount = 0; - _singleton._mark = savedUserMark; + _mark = savedUserMark; if (nextKey == Keys.Enter) { Render(); } else { + // do not append the same char as last char in CompletionText (works for for '(', '\') + userCompletionText = GetUnquotedText(matches[selectedItem].CompletionText, consistentQuoting: false); + bool prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; + int cursorAdjustment = 0; if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) userCompletionText = GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment); _current -= cursorAdjustment; - PrependQueuedKeys(nextKey); + + if (prependNextKey) + PrependQueuedKeys(nextKey); + else + Render(); } } else if (nextKey == Keys.Backspace || nextKey.KeyChar != 0) { - string replacementText = matches[selectedItem].CompletionText; - if (userInitialCompletionLength == userCompletionText.Length && nextKey == Keys.Backspace) { if (backspaceCounter == 0) @@ -701,7 +729,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { Ding(); } - if (backspaceCounter == _backspaceCounter) + if (backspaceCounter == cycleBackspaceCounter) backspaceCounter = 0; } From 98b916c1e5e0a9adea0f1786a8067b31ef8281ab Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Tue, 2 May 2017 17:57:04 +0300 Subject: [PATCH 18/33] Bugfixes --- PSReadLine/Completion.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 4fc0fa594..1fd179acb 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -596,7 +596,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { userCompletionText = unAmbiguousText.Substring(userComplPos); - _current = completions.ReplacementIndex + userComplPos + userCompletionText.Length; + _current = completions.ReplacementIndex + matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase) + userCompletionText.Length; Render(); Ding(); } @@ -649,17 +649,19 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel Render(); } else { - // do not append the same char as last char in CompletionText (works for for '(', '\') - userCompletionText = GetUnquotedText(matches[selectedItem].CompletionText, consistentQuoting: false); - bool prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; - int cursorAdjustment = 0; + // do not append the same char as last char in CompletionText (works for for '(', '\') if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) - userCompletionText = GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment); - _current -= cursorAdjustment; + userCompletionText = GetUnquotedText(GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment), consistentQuoting: false); + else + userCompletionText = GetUnquotedText(matches[selectedItem].CompletionText, consistentQuoting: false); + + bool prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; - if (prependNextKey) + if (prependNextKey) { + _current -= cursorAdjustment; PrependQueuedKeys(nextKey); + } else Render(); } From f45919f970079b19f60ee05823b91f314fb12d27 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Wed, 3 May 2017 14:27:16 +0300 Subject: [PATCH 19/33] Implemented cycle exit on any Chord, some comments inside --- PSReadLine/Completion.cs | 194 +++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 87 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 1fd179acb..27b625bf2 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -436,6 +436,28 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C return cb.ToArray(); } + private System.Collections.ObjectModel.Collection FilterCompletions(System.Collections.ObjectModel.Collection matches, string completionFilter) + { + var result = new System.Collections.ObjectModel.Collection(); + bool consistentQuoting = IsConsistentQuoting(matches); + // add possible first quote to userCompletionText + if (consistentQuoting && + (IsSingleQuote(matches[0].CompletionText[0]) + || IsDoubleQuote(matches[0].CompletionText[0]) + ) + ) + { + completionFilter = matches[0].CompletionText[0] + completionFilter; + } + foreach (CompletionResult item in matches) + { + if (item.ListItemText.StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) || + GetUnquotedText(item.CompletionText, consistentQuoting).StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) + ) + result.Add(item); + } + return result; + } private bool IsDoneWithCompletions(CompletionResult currentCompletion, ConsoleKeyInfo nextKey) { @@ -492,6 +514,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel bool undo = false; int savedUserMark = _mark; + // if user have selection fefore CompletionMenu, SetMark was not used, so do not restore it + bool restoreUserMark = _visualSelectionCommandCount == 0; _visualSelectionCommandCount += 1; bool ambiguous; @@ -589,10 +613,12 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } else if (nextKey == Keys.Tab) { + // Search for possible unAmbiguous common prefix. ... string unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); if (unAmbiguousText.Length == 0) unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + // ... If found - advance IncrementalCompletion ... if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { userCompletionText = unAmbiguousText.Substring(userComplPos); @@ -600,6 +626,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel Render(); Ding(); } + // ... if no - usual Tab behaviour else { selectedItem = (selectedItem + 1) % matches.Count; @@ -620,117 +647,110 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _visualSelectionCommandCount = 0; _mark = savedUserMark; } - else if (nextKey == Keys.Delete) - { - // Esc alternative: - // leave string beginning as in current replacement but don't replace to full completion - if (_current == completions.ReplacementIndex) - { - undo = true; - } - else - { - CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); - DoReplacementForCompletion(r, completions); - } - processingKeys = false; - _visualSelectionCommandCount = 0; - _mark = savedUserMark; - Render(); - } - else if (IsDoneWithCompletions(matches[selectedItem], nextKey)) + else { - ExchangePointAndMark(); - processingKeys = false; - _visualSelectionCommandCount = 0; - _mark = savedUserMark; - if (nextKey == Keys.Enter) - { - Render(); - } - else { - int cursorAdjustment = 0; - // do not append the same char as last char in CompletionText (works for for '(', '\') - if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) - userCompletionText = GetUnquotedText(GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment), consistentQuoting: false); - else - userCompletionText = GetUnquotedText(matches[selectedItem].CompletionText, consistentQuoting: false); + bool prependNextKey = false; + int cursorAdjustment = 0; + bool truncateCurrentCompletion = false; - bool prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; - - if (prependNextKey) { - _current -= cursorAdjustment; - PrependQueuedKeys(nextKey); - } - else - Render(); - } - } - else if (nextKey == Keys.Backspace || nextKey.KeyChar != 0) - { - if (userInitialCompletionLength == userCompletionText.Length && nextKey == Keys.Backspace) + if (IsDoneWithCompletions(matches[selectedItem], nextKey)) { - if (backspaceCounter == 0) - { - Ding(); - backspaceCounter++; - } - else + processingKeys = false; + ExchangePointAndMark(); // cursor to the end of Completion + if (nextKey != Keys.Enter) { - // Double Backspace, exit on next cycle - PrependQueuedKeys(Keys.Delete); + if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) + userCompletionText = GetUnquotedText(GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment), consistentQuoting: false); + else + userCompletionText = GetUnquotedText(matches[selectedItem].CompletionText, consistentQuoting: false); + + // do not append the same char as last char in CompletionText (works for for '(', '\') + prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; } } - else + else if (nextKey == Keys.Backspace || + (nextKey.KeyChar > 0 && !char.IsControl(nextKey.KeyChar) )) { + // TODO: Shift + Backspace does not fall here ? if (nextKey == Keys.Backspace) { - userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); + if (userCompletionText.Length > userInitialCompletionLength) + { + userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); + } + else if (backspaceCounter == 0) + { + Ding(); + backspaceCounter++; + } + else + { + processingKeys = false; + prependNextKey = true; + // we exit loop with current completion up to cursor + truncateCurrentCompletion = true; + } } else { userCompletionText += nextKey.KeyChar; } - - // filter out matches and redraw menu - var tmpMatches = new System.Collections.ObjectModel.Collection(); - bool consistentQuoting = IsConsistentQuoting(matches); - string completionFilter = userCompletionText; - // add possible first quote to userCompletionText - if (consistentQuoting && - ( IsSingleQuote(matches[0].CompletionText[0]) - || IsDoubleQuote(matches[0].CompletionText[0]) - ) - ) + if (processingKeys && backspaceCounter == 0) // do not rebuild menu on backspace with Ding() { - completionFilter = matches[0].CompletionText[0] + completionFilter; + // filter out matches and redraw menu + var tmpMatches = FilterCompletions(completions.CompletionMatches, userCompletionText); + if (tmpMatches.Count > 0) + { + WriteBlankLines(displayRows, menuAreaTop); + matches = tmpMatches; + previousItem = -1; + selectedItem = 0; + menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); + } + else + { + processingKeys = false; + prependNextKey = true; + // we exit loop with current completion up to cursor + truncateCurrentCompletion = true; + } } - foreach (CompletionResult item in completions.CompletionMatches) + } + else // exit with any other Key chord + { + processingKeys = false; + prependNextKey = true; + // without this branch experience doesnt look naturally + // Ctrl+C always do CTRL+C, never clipboard copy. and Ctrl+X too + // but DeleteChar, Copy, Cut and Paste can be on different Chord... + if (nextKey == Keys.Delete || nextKey == Keys.CtrlC || nextKey == Keys.CtrlX || nextKey == Keys.CtrlV) { - if (item.ListItemText.StartsWith(userCompletionText, StringComparison.OrdinalIgnoreCase) || - GetUnquotedText(item.CompletionText, consistentQuoting).StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) - ) - tmpMatches.Add(item); + restoreUserMark = false; } - if (tmpMatches.Count > 0) + } + if (!processingKeys) // time to exit loop + { + if (truncateCurrentCompletion) { - WriteBlankLines(displayRows, menuAreaTop); - matches = tmpMatches; - previousItem = -1; - selectedItem = 0; - menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); + CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); + DoReplacementForCompletion(r, completions); } - else + if (restoreUserMark) { - Ding(); - userCompletionText = userCompletionText.Substring(0, userCompletionText.Length - 1); + _visualSelectionCommandCount = 0; + // if mark was set after cursor, it restored in uninspected position, because text before mark now longer + // should we corrent it ? I think not, beause any other text insertion does not correct it + _mark = savedUserMark; + } + // without render all key chords that just move cursor leave selection visible, but it can be wrong + Render(); + if (prependNextKey) + { + _current -= cursorAdjustment; + PrependQueuedKeys(nextKey); } } } - else - { - Ding(); - } if (backspaceCounter == cycleBackspaceCounter) backspaceCounter = 0; } From 972545838b49652b064a37e85b6982e5a7e587b3 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Wed, 3 May 2017 15:47:06 +0300 Subject: [PATCH 20/33] Selection bugfixes --- PSReadLine/Completion.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 27b625bf2..85edeec54 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -515,8 +515,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int savedUserMark = _mark; // if user have selection fefore CompletionMenu, SetMark was not used, so do not restore it - bool restoreUserMark = _visualSelectionCommandCount == 0; - _visualSelectionCommandCount += 1; + _visualSelectionCommandCount++; bool ambiguous; var userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); @@ -652,6 +651,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel bool prependNextKey = false; int cursorAdjustment = 0; bool truncateCurrentCompletion = false; + bool keepSelection = false; if (IsDoneWithCompletions(matches[selectedItem], nextKey)) { @@ -725,7 +725,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // but DeleteChar, Copy, Cut and Paste can be on different Chord... if (nextKey == Keys.Delete || nextKey == Keys.CtrlC || nextKey == Keys.CtrlX || nextKey == Keys.CtrlV) { - restoreUserMark = false; + keepSelection = true; } } if (!processingKeys) // time to exit loop @@ -735,11 +735,15 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); DoReplacementForCompletion(r, completions); } - if (restoreUserMark) + if (keepSelection) + { + _visualSelectionCommandCount = 1; + } + else { _visualSelectionCommandCount = 0; // if mark was set after cursor, it restored in uninspected position, because text before mark now longer - // should we corrent it ? I think not, beause any other text insertion does not correct it + // should we correct it ? I think not, beause any other text insertion does not correct it _mark = savedUserMark; } // without render all key chords that just move cursor leave selection visible, but it can be wrong From 4439ca092df71417fa5ff66abadbeed3e86604a8 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Wed, 3 May 2017 19:50:36 +0300 Subject: [PATCH 21/33] Additional doneCompletionKeys --- PSReadLine/Completion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 85edeec54..0440b89a2 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -31,6 +31,8 @@ public partial class PSConsoleReadLine { CompletionResultType.ProviderContainer, new ConsoleKeyInfo[] { Keys.Backslash, Keys.Slash } }, { CompletionResultType.Method, new ConsoleKeyInfo[] { Keys.LParen } }, { CompletionResultType.Type, new ConsoleKeyInfo[] { Keys.RBracket } }, + { CompletionResultType.ParameterName, new ConsoleKeyInfo[] { Keys.Colon } }, + { CompletionResultType.ParameterValue, new ConsoleKeyInfo[] { Keys.Comma } }, }; // Stub helper method so completion can be mocked From d37dd436a296961ca8ff5daa7c8aeefd28a1c678 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Thu, 4 May 2017 09:55:09 +0300 Subject: [PATCH 22/33] splitPos variable used now, cursor placed on the same item when rebuild menu --- PSReadLine/Completion.cs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 85edeec54..067769e27 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -350,20 +350,23 @@ private static string ShortenLongCompletions(string s, int maxLength) if (s.Length <= maxLength) return s; // position of split point where ... inserted int splitPos = 10; + // TODO: will crash for console width < splitPos + 3 + ///TODO: is it needed ? // insert '.' //if (s.Length - maxLength <= 2) - // return s.Substring(0, maxLength - splitPos - 1) + '.' + s.Substring(s.Length - 10, 10); + // return s.Substring(0, maxLength - splitPos - 1) + '.' + s.Substring(s.Length - splitPos, splitPos); // insert '...' - return s.Substring(0, maxLength - splitPos - 3) + "..." + s.Substring(s.Length - 10, 10); + return s.Substring(0, maxLength - splitPos - 3) + "..." + s.Substring(s.Length - splitPos, splitPos); } - private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.Collection matches, IConsole console, bool showToolTips, out int MenuColumnWidth, out int DisplayRows) + private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.Collection matches, IConsole console, bool showToolTips, string currentCompletionText, out int currentCompletionIndex, out int MenuColumnWidth, out int DisplayRows) { var minColWidth = matches.Max(c => c.ListItemText.Length); minColWidth += 2; var bufferWidth = console.BufferWidth; var displayColumns = Math.Max(1, bufferWidth / minColWidth); + currentCompletionIndex = 0; ConsoleBufferBuilder cb; if (displayColumns == 1 || showToolTips) @@ -381,6 +384,7 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C for (int index = 0; index < matches.Count; index++) { var match = matches[index]; + if (match.CompletionText.Equals(currentCompletionText)) currentCompletionIndex = index; var listItemText = ShortenLongCompletions(HandleNewlinesForPossibleCompletions(match.ListItemText), minColWidth); cb.Append(listItemText); var spacesNeeded = minColWidth - listItemText.Length; @@ -419,6 +423,7 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C if (index >= matches.Count) break; var match = matches[index]; + if (match.CompletionText.Equals(currentCompletionText)) currentCompletionIndex = index; var item = HandleNewlinesForPossibleCompletions(match.ListItemText); cb.Append(item); cb.Append(' ', minColWidth - item.Length); @@ -490,7 +495,10 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int menuColumnWidth; int displayRows; - var menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); + int selectedItem; + var menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, + "", out selectedItem, + out menuColumnWidth, out displayRows); if (menuSelect) { @@ -510,11 +518,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel RemoveEditsAfterUndo(); var undoPoint = _edits.Count; - int selectedItem = 0; bool undo = false; int savedUserMark = _mark; - // if user have selection fefore CompletionMenu, SetMark was not used, so do not restore it _visualSelectionCommandCount++; bool ambiguous; @@ -702,10 +708,12 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (tmpMatches.Count > 0) { WriteBlankLines(displayRows, menuAreaTop); + var selectedCompletionText = matches[selectedItem].CompletionText; matches = tmpMatches; previousItem = -1; - selectedItem = 0; - menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, out menuColumnWidth, out displayRows); + menuBuffer = CreateCompletionMenu(matches, _console, Options.ShowToolTips, + selectedCompletionText, out selectedItem, + out menuColumnWidth, out displayRows); } else { From 99d8b896b960aa275abadde5a79c556817ea11ef Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Sat, 13 May 2017 12:12:13 +0300 Subject: [PATCH 23/33] Fixed bug with empty completion on cycle exit --- PSReadLine/Completion.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index b0a58a901..8c4d47ab2 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -697,6 +697,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel prependNextKey = true; // we exit loop with current completion up to cursor truncateCurrentCompletion = true; + if (userInitialCompletionLength == 0) + undo = true; } } else @@ -740,7 +742,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel } if (!processingKeys) // time to exit loop { - if (truncateCurrentCompletion) + if (truncateCurrentCompletion && ! undo) { CompletionResult r = new CompletionResult(matches[selectedItem].CompletionText.Substring(0, _current - completions.ReplacementIndex)); DoReplacementForCompletion(r, completions); @@ -757,7 +759,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _mark = savedUserMark; } // without render all key chords that just move cursor leave selection visible, but it can be wrong - Render(); + if (!undo) Render(); if (prependNextKey) { _current -= cursorAdjustment; From 81681829426798de50c9f8555c84ade376b51a8a Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Sat, 13 May 2017 12:22:29 +0300 Subject: [PATCH 24/33] removed extra Render() call on some cases --- PSReadLine/Completion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 8c4d47ab2..22a42e132 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -759,7 +759,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _mark = savedUserMark; } // without render all key chords that just move cursor leave selection visible, but it can be wrong - if (!undo) Render(); + if (!undo && !keepSelection) Render(); if (prependNextKey) { _current -= cursorAdjustment; From 8e3e4d3735f3c0652734428779ec90b0db085c8b Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Mon, 15 May 2017 09:17:10 +0300 Subject: [PATCH 25/33] One more fix for empty completion --- PSReadLine/Completion.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 22a42e132..13c56ba43 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -29,7 +29,7 @@ public partial class PSConsoleReadLine { CompletionResultType.Namespace, new ConsoleKeyInfo[] { Keys.Period } }, { CompletionResultType.Property, new ConsoleKeyInfo[] { Keys.Period } }, { CompletionResultType.ProviderContainer, new ConsoleKeyInfo[] { Keys.Backslash, Keys.Slash } }, - { CompletionResultType.Method, new ConsoleKeyInfo[] { Keys.LParen } }, + { CompletionResultType.Method, new ConsoleKeyInfo[] { Keys.LParen, Keys.RParen } }, { CompletionResultType.Type, new ConsoleKeyInfo[] { Keys.RBracket } }, { CompletionResultType.ParameterName, new ConsoleKeyInfo[] { Keys.Colon } }, { CompletionResultType.ParameterValue, new ConsoleKeyInfo[] { Keys.Comma } }, @@ -725,6 +725,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel prependNextKey = true; // we exit loop with current completion up to cursor truncateCurrentCompletion = true; + if (userInitialCompletionLength == 0) + undo = true; } } } From fea6022666fdaec4a3bed3e3cb4ee77f38267f7b Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Tue, 16 May 2017 09:40:36 +0300 Subject: [PATCH 26/33] Some code formatting --- PSReadLine/Completion.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 13c56ba43..f3dc82cfe 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -423,7 +423,9 @@ private static CHAR_INFO[] CreateCompletionMenu(System.Collections.ObjectModel.C { var index = row + (DisplayRows * col); if (index >= matches.Count) + { break; + } var match = matches[index]; if (match.CompletionText.Equals(currentCompletionText)) currentCompletionIndex = index; var item = HandleNewlinesForPossibleCompletions(match.ListItemText); @@ -461,7 +463,9 @@ private System.Collections.ObjectModel.Collection FilterComple if (item.ListItemText.StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) || GetUnquotedText(item.CompletionText, consistentQuoting).StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) ) + { result.Add(item); + } } return result; } @@ -528,7 +532,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel bool ambiguous; var userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); if (userCompletionText.Length == 0) + { userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); + } // remove possible first quote if (userCompletionText.Length > 0 && ( IsSingleQuote(userCompletionText[0]) @@ -562,7 +568,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel || IsDoubleQuote(matches[selectedItem].CompletionText[0]) ) ) + { curPos++; + } // set mark to the end of UserCompletion but in real completion (because of .\ and so on) _mark = completions.ReplacementIndex + curPos + userCompletionText.Length; DoReplacementForCompletion(matches[selectedItem], completions); @@ -583,7 +591,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _initialY -= (previousMenuTop - menuAreaTop); // return cursor in place if screen was scrolled if (previousMenuTop != menuAreaTop) + { PlaceCursor(); + } if (previousItem != -1 && blanklinesCount > 0) { @@ -623,7 +633,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // Search for possible unAmbiguous common prefix. ... string unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); if (unAmbiguousText.Length == 0) + { unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); + } int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); // ... If found - advance IncrementalCompletion ... if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) @@ -698,7 +710,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // we exit loop with current completion up to cursor truncateCurrentCompletion = true; if (userInitialCompletionLength == 0) + { undo = true; + } } } else @@ -726,7 +740,9 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // we exit loop with current completion up to cursor truncateCurrentCompletion = true; if (userInitialCompletionLength == 0) + { undo = true; + } } } } @@ -761,7 +777,10 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _mark = savedUserMark; } // without render all key chords that just move cursor leave selection visible, but it can be wrong - if (!undo && !keepSelection) Render(); + if (!undo && !keepSelection) + { + Render(); + } if (prependNextKey) { _current -= cursorAdjustment; From 7e5bbfa30f38433b5a2821851c6bb7b821ab73ec Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Tue, 16 May 2017 10:02:21 +0300 Subject: [PATCH 27/33] correct keepSelection implementation --- PSReadLine/Completion.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index f3dc82cfe..14d0f64c7 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -750,10 +750,17 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel { processingKeys = false; prependNextKey = true; + // without this branch experience doesnt look naturally - // Ctrl+C always do CTRL+C, never clipboard copy. and Ctrl+X too - // but DeleteChar, Copy, Cut and Paste can be on different Chord... - if (nextKey == Keys.Delete || nextKey == Keys.CtrlC || nextKey == Keys.CtrlX || nextKey == Keys.CtrlV) + KeyHandler handler; + if (_dispatchTable.TryGetValue(nextKey, out handler) && + ( + handler.Action == CopyOrCancelLine || + handler.Action == Cut || + handler.Action == DeleteChar || + handler.Action == Paste + ) + ) { keepSelection = true; } From 3007ad851ae437936415c9fa85fd84d395c04165 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Fri, 19 May 2017 10:21:46 +0300 Subject: [PATCH 28/33] Fixed cursor placement --- PSReadLine/Completion.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 14d0f64c7..c4e959c4e 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -574,7 +574,6 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // set mark to the end of UserCompletion but in real completion (because of .\ and so on) _mark = completions.ReplacementIndex + curPos + userCompletionText.Length; DoReplacementForCompletion(matches[selectedItem], completions); - ExchangePointAndMark(); endBufferCoords = ConvertOffsetToCoordinates(_buffer.Length); menuAreaTop = endBufferCoords.Y + 1; @@ -589,11 +588,8 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel // Showing the menu may have scrolled the screen or moved the cursor, update initialY to reflect that. _initialY -= (previousMenuTop - menuAreaTop); - // return cursor in place if screen was scrolled - if (previousMenuTop != menuAreaTop) - { - PlaceCursor(); - } + // return cursor in place even if screen was scrolled + ExchangePointAndMark(); if (previousItem != -1 && blanklinesCount > 0) { From 57d792b79aa999b13dbd81bfd7170cb3418ab482 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Mon, 22 May 2017 11:36:28 +0300 Subject: [PATCH 29/33] Moved an unambiguous search in ListItemText inside the method itself --- PSReadLine/Completion.cs | 58 +++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index c4e959c4e..844c5aadb 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -129,37 +129,61 @@ private bool IsConsistentQuoting(System.Collections.ObjectModel.Collection m.CompletionText[0] == matches[0].CompletionText[0])); } - private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection matches, bool useCompletionText, out bool ambiguous) + private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection matches, out bool ambiguous) { // Find the longest unambiguous prefix. This might be the empty // string, in which case we don't want to remove any of the users input, // instead we'll immediately show possible completions. // For the purposes of unambiguous prefix, we'll ignore quotes if // some completions aren't quoted. - ambiguous = false; + bool ambiguous1 = false; + bool ambiguous2 = false; var firstResult = matches[0]; bool consistentQuoting = IsConsistentQuoting(matches); - var replacementText = (useCompletionText) ? GetUnquotedText(firstResult.CompletionText, consistentQuoting) : firstResult.ListItemText; + var replacementText1 = GetUnquotedText(firstResult.CompletionText, consistentQuoting); + var replacementText2 = firstResult.ListItemText; foreach (var match in matches.Skip(1)) { - var matchText = (useCompletionText) ? GetUnquotedText(match.CompletionText, consistentQuoting) : match.ListItemText; - for (int i = 0; i < replacementText.Length; i++) + var matchText = GetUnquotedText(match.CompletionText, consistentQuoting); + for (int i = 0; i < replacementText1.Length; i++) { if (i == matchText.Length - || char.ToLowerInvariant(replacementText[i]) != char.ToLowerInvariant(matchText[i])) + || char.ToLowerInvariant(replacementText1[i]) != char.ToLowerInvariant(matchText[i])) { - ambiguous = true; - replacementText = replacementText.Substring(0, i); + ambiguous1 = true; + replacementText1 = replacementText1.Substring(0, i); break; } } - if (replacementText.Length == 0) + if (replacementText1.Length == 0) { + matchText = match.ListItemText; + for (int i = 0; i < replacementText2.Length; i++) + { + if (i == matchText.Length + || char.ToLowerInvariant(replacementText2[i]) != char.ToLowerInvariant(matchText[i])) + { + ambiguous2 = true; + replacementText2 = replacementText2.Substring(0, i); + break; + } + } + } + if (replacementText1.Length == 0 && replacementText2.Length == 0) { break; } } - return replacementText; + if (string.IsNullOrEmpty(replacementText1)) + { + ambiguous = ambiguous2; + return replacementText2; + } + else + { + ambiguous = ambiguous1; + return replacementText1; + } } private void CompleteImpl(ConsoleKeyInfo? key, object arg, bool menuSelect) @@ -199,7 +223,7 @@ private void CompleteImpl(ConsoleKeyInfo? key, object arg, bool menuSelect) } bool ambiguous; - var replacementText = GetUnambiguousPrefix(completions.CompletionMatches, useCompletionText: true, ambiguous: out ambiguous); + var replacementText = GetUnambiguousPrefix(completions.CompletionMatches, ambiguous: out ambiguous); if (replacementText.Length > 0) { @@ -530,11 +554,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel _visualSelectionCommandCount++; bool ambiguous; - var userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); - if (userCompletionText.Length == 0) - { - userCompletionText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); - } + var userCompletionText = GetUnambiguousPrefix(matches, ambiguous: out ambiguous); // remove possible first quote if (userCompletionText.Length > 0 && ( IsSingleQuote(userCompletionText[0]) @@ -627,11 +647,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel else if (nextKey == Keys.Tab) { // Search for possible unAmbiguous common prefix. ... - string unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: true, ambiguous: out ambiguous); - if (unAmbiguousText.Length == 0) - { - unAmbiguousText = GetUnambiguousPrefix(matches, useCompletionText: false, ambiguous: out ambiguous); - } + string unAmbiguousText = GetUnambiguousPrefix(matches, ambiguous: out ambiguous); int userComplPos = unAmbiguousText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); // ... If found - advance IncrementalCompletion ... if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) From b822c124be4bb5f4d5d3fc4d62b1053cf2e3bc33 Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Mon, 22 May 2017 20:50:18 +0300 Subject: [PATCH 30/33] ambiguous is more ambiguous --- PSReadLine/Completion.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 844c5aadb..d376f4f7e 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -174,16 +174,8 @@ private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection Date: Tue, 23 May 2017 11:55:14 +0300 Subject: [PATCH 31/33] Added support for ${env: variable completion syntax --- PSReadLine/Completion.cs | 54 +++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index d376f4f7e..5fd2e6245 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -85,6 +85,15 @@ private static bool IsQuoted(string s) } return false; } + private static bool IsQuotedVariable(string s) + { + // variable can be "quoted" like ${env:CommonProgramFiles(x86)} + if (s.Length > 2) + { + return (s[1] == '{' && s[s.Length - 1] == '}'); + } + return false; + } private static string GetUnquotedText(string s, bool consistentQuoting) { @@ -97,6 +106,26 @@ private static string GetUnquotedText(string s, bool consistentQuoting) return s; } + private static string GetUnquotedText(CompletionResult match, bool consistentQuoting) + { + var s = match.CompletionText; + if (match.ResultType == CompletionResultType.Variable) + { + if (IsQuotedVariable(s)) + { + return '$' + s.Substring(2, s.Length - 3); + } + else + { + return s; + } + } + else + { + return GetUnquotedText(s, consistentQuoting); + } + } + /// /// Attempt to perform completion on the text surrounding the cursor. /// If there are multiple possible completions, the longest unambiguous @@ -141,11 +170,11 @@ private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection FilterComple foreach (CompletionResult item in matches) { if (item.ListItemText.StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) || - GetUnquotedText(item.CompletionText, consistentQuoting).StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) + GetUnquotedText(item, consistentQuoting).StartsWith(completionFilter, StringComparison.OrdinalIgnoreCase) ) { result.Add(item); @@ -485,6 +514,19 @@ private System.Collections.ObjectModel.Collection FilterComple } return result; } + private int FindUserCompletinTextPosition(CompletionResult match, string userCompletionText) + { + if (match.ResultType == CompletionResultType.Variable && + match.CompletionText[1] == '{' + ) + { + return match.CompletionText.IndexOf(userCompletionText.Substring(1), StringComparison.OrdinalIgnoreCase) - 1; + } + else + { + return match.CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + } + } private bool IsDoneWithCompletions(CompletionResult currentCompletion, ConsoleKeyInfo nextKey) { @@ -574,7 +616,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel int cycleBackspaceCounter = backspaceCounter; if (selectedItem != previousItem) { - int curPos = matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase); + int curPos = FindUserCompletinTextPosition(matches[selectedItem], userCompletionText); if (userCompletionText.Length == 0 && ( IsSingleQuote(matches[selectedItem].CompletionText[0]) || IsDoubleQuote(matches[selectedItem].CompletionText[0]) @@ -645,7 +687,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (unAmbiguousText.Length > 0 && userComplPos >= 0 && unAmbiguousText.Length > (userComplPos + userCompletionText.Length)) { userCompletionText = unAmbiguousText.Substring(userComplPos); - _current = completions.ReplacementIndex + matches[selectedItem].CompletionText.IndexOf(userCompletionText, StringComparison.OrdinalIgnoreCase) + userCompletionText.Length; + _current = completions.ReplacementIndex + FindUserCompletinTextPosition(matches[selectedItem], userCompletionText) + userCompletionText.Length; Render(); Ding(); } @@ -686,7 +728,7 @@ private void PossibleCompletionsImpl(CommandCompletion completions, bool menuSel if (matches[selectedItem].ResultType == CompletionResultType.ProviderContainer) userCompletionText = GetUnquotedText(GetReplacementTextForDirectory(matches[selectedItem].CompletionText, ref cursorAdjustment), consistentQuoting: false); else - userCompletionText = GetUnquotedText(matches[selectedItem].CompletionText, consistentQuoting: false); + userCompletionText = GetUnquotedText(matches[selectedItem], consistentQuoting: false); // do not append the same char as last char in CompletionText (works for for '(', '\') prependNextKey = userCompletionText[userCompletionText.Length - 1] != nextKey.KeyChar; From 5295606c2aeb731d63d55480edeca8e54897525c Mon Sep 17 00:00:00 2001 From: Max Kozlov Date: Sat, 27 May 2017 22:09:56 +0300 Subject: [PATCH 32/33] Fixed Bug - additional optimization lead to errors --- PSReadLine/Completion.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 5fd2e6245..72dea290f 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -185,17 +185,15 @@ private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection Date: Sat, 27 May 2017 22:32:23 +0300 Subject: [PATCH 33/33] same fix, other variant --- PSReadLine/Completion.cs | 48 +++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/PSReadLine/Completion.cs b/PSReadLine/Completion.cs index 72dea290f..1f4da0e61 100644 --- a/PSReadLine/Completion.cs +++ b/PSReadLine/Completion.cs @@ -165,44 +165,52 @@ private string GetUnambiguousPrefix(System.Collections.ObjectModel.Collection