Skip to content

Commit b0b441c

Browse files
committed
Added InsertLineAbove and InsertLineBelow functions and set them up to key bind to Ctrl+Enter and Ctrl+Shift+Enter in Windows mode. Not being an emacs person, I'm not sure what key bindings these two functions would bind to. I also added unit tests for the two functions as well as a unit test for what I believe is a bug in the BeginningOfLine (home) function when in multiline mode. I also updated the project to have the appropriate default namespace so that the resource file gets re-generated with the correct resource name without having to resort to hacking the csproj file. There was also an issue with trying to run unit tests because the unit test setup of PSReadline sets engineInstrincs to null and that caused a null reference exception which I worked-around by testing for null first.
1 parent 7ccac6c commit b0b441c

File tree

9 files changed

+233
-14
lines changed

9 files changed

+233
-14
lines changed

PSReadLine/BasicEditing.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,5 +490,65 @@ public static void AddLine(ConsoleKeyInfo? key = null, object arg = null)
490490
{
491491
Insert('\n');
492492
}
493+
494+
/// <summary>
495+
/// A new empty line is created above the current line regardless of where the cursor
496+
/// is on the current line. The cursor moves to the beginning of the new line.
497+
/// </summary>
498+
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
499+
public static void InsertLineAbove(ConsoleKeyInfo? key = null, object arg = null)
500+
{
501+
// Move the current postion to the beginning of the current line and only the current line.
502+
if (_singleton.LineIsMultiLine())
503+
{
504+
int i = Math.Max(0, _singleton._current - 1);
505+
for (; i > 0; i--)
506+
{
507+
if (_singleton._buffer[i] == '\n')
508+
{
509+
i += 1;
510+
break;
511+
}
512+
}
513+
514+
_singleton._current = i;
515+
}
516+
else
517+
{
518+
_singleton._current = 0;
519+
}
520+
521+
Insert('\n');
522+
PreviousLine();
523+
}
524+
525+
/// <summary>
526+
/// A new empty line is created below the current line regardless of where the cursor
527+
/// is on the current line. The cursor moves to the beginning of the new line.
528+
/// </summary>
529+
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
530+
public static void InsertLineBelow(ConsoleKeyInfo? key = null, object arg = null)
531+
{
532+
// Move the current postion to the end of the current line and only the current line.
533+
if (_singleton.LineIsMultiLine())
534+
{
535+
int i = _singleton._current;
536+
for (; i < _singleton._buffer.Length; i++)
537+
{
538+
if (_singleton._buffer[i] == '\n')
539+
{
540+
break;
541+
}
542+
}
543+
544+
_singleton._current = i;
545+
}
546+
else
547+
{
548+
_singleton._current = _singleton._buffer.Length;
549+
}
550+
551+
Insert('\n');
552+
}
493553
}
494554
}

PSReadLine/Changes.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
### Version 1.1.1
2+
3+
New functions:
4+
* InsertLineAbove
5+
- A new empty line is created above the current line regardless of where the cursor
6+
is on the current line. The cursor moves to the beginning of the new line.
7+
* InsertLineBelow
8+
- A new empty line is created below the current line regardless of where the cursor
9+
is on the current line. The cursor moves to the beginning of the new line.
10+
11+
New key bindings:
12+
* Ctrl+Enter bound to InsertLineAbove in Windows mode
13+
* Ctrl+Shift+Enter bound to InsertLineBelow in Windows mode
14+
115
### Version 1.1.0
216

317
Breaking change:

PSReadLine/KeyBindings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ void SetDefaultWindowsBindings()
103103
{
104104
{ Keys.Enter, MakeKeyHandler(AcceptLine, "AcceptLine") },
105105
{ Keys.ShiftEnter, MakeKeyHandler(AddLine, "AddLine") },
106+
{ Keys.CtrlEnter, MakeKeyHandler(InsertLineAbove, "InsertLineAbove") },
107+
{ Keys.CtrlShiftEnter, MakeKeyHandler(InsertLineBelow, "InsertLineBelow") },
106108
{ Keys.Escape, MakeKeyHandler(RevertLine, "RevertLine") },
107109
{ Keys.LeftArrow, MakeKeyHandler(BackwardChar, "BackwardChar") },
108110
{ Keys.RightArrow, MakeKeyHandler(ForwardChar, "ForwardChar") },
@@ -370,6 +372,5 @@ public static void WhatIsKey(ConsoleKeyInfo? key = null, object arg = null)
370372
_singleton._initialY = Console.CursorTop;
371373
_singleton.Render();
372374
}
373-
374375
}
375376
}

PSReadLine/Keys.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,11 @@ public static class Keys
230230
public static ConsoleKeyInfo ShiftCtrlLeftArrow = new ConsoleKeyInfo((char)0, ConsoleKey.LeftArrow, true, false, true);
231231
public static ConsoleKeyInfo ShiftCtrlRightArrow = new ConsoleKeyInfo((char)0, ConsoleKey.RightArrow, true, false, true);
232232

233-
public static ConsoleKeyInfo ShiftTab = new ConsoleKeyInfo((char)9, ConsoleKey.Tab, true, false, false);
234-
public static ConsoleKeyInfo ShiftEnter = new ConsoleKeyInfo((char)13, ConsoleKey.Enter, true, false, false);
233+
public static ConsoleKeyInfo ShiftTab = new ConsoleKeyInfo((char)9, ConsoleKey.Tab, true, false, false);
234+
235+
public static ConsoleKeyInfo CtrlEnter = new ConsoleKeyInfo((char)10, ConsoleKey.Enter, false, false, true);
236+
public static ConsoleKeyInfo CtrlShiftEnter = new ConsoleKeyInfo((char)0, ConsoleKey.Enter, true, false, true);
237+
public static ConsoleKeyInfo ShiftEnter = new ConsoleKeyInfo((char)13, ConsoleKey.Enter, true, false, false);
235238

236239
public static ConsoleKeyInfo F1 = new ConsoleKeyInfo((char)0, ConsoleKey.F1, false, false, false);
237240
public static ConsoleKeyInfo F2 = new ConsoleKeyInfo((char)0, ConsoleKey.F2, false, false, false);
@@ -286,7 +289,7 @@ public static class Keys
286289
public static ConsoleKeyInfo ShiftF3 = new ConsoleKeyInfo((char)0, ConsoleKey.F3, true, false, false);
287290
public static ConsoleKeyInfo ShiftF8 = new ConsoleKeyInfo((char)0, ConsoleKey.F8, true, false, false);
288291

289-
// Keys to ignore
292+
// Keys to ignore
290293
public static ConsoleKeyInfo VolumeUp = new ConsoleKeyInfo((char)0, ConsoleKey.VolumeUp, false, false, false);
291294
public static ConsoleKeyInfo VolumeDown = new ConsoleKeyInfo((char)0, ConsoleKey.VolumeDown, false, false, false);
292295
public static ConsoleKeyInfo VolumeMute = new ConsoleKeyInfo((char)0, ConsoleKey.VolumeMute, false, false, false);

PSReadLine/PSReadLine.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ProjectGuid>{615788CB-1B9A-4B34-97B3-4608686E59CA}</ProjectGuid>
88
<OutputType>Library</OutputType>
99
<AppDesignerFolder>Properties</AppDesignerFolder>
10-
<RootNamespace>PSReadLine</RootNamespace>
10+
<RootNamespace>Microsoft.PowerShell</RootNamespace>
1111
<AssemblyName>Microsoft.PowerShell.PSReadLine</AssemblyName>
1212
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
@@ -81,9 +81,10 @@
8181
<ItemGroup>
8282
<EmbeddedResource Include="PSReadLineResources.resx">
8383
<Generator>ResXFileCodeGenerator</Generator>
84-
<LogicalName>Microsoft.PowerShell.PSReadline.PSReadLineResources.resources</LogicalName>
84+
<LogicalName>Microsoft.PowerShell.PSReadLineResources.resources</LogicalName>
8585
<LastGenOutput>PSReadlineResources.Designer.cs</LastGenOutput>
8686
<CustomToolNamespace>Microsoft.PowerShell</CustomToolNamespace>
87+
<SubType>Designer</SubType>
8788
</EmbeddedResource>
8889
</ItemGroup>
8990
<ItemGroup>
@@ -120,7 +121,7 @@
120121
</ItemGroup>
121122
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
122123
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
123-
Other similar extension points exist, see Microsoft.Common.targets.
124+
Other similar extension points exist, see Microsoft.Common.targets.
124125
<Target Name="BeforeBuild">
125126
</Target>
126127
<Target Name="AfterBuild">

PSReadLine/PSReadLineResources.Designer.cs

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PSReadLine/PSReadLineResources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,4 +432,10 @@ If there are other parse errors, unresolved commands, or incorrect parameters, s
432432
<data name="UnrecognizedKey" xml:space="preserve">
433433
<value>Unrecognized key '{0}'. Please use a character literal or a well-known key name from the System.ConsoleKey enumeration.</value>
434434
</data>
435+
<data name="InsertLineAboveDescription" xml:space="preserve">
436+
<value>Inserts a new empty line above the current line without attempting to execute the input</value>
437+
</data>
438+
<data name="InsertLineBelowDescription" xml:space="preserve">
439+
<value>Inserts a new empty line below the current line without attempting to execute the input</value>
440+
</data>
435441
</root>

PSReadLine/ReadLine.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -579,12 +579,15 @@ private void DelayedOneTimeInitialize()
579579
// after the constuctor but have an affect before the user starts
580580
// editing their first command line. For example, if the user
581581
// specifies a custom history save file, we don't want to try reading
582-
// from the default one.
583-
584-
var historyCountVar = _engineIntrinsics.SessionState.PSVariable.Get("MaximumHistoryCount");
585-
if (historyCountVar != null && historyCountVar.Value is int)
582+
// from the default one.
583+
// For unit testing _engineIntrinsics is null, so check for that.
584+
if (_engineIntrinsics != null)
586585
{
587-
_options.MaximumHistoryCount = (int)historyCountVar.Value;
586+
var historyCountVar = _engineIntrinsics.SessionState.PSVariable.Get("MaximumHistoryCount");
587+
if (historyCountVar != null && historyCountVar.Value is int)
588+
{
589+
_options.MaximumHistoryCount = (int) historyCountVar.Value;
590+
}
588591
}
589592

590593
_historyFileMutex = new Mutex(false, GetHistorySaveFileMutexName());

UnitTestPSReadLine/BasicEditingTest.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,119 @@ public void TestAddLine()
212212
Test("1\n2", Keys('1', _.ShiftEnter, '2'));
213213
}
214214

215+
[TestMethod]
216+
public void TestInsertLineAbove()
217+
{
218+
TestSetup(KeyMode.Cmd);
219+
220+
var continutationPromptLength = PSConsoleReadlineOptions.DefaultContinuationPrompt.Length;
221+
222+
// Test case - start with single line, cursor at end
223+
Test("56\n1234", Keys("1234", _.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "56"));
224+
225+
// Test case - start with single line, cursor in home position
226+
Test("56\n1234", Keys("1234", _.Home, _.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(0, 0)), "56"));
227+
228+
// Test case - start with single line, cursor in middle
229+
Test("56\n1234", Keys("1234",
230+
_.LeftArrow, _.LeftArrow, _.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(0, 0)),
231+
"56"));
232+
233+
234+
// Test case - start with multi-line, cursor at end of second line (end of input)
235+
Test("1234\n9ABC\n5678", Keys("1234", _.ShiftEnter, "5678",
236+
_.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
237+
"9ABC"));
238+
239+
// Test case - start with multi-line, cursor at beginning of second line
240+
Test("1234\n9ABC\n5678", Keys("1234", _.ShiftEnter, "5678",
241+
_.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
242+
_.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
243+
"9ABC"));
244+
245+
// Test case - start with multi-line, cursor at end of first line
246+
Test("9ABC\n1234\n5678", Keys("1234", _.ShiftEnter, "5678",
247+
_.UpArrow, _.LeftArrow, _.End, CheckThat(() => AssertCursorLeftTopIs(4, 0)),
248+
_.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(0, 0)),
249+
"9ABC"));
250+
251+
// Test case - start with multi-line, cursor at beginning of first line - temporarily having to press Home twice to
252+
// work around bug in home handler.
253+
Test("9ABC\n1234\n5678", Keys("1234", _.ShiftEnter, "5678",
254+
_.UpArrow, _.LeftArrow, _.Home, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)),
255+
_.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(0, 0)),
256+
"9ABC"));
257+
258+
// Test case - insert multiple blank lines
259+
Test("1234\n9ABC\n\n5678", Keys("1234", _.ShiftEnter, "5678",
260+
_.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
261+
_.CtrlEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
262+
"9ABC"));
263+
}
264+
265+
[TestMethod]
266+
public void TestInsertLineBelow()
267+
{
268+
TestSetup(KeyMode.Cmd);
269+
270+
var continutationPromptLength = PSConsoleReadlineOptions.DefaultContinuationPrompt.Length;
271+
272+
// Test case - start with single line, cursor at end
273+
Test("1234\n56", Keys("1234",
274+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
275+
"56"));
276+
277+
// Test case - start with single line, cursor in home position
278+
Test("1234\n56", Keys("1234",
279+
_.Home, _.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
280+
"56"));
281+
282+
// Test case - start with single line, cursor in middle
283+
Test("1234\n56", Keys("1234",
284+
_.LeftArrow, _.LeftArrow, _.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
285+
"56"));
286+
287+
// Test case - start with multi-line, cursor at end of second line (end of input)
288+
Test("1234\n5678\n9ABC", Keys("1234", _.ShiftEnter, "5678",
289+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)),
290+
"9ABC"));
291+
292+
// Test case - start with multi-line, cursor at beginning of second line
293+
Test("1234\n5678\n9ABC", Keys("1234", _.ShiftEnter, "5678",
294+
_.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
295+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)),
296+
"9ABC"));
297+
298+
// Test case - start with multi-line, cursor at end of first line
299+
Test("1234\n9ABC\n5678", Keys("1234", _.ShiftEnter, "5678",
300+
_.UpArrow, _.LeftArrow, _.End, CheckThat(() => AssertCursorLeftTopIs(4, 0)),
301+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
302+
"9ABC"));
303+
304+
// Test case - start with multi-line, cursor at beginning of first line - temporarily having to press Home twice to
305+
// work around bug in home handler.
306+
Test("1234\n9ABC\n5678", Keys("1234", _.ShiftEnter, "5678",
307+
_.UpArrow, _.LeftArrow, _.Home, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0)),
308+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 1)),
309+
"9ABC"));
310+
311+
// Test case - insert multiple blank lines
312+
Test("1234\n5678\n\n9ABC", Keys("1234", _.ShiftEnter, "5678",
313+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 2)),
314+
_.CtrlShiftEnter, CheckThat(() => AssertCursorLeftTopIs(continutationPromptLength, 3)),
315+
"9ABC"));
316+
}
317+
318+
[TestMethod]
319+
public void TestMultilineHomeBugFixed()
320+
{
321+
// Going from second line to first line, press left arrow and then home.
322+
// That puts cursor in column 1 instead of 0. Bug? This could have something
323+
// to do with BeginningOfLine testing against i > 1 in multiline edit instead
324+
// of i > 0.
325+
Test("1234\n9ABC", Keys("1234", _.ShiftEnter, "9ABC", _.UpArrow, _.LeftArrow, _.Home, CheckThat(() => AssertCursorLeftTopIs(0, 0))));
326+
}
327+
215328
[TestMethod]
216329
public void TestIgnore()
217330
{

0 commit comments

Comments
 (0)