/* Function: MarkTextBoxes * * Finds all text boxes in a comment and marks their tokens as <Tokenization.CommentParsingType.CommentDecoration>. * Vertical lines will only be detected if they are continuous throughout the comment and horizontal lines if they * are connected to it. Freestanding horizontal lines are *not* detected here. This function tolerates differing * symbols on corners and where embedded horizontal lines connect to the vertical. It also tolerates tokens * marked with <Tokenization.CommentParsingType.CommentSymbol> differing. * * Examples: * * The box below will be marked completely, including the middle horizontal line. * * > // +----------+ * > // | Title | * > // +----------+ * > // | Text | * > // +----------+ * * The middle horizontal line below will not be marked, because it is not attached. * * > // +----------+ * > // | Title | * > // | -------- | * > // | Text | * > // +----------+ * * Nor will the horizontal line below since there is no vertical. * * > // Title * > // ---------- * > // Text * * Freestanding horizontal lines are not detected because they may be intended literally, such as when part of * a code section. If you're not in such a section use <IsHorizontalLine()> before parsing a line to filter it out. * * > // (start code) * > // +-----+ * > // | box | * > // +-----+ * > // (end code) */ public static void MarkTextBoxes(PossibleDocumentationComment comment) { char symbolA, symbolB, symbolC; int symbolACount, symbolBCount, symbolCCount; char leftSymbol = '\0'; char rightSymbol = '\0'; int leftSymbolCount = 0; int rightSymbolCount = 0; bool symbolIsAloneOnLine = false; bool testedForVerticalLines = false; LineIterator line = comment.Start; TokenIterator lineStart, lineEnd; // This should be okay to use since line numbers start at one. IDObjects.NumberSet horizontalLines = new IDObjects.NumberSet(); // Skip leading blank lines since it's okay if they're not part of the text box. while (line < comment.End && line.IsEmpty(LineBoundsMode.CommentContent)) { line.Next(); } while (line < comment.End && line.IsEmpty(LineBoundsMode.CommentContent) == false) { line.GetBounds(LineBoundsMode.ExcludeWhitespace, out lineStart, out lineEnd); // Shrink the line to exclude its comment symbols, if any. We didn't do this using the line bounds mode because // we need to know whether there was any whitespace between them and any horizontal lines. bool commentSymbolWithoutWhitespaceAtStart = false; bool commentSymbolWithoutWhitespaceAtEnd = false; if (lineStart.CommentParsingType == CommentParsingType.CommentSymbol) { commentSymbolWithoutWhitespaceAtStart = true; do { lineStart.Next(); }while (lineStart.CommentParsingType == CommentParsingType.CommentSymbol); if (lineStart.FundamentalType == FundamentalType.Whitespace) { commentSymbolWithoutWhitespaceAtStart = false; do { lineStart.Next(); }while (lineStart.FundamentalType == FundamentalType.Whitespace); } } lineEnd.Previous(); if (lineEnd.CommentParsingType == CommentParsingType.CommentSymbol) { commentSymbolWithoutWhitespaceAtEnd = true; do { lineEnd.Previous(); }while (lineEnd.CommentParsingType == CommentParsingType.CommentSymbol); if (lineEnd.FundamentalType == FundamentalType.Whitespace) { commentSymbolWithoutWhitespaceAtEnd = false; do { lineEnd.Previous(); }while (lineEnd.FundamentalType == FundamentalType.Whitespace); } } lineEnd.Next(); // Horizontal line detection bool isHorizontalLine = false; CountSymbolLine(ref lineStart, lineEnd, out symbolA, out symbolB, out symbolC, out symbolACount, out symbolBCount, out symbolCCount); if (commentSymbolWithoutWhitespaceAtStart == true && commentSymbolWithoutWhitespaceAtEnd == true && symbolACount >= 4 && symbolBCount == 0) { isHorizontalLine = true; } else if (commentSymbolWithoutWhitespaceAtStart == true && symbolACount >= 4 && (symbolBCount == 0 || (symbolBCount <= 3 && symbolCCount == 0))) { isHorizontalLine = true; } else if (commentSymbolWithoutWhitespaceAtEnd == true && ((symbolACount >= 1 && symbolACount <= 3 && symbolBCount >= 4 && symbolCCount == 0) || (symbolACount >= 4 && symbolBCount == 0))) { isHorizontalLine = true; } else if ((symbolACount >= 4 && symbolBCount == 0) || (symbolACount >= 1 && symbolACount <= 3 && symbolBCount >= 4 && symbolCCount <= 3)) { isHorizontalLine = true; } // The horizontal line has to be the only thing on the line to count. if (isHorizontalLine && lineStart == lineEnd) { horizontalLines.Add(line.LineNumber); } // Vertical line detection else if (testedForVerticalLines == false) { // We permit the very first line to be different to allow for this: // /** text // * text // */ // // However, don't skip the first line if it's a one line comment or we wouldn't be able to handle this: // ### text // if (line != comment.Start || (comment.End.LineNumber - comment.Start.LineNumber) == 1) { if (CountEdgeSymbols(line, out leftSymbol, out rightSymbol, out leftSymbolCount, out rightSymbolCount, out symbolIsAloneOnLine) == false) { return; } testedForVerticalLines = true; } } else // testedForVerticalLines == true { char lineLeftSymbol, lineRightSymbol; int lineLeftSymbolCount, lineRightSymbolCount; bool lineSymbolIsAloneOnLine; CountEdgeSymbols(line, out lineLeftSymbol, out lineRightSymbol, out lineLeftSymbolCount, out lineRightSymbolCount, out lineSymbolIsAloneOnLine); // Account for a lone symbol being the right symbol. if (lineSymbolIsAloneOnLine == true && symbolIsAloneOnLine == false && leftSymbolCount == 0 && rightSymbolCount > 0) { if (lineLeftSymbol != rightSymbol || lineLeftSymbolCount != rightSymbolCount) { return; } } else if (lineSymbolIsAloneOnLine == false && symbolIsAloneOnLine == true && lineLeftSymbolCount == 0 && lineRightSymbolCount > 0) { if (lineRightSymbol != leftSymbol || lineRightSymbolCount != leftSymbolCount) { return; } rightSymbol = leftSymbol; leftSymbol = '\0'; rightSymbolCount = leftSymbolCount; leftSymbolCount = 0; } // Otherwise it's okay to do a straight compare. else { if (lineLeftSymbol != leftSymbol || lineLeftSymbolCount != leftSymbolCount) { leftSymbol = '\0'; leftSymbolCount = 0; } if (lineRightSymbol != rightSymbol || lineRightSymbolCount != rightSymbolCount) { rightSymbol = '\0'; rightSymbolCount = 0; } if (leftSymbolCount == 0 && rightSymbolCount == 0) { return; } } // Turn off the overall alone flag if this line didn't have it. if (lineSymbolIsAloneOnLine == false) { symbolIsAloneOnLine = false; } } line.Next(); } // If we stopped because we hit a blank line, this comment is only acceptable for marking text boxes if all the lines // left are blank. while (line < comment.End && line.IsEmpty(LineBoundsMode.CommentContent)) { line.Next(); } if (line != comment.End) { return; } // If we made it this far without returning it means we have a valid text box which we have to mark as comment decoration. line = comment.Start; while (line < comment.End) { line.GetBounds(LineBoundsMode.CommentContent, out lineStart, out lineEnd); if (horizontalLines.Contains(line.LineNumber)) { while (lineStart < lineEnd) { lineStart.CommentParsingType = CommentParsingType.CommentDecoration; lineStart.Next(); } } else if (lineEnd > lineStart) { // We test the characters against the symbols to account for any exceptions we allowed to go through // in previous code. for (int i = 0; i < leftSymbolCount; i++) { if (lineStart.Character == leftSymbol) { lineStart.CommentParsingType = CommentParsingType.CommentDecoration; lineStart.Next(); } } lineEnd.Previous(); for (int i = 0; i < rightSymbolCount; i++) { if (lineEnd.Character == rightSymbol) { lineEnd.CommentParsingType = CommentParsingType.CommentDecoration; lineEnd.Previous(); } } } line.Next(); } }
/* Function: FindIncludeInOutput * Extracts and returns any comment content marked "include in output". All comment symbols and extra indents * will be removed. Returns null if the comment doesn't have any. */ protected string FindIncludeInOutput(PossibleDocumentationComment comment) { Comments.LineFinder.MarkTextBoxes(comment); LineIterator iterator = comment.Start; // Find the "include in output" header if it exists while (iterator < comment.End && iterator.Match(IncludeInOutputRegex, LineBoundsMode.CommentContent).Success == false) { iterator.Next(); } if (iterator >= comment.End) { return(null); } // Find the bounds of the content excluding whitespace and the shared indent level iterator.Next(); while (iterator < comment.End && iterator.IsEmpty(LineBoundsMode.CommentContent)) { iterator.Next(); } LineIterator start = iterator; LineIterator end = iterator; int commonIndent = 9999; while (iterator < comment.End) { if (iterator.IsEmpty(LineBoundsMode.CommentContent)) { iterator.Next(); } else { if (iterator.Indent(LineBoundsMode.CommentContent) < commonIndent) { commonIndent = iterator.Indent(LineBoundsMode.CommentContent); } iterator.Next(); end = iterator; } } // Build and return the comment content if (start >= end) { return(null); } StringBuilder output = new StringBuilder(); do { int indentDifference = start.Indent(LineBoundsMode.CommentContent) - commonIndent; if (indentDifference > 0) { output.Append(' ', indentDifference); } start.AppendTo(output, LineBoundsMode.CommentContent); output.AppendLine(); start.Next(); }while (start < end); return(output.ToString()); }