// 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
{
///
/// 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.
///
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);
}
///
/// 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.
///
/// String to be displayed.
/// Offset inside the string.
/// Number of display cells.
/// If true compute from the head (i.e. k++) else from the tail (i.e. k--).
/// Number of characters that would fit.
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
}
///
/// 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.
///
internal abstract class LineOutput
{
///
/// Whether the device requires full buffering of formatting
/// objects before any processing.
///
internal virtual bool RequiresBuffering { get { return false; } }
///
/// Delegate the implementor of ExecuteBufferPlayBack should
/// call to cause the playback to happen when ready to execute.
///
internal delegate void DoPlayBackCall();
///
/// If RequiresBuffering = true, this call will be made to
/// start the playback.
///
internal virtual void ExecuteBufferPlayBack(DoPlayBackCall playback) { }
///
/// The number of columns the current device has.
///
internal abstract int ColumnNumber { get; }
///
/// The number of rows the current device has.
///
internal abstract int RowNumber { get; }
///
/// Write a line to the output device.
///
///
/// string to be written to the device
///
internal abstract void WriteLine(string s);
internal WriteStreamType WriteStream
{
get;
set;
}
///
/// Handle the stop processing signal.
/// Set a flag that will be checked during operations.
///
internal void StopProcessing()
{
_isStopping = true;
}
private bool _isStopping;
internal void CheckStopProcessing()
{
if (!_isStopping)
return;
throw new PipelineStoppedException();
}
///
/// Return an instance of the display helper tear off.
///
///
internal virtual DisplayCells DisplayCells
{
get
{
CheckStopProcessing();
// just return the default singleton implementation
return _displayCellsDefault;
}
}
///
/// Singleton used for the default implementation.
/// NOTE: derived classes may chose to provide a different
/// implementation by overriding.
///
protected static DisplayCells _displayCellsDefault = new DisplayCells();
}
///
/// 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.
///
internal class WriteLineHelper
{
#region callbacks
///
/// Delegate definition.
///
/// String to write.
internal delegate void WriteCallback(string s);
///
/// Instance of the delegate previously defined
/// for line that has EXACTLY this.ncols characters.
///
private readonly WriteCallback _writeCall = null;
///
/// Instance of the delegate previously defined
/// for generic line, less that this.ncols characters.
///
private readonly WriteCallback _writeLineCall = null;
#endregion
private readonly bool _lineWrap;
///
/// 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.
///
/// True if we require line wrapping.
/// Delegate for WriteLine(), must ben non null.
/// Delegate for Write(), if null, use the first parameter.
/// Helper object for manipulating strings.
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;
}
///
/// Main entry point to process a line.
///
/// String to process.
/// Width of the device.
internal void WriteLine(string s, int cols)
{
WriteLineInternal(s, cols);
}
///
/// Internal helper, needed because it might make recursive calls to itself.
///
/// String to process.
/// Width of the device.
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;
}
///
/// Implementation of the ILineOutput interface accepting an instance of a
/// TextWriter abstract class.
///
internal class TextWriterLineOutput : LineOutput
{
#region ILineOutput methods
///
/// Get the columns on the screen
/// for files, it is settable at creation time.
///
internal override int ColumnNumber
{
get
{
CheckStopProcessing();
return _columns;
}
}
///
/// Get the # of rows on the screen: for files
/// we return -1, meaning infinite.
///
internal override int RowNumber
{
get
{
CheckStopProcessing();
return -1;
}
}
///
/// Write a line by delegating to the writer underneath.
///
///
internal override void WriteLine(string s)
{
CheckStopProcessing();
if (_suppressNewline)
{
_writer.Write(s);
}
else
{
_writer.WriteLine(s);
}
}
#endregion
///
/// Initialization of the object. It must be called before
/// attempting any operation.
///
/// TextWriter to write to.
/// Max columns widths for the text.
internal TextWriterLineOutput(TextWriter writer, int columns)
{
_writer = writer;
_columns = columns;
}
///
/// Initialization of the object. It must be called before
/// attempting any operation.
///
/// TextWriter to write to.
/// Max columns widths for the text.
/// False to add a newline to the end of the output string, true if not.
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;
}
///
/// TextWriter to generate data for the Monad pipeline in a streaming fashion:
/// the provided callback will be called each time a line is written.
///
internal class StreamingTextWriter : TextWriter
{
#region tracer
[TraceSource("StreamingTextWriter", "StreamingTextWriter")]
private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("StreamingTextWriter", "StreamingTextWriter");
#endregion tracer
///
/// Create an instance by passing a delegate.
///
/// Delegate to write to.
/// Culture for this TextWriter.
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
///
/// Delegate definition.
///
/// String to write.
internal delegate void WriteLineCallback(string s);
///
/// Instance of the delegate previously defined.
///
private readonly WriteLineCallback _writeCall = null;
}
}