Reformatting Code (Managed Package Framework)

The ability to automatically reformat a section of code can be of great benefit to the developer. In the context of Visual Studio, reformatting the source code means manipulating the white space that occurs before and after each line. This can include inserting or removing spaces or tabs at the beginning of each line as well as adding new lines between lines. Reformatting can also involve replacing spaces for tabs or replacing tabs for spaces.

Be aware that, whichever manner the reformatting is accomplished, inserting or deleting new lines can affect markers such as breakpoints and bookmarks whereas adding or removing spaces or tabs does not affect such markers.

Implementation

The managed package framework (MPF) classes provide support for calling your formatting code. The user can start a reformat operation by selecting Format Selection or Format Document from the Advanced menu on the Edit menu. A reformatting operation can also be triggered automatically when a code snippet is inserted or if the language service initiates the reformat operation after a particular character is typed (for example, in C#, when you type a closing brace, everything between the matching open brace and the close brace is automatically indented to the proper level).

When Visual Studio sends the Format Selection or Format Document command to the language service, the ViewFilter class receives the command and converts it into a call to the ReformatSpan method in the Source class. The base class version of the ReformatSpan method does nothing by default, so you must override the ReformatSpan method and supply your own formatting code.

Enabling Support for Code Reformatting

The registry entry, EnableFormatSelection, must be set to 1 to indicate that your language service supports formatting of source code. The value of this registry entry can be accessed through the EnableFormatSelection property in the LanguagePreferences class. The registry entry can be set with a named parameter passed to the ProvideLanguageServiceAttribute user attribute.

The ViewFilter class calls the CanReformat method (in the ViewFilter class) to determine if the ReformatSpan method should be called. The CanReformat method returns the value from the EnableFormatSelection property.

Implementing Support for Formatting

You must derive a class from the Source class and override the ReformatSpan method. An EditArray object and a TextSpan object are passed to the ReformatSpan method. The TextSpan object describes the span to format and the EditArray object collects the edits to be made on the span. Note that this span can be the entire document. How you implement the ReformatSpan method is entirely up to you. Use the EditArray object to make any changes necessary. However, since there are likely to be multiple changes made to the span, all the changes should be reversible in a single action. To do this, wrap all changes in a CompoundAction object (see "Using the CompoundAction Class" section in this topic).

Example

The following example makes sure there is a single space after every comma in the selection except if the comma is followed by a tab or is at the end of the line. Also, any trailing spaces after the last comma in a line are deleted. See the "Using the CompoundAction Class" section in this topic to see how this method is called from the ReformatSpan method.

using Microsoft.VisualStudio.Package;
using Microsoft VisualStudio.TextManager.Interop;

namespace MyLanguagePackage
{
    class MySource : Source
    {
        private void DoFormatting(EditArray mgr, TextSpan span)
        {
            // Make sure there is one space after every comma unless followed
            // by a tab or comma is at end of line.
            IVsTextLines pBuffer = GetTextLines();
            if (pBuffer != null)
            {
                int    startIndex = span.iStartIndex;
                int    endIndex   = 0;
                string line       = "";
                // Loop over all lines in span
                for (int i = span.iStartLine; i <= span.iEndLine; i++)
                {
                    if (i < span.iEndLine)
                    {
                        pBuffer.GetLengthOfLine(i, out endIndex);
                    }
                    else
                    {
                        endIndex = span.iEndIndex;
                    }
                    pBuffer.GetLineText(i, startIndex, i, endIndex, out line);

                    if (line.Length > 0)
                    {
                        int index = 0;
                        // Loop over all commas in line
                        while ((index = line.IndexOf(',', index)) != -1)
                        {
                            int spaceIndex = index + 1;
                            // Determine how many spaces after comma
                            while (spaceIndex < line.Length && line[spaceIndex] == ' ')
                            {
                                ++spaceIndex;
                            }

                            int      spaceCount      = spaceIndex - (index + 1);
                            string   replacementText = " ";
                            bool     fDoEdit         = true;
                            TextSpan editTextSpan    = new TextSpan();

                            editTextSpan.iStartLine  = i;
                            editTextSpan.iEndLine    = i;
                            editTextSpan.iStartIndex = index + 1;

                            if (spaceIndex == line.Length)
                            {
                                if (spaceCount > 0)
                                {
                                    // Delete spaces after a comma at end of line
                                    editTextSpan.iEndIndex = spaceIndex;
                                    replacementText = "";
                                }
                                else
                                {
                                    // Otherwise, do not add spaces if comma is
                                    // at end of line.
                                    fDoEdit = false;
                                }
                            }
                            else if (spaceCount == 0)
                            {
                                if (spaceIndex < line.Length && line[spaceIndex] == '\t')
                                {
                                    // Do not insert space if a tab follows
                                    // a comma.
                                    fDoEdit = false;
                                }
                                else
                                {
                                    // No space after comma so add a space.
                                    editTextSpan.iEndIndex = index + 1;
                                }
                            }
                            else if (spaceCount > 1)
                            {
                                // More than one space after comma, replace
                                // with a single space.
                                editTextSpan.iEndIndex = spaceIndex;
                            }
                            else
                            {
                                // Single space after a comma and not at end
                                // of the line, leave it alone.
                                fDoEdit = false;
                            }
                            if (fDoEdit)
                            {
                                // Add edit operation
                                mgr.Add(new EditSpan(editTextSpan, replacementText));
                            }
                            index = spaceIndex;
                        }
                    }
                    startIndex = 0; // All subsequent lines start at 0
                }
                // Apply all edits
                mgr.ApplyEdits();
            }
        }
    }
}

Using the CompoundAction Class

Since reformatting a span typically affects only white space, multiple edit operations may be required. However, it is extremely helpful if all formatting done on a section of code can be reversed or undone in a single action. This can be accomplished using a CompoundAction class. This class wraps all edit operations applied to the text buffer and treats them as a single edit operation.

Example

Here is an example of how to use the CompoundAction class. See the example in the "Implementing Support for Formatting" section in this topic for an example of the DoFormatting method.

using Microsoft.VisualStudio.Package;
using Microsoft VisualStudio.TextManager.Interop;

namespace MyLanguagePackage
{
    class MySource : Source
    {
        public override void ReformatSpan(EditArray mgr, TextSpan span)
        {
            string description = "Reformat code";
            CompoundAction ca = new CompoundAction(this, description);
            using (ca)
            {
                ca.FlushEditActions();      // Flush any pending edits
                DoFormatting(mgr, span);    // Format the span
            }
        }
    }
}

See Also

Concepts

Language Service Features (Managed Package Framework)