mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-24 02:37:06 +00:00
https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0044
505 lines
17 KiB
C#
505 lines
17 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Management.Automation;
|
|
using System.Text;
|
|
|
|
// interfaces for host interaction
|
|
|
|
namespace Microsoft.PowerShell.Commands.Internal.Format
|
|
{
|
|
/// <summary>
|
|
/// Base class providing support for string manipulation.
|
|
/// This class is a tear off class provided by the LineOutput class
|
|
///
|
|
/// Assumptions (in addition to the assumptions made for LineOutput):
|
|
/// - characters map to one or more character cells
|
|
///
|
|
/// NOTE: we provide a base class that is valid for devices that have a
|
|
/// 1:1 mapping between a UNICODE character and a display cell.
|
|
/// </summary>
|
|
internal class DisplayCells
|
|
{
|
|
internal virtual int Length(string str)
|
|
{
|
|
return Length(str, 0);
|
|
}
|
|
|
|
internal virtual int Length(string str, int offset)
|
|
{
|
|
int length = 0;
|
|
|
|
foreach (char c in str)
|
|
{
|
|
length += LengthInBufferCells(c);
|
|
}
|
|
|
|
return length - offset;
|
|
}
|
|
|
|
internal virtual int Length(char character) { return 1; }
|
|
|
|
internal virtual int GetHeadSplitLength(string str, int displayCells)
|
|
{
|
|
return GetHeadSplitLength(str, 0, displayCells);
|
|
}
|
|
|
|
internal virtual int GetHeadSplitLength(string str, int offset, int displayCells)
|
|
{
|
|
int len = str.Length - offset;
|
|
return (len < displayCells) ? len : displayCells;
|
|
}
|
|
|
|
internal virtual int GetTailSplitLength(string str, int displayCells)
|
|
{
|
|
return GetTailSplitLength(str, 0, displayCells);
|
|
}
|
|
|
|
internal virtual int GetTailSplitLength(string str, int offset, int displayCells)
|
|
{
|
|
int len = str.Length - offset;
|
|
return (len < displayCells) ? len : displayCells;
|
|
}
|
|
|
|
#region Helpers
|
|
|
|
protected static int LengthInBufferCells(char c)
|
|
{
|
|
// The following is based on http://www.cl.cam.ac.uk/~mgk25/c/wcwidth.c
|
|
// which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
|
|
bool isWide = c >= 0x1100 &&
|
|
(c <= 0x115f || /* Hangul Jamo init. consonants */
|
|
c == 0x2329 || c == 0x232a ||
|
|
((uint)(c - 0x2e80) <= (0xa4cf - 0x2e80) &&
|
|
c != 0x303f) || /* CJK ... Yi */
|
|
((uint)(c - 0xac00) <= (0xd7a3 - 0xac00)) || /* Hangul Syllables */
|
|
((uint)(c - 0xf900) <= (0xfaff - 0xf900)) || /* CJK Compatibility Ideographs */
|
|
((uint)(c - 0xfe10) <= (0xfe19 - 0xfe10)) || /* Vertical forms */
|
|
((uint)(c - 0xfe30) <= (0xfe6f - 0xfe30)) || /* CJK Compatibility Forms */
|
|
((uint)(c - 0xff00) <= (0xff60 - 0xff00)) || /* Fullwidth Forms */
|
|
((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0)));
|
|
|
|
// We can ignore these ranges because .Net strings use surrogate pairs
|
|
// for this range and we do not handle surrogage pairs.
|
|
// (c >= 0x20000 && c <= 0x2fffd) ||
|
|
// (c >= 0x30000 && c <= 0x3fffd)
|
|
return 1 + (isWide ? 1 : 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a string and a number of display cells, it computes how many
|
|
/// characters would fit starting from the beginning or end of the string.
|
|
/// </summary>
|
|
/// <param name="str">String to be displayed.</param>
|
|
/// <param name="offset">Offset inside the string.</param>
|
|
/// <param name="displayCells">Number of display cells.</param>
|
|
/// <param name="head">If true compute from the head (i.e. k++) else from the tail (i.e. k--).</param>
|
|
/// <returns>Number of characters that would fit.</returns>
|
|
protected int GetSplitLengthInternalHelper(string str, int offset, int displayCells, bool head)
|
|
{
|
|
int filledDisplayCellsCount = 0; // number of cells that are filled in
|
|
int charactersAdded = 0; // number of characters that fit
|
|
int currCharDisplayLen; // scratch variable
|
|
|
|
int k = (head) ? offset : str.Length - 1;
|
|
int kFinal = (head) ? str.Length - 1 : offset;
|
|
while (true)
|
|
{
|
|
if ((head && (k > kFinal)) || ((!head) && (k < kFinal)))
|
|
{
|
|
break;
|
|
}
|
|
// compute the cell number for the current character
|
|
currCharDisplayLen = this.Length(str[k]);
|
|
|
|
if (filledDisplayCellsCount + currCharDisplayLen > displayCells)
|
|
{
|
|
// if we added this character it would not fit, we cannot continue
|
|
break;
|
|
}
|
|
// keep adding, we fit
|
|
filledDisplayCellsCount += currCharDisplayLen;
|
|
charactersAdded++;
|
|
|
|
// check if we fit exactly
|
|
if (filledDisplayCellsCount == displayCells)
|
|
{
|
|
// exact fit, we cannot add more
|
|
break;
|
|
}
|
|
|
|
k = (head) ? (k + 1) : (k - 1);
|
|
}
|
|
|
|
return charactersAdded;
|
|
}
|
|
#endregion
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base class providing information about the screen device capabilities
|
|
/// and used to write the output strings to the text output device.
|
|
/// Each device supported will have to derive from it.
|
|
/// Examples of supported devices are:
|
|
/// * Screen Layout: it layers on top of Console and RawConsole
|
|
/// * File: it layers on top of a TextWriter
|
|
/// * In Memory text stream: it layers on top of an in memory buffer
|
|
/// * Printer: it layers on top of a memory buffer then sent to a printer device
|
|
///
|
|
/// Assumptions:
|
|
/// - Fixed pitch font: layout done in terms of character cells
|
|
/// - character cell layout not affected by bold, reverse screen, color, etc.
|
|
/// - returned values might change from call to call if the specific underlying
|
|
/// implementation allows window resizing.
|
|
/// </summary>
|
|
internal abstract class LineOutput
|
|
{
|
|
/// <summary>
|
|
/// Whether the device requires full buffering of formatting
|
|
/// objects before any processing.
|
|
/// </summary>
|
|
internal virtual bool RequiresBuffering { get { return false; } }
|
|
|
|
/// <summary>
|
|
/// Delegate the implementor of ExecuteBufferPlayBack should
|
|
/// call to cause the playback to happen when ready to execute.
|
|
/// </summary>
|
|
internal delegate void DoPlayBackCall();
|
|
|
|
/// <summary>
|
|
/// If RequiresBuffering = true, this call will be made to
|
|
/// start the playback.
|
|
/// </summary>
|
|
internal virtual void ExecuteBufferPlayBack(DoPlayBackCall playback) { }
|
|
|
|
/// <summary>
|
|
/// The number of columns the current device has.
|
|
/// </summary>
|
|
internal abstract int ColumnNumber { get; }
|
|
|
|
/// <summary>
|
|
/// The number of rows the current device has.
|
|
/// </summary>
|
|
internal abstract int RowNumber { get; }
|
|
|
|
/// <summary>
|
|
/// Write a line to the output device.
|
|
/// </summary>
|
|
/// <param name="s">
|
|
/// string to be written to the device
|
|
/// </param>
|
|
internal abstract void WriteLine(string s);
|
|
|
|
internal WriteStreamType WriteStream
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle the stop processing signal.
|
|
/// Set a flag that will be checked during operations.
|
|
/// </summary>
|
|
internal void StopProcessing()
|
|
{
|
|
_isStopping = true;
|
|
}
|
|
|
|
private bool _isStopping;
|
|
|
|
internal void CheckStopProcessing()
|
|
{
|
|
if (!_isStopping)
|
|
return;
|
|
throw new PipelineStoppedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return an instance of the display helper tear off.
|
|
/// </summary>
|
|
/// <value></value>
|
|
internal virtual DisplayCells DisplayCells
|
|
{
|
|
get
|
|
{
|
|
CheckStopProcessing();
|
|
// just return the default singleton implementation
|
|
return _displayCellsDefault;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Singleton used for the default implementation.
|
|
/// NOTE: derived classes may chose to provide a different
|
|
/// implementation by overriding.
|
|
/// </summary>
|
|
protected static DisplayCells _displayCellsDefault = new DisplayCells();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper class to provide line breaking (based on device width)
|
|
/// and embedded newline processing
|
|
/// It needs to be provided with two callbacks for line processing.
|
|
/// </summary>
|
|
internal class WriteLineHelper
|
|
{
|
|
#region callbacks
|
|
|
|
/// <summary>
|
|
/// Delegate definition.
|
|
/// </summary>
|
|
/// <param name="s">String to write.</param>
|
|
internal delegate void WriteCallback(string s);
|
|
|
|
/// <summary>
|
|
/// Instance of the delegate previously defined
|
|
/// for line that has EXACTLY this.ncols characters.
|
|
/// </summary>
|
|
private readonly WriteCallback _writeCall = null;
|
|
|
|
/// <summary>
|
|
/// Instance of the delegate previously defined
|
|
/// for generic line, less that this.ncols characters.
|
|
/// </summary>
|
|
private readonly WriteCallback _writeLineCall = null;
|
|
|
|
#endregion
|
|
|
|
private readonly bool _lineWrap;
|
|
|
|
/// <summary>
|
|
/// Construct an instance, given the two callbacks
|
|
/// NOTE: if the underlying device treats the two cases as the
|
|
/// same, the same delegate can be passed twice.
|
|
/// </summary>
|
|
/// <param name="lineWrap">True if we require line wrapping.</param>
|
|
/// <param name="wlc">Delegate for WriteLine(), must ben non null.</param>
|
|
/// <param name="wc">Delegate for Write(), if null, use the first parameter.</param>
|
|
/// <param name="displayCells">Helper object for manipulating strings.</param>
|
|
internal WriteLineHelper(bool lineWrap, WriteCallback wlc, WriteCallback wc, DisplayCells displayCells)
|
|
{
|
|
if (wlc == null)
|
|
throw PSTraceSource.NewArgumentNullException(nameof(wlc));
|
|
if (displayCells == null)
|
|
throw PSTraceSource.NewArgumentNullException(nameof(displayCells));
|
|
|
|
_displayCells = displayCells;
|
|
_writeLineCall = wlc;
|
|
_writeCall = wc ?? wlc;
|
|
_lineWrap = lineWrap;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main entry point to process a line.
|
|
/// </summary>
|
|
/// <param name="s">String to process.</param>
|
|
/// <param name="cols">Width of the device.</param>
|
|
internal void WriteLine(string s, int cols)
|
|
{
|
|
WriteLineInternal(s, cols);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper, needed because it might make recursive calls to itself.
|
|
/// </summary>
|
|
/// <param name="val">String to process.</param>
|
|
/// <param name="cols">Width of the device.</param>
|
|
private void WriteLineInternal(string val, int cols)
|
|
{
|
|
if (string.IsNullOrEmpty(val))
|
|
{
|
|
_writeLineCall(val);
|
|
return;
|
|
}
|
|
|
|
// If the output is being redirected, then we don't break val
|
|
if (!_lineWrap)
|
|
{
|
|
_writeCall(val);
|
|
return;
|
|
}
|
|
|
|
// check for line breaks
|
|
string[] lines = StringManipulationHelper.SplitLines(val);
|
|
|
|
// process the substrings as separate lines
|
|
for (int k = 0; k < lines.Length; k++)
|
|
{
|
|
// compute the display length of the string
|
|
int displayLength = _displayCells.Length(lines[k]);
|
|
|
|
if (displayLength < cols)
|
|
{
|
|
// NOTE: this is the case where where System.Console.WriteLine() would work just fine
|
|
_writeLineCall(lines[k]);
|
|
continue;
|
|
}
|
|
|
|
if (displayLength == cols)
|
|
{
|
|
// NOTE: this is the corner case where System.Console.WriteLine() cannot be called
|
|
_writeCall(lines[k]);
|
|
continue;
|
|
}
|
|
|
|
// the string does not fit, so we have to wrap around on multiple lines
|
|
string s = lines[k];
|
|
|
|
while (true)
|
|
{
|
|
// the string is still too long to fit, write the first cols characters
|
|
// and go back for more wraparound
|
|
int splitLen = _displayCells.GetHeadSplitLength(s, cols);
|
|
WriteLineInternal(s.Substring(0, splitLen), cols);
|
|
|
|
// chop off the first fieldWidth characters, already printed
|
|
s = s.Substring(splitLen);
|
|
if (_displayCells.Length(s) <= cols)
|
|
{
|
|
// if we fit, print the tail of the string and we are done
|
|
WriteLineInternal(s, cols);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly DisplayCells _displayCells;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of the ILineOutput interface accepting an instance of a
|
|
/// TextWriter abstract class.
|
|
/// </summary>
|
|
internal class TextWriterLineOutput : LineOutput
|
|
{
|
|
#region ILineOutput methods
|
|
|
|
/// <summary>
|
|
/// Get the columns on the screen
|
|
/// for files, it is settable at creation time.
|
|
/// </summary>
|
|
internal override int ColumnNumber
|
|
{
|
|
get
|
|
{
|
|
CheckStopProcessing();
|
|
return _columns;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the # of rows on the screen: for files
|
|
/// we return -1, meaning infinite.
|
|
/// </summary>
|
|
internal override int RowNumber
|
|
{
|
|
get
|
|
{
|
|
CheckStopProcessing();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a line by delegating to the writer underneath.
|
|
/// </summary>
|
|
/// <param name="s"></param>
|
|
internal override void WriteLine(string s)
|
|
{
|
|
CheckStopProcessing();
|
|
if (_suppressNewline)
|
|
{
|
|
_writer.Write(s);
|
|
}
|
|
else
|
|
{
|
|
_writer.WriteLine(s);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Initialization of the object. It must be called before
|
|
/// attempting any operation.
|
|
/// </summary>
|
|
/// <param name="writer">TextWriter to write to.</param>
|
|
/// <param name="columns">Max columns widths for the text.</param>
|
|
internal TextWriterLineOutput(TextWriter writer, int columns)
|
|
{
|
|
_writer = writer;
|
|
_columns = columns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialization of the object. It must be called before
|
|
/// attempting any operation.
|
|
/// </summary>
|
|
/// <param name="writer">TextWriter to write to.</param>
|
|
/// <param name="columns">Max columns widths for the text.</param>
|
|
/// <param name="suppressNewline">False to add a newline to the end of the output string, true if not.</param>
|
|
internal TextWriterLineOutput(TextWriter writer, int columns, bool suppressNewline)
|
|
: this(writer, columns)
|
|
{
|
|
_suppressNewline = suppressNewline;
|
|
}
|
|
|
|
private readonly int _columns = 0;
|
|
|
|
private readonly TextWriter _writer = null;
|
|
|
|
private readonly bool _suppressNewline = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// TextWriter to generate data for the Monad pipeline in a streaming fashion:
|
|
/// the provided callback will be called each time a line is written.
|
|
/// </summary>
|
|
internal class StreamingTextWriter : TextWriter
|
|
{
|
|
#region tracer
|
|
[TraceSource("StreamingTextWriter", "StreamingTextWriter")]
|
|
private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("StreamingTextWriter", "StreamingTextWriter");
|
|
#endregion tracer
|
|
|
|
/// <summary>
|
|
/// Create an instance by passing a delegate.
|
|
/// </summary>
|
|
/// <param name="writeCall">Delegate to write to.</param>
|
|
/// <param name="culture">Culture for this TextWriter.</param>
|
|
internal StreamingTextWriter(WriteLineCallback writeCall, CultureInfo culture)
|
|
: base(culture)
|
|
{
|
|
if (writeCall == null)
|
|
throw PSTraceSource.NewArgumentNullException(nameof(writeCall));
|
|
|
|
_writeCall = writeCall;
|
|
}
|
|
|
|
#region TextWriter overrides
|
|
|
|
public override Encoding Encoding { get { return new UnicodeEncoding(); } }
|
|
|
|
public override void WriteLine(string s)
|
|
{
|
|
_writeCall(s);
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Delegate definition.
|
|
/// </summary>
|
|
/// <param name="s">String to write.</param>
|
|
internal delegate void WriteLineCallback(string s);
|
|
|
|
/// <summary>
|
|
/// Instance of the delegate previously defined.
|
|
/// </summary>
|
|
private readonly WriteLineCallback _writeCall = null;
|
|
}
|
|
}
|