Exemple #1
0
 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);
 }
Exemple #3
0
        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);
            }
        }
Exemple #4
0
        /// <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);
        }
Exemple #5
0
        /// <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);
        }
Exemple #6
0
        /// <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);
            }
        }
Exemple #8
0
 public void AddComment(ICommentLine comment)
 {
     comments[comment.Location] = comment;
 }
Exemple #9
0
        /// <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);
        }