PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs
2021-08-17 09:01:04 +05:00

1705 lines
65 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if !UNIX
using System;
using System.ComponentModel;
using System.Globalization;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
using Dbg = System.Management.Automation.Diagnostics;
using WORD = System.UInt16;
namespace Microsoft.PowerShell
{
/// <summary>
/// Implementation of RawConsole for powershell.
/// </summary>
internal sealed
class ConsoleHostRawUserInterface : System.Management.Automation.Host.PSHostRawUserInterface
{
/// <summary>
/// </summary>
/// <exception cref="HostException">
/// If obtaining the buffer's foreground and background color failed
/// </exception>
internal
ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base()
{
defaultForeground = ForegroundColor;
defaultBackground = BackgroundColor;
parent = mshConsole;
// cacheKeyEvent is a value type and initialized automatically
// add "Administrator: " prefix into the window title, but don't wait for it to finish
// (we may load resources which can take some time)
Task.Run(() =>
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
if (principal.IsInRole(WindowsBuiltInRole.Administrator))
{
string prefix = ConsoleHostRawUserInterfaceStrings.WindowTitleElevatedPrefix;
// check using Regex if the window already has Administrator: prefix
// (i.e. from the parent console process)
string titlePattern = ConsoleHostRawUserInterfaceStrings.WindowTitleTemplate;
titlePattern = Regex.Escape(titlePattern)
.Replace(@"\{1}", ".*")
.Replace(@"\{0}", Regex.Escape(prefix));
if (!Regex.IsMatch(this.WindowTitle, titlePattern))
{
this.WindowTitle = StringUtil.Format(ConsoleHostRawUserInterfaceStrings.WindowTitleTemplate,
prefix,
this.WindowTitle);
}
}
});
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentException">
/// If set to an invalid ConsoleColor
/// </exception>
/// <exception cref="HostException">
/// If obtaining information about the buffer failed
/// OR
/// Win32's SetConsoleTextAttribute
/// </exception>
public override
ConsoleColor
ForegroundColor
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
ConsoleColor foreground;
ConsoleColor unused;
ConsoleControl.WORDToColor(bufferInfo.Attributes, out foreground, out unused);
return foreground;
}
set
{
if (ConsoleControl.IsConsoleColor(value))
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
// mask in the foreground from the current color.
short a = (short)bufferInfo.Attributes;
a &= (short)~0x0f;
a = (short)((ushort)a | (ushort)value);
ConsoleControl.SetConsoleTextAttribute(handle, (WORD)a);
}
else
{
throw PSTraceSource.NewArgumentException("value", ConsoleHostRawUserInterfaceStrings.InvalidConsoleColorError);
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentException">
/// If set to an invalid ConsoleColor
/// </exception>
/// <exception cref="HostException">
/// If obtaining information about the buffer failed
/// OR
/// Win32's SetConsoleTextAttribute
/// </exception>
public override
ConsoleColor
BackgroundColor
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
ConsoleColor background;
ConsoleColor unused;
ConsoleControl.WORDToColor(bufferInfo.Attributes, out unused, out background);
return background;
}
set
{
if (ConsoleControl.IsConsoleColor(value))
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
// mask in the background from the current color.
short a = (short)bufferInfo.Attributes;
a &= (short)~0xf0;
a = (short)((ushort)a | (ushort)((uint)value << 4));
ConsoleControl.SetConsoleTextAttribute(handle, (WORD)a);
}
else
{
throw PSTraceSource.NewArgumentException("value", ConsoleHostRawUserInterfaceStrings.InvalidConsoleColorError);
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentOutOfRangeException">
/// If set to outside of the buffer
/// </exception>
/// <exception cref="HostException">
/// If obtaining information about the buffer failed
/// OR
/// Win32's SetConsoleCursorPosition failed
/// </exception>
public override
Coordinates
CursorPosition
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
Coordinates c = new Coordinates(bufferInfo.CursorPosition.X, bufferInfo.CursorPosition.Y);
return c;
}
set
{
try
{
Console.SetCursorPosition(value.X, value.Y);
}
catch (ArgumentOutOfRangeException)
{
// if screen buffer has changed, we cannot set it anywhere reasonable as the screen buffer
// might change again, so we ignore this
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value>
/// Cursor size
/// </value>
/// <exception cref="ArgumentOutOfRangeException">
/// If set to under 0 or over 100
/// </exception>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// Win32's GetConsoleCursorInfo failed
/// OR
/// Win32's SetConsoleCursorInfo failed
/// </exception>
public override
int
CursorSize
{
get
{
ConsoleHandle consoleHandle = ConsoleControl.GetActiveScreenBufferHandle();
int size = (int)ConsoleControl.GetConsoleCursorInfo(consoleHandle).Size;
return size;
}
set
{
const int MinCursorSize = 0;
const int MaxCursorSize = 100;
if (value >= MinCursorSize && value <= MaxCursorSize)
{
ConsoleHandle consoleHandle = ConsoleControl.GetActiveScreenBufferHandle();
ConsoleControl.CONSOLE_CURSOR_INFO cursorInfo =
ConsoleControl.GetConsoleCursorInfo(consoleHandle);
if (value == 0)
{
cursorInfo.Visible = false;
}
else
{
cursorInfo.Size = (uint)value;
cursorInfo.Visible = true;
}
ConsoleControl.SetConsoleCursorInfo(consoleHandle, cursorInfo);
}
else
{
throw PSTraceSource.NewArgumentOutOfRangeException("value", value,
ConsoleHostRawUserInterfaceStrings.InvalidCursorSizeError);
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentOutOfRangeException">
/// If set outside of the buffer
/// </exception>
/// <exception cref="HostException">
/// If obtaining information about the buffer failed
/// OR
/// Win32's SetConsoleWindowInfo failed
/// </exception>
public override
Coordinates
WindowPosition
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
Coordinates c = new Coordinates(bufferInfo.WindowRect.Left, bufferInfo.WindowRect.Top);
return c;
}
set
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
ConsoleControl.SMALL_RECT r = bufferInfo.WindowRect;
// the dimensions of the window can't extend past the dimensions of the screen buffer. This means that the
// X position of the window is limited to the buffer width minus one minus the window width, and the Y
// position of the window is limited to the buffer height minus one minus the window height.
int windowWidth = r.Right - r.Left + 1;
int windowHeight = r.Bottom - r.Top + 1;
if (value.X < 0 || value.X > bufferInfo.BufferSize.X - windowWidth)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.X", value.X,
ConsoleHostRawUserInterfaceStrings.InvalidXWindowPositionError);
}
if (value.Y < 0 || value.Y > bufferInfo.BufferSize.Y - windowHeight)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Y", value.Y,
ConsoleHostRawUserInterfaceStrings.InvalidYWindowPositionError);
}
r.Left = (short)value.X;
r.Top = (short)value.Y;
// subtract 1 from each dimension because the semantics of the win32 api are not "number of characters in
// span" but "starting and ending position"
r.Right = (short)(r.Left + windowWidth - 1);
r.Bottom = (short)(r.Top + windowHeight - 1);
Dbg.Assert(r.Right >= r.Left, "Window size is too narrow");
Dbg.Assert(r.Bottom >= r.Top, "Window size is too short");
ConsoleControl.SetConsoleWindowInfo(handle, true, r);
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentOutOfRangeException">
/// If setting to an invalid size
/// </exception>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// obtaining information about the buffer failed
/// OR
/// Win32's SetConsoleScreenBufferSize failed
/// </exception>
public override
Size
BufferSize
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
return new Size(bufferInfo.BufferSize.X, bufferInfo.BufferSize.Y);
}
set
{
// looking in windows/core/ntcon/server/output.c, it looks like the minimum size is 1 row X however many
// characters will fit in the minimum window size system metric (SM_CXMIN). Instead of going to the effort of
// computing that minimum here, it is cleaner and cheaper to make the call to SetConsoleScreenBuffer and just
// translate any exception that might get thrown.
try
{
ConsoleHandle handle = ConsoleControl.GetActiveScreenBufferHandle();
ConsoleControl.SetConsoleScreenBufferSize(handle, value);
}
catch (HostException e)
{
Win32Exception win32exception = e.InnerException as Win32Exception;
if (win32exception != null &&
win32exception.NativeErrorCode == 0x57)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value", value,
ConsoleHostRawUserInterfaceStrings.InvalidBufferSizeError);
}
else
{
throw;
}
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentOutOfRangeException">
/// If setting width or height to less than 1, larger than the screen buffer,
/// over the maximum window size allowed
/// </exception>
/// <exception cref="HostException">
/// If obtaining information about the buffer failed
/// OR
/// Win32's SetConsoleWindowInfo failed
/// </exception>
public override
Size
WindowSize
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
Size s =
new Size(
bufferInfo.WindowRect.Right - bufferInfo.WindowRect.Left + 1,
bufferInfo.WindowRect.Bottom - bufferInfo.WindowRect.Top + 1);
return s;
}
set
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
// the dimensions of the window can't extend past the dimensions of the screen buffer. This means that the
// width of the window is limited to the buffer width minus one minus the window X position, and the height
// of the window is limited to the buffer height minus one minus the window Y position.
if (value.Width < 1)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Width", value.Width,
ConsoleHostRawUserInterfaceStrings.WindowWidthTooSmallError);
}
if (value.Height < 1)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Height", value.Height,
ConsoleHostRawUserInterfaceStrings.WindowHeightTooSmallError);
}
if (value.Width > bufferInfo.BufferSize.X)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Width", value.Width,
ConsoleHostRawUserInterfaceStrings.WindowWidthLargerThanBufferError);
}
if (value.Height > bufferInfo.BufferSize.Y)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Height", value.Height,
ConsoleHostRawUserInterfaceStrings.WindowHeightLargerThanBufferError);
}
if (value.Width > bufferInfo.MaxWindowSize.X)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Width", value.Width,
ConsoleHostRawUserInterfaceStrings.WindowWidthTooLargeErrorTemplate,
bufferInfo.MaxWindowSize.X);
}
if (value.Height > bufferInfo.MaxWindowSize.Y)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value.Height", value.Height,
ConsoleHostRawUserInterfaceStrings.WindowHeightTooLargeErrorTemplate,
bufferInfo.MaxWindowSize.Y);
}
// if the new size will extend past the edge of screen buffer, then move the window position to try to
// accommodate that.
ConsoleControl.SMALL_RECT r = bufferInfo.WindowRect;
// subtract 1 from each dimension because the semantics of the win32 api are not "number of characters in
// span" but "starting and ending position"
r.Right = (short)(r.Left + value.Width - 1);
r.Bottom = (short)(r.Top + value.Height - 1);
// Now we check if the bottom right coordinate of our window went over the coordinate of the bottom
// right of the buffer. If it did then we need to adjust the window.
// bufferInfo.BufferSize.X - 1 will give us the rightmost coordinate of the buffer.
// r.Right - rightCoordinateOfBuffer will give us how much we need to adjust the window left and right coordinates.
// Then we can do the same for top and bottom.
short adjustLeft = (short)(r.Right - (bufferInfo.BufferSize.X - 1));
short adjustTop = (short)(r.Bottom - (bufferInfo.BufferSize.Y - 1));
if (adjustLeft > 0)
{
r.Left -= adjustLeft;
r.Right -= adjustLeft;
}
if (adjustTop > 0)
{
r.Top -= adjustTop;
r.Bottom -= adjustTop;
}
if (r.Right < r.Left)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value", value,
ConsoleHostRawUserInterfaceStrings.WindowTooNarrowError);
}
if (r.Bottom < r.Top)
{
throw PSTraceSource.NewArgumentOutOfRangeException("value", value,
ConsoleHostRawUserInterfaceStrings.WindowTooShortError);
}
ConsoleControl.SetConsoleWindowInfo(handle, true, r);
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="HostException">
/// If obtaining information about the buffer failed
/// </exception>
public override
Size
MaxWindowSize
{
get
{
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetBufferInfo(out bufferInfo);
Size s = new Size(bufferInfo.MaxWindowSize.X, bufferInfo.MaxWindowSize.Y);
return s;
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// Win32's GetLargestConsoleWindowSize failed
/// </exception>
public override
Size
MaxPhysicalWindowSize
{
get
{
ConsoleHandle handle = ConsoleControl.GetActiveScreenBufferHandle();
return ConsoleControl.GetLargestConsoleWindowSize(handle);
}
}
/// <summary>
/// Helper method to create and trace PipelineStoppedException.
/// </summary>
/// <returns></returns>
private static PipelineStoppedException NewPipelineStoppedException()
{
PipelineStoppedException e = new PipelineStoppedException();
return e;
}
/// <summary>
/// Used by ReadKey, cache KeyEvent based on if input.RepeatCount > 1.
/// </summary>
/// <param name="input">Input key event record.</param>
/// <param name="cache">Cache key event.</param>
private static void CacheKeyEvent(ConsoleControl.KEY_EVENT_RECORD input, ref ConsoleControl.KEY_EVENT_RECORD cache)
{
if (input.RepeatCount > 1)
{
cache = input;
cache.RepeatCount--;
}
}
/// <summary>
/// See base class
/// This method unwraps the repeat count in KEY_EVENT_RECORD by caching repeated keystrokes
/// in a logical queue. The implications are:
/// 1) Per discussion with Sburns on 2005/01/20, calling this method with allowCtrlC | includeKeyUp may not
/// return ctrl-c even it is pressed. This is because ctrl-c could generate the following sequence of
/// key events: {Ctrl, KeyDown}, {Ctrl-c KeyDown}, {Ctrl, KeyUp}, {c, KeyUp} if Ctrl is released before c.
/// In this case, {Ctrl, KeyUp}, {c, KeyUp} would be returned.
/// 2) If the cache is non-empty, a call to ReadLine will not return the cached keys. This
/// behavior is the same as that of System.Console.ReadKey.
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">
/// If neither IncludeKeyDown or IncludeKeyUp is set in <paramref name="options"/>
/// </exception>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// Win32's setting input buffer mode to disregard window and mouse input failed
/// OR
/// Win32's ReadConsoleInput failed
/// </exception>
public override
KeyInfo
ReadKey(ReadKeyOptions options)
{
if ((options & (ReadKeyOptions.IncludeKeyDown | ReadKeyOptions.IncludeKeyUp)) == 0)
{
throw PSTraceSource.NewArgumentException(nameof(options), ConsoleHostRawUserInterfaceStrings.InvalidReadKeyOptionsError);
}
// keyInfo is initialized in the below if-else statement
KeyInfo keyInfo;
if (cachedKeyEvent.RepeatCount > 0)
{
// Ctrl-C is not allowed and Ctrl-C is cached.
if (((options & ReadKeyOptions.AllowCtrlC) == 0) && cachedKeyEvent.UnicodeChar == (char)3)
{
// Ctrl-C is in the cache, stop pipeline immediately
cachedKeyEvent.RepeatCount--;
throw NewPipelineStoppedException();
}
// If IncludeKeyUp is not set and cached key events are KeyUp OR
// IncludeKeyDown is not set and cached key events are KeyDown, clear the cache
if ((((options & ReadKeyOptions.IncludeKeyUp) == 0) && !cachedKeyEvent.KeyDown) ||
(((options & ReadKeyOptions.IncludeKeyDown) == 0) && cachedKeyEvent.KeyDown))
{
cachedKeyEvent.RepeatCount = 0;
}
}
if (cachedKeyEvent.RepeatCount > 0)
{
KEY_EVENT_RECORDToKeyInfo(cachedKeyEvent, out keyInfo);
cachedKeyEvent.RepeatCount--;
}
else
{
ConsoleHandle handle = ConsoleControl.GetConioDeviceHandle();
ConsoleControl.INPUT_RECORD[] inputRecords = new ConsoleControl.INPUT_RECORD[1];
ConsoleControl.ConsoleModes originalMode = ConsoleControl.GetMode(handle);
// set input mode to exclude mouse or window events
// turn off ProcessedInput flag to handle ctrl-c
ConsoleControl.ConsoleModes newMode = originalMode &
~ConsoleControl.ConsoleModes.WindowInput &
~ConsoleControl.ConsoleModes.MouseInput &
~ConsoleControl.ConsoleModes.ProcessedInput;
try
{
ConsoleControl.SetMode(handle, newMode);
while (true)
{
int actualNumberOfInput = ConsoleControl.ReadConsoleInput(handle, ref inputRecords);
Dbg.Assert(actualNumberOfInput == 1,
string.Format(CultureInfo.InvariantCulture, "ReadConsoleInput returns {0} number of input event records",
actualNumberOfInput));
if (actualNumberOfInput == 1)
{
if (((ConsoleControl.InputRecordEventTypes)inputRecords[0].EventType) ==
ConsoleControl.InputRecordEventTypes.KEY_EVENT)
{
Dbg.Assert(!inputRecords[0].KeyEvent.KeyDown || inputRecords[0].KeyEvent.RepeatCount != 0,
string.Format(CultureInfo.InvariantCulture, "ReadConsoleInput returns a KeyEvent that is KeyDown and RepeatCount 0"));
if (inputRecords[0].KeyEvent.RepeatCount == 0)
{
// Sometimes Win32 ReadConsoleInput returns a KeyEvent record whose
// RepeatCount is zero. This type of record does not
// represent a keystroke.
continue;
}
// Ctrl-C is not allowed and Ctrl-C is input
if ((options & ReadKeyOptions.AllowCtrlC) == 0 &&
inputRecords[0].KeyEvent.UnicodeChar == (char)3)
{
CacheKeyEvent(inputRecords[0].KeyEvent, ref cachedKeyEvent);
throw NewPipelineStoppedException();
}
// if KeyDown events are wanted and event is KeyDown OR
// KeyUp events are wanted and event is KeyUp
if ((((options & ReadKeyOptions.IncludeKeyDown) != 0) &&
inputRecords[0].KeyEvent.KeyDown) ||
(((options & ReadKeyOptions.IncludeKeyUp) != 0) &&
!inputRecords[0].KeyEvent.KeyDown))
{
CacheKeyEvent(inputRecords[0].KeyEvent, ref cachedKeyEvent);
KEY_EVENT_RECORDToKeyInfo(inputRecords[0].KeyEvent, out keyInfo);
break;
}
}
}
}
}
finally
{
ConsoleControl.SetMode(handle, originalMode);
}
}
if ((options & ReadKeyOptions.NoEcho) == 0)
{
parent.WriteToConsole(keyInfo.Character, transcribeResult: true);
}
return keyInfo;
}
private static
void
KEY_EVENT_RECORDToKeyInfo(ConsoleControl.KEY_EVENT_RECORD keyEventRecord, out KeyInfo keyInfo)
{
keyInfo = new KeyInfo(
keyEventRecord.VirtualKeyCode,
keyEventRecord.UnicodeChar,
(ControlKeyStates)keyEventRecord.ControlKeyState,
keyEventRecord.KeyDown);
}
/// <summary>
/// See base class.
/// </summary>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// Win32's FlushConsoleInputBuffer failed
/// </exception>
public override
void
FlushInputBuffer()
{
ConsoleHandle handle = ConsoleControl.GetConioDeviceHandle();
ConsoleControl.FlushConsoleInputBuffer(handle);
cachedKeyEvent.RepeatCount = 0;
}
/// <summary>
/// See base class.
/// </summary>
/// <returns></returns>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// Win32's GetNumberOfConsoleInputEvents failed
/// OR
/// Win32's PeekConsoleInput failed
/// </exception>
public override
bool
KeyAvailable
{
get
{
if (cachedKeyEvent.RepeatCount > 0)
{
return true;
}
ConsoleHandle handle = ConsoleControl.GetConioDeviceHandle();
ConsoleControl.INPUT_RECORD[] inputRecords =
new ConsoleControl.INPUT_RECORD[ConsoleControl.GetNumberOfConsoleInputEvents(handle)];
int actualNumberOfInputRecords = ConsoleControl.PeekConsoleInput(handle, ref inputRecords);
for (int i = 0; i < actualNumberOfInputRecords; i++)
{
if (((ConsoleControl.InputRecordEventTypes)inputRecords[i].EventType) ==
ConsoleControl.InputRecordEventTypes.KEY_EVENT)
{
Dbg.Assert(!inputRecords[i].KeyEvent.KeyDown || inputRecords[i].KeyEvent.RepeatCount != 0,
string.Format(CultureInfo.InvariantCulture, "PeekConsoleInput returns a KeyEvent that is KeyDown and RepeatCount 0"));
if (inputRecords[i].KeyEvent.KeyDown && inputRecords[i].KeyEvent.RepeatCount == 0)
{
// Sometimes Win32 ReadConsoleInput returns a KeyEvent record whose
// KeyDown is true and RepeatCount is zero. This type of record does not
// represent a keystroke.
continue;
}
return true;
}
}
return false;
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception cref="ArgumentNullException">
/// If set to null
/// </exception>
/// <exception cref="ArgumentException">
/// If set to a string whose length is not between 1 to 1023
/// </exception>
/// <exception cref="HostException">
/// If Win32's GetConsoleWindowTitle failed
/// OR
/// Win32's SetConsoleWindowTitle failed
/// </exception>
public override string WindowTitle
{
get
{
return ConsoleControl.GetConsoleWindowTitle();
}
set
{
const int MaxWindowTitleLength = 1023;
const int MinWindowTitleLength = 0;
if (value != null)
{
if (
value.Length >= MinWindowTitleLength &&
value.Length <= MaxWindowTitleLength
)
{
ConsoleControl.SetConsoleWindowTitle(value);
}
else if (value.Length < MinWindowTitleLength)
{
throw PSTraceSource.NewArgumentException("value", ConsoleHostRawUserInterfaceStrings.WindowTitleTooShortError);
}
else
{
throw PSTraceSource.NewArgumentException("value",
ConsoleHostRawUserInterfaceStrings
.WindowTitleTooLongErrorTemplate,
MaxWindowTitleLength);
}
}
else
{
throw PSTraceSource.NewArgumentNullException("value");
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="origin">
/// location on screen buffer where contents will be written
/// </param>
/// <param name="contents">
/// array of info to be written
/// </param>
/// <remarks></remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="origin"/> is outside of the screen buffer.
/// OR
/// <paramref name="contents"/> is an ill-formed BufferCell array
/// OR
/// it is illegal to write <paramref name="contents"/> at <paramref name="origin"/> in the buffer
/// </exception>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// obtaining information about the buffer failed
/// OR
/// there is not enough memory to complete calls to Win32's WriteConsoleOutput
/// </exception>
public override
void
SetBufferContents(Coordinates origin, BufferCell[,] contents)
{
if (contents == null)
{
PSTraceSource.NewArgumentNullException(nameof(contents));
}
// the origin must be within the window.
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
CheckCoordinateWithinBuffer(ref origin, ref bufferInfo, nameof(origin));
// The output is clipped by the console subsystem, so we don't have to check that the array exceeds the buffer
// boundaries.
ConsoleControl.WriteConsoleOutput(handle, origin, contents);
}
/// <summary>
/// If <paramref name="region"/> is completely outside of the screen buffer, it's a no-op.
/// </summary>
/// <param name="region">
/// region with all elements = -1 means "entire screen buffer"
/// </param>
/// <param name="fill">
/// character and attribute to fill the screen buffer
/// </param>
/// <remarks>
/// Provided for clearing regions -- less chatty than passing an array of cells.
/// Clear screen is:
/// SetBufferContents(new Rectangle(-1, -1, -1, -1), ' ', ForegroundColor, BackgroundColor);
/// CursorPosition = new Coordinates(0, 0);
///
/// fill.Type is ignored
/// </remarks>
/// <exception cref="ArgumentException">
/// If <paramref name="region"/>'s Left exceeds Right or Bottom exceeds Top
/// OR
/// it is illegal to set <paramref name="region"/> in the buffer with <paramref name="fill"/>
/// </exception>
/// <exception cref="HostException">
/// If Win32's CreateFile fails
/// OR
/// Win32's GetConsoleScreenBufferInfo fails
/// OR
/// there is not enough memory to complete calls to Win32's WriteConsoleOutput
/// </exception>
public override
void
SetBufferContents(Rectangle region, BufferCell fill)
{
// make sure the rect is valid
if (region.Right < region.Left)
{
throw PSTraceSource.NewArgumentException(nameof(region),
ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate,
"region.Right", "region.Left");
}
if (region.Bottom < region.Top)
{
throw PSTraceSource.NewArgumentException(nameof(region),
ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate,
"region.Bottom", "region.Top");
}
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
int bufferWidth = bufferInfo.BufferSize.X;
int bufferHeight = bufferInfo.BufferSize.Y;
WORD attribute = ConsoleControl.ColorToWORD(fill.ForegroundColor, fill.BackgroundColor);
Coordinates origin = new Coordinates(0, 0);
uint codePage;
// region == {-1, -1, -1, -1} is a special case meaning "the whole screen buffer"
if (region.Left == -1 && region.Right == -1 && region.Top == -1 && region.Bottom == -1)
{
if (bufferWidth % 2 == 1 &&
ConsoleControl.IsCJKOutputCodePage(out codePage) &&
LengthInBufferCells(fill.Character) == 2)
{
throw PSTraceSource.NewArgumentException(nameof(fill));
}
int cells = bufferWidth * bufferHeight;
ConsoleControl.FillConsoleOutputCharacter(handle, fill.Character, cells, origin);
ConsoleControl.FillConsoleOutputAttribute(handle, attribute, cells, origin);
return;
}
// The FillConsoleOutputXxx functions wrap at the end of a line. So we need to convert our rectangular region
// into line segments that don't extend past the end of a line. We will also clip the rectangle so that the semantics
// are the same as SetBufferContents(Coordinates, BufferCell[,]), which clips if the rectangle extends past the
// screen buffer boundaries.
if (region.Left >= bufferWidth || region.Top >= bufferHeight || region.Right < 0 || region.Bottom < 0)
{
// region is entirely outside the buffer boundaries
tracer.WriteLine("region outside boundaries");
return;
}
int lineStart = Math.Max(0, region.Left);
int lineEnd = Math.Min(bufferWidth - 1, region.Right);
int lineLength = lineEnd - lineStart + 1;
origin.X = lineStart;
int firstRow = Math.Max(0, region.Top);
int lastRow = Math.Min(bufferHeight - 1, region.Bottom);
origin.Y = firstRow;
if (ConsoleControl.IsCJKOutputCodePage(out codePage))
{
Rectangle existingRegion = new Rectangle(0, 0, 1, lastRow - firstRow);
int charLength = LengthInBufferCells(fill.Character);
// Check left edge
if (origin.X > 0)
{
BufferCell[,] leftExisting = new BufferCell[existingRegion.Bottom + 1, 2];
ConsoleControl.ReadConsoleOutputCJK(handle, codePage,
new Coordinates(origin.X - 1, origin.Y), existingRegion, ref leftExisting);
for (int r = 0; r <= existingRegion.Bottom; r++)
{
if (leftExisting[r, 0].BufferCellType == BufferCellType.Leading)
{
throw PSTraceSource.NewArgumentException(nameof(fill));
}
}
}
// Check right edge
if (lineEnd == bufferWidth - 1)
{
if (charLength == 2)
{
throw PSTraceSource.NewArgumentException(nameof(fill));
}
}
else
{
// use ReadConsoleOutputCJK because checking the left and right edges of the existing output
// is NOT needed
BufferCell[,] rightExisting = new BufferCell[existingRegion.Bottom + 1, 2];
ConsoleControl.ReadConsoleOutputCJK(handle, codePage,
new Coordinates(lineEnd, origin.Y), existingRegion, ref rightExisting);
if (lineLength % 2 == 0)
{
for (int r = 0; r <= existingRegion.Bottom; r++)
{
if (rightExisting[r, 0].BufferCellType == BufferCellType.Leading)
{
throw PSTraceSource.NewArgumentException(nameof(fill));
}
}
}
else
{
for (int r = 0; r <= existingRegion.Bottom; r++)
{
if (rightExisting[r, 0].BufferCellType == BufferCellType.Leading ^ charLength == 2)
{
throw PSTraceSource.NewArgumentException(nameof(fill));
}
}
}
}
if (lineLength % 2 == 1)
{
lineLength++;
}
}
for (int row = firstRow; row <= lastRow; ++row)
{
origin.Y = row;
// we know that lineStart and lineEnd will always be within the buffer area because of previous boundary
// checks already done.
ConsoleControl.FillConsoleOutputCharacter(handle, fill.Character, lineLength, origin);
ConsoleControl.FillConsoleOutputAttribute(handle, attribute, lineLength, origin);
}
}
/// <summary>
/// See base class.
/// If the rectangle is invalid, ie, Right exceeds Left OR Bottom exceeds Top,
/// </summary>
/// <param name="region">
/// area on screen buffer to be read
/// </param>
/// <returns>
/// an array of BufferCell containing screen buffer contents
/// </returns>
/// <exception cref="ArgumentException">
/// If <paramref name="region"/>'s Left exceeds Right or Bottom exceeds Top.
/// </exception>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// obtaining information about the buffer failed
/// OR
/// there is not enough memory to complete calls to Win32's ReadConsoleOutput
/// </exception>
public override
BufferCell[,] GetBufferContents(Rectangle region)
{
// make sure the rect is valid
if (region.Right < region.Left)
{
throw PSTraceSource.NewArgumentException(nameof(region),
ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate,
"region.Right", "region.Left");
}
if (region.Bottom < region.Top)
{
throw PSTraceSource.NewArgumentException(nameof(region),
ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate,
"region.Bottom", "region.Top");
}
ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
ConsoleHandle handle = GetBufferInfo(out bufferInfo);
int bufferWidth = bufferInfo.BufferSize.X;
int bufferHeight = bufferInfo.BufferSize.Y;
if (region.Left >= bufferWidth || region.Top >= bufferHeight || region.Right < 0 || region.Bottom < 0)
{
// region is entirely outside the buffer boundaries
tracer.WriteLine("region outside boundaries");
return new BufferCell[0, 0];
}
int colStart = Math.Max(0, region.Left);
int colEnd = Math.Min(bufferWidth - 1, region.Right);
int rowStart = Math.Max(0, region.Top);
int rowEnd = Math.Min(bufferHeight - 1, region.Bottom);
Coordinates origin = new Coordinates(colStart, rowStart);
// contentsRegion indicates the area in contents (declared below) in which
// the data read from ReadConsoleOutput is stored.
Rectangle contentsRegion = new Rectangle();
contentsRegion.Left = Math.Max(0, 0 - region.Left);
contentsRegion.Top = Math.Max(0, 0 - region.Top);
contentsRegion.Right = contentsRegion.Left + (colEnd - colStart);
contentsRegion.Bottom = contentsRegion.Top + (rowEnd - rowStart);
BufferCell[,] contents = new BufferCell[region.Bottom - region.Top + 1,
region.Right - region.Left + 1];
ConsoleControl.ReadConsoleOutput(handle, origin, contentsRegion, ref contents);
return contents;
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="source">
/// area to be moved
/// </param>
/// <param name="destination">
/// top left corner to which source to be moved
/// </param>
/// <param name="clip">
/// area to be updated caused by the move
/// </param>
/// <param name="fill">
/// character and attribute to fill the area vacated by the move
/// </param>
/// <exception cref="HostException">
/// If obtaining the active screen buffer failed
/// OR
/// Call to Win32's ScrollConsoleScreenBuffer failed
/// </exception>
public override
void
ScrollBufferContents
(
Rectangle source,
Coordinates destination,
Rectangle clip,
BufferCell fill
)
{
ConsoleControl.SMALL_RECT scrollRectangle;
scrollRectangle.Left = (short)source.Left;
scrollRectangle.Right = (short)source.Right;
scrollRectangle.Top = (short)source.Top;
scrollRectangle.Bottom = (short)source.Bottom;
ConsoleControl.SMALL_RECT clipRectangle;
clipRectangle.Left = (short)clip.Left;
clipRectangle.Right = (short)clip.Right;
clipRectangle.Top = (short)clip.Top;
clipRectangle.Bottom = (short)clip.Bottom;
ConsoleControl.COORD origin;
origin.X = (short)destination.X;
origin.Y = (short)destination.Y;
ConsoleControl.CHAR_INFO fillChar;
fillChar.UnicodeChar = fill.Character;
fillChar.Attributes = ConsoleControl.ColorToWORD(fill.ForegroundColor, fill.BackgroundColor);
ConsoleHandle consoleHandle = ConsoleControl.GetActiveScreenBufferHandle();
ConsoleControl.ScrollConsoleScreenBuffer
(
consoleHandle,
scrollRectangle,
clipRectangle,
origin,
fillChar
);
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
/// <exception cref="HostException">
/// If Win32's WideCharToMultiByte fails
/// </exception>
public override
int LengthInBufferCells(string s)
{
return this.LengthInBufferCells(s, 0);
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="s"></param>
/// <param name="offset"></param>
/// <returns></returns>
/// <exception cref="HostException">
/// If Win32's WideCharToMultiByte fails
/// </exception>
public override
int LengthInBufferCells(string s, int offset)
{
if (s == null)
{
throw PSTraceSource.NewArgumentNullException("str");
}
return ConsoleControl.LengthInBufferCells(s, offset, parent.SupportsVirtualTerminal);
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
/// <exception cref="HostException">
/// If Win32's WideCharToMultiByte fails
/// </exception>
public override
int LengthInBufferCells(char c)
{
return ConsoleControl.LengthInBufferCells(c);
}
#region internal
/// <summary>
/// Clear the ReadKey cache.
/// </summary>
/// <exception/>
internal void ClearKeyCache()
{
cachedKeyEvent.RepeatCount = 0;
}
#endregion internal
#region helpers
// pass-by-ref for speed.
/// <summary>
/// </summary>
/// <param name="c"></param>
/// <param name="bufferInfo"></param>
/// <param name="paramName"></param>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="c"/> is outside of the output buffer area
/// </exception>
private static
void
CheckCoordinateWithinBuffer(ref Coordinates c, ref ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo, string paramName)
{
if (c.X < 0 || c.X > bufferInfo.BufferSize.X)
{
throw PSTraceSource.NewArgumentOutOfRangeException(
paramName + ".X",
c.X,
ConsoleHostRawUserInterfaceStrings.CoordinateOutOfBufferErrorTemplate, bufferInfo.BufferSize);
}
if (c.Y < 0 || c.Y > bufferInfo.BufferSize.Y)
{
throw PSTraceSource.NewArgumentOutOfRangeException(
paramName + ".Y",
c.Y,
ConsoleHostRawUserInterfaceStrings.CoordinateOutOfBufferErrorTemplate, bufferInfo.BufferSize);
}
}
/// <summary>
/// Get output buffer info.
/// </summary>
/// <param name="bufferInfo"></param>
/// <returns></returns>
/// <exception cref="HostException">
/// If Win32's CreateFile fails
/// OR
/// Win32's GetConsoleScreenBufferInfo fails
/// </exception>
private static
ConsoleHandle
GetBufferInfo(out ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo)
{
ConsoleHandle result = ConsoleControl.GetActiveScreenBufferHandle();
bufferInfo = ConsoleControl.GetConsoleScreenBufferInfo(result);
return result;
}
#endregion helpers
private readonly ConsoleColor defaultForeground = ConsoleColor.Gray;
private readonly ConsoleColor defaultBackground = ConsoleColor.Black;
private readonly ConsoleHostUserInterface parent = null;
private ConsoleControl.KEY_EVENT_RECORD cachedKeyEvent;
[TraceSourceAttribute("ConsoleHostRawUserInterface", "Console host's subclass of S.M.A.Host.RawConsole")]
private static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleHostRawUserInterface", "Console host's subclass of S.M.A.Host.RawConsole");
}
} // namespace
#else
// Managed code only implementation for portability
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Host;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Microsoft.PowerShell
{
// this is all originally from https://msdn.microsoft.com/library/ee706570%28v=vs.85%29.aspx
internal sealed class ConsoleHostRawUserInterface : PSHostRawUserInterface
{
private readonly ConsoleHostUserInterface _parent = null;
internal ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base()
{
_parent = mshConsole;
}
/// <summary>
/// Gets or sets the background color of the displayed text.
/// This maps to the corresponding Console.Background property.
/// </summary>
public override ConsoleColor BackgroundColor
{
get { return Console.BackgroundColor; }
set { Console.BackgroundColor = value; }
}
// TODO: Make wrap width user-customizable.
private static Size s_wrapSize = new Size(80, 40);
/// <summary>
/// Gets or sets the size of the host buffer.
/// </summary>
public override Size BufferSize
{
get
{
// Console can return zero when a pseudo-TTY is allocated, which
// is useless for us. Instead, map to the wrap size.
return Console.BufferWidth == 0 || Console.BufferHeight == 0
? s_wrapSize
: new Size(Console.BufferWidth, Console.BufferHeight);
}
set
{
Console.SetBufferSize(value.Width, value.Height);
}
}
/// <summary>
/// Gets or sets the cursor position.
/// </summary>
public override Coordinates CursorPosition
{
get
{
return new Coordinates(Console.CursorLeft, Console.CursorTop);
}
set
{
Console.SetCursorPosition(value.X < 0 ? 0 : value.X,
value.Y < 0 ? 0 : value.Y);
}
}
/// <summary>
/// Gets or sets the size of the displayed cursor.
/// This maps to the corresponding Console.CursorSize property.
/// </summary>
public override int CursorSize
{
// Future porting note: this API throws on Windows when output is
// redirected, but never throws on Unix because it's fake.
get { return Console.CursorSize; }
set { Console.CursorSize = value; }
}
/// <summary>
/// Gets or sets the foreground color of the displayed text.
/// This maps to the corresponding Console.ForegroundColor property.
/// </summary>
public override ConsoleColor ForegroundColor
{
get { return Console.ForegroundColor; }
set { Console.ForegroundColor = value; }
}
/// <summary>
/// Gets a value indicating whether the user has pressed a key. This maps
/// to the corresponding Console.KeyAvailable property.
/// </summary>
public override bool KeyAvailable
{
get { return Console.KeyAvailable; }
}
/// <summary>
/// Gets the dimensions of the largest window that could be rendered in
/// the current display, if the buffer was at the least that large.
/// This maps to the MaxWindowSize.
/// </summary>
public override Size MaxPhysicalWindowSize
{
get { return MaxWindowSize; }
}
/// <summary>
/// Gets the dimensions of the largest window size that can be
/// displayed. This maps to the Console.LargestWindowWidth and
/// Console.LargestWindowHeight properties to determine the returned
/// value of this property.
/// </summary>
public override Size MaxWindowSize
{
get
{
// Console can return zero when a pseudo-TTY is allocated, which
// is useless for us. Instead, map to the wrap size.
return Console.LargestWindowWidth == 0 || Console.LargestWindowHeight == 0
? s_wrapSize
: new Size(Console.LargestWindowWidth, Console.LargestWindowHeight);
}
}
/// <summary>
/// Gets or sets the position of the displayed window. This maps to the
/// Console window position APIs to determine the returned value of this
/// property.
/// </summary>
public override Coordinates WindowPosition
{
get { return new Coordinates(Console.WindowLeft, Console.WindowTop); }
set { Console.SetWindowPosition(value.X, value.Y); }
}
/// <summary>
/// Gets or sets the size of the displayed window. This example
/// uses the corresponding Console window size APIs to determine the
/// returned value of this property.
/// </summary>
public override Size WindowSize
{
get
{
// Console can return zero when a pseudo-TTY is allocated, which
// is useless for us. Instead, map to the wrap size.
return Console.WindowWidth == 0 || Console.WindowHeight == 0
? s_wrapSize
: new Size(Console.WindowWidth, Console.WindowHeight);
}
set
{
Console.SetWindowSize(value.Width, value.Height);
}
}
/// <summary>
/// Cached Window Title, for systems that needs it.
/// </summary>
private string _title = string.Empty;
/// <summary>
/// Gets or sets the title of the displayed window. The example
/// maps the Console.Title property to the value of this property.
/// </summary>
public override string WindowTitle
{
get
{
// Console throws an exception on Unix platforms, so we handle
// caching and returning the Window title ourselves.
return Platform.IsWindows ? Console.Title : _title;
}
set
{
Console.Title = value;
_title = value;
}
}
/// <summary>
/// This API resets the input buffer.
/// </summary>
public override void FlushInputBuffer()
{
if (!Console.IsInputRedirected)
{
Console.OpenStandardInput().Flush();
}
}
public void ScrollBuffer(int lines)
{
for (int i = 0; i < lines; ++i)
{
Console.Out.Write('\n');
}
}
internal struct COORD
{
internal short X;
internal short Y;
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y);
}
}
internal struct SMALL_RECT
{
internal short Left;
internal short Top;
internal short Right;
internal short Bottom;
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", Left, Top, Right, Bottom);
}
}
/// <summary>
/// This API returns a rectangular region of the screen buffer. In
/// this example this functionality is not needed so the method throws
/// a NotImplementException exception.
/// </summary>
/// <param name="rectangle">Defines the size of the rectangle.</param>
/// <returns>Throws a NotImplementedException exception.</returns>
public override BufferCell[,] GetBufferContents(Rectangle rectangle)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
/// <summary>
/// This API reads a pressed, released, or pressed and released keystroke
/// from the keyboard device, blocking processing until a keystroke is
/// typed that matches the specified keystroke options.
/// </summary>
/// <param name="options">Only NoEcho is supported.</param>
public override KeyInfo ReadKey(ReadKeyOptions options)
{
ConsoleKeyInfo key = Console.ReadKey((options & ReadKeyOptions.NoEcho) != 0);
return new KeyInfo((int)key.Key, key.KeyChar, new ControlKeyStates(), true);
}
/// <summary>
/// This API crops a region of the screen buffer. In this example
/// this functionality is not needed so the method throws a
/// NotImplementException exception.
/// </summary>
/// <param name="source">The region of the screen to be scrolled.</param>
/// <param name="destination">The region of the screen to receive the
/// source region contents.</param>
/// <param name="clip">The region of the screen to include in the operation.</param>
/// <param name="fill">The character and attributes to be used to fill all cell.</param>
public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
/// <summary>
/// This method copies an array of buffer cells into the screen buffer
/// at a specified location.
/// </summary>
/// <param name="origin">The parameter used to set the origin where the buffer where begin writing to.</param>
/// <param name="contents">The parameter used to contain the contents to be written to the buffer.</param>
public override void SetBufferContents(Coordinates origin,
BufferCell[,] contents)
{
// if there are no contents, there is nothing to set the buffer to
if (contents == null)
{
PSTraceSource.NewArgumentNullException("contents");
}
// if the cursor is on the last line, we need to make more space to print the specified buffer
if (origin.Y == BufferSize.Height - 1 && origin.X >= BufferSize.Width)
{
// for each row in the buffer, create a new line
int rows = contents.GetLength(0);
ScrollBuffer(rows);
// for each row in the buffer, move the cursor y up to the beginning of the created blank space
// but not above zero
if (origin.Y >= rows)
{
origin.Y -= rows;
}
}
#if UNIX
// Make sure that the physical cursor position matches where we think it is.
// This is a problem on *nix, because input that the user types is echoed
// and that moves the cursor. As a consequence, the cursor needs to be repositioned
// before we update the screen.
CursorPosition = origin;
#endif
// iterate through the buffer to set
foreach (var charitem in contents)
{
// set the cursor to false to prevent cursor flicker
Console.CursorVisible = false;
// if x is exceeding buffer width, reset to the next line
if (origin.X >= BufferSize.Width)
{
origin.X = 0;
}
// write the character from contents
Console.Out.Write(charitem.Character);
}
// reset the cursor to the original position
CursorPosition = origin;
// reset the cursor to visible
Console.CursorVisible = true;
}
/// <summary>
/// This method copies a given character, foreground color, and background
/// color to a region of the screen buffer. In this example this
/// functionality is not needed so the method throws a
/// NotImplementException exception./// </summary>
/// <param name="rectangle">Defines the area to be filled.</param>
/// <param name="fill">Defines the fill character.</param>
public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
{
throw new NotImplementedException("The method or operation is not implemented.");
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public override
int LengthInBufferCells(string s)
{
return this.LengthInBufferCells(s, 0);
}
/// <summary>
/// See base class.
/// </summary>
/// <param name="s"></param>
/// <param name="offset"></param>
/// <returns></returns>
public override
int LengthInBufferCells(string s, int offset)
{
if (s == null)
{
throw PSTraceSource.NewArgumentNullException("str");
}
return ConsoleControl.LengthInBufferCells(s, offset, _parent.SupportsVirtualTerminal);
}
}
}
#endif