in DbgProvider/internal/CaStringUtil.cs [735:1046]
public static string IndentAndWrap( string str,
int outputWidth,
IndentAndWrapOptions options,
int indent,
int addtlContinuationIndent )
{
bool truncate = 0 != (options & IndentAndWrapOptions.TruncateInsteadOfWrap);
int minContent = 2; // 1 char of content, and a newline char
if( truncate )
{
minContent = 3; // 1 char of content, 1 ellipsis char, and a newline char
if( addtlContinuationIndent != 0 )
{
throw new ArgumentException( "The combination of TruncateInsteadOfWrap and non-zero addtlContinuationIndent makes no sense." );
}
}
if( (indent + addtlContinuationIndent) > (outputWidth - minContent) )
{
throw new ArgumentOutOfRangeException(
Util.Sprintf( "The outputWidth ({0}) should be somewhat larger than " +
"the max possible indent ({1} + {2}).",
outputWidth,
indent,
addtlContinuationIndent ),
innerException: null );
}
if( null == str )
throw new ArgumentNullException( nameof( str ) );
StringBuilder sb = new StringBuilder( str.Length * 2 ); // just an estimate
int spaceToUse = outputWidth - indent - 1; // "- 1" because the newline actually uses a spot.
int srcIdx = 0;
int lastSpaceOrTabSrcIdx = -1;
int lastSpaceOrTabDstIdx = -1;
// We may need to replace the last char of content (space or not) when
// truncating (to put an ellipsis in its place).
int lastContentDstIdx = -1;
bool inLeadingSpace = true;
int leadingSpaceLen = 0;
// So that we don't need to inserts push/pops for plain text.
bool haveSeenControlSequence = false;
bool pushInserted = false;
void _insertIndent( int numSpaces )
{
sb.Append( ' ', numSpaces );
}
void _rememberLastSpaceOrTabIndexes()
{
lastSpaceOrTabSrcIdx = srcIdx;
lastSpaceOrTabDstIdx = sb.Length;
}
void _rememberLastContentIndex()
{
lastContentDstIdx = sb.Length;
}
bool _weHaveConsumedAllAvailableOutputWidth()
{
return spaceToUse == 0;
}
bool _backtrackToLastSpaceOrTab()
{
bool backtrackingAllowed = 0 == (options & IndentAndWrapOptions.NoWordBreaking);
if( backtrackingAllowed &&
(lastSpaceOrTabSrcIdx > 0) ) // can't be 0, because we don't count leading space
{
Util.Assert( lastSpaceOrTabDstIdx >= 0 );
srcIdx = lastSpaceOrTabSrcIdx;
sb.Length = lastSpaceOrTabDstIdx;
return true;
}
return false;
}
bool _stillInBounds()
{
return srcIdx < str.Length;
}
// Returns true if srcIdx is still within the bounds of str.
bool _consumeControlSequences()
{
while( _stillInBounds() && (str[ srcIdx ] == CSI) )
{
haveSeenControlSequence = true;
int pastSeq = _SkipControlSequence( str, srcIdx ); // TODO: make _SkipControlSequence not assert if first char isn't CSI?
sb.Append( str, srcIdx, pastSeq - srcIdx );
srcIdx = pastSeq;
}
return _stillInBounds();
}
void _saveAndResetSgrState()
{
if( haveSeenControlSequence && !pushInserted )
{
sb.Append( c_PushAndReset );
pushInserted = true;
}
}
void _restoreSgrState()
{
if( haveSeenControlSequence )
{
Util.Assert( pushInserted );
sb.Append( c_StandalonePop );
pushInserted = false;
}
}
void _completeLineAndIndent( int numSpaces, bool isWrap )
{
// Save the current SGR (color) state and (temporarily) reset to default,
// if necessary.
_saveAndResetSgrState();
sb.Append( '\n' );
_insertIndent( numSpaces );
// Restore the SGR state.
_restoreSgrState();
// Need to reset various counters/state:
spaceToUse = outputWidth - numSpaces - 1; // "- 1" because the newline actually uses a spot.
Util.Assert( !pushInserted );
lastSpaceOrTabSrcIdx = -1;
lastSpaceOrTabDstIdx = -1;
// This should not be needed... but I'm doing it defensively.
lastContentDstIdx = -1;
if( !isWrap )
{
// We're actually on the next line of input.
inLeadingSpace = true;
leadingSpaceLen = 0;
}
}
void _appendContentChar( char c )
{
_rememberLastContentIndex();
if( Char.IsWhiteSpace( c ) )
{
if( !inLeadingSpace )
{
_rememberLastSpaceOrTabIndexes();
}
else
{
leadingSpaceLen++;
}
}
else
{
inLeadingSpace = false;
}
sb.Append( c );
spaceToUse--;
}
// Returns 'true' if we actually found a newline; false if we hit the end of
// the string first. Will preserve control sequences.
bool _seekToNextLine()
{
while( _consumeControlSequences() )
{
char c = str[ srcIdx ];
if( c == '\r' )
{
// ignore it
}
else if( c == '\n' )
{
return true;
}
srcIdx++;
}
return false;
}
if( (options & IndentAndWrapOptions.FirstLineAlreadyIndented) == 0 )
{
_insertIndent( indent );
}
while( _consumeControlSequences() )
{
// We consume control sequences in the "while" condition, so the
// characters we consider here are purely content.
char c = str[ srcIdx ];
if( c == '\r' )
{
// ignore it
}
else if( c == '\n' )
{
// We consider the case of a newline char before the
// _weHaveConsumedAllAvailableOutputWidth case, because the newline
// does not consume available space (but rather resets it).
_completeLineAndIndent( indent, isWrap: false );
}
else if( _weHaveConsumedAllAvailableOutputWidth() )
{
// Need to decide where to put a newline... was there a space where we
// could break up the line, or do we have to just chop right where we
// are?
int totalIndent = indent;
bool backtracked = false;
if( Char.IsWhiteSpace( c ) )
{
// So we can "backtrack" to it.
_rememberLastSpaceOrTabIndexes();
}
bool moreContentAfterTruncation = false;
if( truncate )
{
if( lastContentDstIdx == -1 )
throw new Exception( "unexpected" );
// Note that we put the ellipsis /after/ resetting SGR (color)
// state. This is a stylistic choice.
sb.Remove( lastContentDstIdx, 1 ); // make way for the ellipsis.
moreContentAfterTruncation = _seekToNextLine();
_saveAndResetSgrState();
sb.Append( c_ellipsis );
}
else
{
bool doNotIndentContinuation = 0 != (options & IndentAndWrapOptions.DoNotIndentContinuationLines);
if( doNotIndentContinuation )
totalIndent = 0;
else
totalIndent += addtlContinuationIndent;
// Could be a backtrack of 0 chars if the current char is a space.
backtracked = _backtrackToLastSpaceOrTab();
if( !doNotIndentContinuation &&
(options & IndentAndWrapOptions.AddLineLeadingSpaceToAddtlContinuationIndent) != 0 )
{
totalIndent += leadingSpaceLen;
}
}
if( !truncate || moreContentAfterTruncation )
{
_completeLineAndIndent( totalIndent, isWrap: !truncate );
}
else
{
// Don't forget the last POP.
_restoreSgrState();
}
if( !truncate && !backtracked )
{
// If we didn't backtrack, then we haven't yet accounted for the
// current character--don't lose it!
_appendContentChar( c );
}
}
else
{
_appendContentChar( c );
}
srcIdx++;
} // end while( still more str )
return sb.ToString();
} // end _WordWrap()