/// <summary> /// This function is the callback used to execute the command when the menu item is clicked. /// See the constructor to see how the menu item is associated with this function using /// OleMenuCommandService service and MenuCommand class. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event args.</param> private void Execute(object sender, EventArgs e) { // Verify the current thread is the UI thread. ThreadHelper.ThrowIfNotOnUIThread(); // This command will only work when there's an active document to examine. if (FormatCommentCommand.environment.ActiveDocument == null) { return; } // Get the selected text from the environment. TextSelection selection = FormatCommentCommand.environment.ActiveDocument.Selection as TextSelection; // Get the start end points (round down and up to the start of a line). EditPoint startPoint = selection.AnchorPoint.CreateEditPoint(); startPoint.StartOfLine(); // The initial endpoint is one line below the start. EditPoint endPoint = selection.ActivePoint.CreateEditPoint(); endPoint.StartOfLine(); endPoint.LineDown(1); // If nothing is selected, then figure out what needs to be formatted by the start point up and the end point down. As long as we // recognize a comment line we'll keep expanding the selection in both directions. if (selection.IsEmpty) { // Find the start of the block. while (!startPoint.AtStartOfDocument) { if (!FormatCommentCommand.IsCommentLine(startPoint)) { startPoint.LineDown(1); break; } startPoint.LineUp(1); } // Find the end of the block. while (!endPoint.AtEndOfDocument) { if (!FormatCommentCommand.IsCommentLine(endPoint)) { break; } endPoint.LineDown(1); } } // This will swap the old comment for the new right-margin justified and beautified comment. startPoint.ReplaceText( endPoint, FormatCommentCommand.FormatCommentstring(startPoint.GetText(endPoint)), (int)(vsEPReplaceTextOptions.vsEPReplaceTextNormalizeNewlines | vsEPReplaceTextOptions.vsEPReplaceTextTabsSpaces)); }
/// <summary> /// Initializes the singleton instance of the command. /// </summary> /// <param name="package">Owner package, not null.</param> /// <returns>An awaitable task.</returns> public static async Task InitializeAsync(AsyncPackage package) { // Verify the current thread is the UI thread. await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // Instantiate the command. OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; FormatCommentCommand.instance = new FormatCommentCommand(package, commandService); }
/// <summary> /// Beatifies XML comments. /// </summary> /// <param name="outputComment">The output comment.</param> /// <param name="inputComment">The comment to be wrapped and beautified.</param> /// <param name="prefix">The current comment line's prefix.</param> private static void WrapXml(StringBuilder outputComment, string inputComment, string prefix) { // The main idea around formatting XML is to put tags on their own lines when the text can't fit within the margin. To accomplish this, // we'll format the entire tag and then see how long is it. StringBuilder commentBody = new StringBuilder(); FormatCommentCommand.WrapLine(commentBody, inputComment, prefix); // This will pull apart the XML into the tags and the content. Match match = FormatCommentCommand.xmlParts.Match(inputComment); string startTag = match.Groups["startTag"].Value; string body = match.Groups["body"].Value; string endTag = match.Groups["endTag"].Value; // Extract the element name. The name will be used to determine if the tags appear on their own comment lines or if they can be // collapsed onto a single line with their comment. For example, the ' <summary>' tag is generally emitted on its own line whereas the ' // <param>' tag is usually combined with the comments. Regex xmlElementName = new Regex(@"<(?<name>\w+).*>"); string elementName = xmlElementName.Match(startTag).Groups["name"].Value; // If the tags and the content is too long to fit on a line after formatting, we'll put the tags on their own lines. We also force a // certain class of tags to appear on their own line distinct from the content; it's just easier to read this way. if (commentBody.Length >= Properties.Settings.Default.WrapMargin || FormatCommentCommand.breakingElements.Contains(elementName)) { // Write the start tag. outputComment.Append(prefix); outputComment.Append(' '); outputComment.Append(startTag); outputComment.AppendLine(); // This will format the body of the tag to wrap at the right margin. FormatCommentCommand.columnPosition = 0; FormatCommentCommand.WrapLine(outputComment, body, prefix); if (FormatCommentCommand.columnPosition != 0) { outputComment.AppendLine(); } // Write the closing tag. outputComment.Append(prefix); outputComment.Append(' '); outputComment.Append(endTag); outputComment.AppendLine(); } else { // If the XML tags and the line fit within the margin, we just write it to the output comment. outputComment.Append(commentBody.ToString()); outputComment.AppendLine(); } // Reset the wrapping parameters for the next comment line in the block. FormatCommentCommand.columnPosition = 0; }
/// <summary> /// Initialization of the package; this method is called right after the package is sited, so this is the place /// where you can put all the initialization code that rely on services provided by VisualStudio. /// </summary> /// <param name="cancellationToken">A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.</param> /// <param name="progress">A provider for progress updates.</param> /// <returns>A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.</returns> protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress <ServiceProgressData> progress) { // When initialized asynchronously, the current thread may be a background thread at this point. // Do any initialization that requires the UI thread after switching to the UI thread. await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); await FormatCommentCommand.InitializeAsync(this); await FormatXmlCommand.InitializeAsync(this); await InsertModuleHeaderCommand.InitializeAsync(this); await InsertConstructorHeaderCommand.InitializeAsync(this); await SetModuleHeaderCommand.InitializeAsync(this); await SetWrapMarginCommand.InitializeAsync(this); await ScrubXsdCommand.InitializeAsync(this); }
/// <summary> /// Formats a block of comments and beautifies the XML comments. /// </summary> /// <param name="inputComment">The block of comment lines to format.</param> /// <returns>A right justified and beautified block of comments.</returns> private static string FormatCommentstring(string inputComment) { // These variables control the state of the wrapping. FormatCommentCommand.columnPosition = 0; // This buffer keeps track of the reconstructed comment block. StringBuilder outputComment = new StringBuilder(); // This breaks the input comment into individual lines. string[] commentLines = inputComment.Split(new string[] { "\r\n" }, StringSplitOptions.None | StringSplitOptions.RemoveEmptyEntries); // Parse the line into prefixes (the comment start sequence and whitespace) and the actual comment on the line. for (int index = 0; index < commentLines.Length; index++) { // This parses the comment and the prefix out of the current comment line. The prefix is all characters up to the comment delimiter // character. The comment represents all the characters after the delimiter stripped of the leading and trailing space. Match match = FormatCommentCommand.commentRegex.Match(commentLines[index]); string prefix = match.Groups["prefix"].Value; string comment = match.Groups["comment"].Value; // We assume there was some intent if an author left an entire line blank and so we treat it as a paragraph break. That is, we don't // wrap over it. if (comment.Length == 0) { if (FormatCommentCommand.columnPosition != 0) { outputComment.AppendLine(); } outputComment.AppendLine(prefix); FormatCommentCommand.columnPosition = 0; continue; } // We're going to attempt to format the special comment directives so that they'll wrap nicely. If a single-line directive is too // long, we'll wrap it so the directives end up on separate lines. This test is used to distinguish between the special directives // used for commenting functions, classes and modules from regular block comments. bool isCommentDirective = prefix.EndsWith("///", StringComparison.Ordinal); // This section will provide formatting for XML tags inside comment blocks. The tags are examined to see if the text and tags will // fit within the margins. If not, the tags are placed on their own lines and the comment inside the tags is formatted to wrap // around the margins. The algorithm will also eat up partial lines in order to fill out the XML content to the margin. if (isCommentDirective && xmlStartTag.IsMatch(comment) && !xmlEmptyElement.IsMatch(comment)) { while (!xmlEndTag.IsMatch(comment) && index < commentLines.Length) { match = FormatCommentCommand.commentRegex.Match(commentLines[++index]); comment += ' ' + match.Groups["comment"].Value; } FormatCommentCommand.WrapXml(outputComment, comment, prefix); continue; } // This is used to force a line break on comment lines that meet certain criteria, such as bullet marks and examples. bool isBreakingLine = false; // Lines that begin with an Asterisk are considered bullet marks and do not wrap and are given an extra margin. if (comment.StartsWith("*", StringComparison.Ordinal)) { // If the previous line was waiting for some wrapping to occur, it's going to be disappointed. This line is going to start all // on its own. if (FormatCommentCommand.columnPosition != 0) { outputComment.AppendLine(); } FormatCommentCommand.columnPosition = 0; // The prefix will be indented for all bullet marks. prefix += " "; // This will force a line break after the bullet mark is formatted. isBreakingLine = true; } // Lines that end with colons do not wrap. if (comment.EndsWith(":", StringComparison.Ordinal)) { isBreakingLine = true; } // This is where all the work is done to right justify the block comment to the margin. FormatCommentCommand.WrapLine(outputComment, comment, prefix); // This will force a new line for comment lines that don't wrap such as bullet marks and colons. if (isBreakingLine) { outputComment.AppendLine(); FormatCommentCommand.columnPosition = 0; } } // This will finish off any incomplete lines. if (FormatCommentCommand.columnPosition > 0) { outputComment.AppendLine(); } // At this point we've transformed the input block of comments into a right justified block. return(outputComment.ToString()); }