public void Step(IDocumentAccessor doc, IndentationSettings set)
{
var line = doc.Text;
if (set.LeaveEmptyLines && line.Length == 0) return; // leave empty lines empty
line = line.TrimStart();
var indent = new StringBuilder();
if (line.Length == 0)
{
// Special treatment for empty lines:
if (blockComment || (inString && verbatim))
return;
indent.Append(block.InnerIndent);
indent.Append(Repeat(set.IndentString, block.OneLineBlock));
if (block.Continuation)
indent.Append(set.IndentString);
if (doc.Text != indent.ToString())
doc.Text = indent.ToString();
return;
}
if (TrimEnd(doc))
line = doc.Text.TrimStart();
var oldBlock = block;
var startInComment = blockComment;
var startInString = inString && verbatim;
#region Parse char by char
lineComment = false;
inChar = false;
escape = false;
if (!verbatim) inString = false;
lastRealChar = '\n';
var lastchar = ' ';
var c = ' ';
var nextchar = line[0];
for (var i = 0; i < line.Length; i++)
{
if (lineComment) break; // cancel parsing current line
lastchar = c;
c = nextchar;
if (i + 1 < line.Length)
nextchar = line[i + 1];
else
nextchar = '\n';
if (escape)
{
escape = false;
continue;
}
#region Check for comment/string chars
switch (c)
{
case '/':
if (blockComment && lastchar == '*')
blockComment = false;
if (!inString && !inChar)
{
if (!blockComment && nextchar == '/')
lineComment = true;
if (!lineComment && nextchar == '*')
blockComment = true;
}
break;
case '#':
if (!(inChar || blockComment || inString))
lineComment = true;
break;
case '"':
if (!(inChar || lineComment || blockComment))
{
inString = !inString;
if (!inString && verbatim)
{
if (nextchar == '"')
{
escape = true; // skip escaped quote
inString = true;
}
else
{
verbatim = false;
}
}
else if (inString && lastchar == '@')
{
verbatim = true;
}
}
break;
case '\'':
if (!(inString || lineComment || blockComment))
{
inChar = !inChar;
}
break;
case '\\':
if ((inString && !verbatim) || inChar)
escape = true; // skip next character
break;
}
#endregion
if (lineComment || blockComment || inString || inChar)
{
if (wordBuilder.Length > 0)
block.LastWord = wordBuilder.ToString();
wordBuilder.Length = 0;
continue;
}
if (!char.IsWhiteSpace(c) && c != '[' && c != '/')
{
if (block.Bracket == '{')
block.Continuation = true;
}
if (char.IsLetterOrDigit(c))
{
wordBuilder.Append(c);
}
else
{
if (wordBuilder.Length > 0)
block.LastWord = wordBuilder.ToString();
wordBuilder.Length = 0;
}
#region Push/Pop the blocks
switch (c)
{
case '{':
block.ResetOneLineBlock();
blocks.Push(block);
block.StartLine = doc.LineNumber;
if (block.LastWord == "switch")
{
block.Indent(set.IndentString + set.IndentString);
/* oldBlock refers to the previous line, not the previous block
* The block we want is not available anymore because it was never pushed.
* } else if (oldBlock.OneLineBlock) {
// Inside a one-line-block is another statement
// with a full block: indent the inner full block
// by one additional level
block.Indent(set, set.IndentString + set.IndentString);
block.OuterIndent += set.IndentString;
// Indent current line if it starts with the '{' character
if (i == 0) {
oldBlock.InnerIndent += set.IndentString;
}*/
}
else
{
block.Indent(set);
}
block.Bracket = '{';
break;
case '}':
while (block.Bracket != '{')
{
if (blocks.Count == 0) break;
block = blocks.Pop();
}
if (blocks.Count == 0) break;
block = blocks.Pop();
block.Continuation = false;
block.ResetOneLineBlock();
break;
case '(':
case '[':
blocks.Push(block);
if (block.StartLine == doc.LineNumber)
block.InnerIndent = block.OuterIndent;
else
block.StartLine = doc.LineNumber;
block.Indent(Repeat(set.IndentString, oldBlock.OneLineBlock) +
(oldBlock.Continuation ? set.IndentString : "") +
(i == line.Length - 1 ? set.IndentString : new string(' ', i + 1)));
block.Bracket = c;
break;
case ')':
if (blocks.Count == 0) break;
if (block.Bracket == '(')
{
block = blocks.Pop();
if (IsSingleStatementKeyword(block.LastWord))
block.Continuation = false;
}
break;
case ']':
if (blocks.Count == 0) break;
if (block.Bracket == '[')
block = blocks.Pop();
break;
case ';':
case ',':
block.Continuation = false;
block.ResetOneLineBlock();
break;
case ':':
if (block.LastWord == "case"
|| line.StartsWith("case ", StringComparison.Ordinal)
|| line.StartsWith(block.LastWord + ":", StringComparison.Ordinal))
{
block.Continuation = false;
block.ResetOneLineBlock();
}
break;
}
if (!char.IsWhiteSpace(c))
{
// register this char as last char
lastRealChar = c;
}
#endregion
}
#endregion
if (wordBuilder.Length > 0)
block.LastWord = wordBuilder.ToString();
wordBuilder.Length = 0;
if (startInString) return;
if (startInComment && line[0] != '*') return;
if (doc.Text.StartsWith("//\t", StringComparison.Ordinal) || doc.Text == "//")
return;
if (line[0] == '}')
{
indent.Append(oldBlock.OuterIndent);
oldBlock.ResetOneLineBlock();
oldBlock.Continuation = false;
}
else
{
indent.Append(oldBlock.InnerIndent);
}
if (indent.Length > 0 && oldBlock.Bracket == '(' && line[0] == ')')
{
indent.Remove(indent.Length - 1, 1);
}
else if (indent.Length > 0 && oldBlock.Bracket == '[' && line[0] == ']')
{
indent.Remove(indent.Length - 1, 1);
}
if (line[0] == ':')
{
oldBlock.Continuation = true;
}
else if (lastRealChar == ':' && indent.Length >= set.IndentString.Length)
{
if (block.LastWord == "case" || line.StartsWith("case ", StringComparison.Ordinal) ||
line.StartsWith(block.LastWord + ":", StringComparison.Ordinal))
indent.Remove(indent.Length - set.IndentString.Length, set.IndentString.Length);
}
else if (lastRealChar == ')')
{
if (IsSingleStatementKeyword(block.LastWord))
{
block.OneLineBlock++;
}
}
else if (lastRealChar == 'e' && block.LastWord == "else")
{
block.OneLineBlock = Math.Max(1, block.PreviousOneLineBlock);
block.Continuation = false;
oldBlock.OneLineBlock = block.OneLineBlock - 1;
}
if (doc.IsReadOnly)
{
// We can't change the current line, but we should accept the existing
// indentation if possible (=if the current statement is not a multiline
// statement).
if (!oldBlock.Continuation && oldBlock.OneLineBlock == 0 &&
oldBlock.StartLine == block.StartLine &&
block.StartLine < doc.LineNumber && lastRealChar != ':')
{
// use indent StringBuilder to get the indentation of the current line
indent.Length = 0;
line = doc.Text; // get untrimmed line
for (var i = 0; i < line.Length; ++i)
{
if (!char.IsWhiteSpace(line[i]))
break;
indent.Append(line[i]);
}
// /* */ multiline comments have an extra space - do not count it
// for the block's indentation.
if (startInComment && indent.Length > 0 && indent[indent.Length - 1] == ' ')
{
indent.Length -= 1;
}
block.InnerIndent = indent.ToString();
}
return;
}
if (line[0] != '{')
{
if (line[0] != ')' && oldBlock.Continuation && oldBlock.Bracket == '{')
indent.Append(set.IndentString);
indent.Append(Repeat(set.IndentString, oldBlock.OneLineBlock));
}
// this is only for blockcomment lines starting with *,
// all others keep their old indentation
if (startInComment)
indent.Append(' ');
if (indent.Length != doc.Text.Length - line.Length ||
!doc.Text.StartsWith(indent.ToString(), StringComparison.Ordinal) ||
char.IsWhiteSpace(doc.Text[indent.Length]))
{
doc.Text = indent + line;
}
}