public CommentBlock(ICommentLine firstLine) { lines = new List <ICommentLine> { firstLine }; Location = firstLine.Location; }
/// <summary> /// Adds a comment line to the this comment block. /// </summary> /// <param name="line">The line to add.</param> public void AddCommentLine(ICommentLine line) { Location = !lines.Any() ? line.Location : Location.Create(line.Location.SourceTree, new TextSpan(Location.SourceSpan.Start, line.Location.SourceSpan.End - Location.SourceSpan.Start)); lines.Add(line); }
public CommentFormatter(ICommentLine line, string commentPrefix, int tabSize, Regex regex) { _builder = new StringBuilder(); _currentPosition = 0; _regex = regex; _tabSize = tabSize; _isFirstWord = true; _isIndented = false; // Handle optionally empty prefix. if (!string.IsNullOrWhiteSpace(commentPrefix)) { _commentPrefix = commentPrefix + CodeCommentHelper.Spacer; _commentPrefixLength = WordLength(_commentPrefix); } else { _commentPrefix = string.Empty; _commentPrefixLength = 0; } // Special handling for the root XML line, it should not output it's surrounding xml // tags, only it's child lines. var xml = line as CommentLineXml; if (xml != null) { // On the content of the root, fix the optional alignment of param tags. This is // not important if all tags will be broken onto seperate lines anyway. if (!Settings.Default.Formatting_CommentXmlSplitAllTags && Settings.Default.Formatting_CommentXmlAlignParamTags) { var paramPhrases = xml.Lines.OfType <CommentLineXml>().Where(p => string.Equals(p.TagName, "param", StringComparison.OrdinalIgnoreCase)); if (paramPhrases.Count() > 1) { var longestParam = paramPhrases.Max(p => p.OpenTag.Length); foreach (var phrase in paramPhrases) { phrase.OpenTag = phrase.OpenTag.PadRight(longestParam); } } } // Process all the lines inside the root XML line. foreach (var l in xml.Lines) { NewLine(); Parse(l); } } else { // Normal comment line has no child-lines and can be processed normally. NewLine(); Parse(line); } }
/// <summary> /// Formats the comment. /// </summary> public TextPoint Format() { if (!IsValid) { throw new InvalidOperationException("Cannot format comment, the comment is not valid."); } var originalText = _startPoint.GetText(_endPoint); var matches = _commentLineRegex.Matches(originalText).OfType <Match>().ToArray(); var commentOptions = new CommentOptions { Prefix = matches.First(m => m.Success).Groups["prefix"].Value ?? string.Empty, Regex = CodeCommentHelper.GetCommentRegex(_document.GetCodeLanguage(), false) }; // Concatenate the comment lines without comment prefixes and see if the resulting bit // can be parsed as XML. ICommentLine line = null; var lineTexts = matches.Select(m => m.Groups["line"].Value).ToArray(); var commentText = string.Join(Environment.NewLine, lineTexts); if (commentText.Contains('<')) { try { var xml = XElement.Parse($"<doc>{commentText}</doc>"); line = new CommentLineXml(xml); } catch (System.Xml.XmlException) { // If XML cannot be parsed, comment will be handled as a normal text comment. } } if (line == null) { line = new CommentLine(commentText); } var formatter = new CommentFormatter( line, _formatterOptions, commentOptions); if (!formatter.Equals(originalText)) { var cursor = StartPoint.CreateEditPoint(); cursor.Delete(EndPoint); cursor.Insert(formatter.ToString()); _endPoint = cursor.CreateEditPoint(); } return(EndPoint); }
/// <summary> /// Determine whether commentlines should be merged. /// </summary> /// <param name="newLine">A comment line to be appended to this comment block.</param> /// <returns>Whether the new line should be appended to this block.</returns> public bool CombinesWith(ICommentLine newLine) { if (!CommentLines.Any()) { return(true); } var sameFile = Location.SourceTree == newLine.Location.SourceTree; var sameRow = Location.EndLine() == newLine.Location.StartLine(); var sameColumn = Location.EndLine() + 1 == newLine.Location.StartLine(); var nextRow = Location.StartColumn() == newLine.Location.StartColumn(); var adjacent = sameFile && (sameRow || (sameColumn && nextRow)); return (newLine.Type == CommentLineType.MultilineContinuation || adjacent); }
/// <summary> /// Determine whether commentlines should be merged. /// </summary> /// <param name="newLine">A comment line to be appended to this comment block.</param> /// <returns>Whether the new line should be appended to this block.</returns> public bool CombinesWith(ICommentLine newLine) { if (CommentLines.Count == 0) { return(true); } bool sameFile = Location.SourceTree == newLine.Location.SourceTree; bool sameRow = Location.EndLine() == newLine.Location.StartLine(); bool sameColumn = Location.EndLine() + 1 == newLine.Location.StartLine(); bool nextRow = Location.StartColumn() == newLine.Location.StartColumn(); bool adjacent = sameFile && (sameRow || (sameColumn && nextRow)); return (newLine.Type == CommentType.MultilineContinuation || adjacent); }
public CommentFormatter(ICommentLine line, FormatterOptions formatterOptions, CommentOptions commentOptions) { _formatterOptions = formatterOptions; _commentOptions = commentOptions; _builder = new StringBuilder(); _currentPosition = 0; _isFirstWord = true; _isPrefixWritten = false; _isIndented = false; _indentAmount = 0; _commentPrefixLength = WordLength(commentOptions.Prefix); // Special handling for the root XML line, it should not output it's surrounding xml // tags, only it's child lines. if (line is CommentLineXml xml) { // On the content of the root, fix the optional alignment of param tags. if (_formatterOptions.Xml.AlignParamTags) { AlignParamTags(xml); } // Process all the lines inside the root XML line. foreach (var l in xml.Lines) { NewLine(); Format(l); } } else { // Normal comment line has no child-lines and can be processed normally. Format(line); } }
public void AddComment(ICommentLine comment) { comments[comment.Location] = comment; }
/// <summary> /// Parse a code comment line into a string and write it to the buffer. /// </summary> /// <param name="line">The comment line.</param> /// <param name="indentLevel">The level of indenting for the content of this tag.</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> /// <returns><c>true</c> if line fitted on single line, <c>false</c> if it wrapped on multiple lines.</returns> private bool Parse(ICommentLine line, int indentLevel = 0, int xmlTagLength = 0) { var xml = line as CommentLineXml; if (xml != null) { return(ParseXml(xml, indentLevel)); } else { var matches = _regex.Matches(line.Content).OfType <Match>().Select(x => new CodeCommentMatch(x)).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; if (!forceBreak && matchCount == 1 && matches[0].Words.Any()) { // Calculate the length of the first line. var firstLineLength = _commentPrefixLength + xmlTagLength + matches[0].Length + (indentLevel * Settings.Default.Formatting_CommentXmlValueIndent); // Tag spacing adds a space before and after. if (Settings.Default.Formatting_CommentXmlSpaceTags) { firstLineLength += 2; } // If set to skip wrapping on the last word, the last word's length does not matter. if (Settings.Default.Formatting_CommentSkipWrapOnLastWord) { firstLineLength -= WordLength(matches[0].Words.Last()) + 1; } forceBreak = firstLineLength > Settings.Default.Formatting_CommentWrapColumn; } if (_currentPosition == 0 || !_isFirstWord && forceBreak) { NewLine(indentLevel); } else if (!_isFirstWord && Settings.Default.Formatting_CommentXmlSpaceTags) { 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.IsList) { if (!_isFirstWord) { NewLine(indentLevel); } 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.Words != null) { 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 > Settings.Default.Formatting_CommentWrapColumn) { 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 && Settings.Default.Formatting_CommentSkipWrapOnLastWord) { wrap = false; } if (wrap) { NewLine(indentLevel); // If linewrap is on a list item, add spacing to align the text. if (match.IsList) { Append(string.Empty.PadLeft(WordLength(match.ListPrefix), CodeCommentHelper.Spacer)); // This is just padding and not a word. _isFirstWord = true; } } if (!_isFirstWord) { Append(CodeCommentHelper.Spacer); } Append(word); } } else { // Line without words, create a blank line. if (!_isFirstWord) { NewLine(0); } NewLine(indentLevel, true); } } if (!forceBreak && Settings.Default.Formatting_CommentXmlSpaceTags) { Append(CodeCommentHelper.Spacer); } if (_currentPosition == 0 || _currentPosition > _commentPrefixLength && forceBreak) { // This comment fitted on a single line. return(true); } } // This comment did not fit on a single line. return(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); }