private void Append(string value) { if (String.IsNullOrEmpty(value)) { return; } _builder.Append(Settings.Default.Formatting_CommentXmlKeepTagsTogether ? CodeCommentHelper.FakeToSpace(value) : value); _currentPosition += WordLength(value); _isFirstWord = false; }
/// <summary> /// Parse a comment line into individual words and write it to the buffer. /// </summary> /// <param name="line">The comment line.</param> /// <param name="xmlTagLength"> /// The length of the enclosing XML tags, this is needed to calculate the line length for /// single line XML comments. /// </param> /// <param name="xmlSpaceParentTagContent"> /// Set to <c>true</c> when parent is an XML tag and wants space between tags and content. /// </param> /// <returns> /// <c>true</c> if line fitted on single line, <c>false</c> if it wrapped on multiple lines. /// </returns> private bool Format(ICommentLine line, int xmlTagLength = 0, bool xmlSpaceParentTagContent = false) { if (line is CommentLineXml xml) { return(FormatXml(xml)); } if (line.Content == null) { return(true); } var matches = _commentOptions.Regex.Matches(line.Content).OfType <Match>().Select(x => new CodeCommentMatch(x, _formatterOptions)).ToList(); // Remove empty matches from the start and end of the comment. CodeCommentMatch m; while (((m = matches.FirstOrDefault()) != null && m.IsEmpty) || ((m = matches.LastOrDefault()) != null && m.IsEmpty)) { matches.Remove(m); } // Join the comment matches into single lines where possible. if (matches.Count > 1) { int i = 0; do { m = matches[i]; if (m.TryAppend(matches[i + 1])) { matches.RemoveAt(i + 1); } else { i++; } } while (i < matches.Count - 1); } // Extended logic for line breaks. // - Break if there is more than 1 line match (eg. due to a list or child xml tags). // - Break if the content does not fit on a single line. var matchCount = matches.Count; var forceBreak = matchCount > 1; var fittedOnLine = true; if (!forceBreak && matchCount == 1 && matches[0].Words.Any()) { // Calculate the length of the first line. var firstLineLength = _commentPrefixLength + xmlTagLength + matches[0].Length + _indentAmount; // If set to skip wrapping on the last word, the last word's length does not matter. if (_formatterOptions.SkipWrapOnLastWord) { firstLineLength -= WordLength(matches[0].Words.Last()) + 1; } forceBreak = firstLineLength > _formatterOptions.WrapColumn; } if (_currentPosition == 0 || (!_isFirstWord && forceBreak)) { NewLine(); fittedOnLine = false; } else if (!_isFirstWord && xmlSpaceParentTagContent) { // Parent is XML tag and wants space between tags and content. Append(CodeCommentHelper.Spacer); } // Always consider the word after the opening tag as the first word to prevent an extra // space before. _isFirstWord = true; foreach (var match in matches) { if (match.IsLiteral || match.IsList) { if (!_isFirstWord) { NewLine(); fittedOnLine = false; } } if (match.IsList) { Append(match.ListPrefix); // List items include their spacing and do not require additional space, thus we // are logically still on the first word. _isFirstWord = true; } if (!match.IsEmpty) { var wordCount = match.Words.Count - 1; for (int i = 0; i <= wordCount; i++) { var word = match.Words[i]; var length = WordLength(word); var wrap = false; // If current position plus word length exceeds the maximum comment length, // wrap to the next line. Take care not to wrap on the first word, otherwise // a word that never fits a line (ie. too long) would cause endless linewrapping. if (!_isFirstWord && _currentPosition + length + 1 > _formatterOptions.WrapColumn) { wrap = true; } // If this is the last word and user selected to not wrap on the last word, // don't wrap. if (wrap && i == wordCount && _formatterOptions.SkipWrapOnLastWord) { wrap = false; } if (wrap) { NewLine(); fittedOnLine = false; // If linewrap is on a list item, add extra spacing to align the text // with the previous line. if (match.IsList) { Append(string.Empty.PadLeft(WordLength(match.ListPrefix), CodeCommentHelper.Spacer)); // Unset the first-word flag, because this is just padding and not a // proper word. _isFirstWord = true; } } else if (!_isFirstWord) { Append(CodeCommentHelper.Spacer); } Append(CodeCommentHelper.FakeToSpace(word)); } } else { // Line without words, create a blank line. First end the current line. NewLine(); // And then force a newline creating an empty one. NewLine(true); fittedOnLine = false; } } return(fittedOnLine); }
/// <returns> /// Returns <c>true</c> if the line requests a break afterwards (did not fit on a single /// line), otherwise <c>false</c>. /// </returns> private bool FormatXml(CommentLineXml xml) { var isLiteralContent = !string.IsNullOrEmpty(xml.Content); var split = xml.TagOptions.Split; if (isLiteralContent) { // Tags containing literal content with multiple with should always be on their own line. if (xml.Content.Contains('\n')) { split = XmlTagNewLine.Always; } } else if ((split == XmlTagNewLine.Default || split == XmlTagNewLine.Content) && xml.Lines.Count > 1) { // Split always if there is more than one child line. split = XmlTagNewLine.Always; } if (split.HasFlag(XmlTagNewLine.BeforeOpen) && !_isFirstWord) { NewLine(); } Append(xml.TagOptions.KeepTogether ? CodeCommentHelper.FakeToSpace(xml.OpenTag) : xml.OpenTag); // Self closing tags have no content, skip all further logic and just output. if (xml.IsSelfClosing) { if (split.HasFlag(XmlTagNewLine.AfterClose)) { if (!xml.IsLast) { NewLine(); } return(false); } return(true); } if (split.HasFlag(XmlTagNewLine.AfterOpen)) { NewLine(); } // Increase the indenting. _indentAmount += xml.TagOptions.Indent; if (isLiteralContent) { // If the literal content of an XML tag is set, output that content without formatting. var literals = xml.Content.Trim('\r', '\n').TrimEnd('\r', '\n', '\t', ' ').Split('\n'); for (int i = 0; i < literals.Length; i++) { if (i > 0) { NewLine(true); } Append(literals[i].TrimEnd(), true); } } else { // Else output the child lines. var xmlTagLength = WordLength(xml.OpenTag) + WordLength(xml.CloseTag) + (xml.TagOptions.SpaceContent ? 2 : 0); foreach (var line in xml.Lines) { if (!Format(line, xmlTagLength, xml.TagOptions.SpaceContent)) { split |= XmlTagNewLine.BeforeClose | XmlTagNewLine.AfterClose; } } } // Remove the indenting. _indentAmount -= xml.TagOptions.Indent; // If opening tag was on own line, do the same for the closing tag. if (split.HasFlag(XmlTagNewLine.BeforeClose)) { NewLine(); } else if (xml.TagOptions.SpaceContent) { Append(CodeCommentHelper.Spacer); } Append(xml.CloseTag); if (split.HasFlag(XmlTagNewLine.AfterClose)) { //if (!xml.IsLast) { NewLine(); } return(false); } return(true); }
private void Append(string value) { _builder.Append(_options.XmlKeepTagsTogether ? CodeCommentHelper.FakeToSpace(value) : value); _currentPosition += WordLength(value); _isFirstWord = false; }