Visual Studio Text Editor Extension

15,900

Solution 1

I had the exact same question and now have browsed the web several hours until I was being able to understand and explain how you'd need to start with such an extension.

In my following example we will create a small and dumb extension which will always add "Hello" to the beginning of a code file when an edit has been made. It's very basic but should give you an idea how to continue developing this thing.

Be warned: You have to parse the code files completely on your own - Visual Studio does not give you any information about where classes, methods or whatever are and what they contain. That's the biggest hurdle to be taken when doing a code formatting tool and will not be covered in this answer.[*]

For those who skipped to this answer, make sure you downloaded and installed the Visual Studio SDK first or you will not find the project type mentioned in step one.

Creating the project

  1. Start by creating a new project of the type "Visual C# > Extensibility > VSIX Project" (only visible if you selected .NET Framework 4 as the target framework). Please note that you may have to select the "Editor Classifier" project type instead of the "VSIX Project" type to get it working, s. comment below.

  2. After the project has been created, the "source.extension.vsixmanifest" file will be opened, giving you the ability to set up product name, author, version, description, icon and so on. I think this step is pretty self-explaining, you can close the tab now and restore it later by opening the vsixmanifest file.

Creating a listener class to get notified about text editor instance creations

Next, we need to listen whenever a text editor has been created in Visual Studio and bind our code formatting tool to it. A text editor in VS2010 is an instance of IWpfTextView.

  1. Add a new class to our project and name it TextViewCreationListener. This class has to implement the Microsoft.VisualStudio.Text.Editor.IWpfTextViewCreationListener interface. You need to add a reference to Microsoft.VisualStudio.Text.UI.Wpf to your project. The assembly DLL is found in your Visual Studio SDK directory under VisualStudioIntegration\Common\Assemblies\v4.0.

  2. You have to implement the TextViewCreated method of the interface. This method has a parameter specifying the instance of the text editor which has been created. We will create a new code formatting class which this instance is passed to later on.

  3. We need to make the TextViewCreationListener class visible to Visual Studio by specifying the attribute [Export(typeof(IWpfTextViewCreationListener))]. Add a reference to System.ComponentModel.Composition to your project for the Export attribute.

  4. Additionally, we need to specify with which types of files the code formatter should be bound to the text editor. We only like to format code files and not plain text files, so we add the attribute [ContentType("code")] to the listener class. You have to add a reference to Microsoft.VisualStudio.CoreUtility to your project for this.

  5. Also, we only want to change editable code and not the colors or adornments around it (as seen in the example projects), so we add the attribute [TextViewRole(PredefinedTextViewRoles.Editable)] to the class. Again you need a new reference, this time to Microsoft.VisualStudio.Text.UI.

  6. Mark the class as internal sealed. At least that's my recommendation. Now your class should look similar to this:

    [ContentType("code")]
    [Export(typeof(IWpfTextViewCreationListener))]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal sealed class TextViewCreationListener : IWpfTextViewCreationListener
    {
        public void TextViewCreated(IWpfTextView textView)
        {
        }
    }
    

Creating a class for code formatting

Next, we need a class handling the code formatting logic, sorting methods and so on. Again, in this example it will simply add "Hello" to the start of the file whenever an edit has been made.

  1. Add a new class called Formatter to your project.

  2. Add a constructor which takes one IWpfTextView argument. Remember that we wanted to pass the created editor instance to this formatting class in the TextViewCreated method of our listener class (simply add new Formatter(textView); to the method there).

  3. Save the passed instance in a member variable. It'll become handy when formatting the code later on (e.g. for retrieving the caret position). Also tie up the Changed and PostChanged events of the TextBuffer property of the editor instance:

    public Formatter(IWpfTextView view)
    {
        _view = view;
        _view.TextBuffer.Changed += new EventHandler<TextContentChangedEventArgs>(TextBuffer_Changed);
        _view.TextBuffer.PostChanged += new EventHandler(TextBuffer_PostChanged);
    }
    
  4. The Changed event is called every time an edit has been made (e.g. typing a char, pasting code or programmatical changes). Because it also reacts on programmatical changes I use a bool determining if our extension or the user / anything else is changing the code at the moment and call my custom FormatCode() method only if our extension is not already editing. Otherwise you'll recursively call this method which would crash Visual Studio:

    private void TextBuffer_Changed(object sender, TextContentChangedEventArgs e)
    {
        if (!_isChangingText)
        {
            _isChangingText = true;
            FormatCode(e);
        }
    }
    
  5. We have to reset this bool member variable in the PostChanged event handler again to false.

  6. Let's pass the event args of the Changed event to our custom FormatCode method because they contain what has changed between the last edit and now. Those edits are stored in the array e.Changes of the type INormalizedTextChangeCollection (s. the link at the end of my post for more information about this type). We loop through all those edits and call our custom HandleChange method with the new text which this edit has produced.

    private void FormatCode(TextContentChangedEventArgs e)
    {
        if (e.Changes != null)
        {
            for (int i = 0; i < e.Changes.Count; i++)
            {
                HandleChange(e.Changes[0].NewText);
            }
        }
    }
    
  7. In the HandleChange method we could actually scan for keywords to handle those in a specific way (remember, you have to parse any code on yourself!) - but here we just dumbly add "Hello" to the start of the file for testing purposes. E.g. we have to change the TextBuffer of our editor instance. To do so, we need to create an ITextEdit object with which we can manipulate text and apply it's changes afterwards. The code is pretty self-explaining:

    private void HandleChange(string newText)
    {
        ITextEdit edit = _view.TextBuffer.CreateEdit();
        edit.Insert(0, "Hello");
        edit.Apply();
    }
    

When compiling this add-in, an experimental hive of Visual Studio starts up with only our extension loaded. Create a new C# file and start typing to see the results.

I hope this gives you some ideas how to continue in this topic. I have to explore it myself now.

I highly recommend the documentation of the text model of the editor on MSDN to get hints about how you could do this and that. http://msdn.microsoft.com/en-us/library/dd885240.aspx#textmodel


Footnotes

[*] Note that Visual Studio 2015 or newer come with the Rosyln Compiler Platform, which indeed already analyzes C# and VB.NET files for you (and probably other pre-installed languages too) and exposes their hierarchical syntactical structure, but I'm not an expert in this topic yet to give an answer on how to use these new services. The basic progress of starting an editor extension stays the same as described in this answer anyway. Be aware that - if you use these services - you will become dependent on Visual Studio 2015+, and the extension will not work in earlier versions.

Solution 2

just have a look at the "Getting started with Editor extensions" site on the MSDN http://msdn.microsoft.com/en-us/library/dd885122.aspx

Thorsten

Share:
15,900
A.R.
Author by

A.R.

Updated on June 12, 2022

Comments

  • A.R.
    A.R. almost 2 years

    I am trying to get started in Visual Studio (2010) extensions and I am having a hard time finding the right materials. I have the SDK, but the included samples seem to be things like adorners, windows, and icons.

    I am trying to make an extension that will work directly with the text editor (to alphabetize all of my method names in a class, or make all constant names upper case for example) but I can't find a demo for this type of functionality, or even a tutorial.

    Does anyone know where I can find this kind of stuff?

  • Mike Christian
    Mike Christian almost 12 years
    I built the program to your specifications. It isn't working. I inserted some logging, and no methods are being called. Any ideas?
  • Mike Christian
    Mike Christian almost 12 years
    I got this working by using the "Editor Classifier" project type, instead of VISX Project type. Your demo and my extension are working perfectly.
  • Syroot
    Syroot almost 12 years
    @Mike Christian: OK, I can't check why the VISX project type didn't work out correctly, but I added a note to the project type in my answer.
  • Mike Christian
    Mike Christian over 11 years
    Thanks for checking that out!
  • Isaac Supeene
    Isaac Supeene over 9 years
    Note also that you'll need to add a reference to Microsoft.VisualStudio.Text.Data for the event handling stuff. I also could not get it to work when using a VSIX project type, but it worked when I used Editor Classifier - to solve this, you need to take two steps: 1. Add the project as an Asset in the VSIX manifest. 2. In the project file, remove or set to true the variables IncludeAssemblyInVSIXContainer, IncludeDebugSymbolsInVSIXContainer, and IncludeDebugSymbolsInLocalVSIXDeployment. Other than that, great tutorial - really helped me out!
  • Syroot
    Syroot over 9 years
    @IsaacSupeene thanks for the info, the tutorial is a bit old now... I'll probably update it in the future to be 100% correct then.
  • JoanComasFdz
    JoanComasFdz almost 9 years
    I got this working without marking the Editor Classifier in a VS 2013 package. Thanks!
  • jsea
    jsea over 8 years
    I had to add the Editor Classifier in my VS2015 VSIX project to get this working. In other words: Right Click Project > Add > New Item > Visual C# Items > Extensibility > Editor Classifier > Add and it just worked after that. Thanks for the walkthrough!
  • Martin
    Martin about 7 years
    The reason why it works only with Editor Classifier project item is that adding it into project will register the project as an asset in VSIX manifest as MefComponent. If you register it as VsPackage (either manually or by adding a Visual Studio Package project item), the IWpfTextViewCreationListener will not be called.
  • ElDoRado1239
    ElDoRado1239 over 3 years
    Just wanted to confirm the tutorial, once added the Editor Classifier as jsea points out, it works just fine - VS2019, .NET Framework 4.8, C# 9.0. You should consider adding that to the tutorial itself. Anyways, thank you for helping me navigate through this enigmatic process. VS would benefit greatly from making extension creation a much simpler than that...
  • Syroot
    Syroot over 3 years
    @ElDoRado1239 Yeah, I gave up on extending VS as it's a crazy mess. Feel free to edit my answer as I don't know where to best add that step to, and if it replaces any other steps.
  • ElDoRado1239
    ElDoRado1239 about 3 years
    @Ray I have jumped the ship too. Made a few things work thanks to this tutorial, but the amount of effort required for each and every tiniest thing is insane. And there is zero to none help, other than long tutorials that show some code for a very particular task but explain nothing. It's horrible and I have gained respect for creators of available extensions as well as stopped complaining they don't work perfectly.